1.工作原理
? 8086和8088的中斷設計的是很巧妙的,比如給8259的IR1配置了一個中斷,中斷號為21H,那么當真個引腳出現高電平的時候,就會觸發相應上的中斷響應。但,這不是唯一能夠觸發21H中斷的方法,還可以通過軟件指令 INT? 21H,實現這個中斷的觸發。
2.實現細節
3.測試結果
?
4.完整程序
; 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, LCRMOV AL, 80H ; 設置DLAB=1OUT DX, ALMOV DX, DLL ; 除數鎖存低字節MOV AL, 78H ; 120 = 78H (18.432MHz / (16 * 9600))OUT DX, ALMOV DX, DLM ; 除數鎖存高字節MOV AL, 00HOUT DX, AL; 設置線路參數: 8位數據, 1停止位, 無校驗MOV DX, LCRMOV AL, 03H ; 8N1, DLAB=0OUT DX, AL; 啟用并復位FIFOMOV DX, IIR_FCRMOV AL, 0C7H ; 啟用FIFO, 14字節觸發點, 清除接收FIFOOUT DX, AL; 設置調制解調器控制MOV DX, MCRMOV AL, 0BH ; 啟用OUT2(中斷使能), RTS和DTROUT DX, AL; 啟用接收數據中斷MOV DX, IER; MOV AL, 01H ; 僅啟用接收數據中斷MOV AL, 00H ; 僅啟用接收數據中斷OUT DX, ALRET; =============================================
; 8259 PIC初始化 (適配400H/402H端口)
; =============================================
INIT_PIC:; 保存原始中斷屏蔽字MOV DX, PIC_DATAIN AL, DXMOV [original_mask], AL; 初始化8259MOV DX, PIC_CMDMOV AL, 17H ; ICW1: 邊沿觸發, 級聯, 需要ICW4OUT DX, ALMOV DX, PIC_DATAMOV AL, UART_IRQ-1 ; ICW2: 中斷向量基值OUT DX, ALMOV AL, 01H ; ICW4: 8086模式, 非緩沖, 正常EOIOUT DX, AL; 允許IRQ1中斷IN AL, DXAND AL, 0FDH ; 清除IRQ1屏蔽位(11111101)OUT DX, ALRET; =============================================
; 設置中斷向量
; =============================================
SET_INTERRUPT_VECTOR:CLI ; 關中斷XOR AX, AXMOV ES, AX ; ES = 0 (中斷向量表段地址); 計算中斷向量位置 (中斷號 * 4)MOV AX, UART_IRQSHL AX, 2 ; 乘以4; 設置中斷向量MOV DI, AXMOV AX, OFFSET UART_ISRCLDSTOSW ; 存儲偏移地址MOV AX, CSSTOSW ; 存儲段地址STI ; 開中斷RET; =============================================
; UART中斷服務程序 (IRQ1) - 增加中斷計數器
; =============================================
UART_ISR PROC FARPUSH AXPUSH BXPUSH DXPUSH DS; 設置DS為當前數據段MOV AX, CSMOV DS, AX; 增加中斷計數器INC [interrupt_counter]ISR_LOOP:; 檢查中斷源MOV DX, IIR_FCRIN AL, DXTEST AL, 01H ; 檢查是否有待處理中斷 (bit0=1表示無中斷)JNZ ISR_EXIT ; 無中斷則退出; 檢查是否為接收數據中斷TEST AL, 04H ; 檢查中斷類型位 (bit1-2)JNZ CHECK_OTHER ; 不是接收中斷則檢查其他; 處理接收數據中斷MOV DX, RBR_THRIN AL, DX ; 讀取接收到的字符; 將字符存入緩沖區CALL BUFFER_STORE; 繼續檢查其他中斷JMP ISR_LOOPCHECK_OTHER:; 處理其他中斷類型 (可選); 這里可以添加發送中斷或錯誤中斷的處理; ...ISR_EXIT:; 發送EOI到8259 (使用新端口)MOV AL, 20HMOV DX, PIC_CMD ; PIC_CMD = 400HOUT DX, ALPOP DSPOP DXPOP BXPOP AXIRET
UART_ISR ENDP; =============================================
; 串口發送字符子程序
; 輸入: AL = 要發送的字符
; =============================================
SEND_CHAR:PUSH AXPUSH DX; 保存字符MOV AH, ALSEND_WAIT:; 檢查發送保持寄存器是否為空MOV DX, LSRIN AL, DXTEST AL, 20H ; 檢查THRE位(bit5)JZ SEND_WAIT ; 不為空則等待; 發送字符MOV DX, RBR_THRMOV AL, AHOUT DX, ALPOP DXPOP AXRET; =============================================
; LED控制子程序
; =============================================
UPDATE_LED:PUSH AXPUSH DX; 更新閃爍計數器INC [flash_counter]CMP [flash_counter], FLASH_INTERVALJB LED_DONE ; 未達到間隔; 重置計數器MOV [flash_counter], 0; 切換LED狀態XOR [led_state], 0FFH; 輸出到LED端口MOV DX, 800HMOV AL, [led_state]OUT DX, ALLED_DONE:POP DXPOP AXRET; =============================================
; 將字符存入緩沖區
; =============================================
BUFFER_STORE:PUSH BX; 檢查緩沖區是否已滿MOV BX, buffer_countCMP BX, BUFFER_SIZEJAE BUFFER_FULL ; 緩沖區已滿,丟棄字符; 存儲字符MOV BX, buffer_tailMOV [recv_buffer + BX], AL; 更新尾指針INC BXCMP BX, BUFFER_SIZEJB NO_WRAP_TAILXOR BX, BX ; 回繞到緩沖區開頭NO_WRAP_TAIL:MOV buffer_tail, BX; 更新字符計數INC buffer_countBUFFER_FULL:POP BXRET; =============================================
; 從緩沖區讀取字符
; =============================================
BUFFER_READ:PUSH BX; 檢查緩沖區是否為空CMP buffer_count, 0JE BUFFER_EMPTY; 讀取字符MOV BX, buffer_headMOV AL, [recv_buffer + BX]; 更新頭指針INC BXCMP BX, BUFFER_SIZEJB NO_WRAP_HEADXOR BX, BX ; 回繞到緩沖區開頭NO_WRAP_HEAD:MOV buffer_head, BX; 更新字符計數DEC buffer_count; 設置成功標志STCJMP READ_DONEBUFFER_EMPTY:XOR AL, AL ; 返回0CLC ; 清除進位標志 (失敗)READ_DONE:POP BXRET; =============================================
; 串口發送字符串
; 輸入: SI = 字符串偏移地址
; =============================================
SEND_STRING:PUSH AXPUSH SISEND_STR_LOOP:LODSB ; 加載字符到ALOR AL, AL ; 檢查是否結束(0)JZ SEND_STR_DONE ; 是則結束CALL SEND_CHAR ; 發送字符JMP SEND_STR_LOOP ; 繼續發送SEND_STR_DONE:POP SIPOP AXRET; =============================================
; 將數字轉換為字符串并發送
; 輸入: AX = 要發送的數字
; =============================================
SEND_NUMBER:PUSH AXPUSH BXPUSH CXPUSH DXPUSH DI; 準備數字轉換MOV CX, 0 ; 數字位數計數器MOV BX, 10 ; 除數; 處理0特殊情況TEST AX, AXJNZ CONVERT_LOOPMOV AL, '0'CALL SEND_CHARJMP SEND_NUM_DONECONVERT_LOOP:XOR DX, DX ; 清零DXDIV BX ; AX = AX/10, DX = 余數ADD DL, '0' ; 轉換為ASCIIPUSH DX ; 保存數字字符INC CX ; 增加位數計數TEST AX, AX ; 檢查商是否為0JNZ CONVERT_LOOP ; 不為0則繼續SEND_LOOP:POP AX ; 獲取數字字符CALL SEND_CHAR ; 發送字符LOOP SEND_LOOP ; 循環發送所有數字SEND_NUM_DONE:POP DIPOP DXPOP CXPOP BXPOP AXRET; =============================================
; 報告中斷計數器狀態
; =============================================
REPORT_COUNTER:PUSH AXPUSH SI; 發送前綴消息MOV SI, OFFSET counter_msgCALL SEND_STRING; 發送中斷計數MOV AX, [interrupt_counter]CALL SEND_NUMBER; 發送后綴消息MOV SI, OFFSET counter_endCALL SEND_STRINGPOP SIPOP AXRET; =============================================
; 初始化系統
; =============================================
INIT_SYSTEM:; 初始化緩沖區MOV buffer_head, 0MOV buffer_tail, 0MOV buffer_count, 0; 初始化LED狀態MOV [led_state], 0FFH ; 初始狀態: 亮MOV [flash_counter], 0; 初始化LED端口MOV DX, 800HMOV AL, [led_state]OUT DX, AL; 初始化計數器MOV [interrupt_counter], 0MOV [last_report], 0RET; =============================================
; 主初始化程序
; =============================================
INITIALIZATION:; 初始化系統CALL INIT_SYSTEM; 初始化UARTCALL INIT_UART; 初始化8259 PICCALL INIT_PIC; 設置中斷向量CALL SET_INTERRUPT_VECTOR; 通過串口發送啟動消息MOV SI, OFFSET startup_msgCALL SEND_STRING; =============================================
; 主程序循環
; =============================================
MAIN_LOOP:; 更新LED狀態CALL UPDATE_LED INT 21H ; 更新報告計數器INC [last_report]CMP [last_report], REPORT_INTERVALJB SKIP_REPORT; 重置報告計數器MOV [last_report], 0; 報告中斷計數CALL REPORT_COUNTER ;INT 21 SKIP_REPORT:; 檢查是否有接收到的字符CALL BUFFER_READJNC NO_DATA ; 無數據則繼續等待; 通過串口回顯接收到的字符CALL SEND_CHAR; 檢查是否為退出命令;CMP AL, 1BH ; ESC鍵;JE EXIT_PROGRAMNO_DATA:; 短延時以控制閃爍頻率MOV CX, 3000 ; 延時參數
DELAY_SHORT:LOOP DELAY_SHORT ;INT 21HJMP MAIN_LOOP; =============================================
; 退出程序
; =============================================
EXIT_PROGRAM:; 發送最終中斷計數報告CALL REPORT_COUNTER; 恢復原始中斷屏蔽字MOV DX, PIC_DATAMOV AL, [original_mask]OUT DX, AL; 禁用UART中斷MOV DX, IERMOV AL, 00HOUT DX, AL; 關閉LEDMOV DX, 800HMOV AL, 00HOUT DX, AL; 通過串口發送退出消息MOV SI, OFFSET exit_msgCALL SEND_STRING; 返回DOSMOV AH, 4CHINT 21H; =============================================
; 數據區
; =============================================
original_mask DB 0 ; 原始8259中斷屏蔽字
startup_msg DB 0Dh, 0Ah, 'UART Interrupt Counter Program Started', 0Dh, 0AhDB '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,55hEND INITIALIZATION
?