8088 单板机 汇编 NMI 中断程序示例 (脱离 DOS 环境)

cpp 复制代码
; =================================================================
; nmi_demo.asm - 8088 单板机 NMI 中断演示程序
; 脱离 DOS 环境,直接运行在裸机上
; =================================================================

; 硬件配置假设:
; - 8088 CPU @ 4.77MHz
; - 8259 PIC (可编程中断控制器)
; - 8255 PPI (可编程外设接口) 连接 LED
; - 7 段数码管显示
; - NMI 按钮连接到 NMI 引脚

PORT_PPI_A     EQU 60h    ; 8255 PPI 端口 A (LED 控制)
PORT_PPI_B     EQU 61h    ; 8255 PPI 端口 B (7 段数码管数据)
PORT_PPI_C     EQU 62h    ; 8255 PPI 端口 C (数码管位选)
PORT_PPI_CTRL  EQU 63h    ; 8255 PPI 控制端口

PORT_PIC_CMD   EQU 20h    ; 8259 PIC 命令端口
PORT_PIC_DATA  EQU 21h    ; 8259 PIC 数据端口

; 7 段数码管编码 (共阴极)
;       0     1     2     3     4     5     6     7     8     9
SEG7 db 3Fh, 06h, 5Bh, 4Fh, 66h, 6Dh, 7Dh, 07h, 7Fh, 6Fh

org 0FFF0h                ; 8088 复位向量地址
reset:
    jmp 0F000h:start      ; 跳转到实际起始地址

org 0F000h                ; BIOS 区域起始地址 (典型)

; === 主程序入口 ===
start:
    ; 设置段寄存器
    cli                   ; 禁用中断
    mov ax, cs
    mov ds, ax            ; DS = CS
    mov ss, ax            ; SS = CS
    mov sp, 0FFFEh        ; 栈指针在 64KB 顶部
    
    ; 初始化硬件
    call init_ppi         ; 初始化 8255 PPI
    call init_pic         ; 初始化 8259 PIC
    call init_nmi         ; 初始化 NMI 处理
    
    ; 显示启动信息
    call clear_display
    mov si, msg_welcome
    call display_string
    
    ; 主循环 - 等待 NMI
    sti                   ; 启用中断
main_loop:
    hlt                   ; 暂停 CPU 等待中断
    jmp main_loop

; === 初始化 8255 PPI ===
init_ppi:
    ; 设置 8255 控制字: 端口A输出, 端口B输出, 端口C输出, 模式0
    mov al, 10000000b     ; 控制字: 1 (I/O模式) 00 (A组模式0) 0 (A输出) 1 (C上输出) 0 (B组模式0) 0 (B输出) 0 (C下输出)
    out PORT_PPI_CTRL, al
    
    ; 初始状态: 关闭所有 LED 和数码管
    xor al, al
    out PORT_PPI_A, al    ; 关闭 LED
    out PORT_PPI_B, al    ; 关闭数码管段
    out PORT_PPI_C, al    ; 关闭数码管位选
    ret

; === 初始化 8259 PIC ===
init_pic:
    ; ICW1: 边沿触发, 级联, 需要 ICW4
    mov al, 00010011b     ; ICW1: 1 (需要ICW4) 0 (级联) 0 (边沿触发) 1 (需要ICW4) 1 (主PIC)
    out PORT_PIC_CMD, al
    
    ; ICW2: 中断向量基地址
    mov al, 08h           ; 中断向量号从 08h 开始
    out PORT_PIC_DATA, al
    
    ; ICW3: 主片 IR2 连接从片 (本示例不使用从片)
    mov al, 00000100b     ; IR2 连接从片 (00000100b)
    out PORT_PIC_DATA, al
    
    ; ICW4: 8086/88 模式
    mov al, 00000001b     ; ICW4: 0 (非特殊全嵌套) 0 (非缓冲) 0 (主片) 1 (8086模式)
    out PORT_PIC_DATA, al
    
    ; OCW1: 屏蔽所有中断 (NMI 不受影响)
    mov al, 11111111b     ; 屏蔽所有 IRQ
    out PORT_PIC_DATA, al
    ret

; === 初始化 NMI 处理 ===
init_nmi:
    ; 保存原始 NMI 向量
    xor ax, ax
    mov es, ax            ; ES = 0000h (中断向量表段)
    mov di, 2*4           ; NMI 向量位置 (中断号 2 * 4 字节)
    
    mov ax, [es:di]       ; 获取偏移
    mov [old_nmi_offset], ax
    mov ax, [es:di+2]     ; 获取段地址
    mov [old_nmi_segment], ax
    
    ; 设置新的 NMI 处理程序
    cli
    mov word [es:di], nmi_handler    ; 设置新偏移
    mov [es:di+2], cs                ; 设置段地址 (当前代码段)
    sti
    
    ; 启用 NMI (清除 CMOS 寄存器第7位)
    in al, 70h
    and al, 01111111b     ; 清除第7位 (NMI 启用)
    out 70h, al
    ret

; === NMI 中断处理程序 ===
nmi_handler:
    ; 保存寄存器
    push ax
    push bx
    push cx
    push dx
    push si
    push ds
    
    ; 设置数据段
    mov ax, cs
    mov ds, ax
    
    ; 增加 NMI 计数器
    inc byte [nmi_count]
    
    ; 在 LED 上显示 NMI 发生
    mov al, 0FFh          ; 点亮所有 LED
    out PORT_PPI_A, al
    
    ; 在数码管上显示计数
    call update_display
    
    ; 延迟一段时间 (视觉反馈)
    mov cx, 0FFFFh
delay_loop:
    nop
    loop delay_loop
    
    ; 关闭 LED
    xor al, al
    out PORT_PPI_A, al
    
    ; 发送中断结束信号 (EOI)
    mov al, 20h           ; 非特定 EOI
    out PORT_PIC_CMD, al
    
    ; 恢复寄存器
    pop ds
    pop si
    pop dx
    pop cx
    pop bx
    pop ax
    
    iret                  ; 中断返回

; === 更新数码管显示 ===
update_display:
    ; 显示 NMI 计数
    mov al, [nmi_count]
    and al, 0Fh           ; 只显示低4位
    
    ; 获取数码管编码
    mov bx, SEG7
    xlat                  ; AL = DS:[BX + AL]
    
    ; 输出到数码管
    out PORT_PPI_B, al    ; 段数据
    
    ; 启用第一个数码管
    mov al, 00000001b     ; 位选: 启用第一个数码管
    out PORT_PPI_C, al
    
    ret

; === 清除数码管显示 ===
clear_display:
    xor al, al
    out PORT_PPI_B, al    ; 关闭所有段
    out PORT_PPI_C, al    ; 关闭所有位选
    ret

; === 显示字符串 (SI = 字符串地址) ===
display_string:
    push ax
    push bx
    push cx
    push dx
    push si
    
    mov cx, 0             ; 字符计数器
next_char:
    lodsb                 ; 加载下一个字符
    test al, al           ; 检查是否结束
    jz display_done
    
    ; 查找字符的7段编码
    mov bx, SEG7
    xlat                  ; AL = 7段编码
    
    ; 输出到数码管
    out PORT_PPI_B, al    ; 段数据
    
    ; 设置位选
    mov al, cl
    inc al
    out PORT_PPI_C, al    ; 位选
    
    ; 延迟
    push cx
    mov cx, 1000h
delay_char:
    loop delay_char
    pop cx
    
    inc cx                ; 下一个数码管
    cmp cx, 4             ; 最多4个数码管
    jb next_char
    
display_done:
    pop si
    pop dx
    pop cx
    pop bx
    pop ax
    ret

; === 数据区 ===
msg_welcome db 'NMI', 0   ; 欢迎消息 (以0结尾)

; NMI 计数器
nmi_count db 0

; 原始 NMI 向量保存位置
old_nmi_offset  dw 0
old_nmi_segment dw 0

; === 填充到 64KB ROM 结束 ===
times 0FFFFh - $ + 1 db 0FFh
相关推荐
苇柠9 分钟前
Java补充(Java8新特性)(和IO都很重要)
java·开发语言·windows
156082072191 小时前
在QT中,利用charts库绘制FFT图形
开发语言·qt
小鹭同学_1 小时前
Java基础 Day27
java·开发语言
EdmundXjs1 小时前
IO Vs NIO
java·开发语言·nio
why1512 小时前
字节golang后端二面
开发语言·后端·golang
RedJACK~2 小时前
【Go语言】Ebiten游戏库开发者文档 (v2.8.8)
开发语言·游戏·golang
lyh13442 小时前
【Go语言生态】
开发语言
还是鼠鼠2 小时前
单元测试-断言&常见注解
java·开发语言·后端·单元测试·maven
我们的五年2 小时前
【Qt】Bug:findChildren找不到控件
开发语言·qt·bug
虾球xz2 小时前
CppCon 2014 学习:Decomposing a Problem for Parallel Execution
开发语言·c++·学习