1.程序功能
在中断服务程序中增加了中断计数器,并在主循环中通过串口输出中断次数。
2.验证
2.1手动触发8259的IR1,高电平有效
2.2串口接收
3.测试程序
; You may customize this and other start-up templates;
; The location of this template is c:\emu8086\inc\0_com_template.txt
; =============================================
; PC16550 UART接收中断程序 + LED闪烁 + 中断计数器
; 硬件配置:
; - UART基地址: 100H
; - LED端口: 800H
; - 8259 PIC端口: 400H(命令), 402H(数据)
; - 中断请求线: IRQ1 (8259 IR1)
; - 中断向量号: 21H
; - 时钟频率: 18.432MHz
; - 波特率: 9600 bps (除数78H)
; =============================================
ORG 100H
JMP INITIALIZATION ; 跳过数据区到初始化代码
; 数据段定义
BUFFER_SIZE EQU 256 ; 接收缓冲区大小
recv_buffer DB BUFFER_SIZE DUP(0) ; 接收缓冲区
buffer_head DW 0 ; 缓冲区头指针
buffer_tail DW 0 ; 缓冲区尾指针
buffer_count DW 0 ; 缓冲区中字符计数
; LED控制变量
led_state DB 0FFH ; LED状态: FFH=亮, 00H=灭
flash_counter DW 0 ; 闪烁计数器
FLASH_INTERVAL EQU 50 ; 闪烁间隔(约0.5秒)
; 中断计数器
interrupt_counter DW 0 ; 中断次数计数器
last_report DW 0 ; 上次报告时间
REPORT_INTERVAL EQU 1000 ; 报告间隔(主循环次数)
; 16550寄存器偏移
UART_BASE EQU 100H
RBR_THR EQU UART_BASE + 0 ; 接收缓冲/发送保持寄存器
IER EQU UART_BASE + 1 ; 中断使能寄存器
IIR_FCR EQU UART_BASE + 2 ; 中断标识/FIFO控制寄存器
LCR EQU UART_BASE + 3 ; 线路控制寄存器
MCR EQU UART_BASE + 4 ; 调制解调器控制寄存器
LSR EQU UART_BASE + 5 ; 线路状态寄存器
DLL EQU UART_BASE + 0 ; 除数锁存低字节 (DLAB=1)
DLM EQU UART_BASE + 1 ; 除数锁存高字节 (DLAB=1)
; 8259 PIC端口 (修改为400H/402H)
PIC_CMD EQU 400H
PIC_DATA EQU 402H
; 中断向量号
UART_IRQ EQU 21H ; IRQ1对应中断21H
; =============================================
; UART初始化子程序
; =============================================
INIT_UART:
; 设置波特率除数 (9600 @ 18.432MHz)
MOV DX, LCR
MOV AL, 80H ; 设置DLAB=1
OUT DX, AL
MOV DX, DLL ; 除数锁存低字节
MOV AL, 78H ; 120 = 78H (18.432MHz / (16 * 9600))
OUT DX, AL
MOV DX, DLM ; 除数锁存高字节
MOV AL, 00H
OUT DX, AL
; 设置线路参数: 8位数据, 1停止位, 无校验
MOV DX, LCR
MOV AL, 03H ; 8N1, DLAB=0
OUT DX, AL
; 启用并复位FIFO
MOV DX, IIR_FCR
MOV AL, 0C7H ; 启用FIFO, 14字节触发点, 清除接收FIFO
OUT DX, AL
; 设置调制解调器控制
MOV DX, MCR
MOV AL, 0BH ; 启用OUT2(中断使能), RTS和DTR
OUT DX, AL
; 启用接收数据中断
MOV DX, IER
; MOV AL, 01H ; 仅启用接收数据中断
MOV AL, 00H ; 仅启用接收数据中断
OUT DX, AL
RET
; =============================================
; 8259 PIC初始化 (适配400H/402H端口)
; =============================================
INIT_PIC:
; 保存原始中断屏蔽字
MOV DX, PIC_DATA
IN AL, DX
MOV [original_mask], AL
; 初始化8259
MOV DX, PIC_CMD
MOV AL, 17H ; ICW1: 边沿触发, 级联, 需要ICW4
OUT DX, AL
MOV DX, PIC_DATA
MOV AL, UART_IRQ-1 ; ICW2: 中断向量基值
OUT DX, AL
MOV AL, 01H ; ICW4: 8086模式, 非缓冲, 正常EOI
OUT DX, AL
; 允许IRQ1中断
IN AL, DX
AND AL, 0FDH ; 清除IRQ1屏蔽位(11111101)
OUT DX, AL
RET
; =============================================
; 设置中断向量
; =============================================
SET_INTERRUPT_VECTOR:
CLI ; 关中断
XOR AX, AX
MOV ES, AX ; ES = 0 (中断向量表段地址)
; 计算中断向量位置 (中断号 * 4)
MOV AX, UART_IRQ
SHL AX, 2 ; 乘以4
; 设置中断向量
MOV DI, AX
MOV AX, OFFSET UART_ISR
CLD
STOSW ; 存储偏移地址
MOV AX, CS
STOSW ; 存储段地址
STI ; 开中断
RET
; =============================================
; UART中断服务程序 (IRQ1) - 增加中断计数器
; =============================================
UART_ISR PROC FAR
PUSH AX
PUSH BX
PUSH DX
PUSH DS
; 设置DS为当前数据段
MOV AX, CS
MOV DS, AX
; 增加中断计数器
INC [interrupt_counter]
ISR_LOOP:
; 检查中断源
MOV DX, IIR_FCR
IN AL, DX
TEST AL, 01H ; 检查是否有待处理中断 (bit0=1表示无中断)
JNZ ISR_EXIT ; 无中断则退出
; 检查是否为接收数据中断
TEST AL, 04H ; 检查中断类型位 (bit1-2)
JNZ CHECK_OTHER ; 不是接收中断则检查其他
; 处理接收数据中断
MOV DX, RBR_THR
IN AL, DX ; 读取接收到的字符
; 将字符存入缓冲区
CALL BUFFER_STORE
; 继续检查其他中断
JMP ISR_LOOP
CHECK_OTHER:
; 处理其他中断类型 (可选)
; 这里可以添加发送中断或错误中断的处理
; ...
ISR_EXIT:
; 发送EOI到8259 (使用新端口)
MOV AL, 20H
MOV DX, PIC_CMD ; PIC_CMD = 400H
OUT DX, AL
POP DS
POP DX
POP BX
POP AX
IRET
UART_ISR ENDP
; =============================================
; 串口发送字符子程序
; 输入: AL = 要发送的字符
; =============================================
SEND_CHAR:
PUSH AX
PUSH DX
; 保存字符
MOV AH, AL
SEND_WAIT:
; 检查发送保持寄存器是否为空
MOV DX, LSR
IN AL, DX
TEST AL, 20H ; 检查THRE位(bit5)
JZ SEND_WAIT ; 不为空则等待
; 发送字符
MOV DX, RBR_THR
MOV AL, AH
OUT DX, AL
POP DX
POP AX
RET
; =============================================
; LED控制子程序
; =============================================
UPDATE_LED:
PUSH AX
PUSH DX
; 更新闪烁计数器
INC [flash_counter]
CMP [flash_counter], FLASH_INTERVAL
JB LED_DONE ; 未达到间隔
; 重置计数器
MOV [flash_counter], 0
; 切换LED状态
XOR [led_state], 0FFH
; 输出到LED端口
MOV DX, 800H
MOV AL, [led_state]
OUT DX, AL
LED_DONE:
POP DX
POP AX
RET
; =============================================
; 将字符存入缓冲区
; =============================================
BUFFER_STORE:
PUSH BX
; 检查缓冲区是否已满
MOV BX, buffer_count
CMP BX, BUFFER_SIZE
JAE BUFFER_FULL ; 缓冲区已满,丢弃字符
; 存储字符
MOV BX, buffer_tail
MOV [recv_buffer + BX], AL
; 更新尾指针
INC BX
CMP BX, BUFFER_SIZE
JB NO_WRAP_TAIL
XOR BX, BX ; 回绕到缓冲区开头
NO_WRAP_TAIL:
MOV buffer_tail, BX
; 更新字符计数
INC buffer_count
BUFFER_FULL:
POP BX
RET
; =============================================
; 从缓冲区读取字符
; =============================================
BUFFER_READ:
PUSH BX
; 检查缓冲区是否为空
CMP buffer_count, 0
JE BUFFER_EMPTY
; 读取字符
MOV BX, buffer_head
MOV AL, [recv_buffer + BX]
; 更新头指针
INC BX
CMP BX, BUFFER_SIZE
JB NO_WRAP_HEAD
XOR BX, BX ; 回绕到缓冲区开头
NO_WRAP_HEAD:
MOV buffer_head, BX
; 更新字符计数
DEC buffer_count
; 设置成功标志
STC
JMP READ_DONE
BUFFER_EMPTY:
XOR AL, AL ; 返回0
CLC ; 清除进位标志 (失败)
READ_DONE:
POP BX
RET
; =============================================
; 串口发送字符串
; 输入: SI = 字符串偏移地址
; =============================================
SEND_STRING:
PUSH AX
PUSH SI
SEND_STR_LOOP:
LODSB ; 加载字符到AL
OR AL, AL ; 检查是否结束(0)
JZ SEND_STR_DONE ; 是则结束
CALL SEND_CHAR ; 发送字符
JMP SEND_STR_LOOP ; 继续发送
SEND_STR_DONE:
POP SI
POP AX
RET
; =============================================
; 将数字转换为字符串并发送
; 输入: AX = 要发送的数字
; =============================================
SEND_NUMBER:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DI
; 准备数字转换
MOV CX, 0 ; 数字位数计数器
MOV BX, 10 ; 除数
; 处理0特殊情况
TEST AX, AX
JNZ CONVERT_LOOP
MOV AL, '0'
CALL SEND_CHAR
JMP SEND_NUM_DONE
CONVERT_LOOP:
XOR DX, DX ; 清零DX
DIV BX ; AX = AX/10, DX = 余数
ADD DL, '0' ; 转换为ASCII
PUSH DX ; 保存数字字符
INC CX ; 增加位数计数
TEST AX, AX ; 检查商是否为0
JNZ CONVERT_LOOP ; 不为0则继续
SEND_LOOP:
POP AX ; 获取数字字符
CALL SEND_CHAR ; 发送字符
LOOP SEND_LOOP ; 循环发送所有数字
SEND_NUM_DONE:
POP DI
POP DX
POP CX
POP BX
POP AX
RET
; =============================================
; 报告中断计数器状态
; =============================================
REPORT_COUNTER:
PUSH AX
PUSH SI
; 发送前缀消息
MOV SI, OFFSET counter_msg
CALL SEND_STRING
; 发送中断计数
MOV AX, [interrupt_counter]
CALL SEND_NUMBER
; 发送后缀消息
MOV SI, OFFSET counter_end
CALL SEND_STRING
POP SI
POP AX
RET
; =============================================
; 初始化系统
; =============================================
INIT_SYSTEM:
; 初始化缓冲区
MOV buffer_head, 0
MOV buffer_tail, 0
MOV buffer_count, 0
; 初始化LED状态
MOV [led_state], 0FFH ; 初始状态: 亮
MOV [flash_counter], 0
; 初始化LED端口
MOV DX, 800H
MOV AL, [led_state]
OUT DX, AL
; 初始化计数器
MOV [interrupt_counter], 0
MOV [last_report], 0
RET
; =============================================
; 主初始化程序
; =============================================
INITIALIZATION:
; 初始化系统
CALL INIT_SYSTEM
; 初始化UART
CALL INIT_UART
; 初始化8259 PIC
CALL INIT_PIC
; 设置中断向量
CALL SET_INTERRUPT_VECTOR
; 通过串口发送启动消息
MOV SI, OFFSET startup_msg
CALL SEND_STRING
; =============================================
; 主程序循环
; =============================================
MAIN_LOOP:
; 更新LED状态
CALL UPDATE_LED
; 更新报告计数器
INC [last_report]
CMP [last_report], REPORT_INTERVAL
JB SKIP_REPORT
; 重置报告计数器
MOV [last_report], 0
; 报告中断计数
CALL REPORT_COUNTER
SKIP_REPORT:
; 检查是否有接收到的字符
CALL BUFFER_READ
JNC NO_DATA ; 无数据则继续等待
; 通过串口回显接收到的字符
CALL SEND_CHAR
; 检查是否为退出命令
;CMP AL, 1BH ; ESC键
;JE EXIT_PROGRAM
NO_DATA:
; 短延时以控制闪烁频率
MOV CX, 3000 ; 延时参数
DELAY_SHORT:
LOOP DELAY_SHORT
JMP MAIN_LOOP
; =============================================
; 退出程序
; =============================================
EXIT_PROGRAM:
; 发送最终中断计数报告
CALL REPORT_COUNTER
; 恢复原始中断屏蔽字
MOV DX, PIC_DATA
MOV AL, [original_mask]
OUT DX, AL
; 禁用UART中断
MOV DX, IER
MOV AL, 00H
OUT DX, AL
; 关闭LED
MOV DX, 800H
MOV AL, 00H
OUT DX, AL
; 通过串口发送退出消息
MOV SI, OFFSET exit_msg
CALL SEND_STRING
; 返回DOS
MOV AH, 4CH
INT 21H
; =============================================
; 数据区
; =============================================
original_mask DB 0 ; 原始8259中断屏蔽字
startup_msg DB 0Dh, 0Ah, 'UART Interrupt Counter Program Started', 0Dh, 0Ah
DB 'Press ESC to exit...', 0Dh, 0Ah, 0
exit_msg DB 0Dh, 0Ah, 'Program terminated.', 0Dh, 0Ah, 0
counter_msg DB 0Dh, 0Ah, 'Interrupt count: ', 0
counter_end DB ' times', 0Dh, 0Ah, 0
end_flag DB 55h,55h,55h,55h,55h
END INITIALIZATION