中断和动态时钟显示
中断和动态时钟显示
这一章的目的就是编写代码访问 CMOS RAM 在屏幕上显示一个动态走的时钟
外部硬件中断
外部硬件中断是处理器外部传进来的中断信号,通过两根信号线引入(NMI 和 INTR)有些中断比较紧急,有些中断不那么紧急,所以会区分出来不同类型的中断,通过两个信号线来区分,NMI 是非屏蔽中断,不会被阻断和屏蔽,NMI 中断一般来说都是些致命的问题,被赋予了统一的中断号 2
INTR 是可屏蔽中断,比不上 NMI 那么严重,但是也需要根据优先级来判断先处理哪一个,这就需要一个代理,根据优先级进行仲裁,发送给处理器
intel 处理器允许 256 个中断,8259 中断控制器有 8 个中断输入引脚,需要用两块,一块作为主片(端口号 0x20 和 0x21)输出 INT 直接传送到处理器的 INTR 引脚,第二块作为从片(端口号 0xa0 和 0xa1)INT 输出传送到第一块的引脚 2 上,这样能向处理器提供 15 个中断信号了
主片的引脚 0 接的是系统定时器/计数器,从片的引脚 0 接的是实时时钟芯片 RTC,实时时钟芯片是重点要学的
8259 芯片里面有个中断屏蔽寄存器,是个 8 位寄存器,对应着芯片的中断输入引脚,表示从这些引脚来的中断信号要不要送到处理器,0 表示允许,1 表示不允许
最终处理器是否处理 INTR 引脚的中断还要看自己想不想处理,在标志寄存器中有个 IF 标志位(中断标志),当这个位是 0 的时候中断信号会被忽略掉,当为 1 是处理器会接受和响应中断。cli 指令可以置为 0,sti 指令可以置为 1
实模式下内存中从 0x00000 到 0x003ff 结束中断向量表,每个中断在中断向量表占 2 个字,4 个字节,分别是偏移地址和段地址
通过对 8259 进行编程可以设置他每个引脚对应的中断号,8259 某一个引脚收到了中断信号后会把该引脚对应的中断号发送给处理器,处理器收到中断号后先会把标志寄存器 FLAGS 压栈,然后清除 IF 位和 TF 位,将 cs、ip 压栈,然后把中断号乘以 4,得到在中断向量表中的偏移地址,然后从表中去除中断程序的段地址和偏移地址给 cs:ip 去执行中断程序,中断程序最后一条指令是 iret 处理器会从栈中弹出 IP、CS、标志寄存器
实时时钟、CMOS RAM
外围设备控制器芯片 ICH 内部集成了实时时钟电路(RTC)和两小块由互补金属氧化物(CMOS)材料组成的静态存储器(CMOS RAM),实时时钟电路负责计时,日期和时间的数值存储在这块存储器中
对 CMOS RAM 的访问通过端口 0x70 或 0x74 索引来指定 CMOS RAM 内的单元;0x71 或 0x75 是数据端口用来读写相应单元里的内容
端口 0x70 的最高位是 NMI 中断开关,当它为 0 时允许 NMI 中断到达处理器,为 1 时则阻断,这并不是中断信号,但他能控制与非门的输出来控制 NMI 中断信号是否能到达处理器,0x70 是只写的,后来有了读取的办法,但是比较复杂,我们用的时候先把他覆写掉,不管写成啥,最后再给他打开就行了
CMOS RAM 中的 0xA 0xB 0xC 0xD 都有不同的作用,具体用到了再说
;代码清单9-1
;文件名:c09_1.asm
;文件说明:用户程序
;创建日期:2011-4-16 22:03
;===============================================================================
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00]
;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code.start ;段地址[0x06]
realloc_tbl_len dw (header_end-realloc_begin)/4
;段重定位表项个数[0x0a]
realloc_begin:
;段重定位表
code_segment dd section.code.start ;[0x0c]
data_segment dd section.data.start ;[0x14]
stack_segment dd section.stack.start ;[0x1c]
header_end:
;===============================================================================
SECTION code align=16 vstart=0 ;定义代码段(16字节对齐)
new_int_0x70:
push ax
push bx
push cx
push dx
push es
.w0:
mov al,0x0a ;阻断NMI。当然,通常是不必要的
or al,0x80
out 0x70,al
in al,0x71 ;读寄存器A
test al,0x80 ;测试第7位UIP
jnz .w0 ;以上代码对于更新周期结束中断来说
;是不必要的
xor al,al
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(秒)
push ax
mov al,2
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(分)
push ax
mov al,4
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(时)
push ax
mov al,0x0c ;寄存器C的索引。且开放NMI
out 0x70,al
in al,0x71 ;读一下RTC的寄存器C,否则只发生一次中断
;此处不考虑闹钟和周期性中断的情况
mov ax,0xb800
mov es,ax
pop ax
call bcd_to_ascii
mov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示
mov [es:bx],ah
mov [es:bx+2],al ;显示两位小时数字
mov al,':'
mov [es:bx+4],al ;显示分隔符':'
not byte [es:bx+5] ;反转显示属性
pop ax
call bcd_to_ascii
mov [es:bx+6],ah
mov [es:bx+8],al ;显示两位分钟数字
mov al,':'
mov [es:bx+10],al ;显示分隔符':'
not byte [es:bx+11] ;反转显示属性
pop ax
call bcd_to_ascii
mov [es:bx+12],ah
mov [es:bx+14],al ;显示两位小时数字
mov al,0x20 ;中断结束命令EOI
out 0xa0,al ;向从片发送
out 0x20,al ;向主片发送
pop es
pop dx
pop cx
pop bx
pop ax
iret
;-------------------------------------------------------------------------------
bcd_to_ascii: ;BCD码转ASCII
;输入:AL=bcd码
;输出:AX=ascii
mov ah,al ;分拆成两个数字
and al,0x0f ;仅保留低4位
add al,0x30 ;转换成ASCII
shr ah,4 ;逻辑右移4位
and ah,0x0f
add ah,0x30
ret
;-------------------------------------------------------------------------------
start:
mov ax,[stack_segment]
mov ss,ax
mov sp,ss_pointer
mov ax,[data_segment]
mov ds,ax ;初始化各个寄存器
mov bx,init_msg ;显示初始信息
call put_string
mov bx,inst_msg ;显示安装信息
call put_string
mov al,0x70
mov bl,4
mul bl ;计算0x70号中断在IVT中的偏移
mov bx,ax
cli ;防止改动期间发生新的0x70号中断
push es
mov ax,0x0000
mov es,ax
mov word [es:bx],new_int_0x70 ;偏移地址。
mov word [es:bx+2],cs ;段地址
pop es
mov al,0x0b ;RTC寄存器B
or al,0x80 ;阻断NMI
out 0x70,al
mov al,0x12 ;设置寄存器B,禁止周期性中断,开放更
out 0x71,al ;新结束后中断,BCD码,24小时制
mov al,0x0c
out 0x70,al
in al,0x71 ;读RTC寄存器C,复位未决的中断状态
in al,0xa1 ;读8259从片的IMR寄存器
and al,0xfe ;清除bit 0(此位连接RTC)
out 0xa1,al ;写回此寄存器
sti ;重新开放中断
mov bx,done_msg ;显示安装完成信息
call put_string
mov bx,tips_msg ;显示提示信息
call put_string
mov cx,0xb800
mov ds,cx
mov byte [12*160 + 33*2],'@' ;屏幕第12行,35列
.idle:
hlt ;使CPU进入低功耗状态,直到用中断唤醒
not byte [12*160 + 33*2+1] ;反转显示属性
jmp .idle
;-------------------------------------------------------------------------------
put_string: ;显示串(0结尾)。
;输入:DS:BX=串地址
mov cl,[bx]
or cl,cl ;cl=0 ?
jz .exit ;是的,返回主程序
call put_char
inc bx ;下一个字符
jmp put_string
.exit:
ret
;-------------------------------------------------------------------------------
put_char: ;显示一个字符
;输入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es
;以下取当前光标位置
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
in al,dx ;高8位
mov ah,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
in al,dx ;低8位
mov bx,ax ;BX=代表光标位置的16位数
cmp cl,0x0d ;回车符?
jnz .put_0a ;不是。看看是不是换行等字符
mov ax,bx ;
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor
.put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,80
jmp .roll_screen
.put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,1
mov [es:bx],cl
;以下将光标位置推进一个字符
shr bx,1
add bx,1
.roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor
mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,1920
rep movsw
mov bx,3840 ;清除屏幕最底一行
mov cx,80
.cls:
mov word[es:bx],0x0720
add bx,2
loop .cls
mov bx,1920
.set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
;===============================================================================
SECTION data align=16 vstart=0
init_msg db 'Starting...',0x0d,0x0a,0
inst_msg db 'Installing a new interrupt 70H...',0
done_msg db 'Done.',0x0d,0x0a,0
tips_msg db 'Clock is now working.',0
;===============================================================================
SECTION stack align=16 vstart=0
resb 256
ss_pointer:
;===============================================================================
SECTION program_trail
program_end: