美文网首页
中断与外部设备操作

中断与外部设备操作

作者: freemanIT | 来源:发表于2024-03-11 16:20 被阅读0次

    位移指令

    示例一:

    mov al,01001000b
    shl al,1
    
    图1:shl左移结果 图2:左移和右移与cf值

    示例二:

    SHL OPR,CNT, 将OPR 逻辑左移CNT 位

    1. 将寄存器或内存单元中的数据向左移位
    2. 将最后移出的移位写入CF 中
    3. 最低位用0 补充
    mov al,01010001b
    mov cl,3 ;移动位数大于1时必须用cl
    shl al,cl
    

    结果为(al)=10001000b cf 为0

    惯常用法, 将x 逻辑左移一位, 相当于执行x=x*2, 右移一位, 相当于执行x=x/2

    操作显存数据

    显示的原理

    图3:显示原理

    显示缓冲区的结构

    图4:dos显示结构
    各行所需字节数 显示缓冲区地址范围
    160(A0H) B800:0000~B800:009F
    160(A0H) B800:00A0~B800:013F
    160(A0H) B800:0140~B800:01DF
    160(A0H) B800:0F00~B800:0F9F

    示例三:

    在屏幕中间, 白底蓝字, 显示welcome to masm!

    assume cs:code,ds:data
    data segment
      db 'wolcome to masm!'
    data ends
    
    code segment
    start:
      ; 初始化寄存器
      mov ax,data
      mov ds,ax
      mov ax,0B800H
      mov es,ax
      mov si,0
      mov di,160*12+80-16
      ; 显示字符串
      mov cx,16
    w:
      mov al,[si]
      mov es:[di],al
      inc di
      mov al,71H
      mov es:[di],al
      inc si
      inc di
      loop w
    
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    描述内存单元的标号

    • 代码段中的标号可以用来标记指令, 段的起始地址

    • 代码段中的数据也可以用标号

    • 数据标号不同于仅仅表示地址的地址标号, 数据标号存储数据单元的地址和长度

    示例四:

    assume cs:code
    
    code segment
      a db 1,2,3,4,5,6,7,8
      b dw 0
    start:
      mov si,0
      mov cx,8
    s:
      mov al,a[si]
      mov ah,0
      add b,ax
      inc si
      loop s
      
      mov ax,4c00h
      int 21h
    code ends
    end start
    
    • 在code 段中使用标号a, b 后面没有冒号:, 它们同时描述内存地址和单元长度的标好
    • 标号a
      • 地址code:0
      • 以后内存单元都是字节
    • 标号b
      • 地址code:8
      • 以后内存单元都是字

    示例五:

    ; a 代表地址为code:0, 长度为字节的内存
    mov al,a[si]
    ==> mov al,cs:0[si]
    ====================
    mov al,a[3]
    ==> mov al.cs:0[3]
    ====================
    mov al,a[bx+si+3]
    ==> mov al,cs:0[bx+si+3]
    ====================
    ; b 代表地址为code:8, 长度为字的内存单元
    mov ax,b
    ==> mov ax,cs:[8]
    ====================
    mov b,2
    ==> mov word ptr cs:[8],2
    ====================
    inc b
    ==> inc word ptr cs:[8]
    ====================
    mov al,b
    ==> error!
    

    更常见的方式: 数据段中的数据标号

    示例六:

    assume cs:code,ds:data
    data segment
      a db 1,2,3,4,5,6,7,8
      b dw 0
    data ends
    
    code segment
    start:
      mov ax,data
      mov ds,ax
      mov si,0
      mov cx,8
    s:
      mov al,a[si]
      mov ah,0
      add b,ax
      inc si
      loop s
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    扩展用法: 将标号当做数据来定义

    data segment
        a db 1,2,3,4,5,6,7,8
        b dw 0
        c dw a,b ; ==>> c dw offset a, offset b
    data ends
    ====================
    data segment
        a db 1,2,3,4,5,6,7,8
        b dw 0
        c dd a,b ; ==>> c dw offset a,seg a, offset b,seg b, seg 操作符为取段地址
    data ends
    

    定址表

    数据的直接定址表

    示例七:

    16 进制的形式在屏幕中间显示给定的byte 型数据

    建立一张表, 表中一次存储字符"0"~"F", 通过数值0~15 直接查找到对应的字符

    assume cs:code
    
    code segment
    start:
      mov al,2Bh
      call showbyte
      mov ax,4c00h
      int 21h
    
      ; 子程序
    showbyte:
      jmp short show
      table db '0123456789ABCDEF'
    show:
      push bx
      push es
      push cx
    
      mov ah,al
      mov cl,4
      shr ah,cl ; 右移4位, ah 中得到高4 位的值
      and al,00001111b ; al 中为低4 位的值
      ; 用高4 位的值ah 作为相对于table 的偏移, 取得对应的字符并显示
      mov bl,ah
      mov bh,0
      mov ah,table[bx]
    
      mov bx,0b800h
      mov es,bx
      mov es:[160*12+40*2],ah
      ; 用低4 位的值al 作为相对于table 的偏移, 取得对应的字符
      mov bl,al
      mov bh,0
      mov al,table[bx]
      mov es:[160*12+40*2+2],al
    
      pop cx
      pop es
      pop bx
      ret
    code ends
    end start
    

    直接定址表

    思路:

    • 利用表, 在两个数据集合之间建立一种映射关系, 用查表的方法根据给出的数据得到其在另一集合中的对应数据
    • 优点:
      • 算法清晰简洁
      • 加快运算速度
      • 程序易于扩展

    示例八:

    编写程序, 计算sin(x), x ∈{0°, 30°, 60°, 90°, 120°, 150°, 180°}, 并在屏幕中间显示计算结果

    常规解法

    • 利用麦克劳林公式计算
    • 将角度x 换位弧度y=x/180*3.1415926

    计算sin(x) 需要进行多次乘法和除法, 乘除都是比较昂贵的操作

    另一种解法

    空间换时间

    • 将所要计算的sin(x) 的结果都存储到一张表中, 然后用角度值来查表, 找到对应的sin(x) 的值
    • 方法
      • 用ax 向子程序传递角度
      • 角度值/30 为table 表中的偏移, 找到对应的字符串的首地址
    assume cs:code
    
    code segment
    start:
      mov al,60
      call showsin
      mov ax,4c00h
      int 21h
    
    showsin:
      jmp short show
      ; 字符串偏移地址表
      table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
      ag0 db'0',0 ; sin(0) 对应的字符串'0
      ag30 db'0.5',0
      ag60 db'0.866',0
      ag90 db'1',0
      ag120 db'0.866',0
      ag150 db'0.5',0
      ag180 db'0',0 ; sin(180) 对应字符串'0'
    show:
      push bx
      push es
      push si
      mov bx,0b800h
      mov es,bx
      ; 用角度/30 作为相对于table 的偏移量, 取得对应的字符串的偏移地址, 放在bx 中
      mov ah,0
      mov bl,30
      div bl
      mov bl,al
      mov bh,0
      add bx,bx
      mov bx,table[bx]
      ; 显示sin(x) 对应的字符串
      mov si,160*12+40*2
    shows:
      mov ah,cs:[bx]
      cmp ah,0
      je showret
      mov es:[si],ah
      inc bx
      add si,2
      jmp shows
    showret:
      pop si
      pop es
      pop bx
      ret
    
    code ends
    end start
    

    代码的直接定址表

    直接定址表法

    • 用查表的方式, 通过依据数据, 直接计算出所要找的元素位置

    直接定址表分类

    • 数据段额直接定址表
    • 代码的直接诶定址表

    示例九:

    实现一个子程序setscreen, 为显示输出提供如下功能

    1. 清屏
    2. 设置前景色
    3. 设置背景色
    4. 向上滚动一行

    方案:

    • 将4 个功能写成4 个子程序
    • 这些功能子程序入口地址存储在一个表中, 他们在表中位置和功能号相对应
    • 对应关系, 功能号*2=对应功能子程序在地址表中的偏移
    assume cs:code
    code segment
    start:
      ; 主程序
      mov ah,3
      mov al,2 ; 传递颜色
      call setscreen
      mov ax,4c00h
      int 21h
      ; 子程序
    setscreen:
      jmp short set
      table dw sub1,sub2,sub3,sub4
    set:
      push bx
      cmp ah,3; 判断是否大于3
      ja sret
      mov bl,ah
      mov bh,0
      add bx,bx
      call word ptr table[bx]
    sret:
     pop bx
     ret
    ; 各功能实现
    sub1:
      push bx
      push cx
      push es
    
      mov bx,0b800h
      mov es,bx
      mov bx,0
      mov cx,2000
    sub1s:
      mov byte ptr es:[bx],' '
      add bx,2
      loop sub1s
    
      pop es
      pop cx
      pop bx
      ret
    ;sub1 ends
    
    sub2:
      push bx
      push cx
      push es
    
      mov bx,0b800h
      mov es,bx
      mov bx,1
      mov cx,2000
    sub2s:
      and byte ptr es:[bx],11111000b
      or es:[bx],al
      add bx,2
      loop sub2s
    
      pop es
      pop cx
      pop bx
      ret
    ;sub2 ends
    
    sub3:
      push bx
      push cx
      push es
      mov cl,4
      shl al,cl
      mov bx,0b800h
      mov es,bx
      mov bx,1
      mov cx,2000
    sub3s:
      and byte ptr es:[bx],10001111b
      or es:[bx],al
      add bx,2
      loop sub3s
      pop es
      pop cx
      pop bx
      ret
    ;sub3 ends
    
    sub4:
      push cx
      push si
      push di
      push es
      push ds
      
      mov si,0b800h
      mov es,si
      mov ds,si
      mov si,160
      mov di,0
      cld
      mov cx,24
    sub4s:
      push cx
      mov cx,160
      rep movsb
      pop cx
      loop sub4s
    
      mov cx,80
      mov si,0
    sub4s1:
      mov byte ptr es:[160*24+si],' '
      add si,2
      loop sub4s1
      
      pop ds
      pop es
      pop di
      pop si
      pop cx
      ret
    ;sub4 ends
    code ends
    end start
    

    中断及其处理

    概念:

    • 中断: cpu 不再接着(刚执行完的指令) 向下执行, 而是专区处理中断信息
    • 内中断: 由cpu 内部发生的事件而引起的中断
    • 外中断: 由外部设备发生的事件引起的中断
    图5:中断及其处理

    8086的内中断

    • 除法错误, 比如执行div 指令产生的除法溢出
    • 单步执行
    • 执行into 指令
    • 执行int 指令

    8086 中断类型码:

    1. 除法错误: 0
    2. 单步执行: 1
    3. 执行into 指令: 4
    4. 执行int n 指令, 立即数n wei中断类型码

    示例十:

    利用中断在屏幕上显示一行字符串

    assume cs:code,ss:stack,ds:data
    stack segment
      db 200h dup(0)
    stack ends
    data segment
      szmsg db 13,10,'hello world!',13,10,'$'
    data ends
    code segment
    start:
      mov ax,data
      mov ds,ax
      lea dx,szmsg
      mov ah,9
      int 21h
      
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    中断处理程序

    cpu 接到中断信息时, 执行中断处理程序

    中断处理程序位置

    • 中断信息和其处理程序的入口地址之间有某种联系, cpu 根据中断信息可以找到要执行的处理程序

    中断向量表

    • 由中断类型码, 查表得到中断处理程序入口地址, 从而定位中断处理程序
    图6:8086cpu中的中断向量表

    示例十一:

    图7:系统中的0 号中断

    中断过程

    是由cpu 的硬件自动完成, 用中断类型码找到中断向量, 并用它设置cs 和ip

    8086 cpu 的中断过程

    1. 从中断信息中取得中断类型码
    2. 标志寄存器的值入栈-- 中断过程中药改变标志寄存器的值, 需要现行保护
    3. 设置标志寄存器的第8 位TF 和第9 位IF 的值为0
    4. cs 的内容入栈
    5. ip 内容入栈
    6. 从终端向量表读取中断处理程序入口地址, 设置ip 和cs
    图8:中断过程
    1. 取得中断类型码N
    2. pushf
    3. TF=0, IF=0
    4. push cs
    5. push ip
    6. (ip)=(n*4), (cs)=(n*4+2)

    编制中断处理程序

    • cpu 随时都可能检测到中断信息, 所以中断处理程序须常驻内存
    • 中断处理程序的入口地址, 即中断向量, 必须存储在对应的中断向量表中(0000:0000-0000:03ff)

    如何编制中断处理程序呢?

    对0 号中断, 即出发错误的中断处理

    编写一个0 号中断处理程序, 功能为在屏幕中间显示"overflow!" 后, 返回到操作系统

    图9:0号中断,屏幕中间显示字符串

    do0:

    1. 相关处理
    2. 向现实缓冲区送字符串"overflow!"
    3. 返回dos

    子程序存放位置

    • 子程序应该存放在内存的确定位置, 不能破坏系统
    • 绕过操作系统, 直接找到一块别的程序不会用到的内存区, 将do0 传送到其中
    • 内存0000:0000~0000:03ff, 大小为1kb 的空间是系统存放终端向量表, dos 系统和其他应用程序都不会随便使用这段空间, 8086 支持256 个中断, 但是实际系统重要处理的中断事件远没有达到256 个
    • 利用中断向量表中的空闲低钠盐存放我们的程序, 使用0000:0200~0000:02ff 空间

    示例十二:

    assume cs:code
    code segment
    start:
      ; do0安装程序
      mov ax,cs
      mov ds,ax
      mov si,offset do0
      mov ax,0
      mov es,ax
      mov di,200h
      mov cx,offset do0end-offset do0
      cld
      rep movsb
      ; 设置中断向量表
      mov ax,0
      mov es,ax
      mov word ptr es:[0*4],200h
      mov word ptr es:[0*4+2],0
      mov ax,4c00h
      int 21h
    do0:
      jmp short do0start
      db "overflow!"
    do0start:
      mov ax,cs
      mov ds,ax
      mov si,202h
      mov ax,0b800h
      mov es,ax
      mov di,12*160+36*2
      mov cx,9
    s:
      mov al,[si]
      mov es:[di],al
      inc si
      add di,2
      loop s
      mov ax,4c00h
      int 21h
    do0end:nop
    code ends
    end start
    

    单步中断

    debug 提供了单步中断的中断处理程序, wei显示所有寄存器的内容后等待输入命令

    debug 利用cpu 提供的单步中断功能, 使用t 命令时, debug 将tf 标识设为1, 使cpu 工作在单步中断方式

    单步中断过程与处理

    两个和中断相关的寄存器标志位

    • TF-陷阱标识(Trap Flag), 用于调试时单步操作, 当TF=1, 每条指令执行后产生陷阱, 由系统控制计算机, TF=0, cpu 正常工作, 不产生陷阱
    • IF-中断标志(Interrupt Flag), IF=1, 允许cpu 响应可屏蔽中断请求, IF=0, 关闭中断

    中断过程:

    1. 取得中断类型码1
    2. 标识寄出去入栈, TF, IF 设置为0
    3. cs, ip 入栈
    4. (ip)=(1*4), cs=(1*4+2)
    • 中断处理程序也由一条条指令组成的

    • 如果在执行中断处理程序之前, TF=1, 则CPU 在执行完中断处理程序的第一条指令后, 又要产生单步中断, 转去执行单步中断的中断处理程序的第一条指令……

    • 上面的过程将陷入一个永远不能结束的循环, CPU永远执行单步中断处理程序的第一条指令

    • 所以, 在进入中断处理程序之前, 设置TF=0

    中断不响应情况

    有些情况下, cpu 执行完当前指令后, 即便发生中断, 也不会响应

    例如, 执行完向ss 寄出去传输数据指令后, 即便发送中断, cpu 也不会响应

    原因, ss:sp 联合指向栈顶, 它们的设置应该连续完成, 一次保证对栈的正确操作

    由int指令引发的中断

    格式: int n, n为中断类型码

    执行过程

    1. 取中断类型码n
    2. 标志寄存器入栈, IF = 0, TF = 0
    3. CS, IP入栈
    4. (IP) = (n*4),(CS) = (n*4+2)
    • int 指令的最终功能和call指令相似, 都是调用一段程序
    • 一般情况下, 系统将一些具有一定功能的子程序, 以中断处理程序的方式提供给应用程序调用

    可以自定义中断例程实现特定功能

    中断7ch 的中断例程的编写和安装

    7ch 中断例程, 求一个word 型数据平方, 参数(ax)=要计算的数据, 返回值dx,ax 存放高低16 位

    示例十三:

    求2*3456^2

    assume cs:code
    code segment
    start:
      mov ax,cx
      mov ds,ax
      mov si,offset sqr
      mov ax,0
      mov es,ax
      mov di,200h
      mov cx,offset sqrend-offset sqr
      cld
      rep movsb
      mov ax,0
      mov es,ax
      mov word ptr es:[7ch*4],200h
      mov word ptr es:[7ch*4+2],0
      mov ax,4c00h
      int 21h
    sqr:
      mul ax
      iret
    code ends
    end start
    

    示例十四:

    将以0 结尾的字符串转化为大写, 参数ds:si 执行字符串首地址

    assume cs:code
    
    code segment
    start:
      mov ax,cs
      mov ds,ax
      mov si,offset capital
      mov ax,0
      mov es,ax
      mov di,200h
      mov cx,offset capitalend-offset capital
      cld
      rep movsb
    
      mov ax,0
      mov es,ax
      mov word ptr es:[7ch*4],200h
      mov word ptr es:[7ch*4+2],0
    
      mov ax,4c00h
      int 21h
    capital:
      push cx
      push si
    change:
      mov cl,[si]
      mov ch,0
      jcxz ok
      and byte ptr [si],11011111b
      inc si
      jmp short change
    ok:
      pop si
      pop cx
      iret
    capitalend:nop
    code ends
    end start
    ====================
    assume cs:code
    data segment
      db 'conversation',0
    data ends
    
    code segment
    start:
      mov ax,data
      mov ds,ax
      mov si,0
      int 7ch
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    BIOS和DOS中断处理

    bios 基本输入输出系统, 在系统板的rom 中存放着一套程序, 容量8kb, 地址从FE000H 开始

    其中的主要内容:

    1. 硬件系统的检测和初始化程序
    2. 外部中断和内部中断的中断例程
    3. 用于对硬件设备进行I/O操作的中断例程
    4. 其他和硬件系统相关的中断例程

    示例十五:

    在屏幕的5 行12 列显示3 个红底高亮闪烁绿色的'a'

    使用bios 的10h 中断

    • (ah)=2, 调用10h 汇总单例程的2 号子程序, 设置光标位置
    • (ah)=9, 调用10h 中断例程的9 号子程序, 在光标位置显示字符
    assume cs:code
    code segment
      mov ah,2 ; 设置光标位置
      mov bh,0 ; 第0 页
      mov dh,5 ; dh 中放行号
      mov dl,12 ; dl 中放列号
      int 10h
      
      mov ah,9 ; 显示字符功能
      mov al,'a' ; 字符
      mov bl,11001010b ; 颜色设置
      mov bh,0 ; 第0 页
      mov cx,3 ; 字符重复个数
      mov ax,4c00h
      int 21h
    code ends
    end
    

    其他bios 中断

    图10:其他bios中断

    int 21h dos 中断例程应用

    4ch 号功能, 程序返回, 功能号在ah, 返回结果在al

    09h 号功能, 在光标位置显示字符串, ds:dx 指向要显示的字符串, 用'$' 结束

    示例十六:

    在屏幕5 行12 列 显示字符串"welcome to masm!"

    assume cs:code,ds:data
    data segment
      db 'welcome to masm!','$'
    data ends
    code segment
    start:
      mov ah,2 ; 光标
      mov bh,0 ; 第0页
      mov dh,5 ; dh 行号
      mov dl,12 ; dl 列号
      int 10h
    
      mov ax,data
      mov ds,ax
      mov dx,0 ; ds:dx 指向字符串的首地址data:0
      mov ah,9
      int 21h
    
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    BIOS 和DOS 中断例程的安装过程

    1. CPU 一加电,初始化(CS)=0FFFFH, (IP)=0, 自动从FFFF:0单元开始执行程序. FFFF:0处有一条转跳指令, CPU执行该指令后, 转去执行BIOS中的硬件系统检和初始化程序
    2. 初始化程序将建立BIOS 所支持的中断向量, 即将BIOS提供的中断例程的入口地址登记在中断向量表中
    3. 硬件系统检测和初始化完成后, 调用int 19h进行操作系统的引导. 从此将计算机交由操作系统控制
    4. DOS 启动后, 除完成其它工作外, 还将它所提供的中断例程装入内存, 并建立相应的中断向量
    图11:中断例程安装过程

    端口的读写

    图12:端口的读写

    示例十七:

    用端口访问外设, 发声

    assume cs:code
    code segment
    start:
      mov al,08h ; 设置剩余频率
      out 42h,al
      out 42h,al
      in al,61h ; 读设备控制器端口原值
      mov ah,al ; 保存原值
      or al,3 ; 打开扬声器和定时器
      out 61h,al ; 接通扬声器, 发声
      mov cx,60000 ; 延时
    delay:
      nop
      loop delay
      mov al,ah ; 恢复端口值
      out 61h,al
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    cpu 的邻居

    cpu 可以直接读写3 个地方的数据

    1. cpu 内部寄存器
    2. 内存单元
    3. 端口
      1. 各种接口卡, 网卡, 显卡
      2. 主板上的接口芯片
      3. 其他芯片

    读写内存与寄存器的指令, mov, add, push...

    读写端口的指令

    • in, cpu 从端口读取数据
    • out: cpu 往端口写入数据

    端口的读写

    访问端口的方法

    in al,60h, 从60h 端口读入一个字节

    执行时与总线相关的操作

    1. CPU通过地址线将地址信息60h发出
    2. CPU通过控制线发出端口读命令, 选中端口所在的芯片, 并通知要从中读取数据
    3. 端口所在的芯片将60h端口中的数据通过数据总线送入CPU

    I/O 端口分配

    图13:I/O端口分配

    端口读写指令示例

    0~255 以内的端口读写, 端口号用立即数给出

    in al,20h ;从20h端口读入一个字节

    out 21h,al ; 往21h 端口写入一个字节

    对256~65535的端口进行读写时, 端口号放在dx中:

    mov dx,3f8h ; 将端口号3f8送入dx

    in al,dx ; 从3f8h端口读入一个字节

    out dx,al ; 向3f8h端口写入一个字节

    在in和out 指令中,只能使用ax 或al 来存放从端口中读入的数据或要发送到端口中的数据

    访问8 位端口时用 al , 访问 16 位端口时用ax

    操作CMOS RAM芯片

    1. 包含一个实时钟和一个有128个存储单元的RAM存储器
    2. 128 个字节的 RAM 中存储: 内部实时钟, 系统配置信息, 相关的程序(用于开机时配置系统信息)
    3. CMOS RAM 芯片靠电池供电, 关机后其内部的实时钟仍可正常工作, RAM 中的信息不丢失
    4. 该芯片内部有两个端口, 端口地址为70h和71h, CPU 通过两个端口读写CMOS RAM
      1. 70h地址端口, 存放要访问的CMOS RAM单元的地址
      2. 71h数据端口, 存放从选定的单元中读取的数据, 或要写入到其中的数据
    5. 读取CMOS RAM的两个步骤
      1. 将要读取的单元地址送入70h 地址端口
      2. 从数据端口71h 读出指定单元的内容

    示例十八:

    在屏幕中间显示当前的月份

    事实上, 在CMOS RAM中的时间信息含有月份

    图14:在cmos中包含的时间

    屏幕中间显示当前月份

    1. 从CMOS RAM的8号单元读出当前月份的BCD码
      1. 首先要向地址端口70h写入要访问的单元的地址-月份8
      2. 再从数据端口71h中取得指定单元中的数据-月份值
    2. 将用BCD码表示的月份以十进制的形式显示到屏幕上
      1. 将读到的数据前4位和后4位分离出来
      2. 由低4位为BCD码的“数字”转为ASCII码
        1. BCD 码值=十进制数码值
        2. 十进制数对应的ASCII码 = BCD码值+30h
    assume cs:code
    code segment
    start:
      mov al,8
      out 70h,al ; 取得月份
      in al,71h
    
      mov ah,al
      mov cl,4 ; 分离月份的十, 个位
      shr ah,cl
      and al,00001111b
    
      add ah,30h ; 转换为ASCII 码
      add al,30h
    
      mov bx,0b800h
      mov es,bx
      mov byte ptr es:[160*12+40*2],ah
      mov byte ptr es:[160*12+40*2+2],al
    
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    外设连接与中断

    cpu 通过端口与外部设备"连接"

    cpu 在执行指令过程中, 可以检测到发送过来的中断信息, 引发中断过程, 处理外设的输入

    外中断

    由外部设备发生的时间引起的中断

    可屏蔽中断

    • 可屏蔽中断是CPU 可以不响应的外中断
    • CPU 是否响应可屏蔽中断, 要看标志寄存器的IF 位的设置
    • 当CPU 检测到可屏蔽中断信息时:
      • 如果IF=1, 则CPU 在执行完当前指令后响应中断,引发中断过程
      • 如果IF=0, 则不响应可屏蔽中断

    不可屏蔽中断

    • 不可屏蔽中断是CPU 必须响应的外中断
    • 当CPU 检测到不可屏蔽中断信息时, 则在执行完当前指令后,立即响应, 引发中断过程
    • 对于8086CPU 不可屏蔽中断的中断类型码固定为2

    外中断处理过程

    可屏蔽中断所引发的中断过程

    1. 取中断类型码n
    2. 标志寄存器入栈, IF=0, TF=0
    3. CS , IP 入栈
    4. (IP)=(n*4), (CS)=(n*4+2)

    可屏蔽中断信息来自于CPU外部, 中断类型码是通过数据总线送入CPU

    (对比内中断: 中断类型码是在CPU内部产生的)

    将IF置0的原因: 进入中断处理程序后, 禁止其他的可屏蔽中断

    如果在中断处理程序中需要处理可屏蔽中断, 可以用指令将IF 置1

    不可屏蔽中断的中断过程

    1. 标志寄存器入栈, IF=0, TF=0
    2. CS, IP入栈
    3. (IP)=(8), (CS)=(0AH)

    8086 cpu 提供的设置if 指令, sti, 用于设置if=1, cli用于设置if=0

    PC机键盘的处理过程

    1. 键盘输入

    • 键盘上的每一个键相当于一个开关, 键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描
    • 按下一个键时的操作
      • 开关接通, 该芯片就产生一个扫描码, 扫描码说明了按下的键在键盘上的位置
      • 扫描码被送入主板上的相关接口芯片的寄存器中, 该寄存器的端口地址为60H
    • 松开按下的键时的操作
      • 产生一个扫描码, 扫描码说明了松开的键在键盘上的位置
      • 松开按键时产生的扫描码也被送入60H 端口中
    • 扫描码--长度为一个字节的编码
      • 按下一个键时产生的扫描码--通码, 通码的第7 位为 0
      • 松开一个键时产生的扫描码--断码, 断码的第7位为 1
    图15:键盘上键的扫描码(通码)

    2. 引发9 号中断

    • 键盘的输入到达60H 端口时, 相关的芯片就会向CPU 发出中断类型码为 9 的可屏蔽中断信息
    • CPU检测到该中断信息后, 如果IF=1, 则响应中断, 引发中断过程, 转去执行int 9中断例程

    输入的字符键盘值如何保存

    • 有BIOS键盘缓冲区
    • BIOS键盘缓冲区: 是系统启动后, BIOS用于存放int 9 中断例程所接收的键盘输入的内存区
    • BIOS键盘缓冲区: 可以存储15 个键盘输入, 一个键盘输入用一个字单元存放, 高位字节存放扫描码, 低位字节存放字符码
    7 6 5 4 3 2 1 0
    Insert CapsLock NumLock ScrollLock Alt Ctrl 左Shift 右Shift

    执行int 9 中断例程

    BIOS 中提供的处理键盘输入的int 9中断例程的工作

    1. 读出60H 端口中的扫描码

    2. 根据扫描码分情况对待

      1. 如果是字符键的扫描码,将该扫描码和它所对应的字符码(

        即 ASCII码) 送入内存中的BIOS 键盘缓冲区

      2. 如果是控制键(比如 Ctrl ) 和切换键(比如 CapsLock) 的扫描码, 则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元

    3. 对键盘系统进行相关的控制, 如向相关芯片发出应答信息

    定制键盘输入处理(int 9 中断例程)

    处理过程

    1. 键盘产生扫描码
    2. 扫描码送入60h 端口
    3. 引发9 号中断

    ========> 以上由硬件系统完成

    1. CPU执行int 9中断例程, 处理键盘输入
      1. DOS系统提供int 9中断例程
      2. 按照开发需求定制处理键盘的输入

    示例十九:

    编程任务

    • 在屏幕中间依次显示 'a'~'z', 并可以让人看清
    • 在显示的过程中, 按下Esc键后, 改变显示的颜色
    assume cs:code,ds:data,ss:stack
    stack segment
      db 128 dup(0)
    stack ends
    data segment
      dw 0,0
    data ends
    
    code segment
      ; 代码段
    start:
      mov ax,stack
      mov ss,ax
      mov sp,128
      mov ax,data
      mov ds,ax
    
      ; 改中断例程入口地址
      mov ax,0
      mov es,ax
      push es:[9*4]
      pop ds:[0]
      push es:[9*4+2]
      pop ds:[2]
      mov word ptr es:[9*4],offset int9
      mov es:[9*4+2],cs
    
      ; 显示a~z
      mov ax,0b800h
      mov es,ax
      mov ah,'a'
    s:
      mov es:[160*12+40*2],ah
      call delay
      inc ah
      cmp ah,'z'
      jna s
      mov ax,0
      mov es,ax
    
      ; 恢复原来地址
      push ds:[0]
      pop es:[9*4]
      push ds:[2]
      pop es:[9*4+2]
    
      mov ax,4c00h
      int 21h
    
      ; 定义延迟程序
    delay:
      push ax
      push dx
      mov dx,10h
      mov ax,0
    s1:
      sub ax,1
      sbb dx,0
      cmp ax,0
      jne s1
      cmp dx,0
      jne s1
      pop dx
      pop ax
      ret
    
      ; 定义中断例程
    int9:
      push ax
      push bx
      push es
      in al,60h
      pushf
      pushf
      pop bx
      and bh,11111100b
      push bx
      popf
      call dword ptr ds:[0]
    
      cmp al,1 ; esc 扫描码是1
      jne int9ret
      ; 改变颜色
      mov ax,0b800h
      mov es,ax
      inc byte ptr es:[160*12+40*2+1]
    int9ret:
      pop es
      pop bx
      pop ax
      iret
    code ends
    end start
    

    改写中断例程的方法

    示例二十:

    任务: 安装一个新的int 9中断例程

    功能: 在DOS下, 按F1键后改变当前屏幕的显示颜色, 其他的键照常处理

    解决的问题

    1. 改变屏幕的显示颜色

      改变从B800开始的4000个字节中的所有奇地址单元中的内容, 当前屏幕的显示颜色即发生改变

    2. F1改变功能,其他键照常

      可以调用原int 9中断处理程序, 来处理其他的键盘输入

    3. 原int 9中断例程入口地址的保存

      要保存原int 9中断例程的入口地址原因: 在新int 9中断例程中要调用原int 9中断例程

      保存在哪里? 我们将地址保存在0:200单元处

    4. 新int 9中断例程的安装

      我们可将新的int 9中断例程安装在0:204 处

    assume cs:code
    stack segment
      db 128 dup(0)
    stack ends
    code segment
      ; 设置各段地址
    start:
      mov ax,stack
      mov ss,ax
      mov sp,128
      push cs ;ds 和cs 相同
      pop ds
      mov ax,0
      mov es,ax
      ; 安装新程序
      mov si,offset int9
      mov di,204h
      mov cx,offset int9end-offset int9
      cld
      rep movsb
      ; 将原来中断地址保存在0:200 单元
      push es:[9*4]
      pop es:[200h]
      push es:[9*4+2]
      pop es:[202h]
      ; 改变后中断的入口地址
      cli
      mov word ptr es:[9*4],204h
      mov word ptr es:[9*4+2],0
      sti
      mov ax,4c00h
      int 21h
      ; 定义新中断例程
    int9:
      push ax
      push bx
      push cx
      push es
      in al,60h
      pushf
      ; 调用中断例程
      call dword ptr cs:[200h]
      ; F1 键处理
      cmp al,3bh
      jne int9ret
      mov ax,0b800h
      mov es,ax
      mov bx,1
      mov cx,2000
    s:
      inc byte ptr es:[bx]
      add bx,2
      loop s
    int9ret:
      pop es
      pop cx
      pop bx
      pop ax
      iret
    int9end:
      nop
    code ends
    end start
    

    用中断响应外设

    如何操作外部设备

    硬件中断int 9h BIOS中断int 16h DOS中断int 21h
    由键盘上按下或松开一个键时,
    如果中断是允许的, 就会产生int 9h中断, 并转到BIOS的键盘中断处理程序
    BIOS中断提供基本的键盘操作,
    功能号(AH)=
    00H, 10H - 从键盘读入字符
    01H, 11H - 读取键盘状态
    02H, 12H - 读取键盘标志
    03H - 设置重复率
    04H - 设置键盘点击
    05H - 字符及其扫描码进栈
    在使用功能键和变换键的程序中很重要
    DOS中断提供丰富, 便捷的功能调用功能号(AH)=
    01H - 从键盘输入一个字符并回
    06H - 读键盘字符
    07H - 从键盘输入一个字符不回显
    08H - 从键盘输入一个字符, 不回显, 检测CTRL-Break
    0AH - 输入字符到指定地址的缓冲区
    0BH - 读键盘状态
    0CH - 清除键盘缓冲区, 并调用一种键盘功能

    对键盘输入的处理的int 9h中断和int 16h中断

    • int 9h将键盘输入存入缓冲或改变状态字
    • 键盘输入将引发9 号中断, BIOS 提供了int 9 中断例程
    • int 9中断例程从60h 端口读出扫描码, 并将其转化为相应的ASCII 码或状态信息, 存储在内存的指定空间(键盘缓冲区或状态字节)中
    • 键盘缓冲区中有16 个字单元, 可以存储15个按键的扫描码和对应的入ASCII 码
    • BIOS提供了int 16h 中断例程供程序员调用, 以完成键盘的各种操作
    • 例: 当(AH)=0时, 读取键盘缓冲区
    • 功能: 从键盘缓冲区中读取一个键盘输入, 并且将其从缓冲区中删除

    mov ah,0

    int 16h

    结果: (ah)=扫描码, (al)=ASCII码

    调用int 16h 从键盘缓冲区中读取键盘的输入

    综前所述: int 16h 中断例程 0 号功能的实现过程

    1. 检测键盘缓冲区中是否有数据
    2. 没有则继续做第1 步
    3. 读取缓冲区第一个字单元中的键盘输入
    4. 将读取的扫描码送入ah, ASCII 码送入al
    5. 将己读取的键盘输入从缓冲区中删除

    事实:

    • B1OS 的int 9 中断例程和int 16h 中断例程是一对相互配合的程序, int 9 中断例程向键盘缓冲区中写入int 16h 中断例程从缓冲区中读出
    • 它们写入和读出的时机不同, int 9 中断例程在有键按下的时候向键盘缓冲区中写入数据, 而int 16h 中断例程是在应用程序对其进行调用的时候, 将数据从键盘缓冲区中读出

    示例二十一:

    要求:

    • 输入"r", 将屏幕上的字符设置为红色
    • 输入"g" 将屏幕上的字符设置为绿色
    • 输入"b" 将屏幕上的字符设置为蓝色
    assume cs:code
    code segment
    start:
      ; 调用中断,等待输入
      mov ah,0
      int 16h
      ; 识别按键
      mov ah,1
      cmp al,'r'
      je red
      cmp al,'g'
      je green
      cmp al,'b'
      je blue
      jmp short sret
    
      ; 设置屏幕颜色
    red:
      shl ah,1
    green:
      shl ah,1
    blue:
      mov bx,0b800h
      mov es,bx
      mov bx,1
      mov cx,2000
    s:
      and byte ptr es:[bx],11111000b
      or es:[bx],ah
      add bx,2
      loop s
    sret:
      mov ax,4c00h
      int 21h
    code ends
    end start
    

    字符串的输入

    示例二十二:

    问题: 设计一个最基本的字符串输入程序,需要具备下面的功能

    1. 在输入的同时需要显示这个字符串
    2. 一般在输入回车符后, 字符串输入结束
    3. 能够删除已经输入的字符--用退格键

    解决:

    1. 在输入的同时需要显示这个字符串

      需要用栈的方式来管理字符串的存储空间

    2. 在输入回车符后,字符串输入结束

      输入回车符后 , 在字符串中加入0, 表示字符串结束

    3. 在输入的同时需要显示这个字符串

      每次有新的字符输入和删除一个字符的时候, 都应该重新显示字符串, 即从字符栈的栈底到栈顶, 显示所有的字符

    过程

    1. 调用int 16h读取键盘输入
    2. 如果不是字符: ①如果是退格键, 从字符栈中弹出一个字符, 显示字符栈中的所有字符,继续执行1; ②如果是Enter 键, 向字符栈中压入0, 返回
    3. 如果是字符键: 字符入栈; 显示字符栈中的所有字符; 继续执行1
    assume cs:code,ds:data
    data segment
      db 32 dup(?)
    data ends
    code segment
    start:
      mov ax,data
      mov ds,ax
      mov si,0 ; 设置字符串的存储空间
      mov dh,12
      mov dl,20 ; 显示位置
      call getstr
    return:
      mov ax,4c00h
      int 21h
    
    getstr:
      push ax
    getstrs:
      mov ah,0
      int 16h
      cmp al,20h
      jb nochar ; 小于20h 为非字符
      mov ah,0
      ; 字符入栈
      call charstack
      mov ah,2
      ; 显示栈中字符
      call charstack
      jmp getstrs
    
    nochar: ; 处理非字符
      cmp ah,0eh
      je backspace
      cmp ah,1ch ; 回车键的扫描
      je enter
      jmp getstrs
    
      ; 对退格键, 回车键处理
    backspace: ; 退格
      mov ah,1
      ; 字符出栈
      call charstack
      ; 显示栈中的字符
      mov ah,2
      call charstack
      jmp getstrs
    enter: ; 回车
      mov al,0
      mov ah,0
      ; 0 字符入栈
      call charstack
      mov ah,2
      ; 显示栈中的字符
      call charstack
      pop ax
      ret ; getstr 结束
    ; 子程序实现
    charstack:
      jmp short charstart
      table dw charpush,charpop,charshow
      top dw 0 ; 栈顶
    charstart:
      push bx
      push dx
      push di
      push es
      ; 实现各功能
      cmp ah,2
      ja sret
      mov bl,ah
      mov bh,0
      add bx,bx
      jmp word ptr table[bx]
    
    charpush:
      mov bx,top
      mov [si][bx],al
      inc top
      mov bx,top
      mov al,[si][bx]
      jmp sret
    
    charpop:
      cmp top,0
      je sret
      dec top
      mov bx,top
      mov al,[si][bx]
      jmp sret
    
    charshow:
      mov bx,0b800h
      mov es,bx
      mov al,160
      mov ah,0
      mul dh
      mov di,ax
      add dl,dl
      mov dh,0
      add di,dx
    
      mov bx,0
    
    charshows:
      cmp bx,top
      jne noempty
      mov byte ptr es:[di],' '
      jmp sret
    noempty:
      mov al,[si][bx]
      mov es:[di],al
      mov byte ptr es:[di+2],' '
      inc bx
      add di,2
      jmp charshows
    sret:
      pop es
      pop di
      pop dx
      pop bx
      ret
    code ends
    end start
    
    

    读写磁盘

    图16:bios提供的磁盘直接服务int 13

    用BIOS int 13h 对磁盘进行读操作

    入口参数:

    • (ah)=2 (2表示读扇区)
    • (al)=读取的扇区数
    • (ch)=磁道号, (cl)=扇区号
    • (dh)=磁头号(对于软盘即面号, 一个面用一个磁头来读写)
    • (dl)=驱动器号: 软驱从0开始, 0: 软驱A, 1: 软驱B; 硬盘从80h 开始, 80h: 硬盘C, 81h: 硬盘D
    • es:bx 指向接收从扇区读入数据的内存区

    返回参数:

    • 操作成功: (ah)=0, (al)=读入的扇区数

    • 操作失败: (ah)=出错代码

    mov ax,0
    mov es,ax
    mov bx,200h ; 读入0:200h
    mov al,1 ; 1 个扇区
    mov ch,0 ; 0 磁道
    mov cl,1 ; 1 扇区
    mov dl,80h ; c 盘
    mov dh,0 ; 0 面
    mov ah,2 ; 读扇区
    int 13h
    

    用BIOS int 13h 对磁盘进行写操作

    入口参数:

    • (ah)=3 (3表示写扇区)
    • (al)=写入的扇区数
    • (ch)=磁道号, (cl)=扇区号
    • (dh)=磁头号(对于软盘即面号)
    • (dl)=驱动器号: 软驱从0开始, 0: 软驱A, 1: 软驱B, 硬盘从80h 开始, 80h: 硬盘C, 81h: 硬盘D
    • es:bx 指向将写入磁盘的数据

    返回参数:

    • 操作成功: (ah)=0, (al)=写入的扇区数
    • 操作失败: (ah)=出错代码
    mov ax,0
    mov es,ax
    mov bx,200h ; 写0:200h
    mov al,1 ; 写1 个扇区
    mov ch,0 ; 0 磁道
    mov cl,1 ; 1 扇区
    mov dl,80h ; c 盘
    mov dh,0 ; 0 面
    mov ah,3 ; 写入扇区
    int 13h
    
    图17:dos中断对磁盘恩建额支持int 21h

    计算机"唱歌"

    图18:有关的硬件及控制
    ; 8253 芯片(定时/计数器)的设置
    mov al,0b6h ;8253初始化
    out 43h,al ;43H是8253芯片控制口的端口地址
    mov dx,12h
    mov ax,34dch
    div word ptr [si] ;计算分频值,赋给ax, [si]中存放声音的频率值。
    out 42h, al ;先送低8位到计数器,42h是8253芯片通道2的端口地址
    mov al, ah
    out 42h, al ;后送高8位计数器
    
    ;设置8255芯片(并行I/O), 控制扬声器的开/关
    in al,61h ;读取8255 B端口原值
    mov ah,al ;保存原值
    or al,3 ;使低两位置1,以便打开开关
    out 61h,al ;开扬声器, 发声
    ... ;延时,保持时间
    ... mov al, ah
    out 61h, al;恢复扬声器端口原值
    

    示例二十三:

    演奏程序

    assume cs:codeseg, ds:dataseg, ss:stackseg
    dataseg segment
    mus_freq dw 262,262,262,196
             dw 330,330,330,262
             dw 262,330,392,392
             dw 349,330,294
             dw 294,330,349,349
             dw 330,294,330,262
             dw 262,330,294,196
             dw 247,294,262,-1
    mus_time dw 3 dup(12,12,25,25),12,12,50
             dw 3 dup(12,12,25,25),12,12,50
    dataseg ends
    
    stackseg segment
       db 100h dup (0)
    stackseg ends
    
    codeseg segment
    start:
        mov ax, stackseg
        mov ss, ax
        mov sp, 100h
    
        mov ax, dataseg
        mov ds, ax
    
        lea si, mus_freq
        lea di, mus_time
    
    play:
        mov dx, [si]
        cmp dx, -1
        je end_play
        call sound
        add si, 2
        add di, 2
        jmp play
    
    end_play:
        mov ax, 4c00h
        int 21h
    
    ;演奏一个音符
    ;入口参数:si - 要演奏的音符的频率的地址
    ;          di - 要演奏的音符的音长的地址
    sound:
        push ax
        push dx
        push cx
    
        ;8253 芯片(定时/计数器)的设置
        mov al,0b6h    ;8253初始化
        out 43h,al     ;43H是8253芯片控制口的端口地址
        mov dx,12h
        mov ax,34dch
        div word ptr [si] ;计算分频值,赋给ax。[si]中存放声音的频率值。
        out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
        mov al, ah
        out 42h, al       ;后送高8位计数器
    
    
        ;设置8255芯片, 控制扬声器的开/关
        in al,61h   ;读取8255 B端口原值
        mov ah,al   ;保存原值
        or al,3     ;使低两位置1,以便打开开关
        out 61h,al  ;开扬声器, 发声
    
        mov dx, [di]       ;保持[di]时长
    wait1:
        mov cx, 28000
    delay:
        nop
        loop delay
        dec dx
        jnz wait1
    
        mov al, ah         ;恢复扬声器端口原值
        out 61h, al
    
        pop cx
        pop dx
        pop ax
        ret
    
    codeseg ends
    end start
    

    相关文章

      网友评论

          本文标题:中断与外部设备操作

          本文链接:https://www.haomeiwen.com/subject/uebmzdtx.html