8086实时时钟实验(一)——《x86汇编语言:从实模式到保护模式》05
1.代码清单
;代码清单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 axpush bxpush cxpush dxpush es.w0: mov al,0x0a ;阻断NMI。当然,通常是不必要的or al,0x80 out 0x70,alin al,0x71 ;读寄存器Atest al,0x80 ;测试第7位UIP jnz .w0 ;以上代码对于更新周期结束中断来说 ;是不必要的 xor al,alor al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(秒)push axmov al,2or al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(分)push axmov al,4or al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(时)push axmov al,0x0c ;寄存器C的索引。且开放NMI out 0x70,alin al,0x71 ;读一下RTC的寄存器C,否则只发生一次中断;此处不考虑闹钟和周期性中断的情况 mov ax,0xb800mov es,axpop axcall bcd_to_asciimov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示mov [es:bx],ahmov [es:bx+2],al ;显示两位小时数字mov al,':'mov [es:bx+4],al ;显示分隔符':'not byte [es:bx+5] ;反转显示属性 pop axcall bcd_to_asciimov [es:bx+6],ahmov [es:bx+8],al ;显示两位分钟数字mov al,':'mov [es:bx+10],al ;显示分隔符':'not byte [es:bx+11] ;反转显示属性pop axcall bcd_to_asciimov [es:bx+12],ahmov [es:bx+14],al ;显示两位小时数字mov al,0x20 ;中断结束命令EOI out 0xa0,al ;向从片发送 out 0x20,al ;向主片发送 pop espop dxpop cxpop bxpop axiret;------------------------------------------------------------------------------- bcd_to_ascii: ;BCD码转ASCII;输入:AL=bcd码;输出:AX=asciimov ah,al ;分拆成两个数字 and al,0x0f ;仅保留低4位 add al,0x30 ;转换成ASCII shr ah,4 ;逻辑右移4位 and ah,0x0f add ah,0x30ret;------------------------------------------------------------------------------- start:mov ax,[stack_segment]mov ss,axmov sp,ss_pointermov ax,[data_segment]mov ds,axmov bx,init_msg ;显示初始信息 call put_stringmov bx,inst_msg ;显示安装信息 call put_stringmov al,0x70mov bl,4mul bl ;计算0x70号中断在IVT中的偏移mov bx,ax cli ;防止改动期间发生新的0x70号中断push esmov ax,0x0000mov es,axmov word [es:bx],new_int_0x70 ;偏移地址。mov word [es:bx+2],cs ;段地址pop esmov al,0x0b ;RTC寄存器Bor al,0x80 ;阻断NMI out 0x70,almov al,0x12 ;设置寄存器B,禁止周期性中断,开放更 out 0x71,al ;新结束后中断,BCD码,24小时制 mov al,0x0cout 0x70,alin 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_stringmov bx,tips_msg ;显示提示信息call put_stringmov cx,0xb800mov ds,cxmov 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_charinc bx ;下一个字符 jmp put_string.exit:ret;------------------------------------------------------------------------------- put_char: ;显示一个字符;输入:cl=字符asciipush axpush bxpush cxpush dxpush dspush es;以下取当前光标位置mov dx,0x3d4mov al,0x0eout dx,almov dx,0x3d5in al,dx ;高8位 mov ah,almov dx,0x3d4mov al,0x0fout dx,almov dx,0x3d5in al,dx ;低8位 mov bx,ax ;BX=代表光标位置的16位数cmp cl,0x0d ;回车符?jnz .put_0a ;不是。看看是不是换行等字符 mov ax,bx ; mov bl,80 div blmul blmov bx,axjmp .set_cursor.put_0a:cmp cl,0x0a ;换行符?jnz .put_other ;不是,那就正常显示字符 add bx,80jmp .roll_screen.put_other: ;正常显示字符mov ax,0xb800mov es,axshl bx,1mov [es:bx],cl;以下将光标位置推进一个字符shr bx,1add bx,1.roll_screen:cmp bx,2000 ;光标超出屏幕?滚屏jl .set_cursormov ax,0xb800mov ds,axmov es,axcldmov si,0xa0mov di,0x00mov cx,1920rep movswmov bx,3840 ;清除屏幕最底一行mov cx,80.cls:mov word[es:bx],0x0720add bx,2loop .clsmov bx,1920.set_cursor:mov dx,0x3d4mov al,0x0eout dx,almov dx,0x3d5mov al,bhout dx,almov dx,0x3d4mov al,0x0fout dx,almov dx,0x3d5mov al,blout dx,alpop espop dspop dxpop cxpop bxpop axret;=============================================================================== SECTION data align=16 vstart=0init_msg db 'Starting...',0x0d,0x0a,0inst_msg db 'Installing a new interrupt 70H...',0done_msg db 'Done.',0x0d,0x0a,0tips_msg db 'Clock is now working.',0;=============================================================================== SECTION stack align=16 vstart=0resb 256 ss_pointer:;=============================================================================== SECTION program_trail program_end:
以上就是全部的代码了(加载器采用第八章的)
也不知道我这个插件怎么了,显示出的源码歪歪扭扭,没有对齐好吧,咱们就凑合看吧。
2.用户程序结构图
3.中断处理程序
最开始的部分是头部,严格遵循第八章作者约定的格式,我们就不多说了。
;=============================================================================== SECTION code align=16 vstart=0 ;定义代码段(16字节对齐) new_int_0x70:push axpush bxpush cxpush dxpush es
这里就开始中断处理程序了。首先是把用到的寄存器入栈,这是必须的。
.w0: mov al,0x0a ;阻断NMI。当然,通常是不必要的or al,0x80 out 0x70,alin al,0x71 ;读寄存器Atest al,0x80 ;测试第7位UIP jnz .w0 ;以上代码对于更新周期结束中断来说 ;是不必要的 xor al,alor al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(秒)push axmov al,2or al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(分)push axmov al,4or al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(时)push ax
这段代码要细说,有很多新知识。
(1)CMOS RAM
在外围设备控制芯片(ICH)内部,集成了实时时钟电路(RTC)和两小块由互补金属氧化物(CMOS)材料组成的静态存储器(CMOS RAM)。实时时钟电路负责计时,而日期和时间的数值则存储在这块存储器中,它们由电脑主板上的一个小纽扣电池提供能量。
日期和时间信息存储在CMOS RAM中,通常CMOS RAM有128个存储单元,而日期和时间信息只占了一小部分容量,其余空间则保存整机的配置信息。
RTC芯片由一个频率为32.768kHz的晶振驱动,经过分频后,用于对CMOS RAM进行每秒一次的时间刷新。
表格9-1 CMOS RAM中的时间信息
偏移地址 |
内容 |
偏移地址 |
内容 |
0x00 |
秒 |
0x07 |
日 |
0x01 |
闹钟秒 |
0x08 |
月 |
0x02 |
分 |
0x09 |
年 |
0x03 |
闹钟分 |
0x0a |
寄存器A |
0x04 |
时 |
0x0b |
寄存器B |
0x05 |
闹钟时 |
0x0c |
寄存器C |
0x06 |
星期 |
0x0d |
寄存器D |
CMOS RAM的访问,需要两个端口:0x70是索引端口,用来指定内存单元;0x71是数据端口,用来读写相应单元里的内容。
举例:
mov al,2
out 0x70,al ;指定内存单元为2
in al,0x71 ;读RTC当前时间(分)
需要说明的是,从很早的时候开始,端口0x70的最高位是控制NMI中断的开关,当它为0时,允许NMI中断;为1时,阻断所有的NMI信号。其他7个bit,实际上用来指定CMOS RAM单元的索引号。
作者为了简化问题,所以在访问RTC时,直接关闭NMI,访问结束后,再打开NMI(不管它之前是不是打开的)。
查阅资料,有的朋友说“访问CMOS RAM可能导致产生NMI,所以需要关闭NMI。”
还有一点要注意:CMOS RAM中保存的日期和时间,默认是8421 BCD编码,也就是用0000~1001分别代表它所对应的十进制数。
.w0: mov al,0x0a ;访问寄存器Aor al,0x80 ;阻断NMIout 0x70,alin al,0x71 ;读寄存器Atest al,0x80 ;测试第7位UIP jnz .w0 ;以上代码对于更新周期结束中断来说是不必要的
test al,0x80 ,这句是测试寄存器A的bit7
正如书上155页所说:
CMOS RAM中的时间和日期会由RTC周期性地更新,在此期间,用户程序不应当访问它们。
寄存器A的bit7为0时,表示更新周期至少在488us内不会启动。换句话说,此时访问时间信息是安全的。
寄存器A的bit7为1时,表示正处于更新周期或者马上就要启动。
可以看到,上面的代码就是反复测试寄存器A的bit7,如果是0,可以向下执行。
xor al,alor al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(秒)push ax
这段代码很好理解,就是读出秒,并且把结果压栈(压栈时为了之后显示在屏幕上)
mov al,2or al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(分)push axmov al,4or al,0x80out 0x70,alin al,0x71 ;读RTC当前时间(时)push ax
道理同上。
mov al,0x0c ;寄存器C的索引。且开放NMI out 0x70,alin al,0x71 ;读一下RTC的寄存器C,否则只发生一次中断;此处不考虑闹钟和周期性中断的情况
这里要说一下寄存器C,这个寄存器是只读寄存器。可以通过读取这个寄存器,知道中断是否发生,如果发生,还可以知道中断原因。
寄存器C是8位寄存器。
[3:0]:保留;
[7]:中断请求标志,周期性中断/闹钟中断/更新结束中断,任何一种发生都会使这位置1;
[6]:周期性中断标志,置1则表示发生了周期性中断
[5]:闹钟中断标志,置1则表示发生了闹钟中断
[4]:更新结束中断标志,置1则表示发生了更新结束中断
注意,对寄存器的读操作将导致[7:4]清零。在中断发生后,我们应该读取这个寄存器,将其清零,否则同样的中断不再产生。
(2)把BCD码转换为ascii码
前面的代码中,把时分秒都读取出来并且压栈了。下一步的工作就是出栈,在屏幕上显示。前文已经说过,CMOS RAM中保存的日期和时间,默认是8421 BCD编码,所以我们可以利用一个过程,把BCD编码转换成与其对应的ascii码。
bcd_to_ascii: ;BCD码转ASCII;输入:AL=bcd码;输出:AX=asciimov ah,al ;分拆成两个数字 and al,0x0f ;仅保留低4位 add al,0x30 ;转换成ASCII shr ah,4 ;逻辑右移4位 and ah,0x0f add ah,0x30ret
举个例子来说吧,比如前面我们读取了小时到AL中,比如是12时,那么al=00010010b;前文我们压栈是把AX压进去,也就是说AX的低8位(AL)是有用的。现在我们需要调用这个过程,把00010010b转换成0x3132(因为字符‘1’对应的ASCII码是0x31,字符‘2’对应的ASCII码是0x32)。
mov ah,al ;分拆成两个数字
and al,0x0f ;仅保留低4位(就是个位)
add al,0x30 ;把个位转换成ASCII
shr ah,4 ;逻辑右移4位 ,ah中是十位数字
and ah,0x0f
add ah,0x30 ;把十位转换成ASCII
OK,这样之后,AX的高八位就是十位的ASCII,低八位就是个位的ASCII;
(3)把时间信息显示在屏幕上
mov ax,0xb800mov es,axpop axcall bcd_to_asciimov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示mov [es:bx],ahmov [es:bx+2],al ;显示两位小时数字mov al,':'mov [es:bx+4],al ;显示分隔符':'not byte [es:bx+5] ;反转显示属性
前两句让es指向了显示缓冲区;
pop ax ;小时出栈
call bcd_to_ascii ;转为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] ;反转显示属性
其实前两句可以写成
mov [es:bx+4],':’ ;显示分隔符':'
not是按位取反指令,假如之前属性是0x07(黑底白字),那么Not之后就是0xf8(闪烁白底灰色字)。
pop axcall bcd_to_asciimov [es:bx+6],ahmov [es:bx+8],al ;显示两位分钟数字mov al,':'mov [es:bx+10],al ;显示分隔符':'not byte [es:bx+11] ;反转显示属性pop axcall bcd_to_asciimov [es:bx+12],ahmov [es:bx+14],al ;显示两位小时数字
上面的代码同理。
mov al,0x20 ;中断结束命令EOI out 0xa0,al ;向从片发送 out 0x20,al ;向主片发送
书上162页已经说明:在中断处理过程的结尾,我们要显式地向8259芯片写中断结束命令EOI(至于具体原因,可以参考361页,图17-17:8259A的初始化命令字)。如果外部中断是8259主片处理的,那么仅发送给主片即可,端口号是0x20;如果外部中断是由从片处理的,那么命令既要发给主片也要发给从片,端口号是0xa0. 中断结束命令的代码是0x20.
pop espop dxpop cxpop bxpop axiret
寄存器出栈,用iret命令返回。
4.主程序
(1)初始化
start:mov ax,[stack_segment]mov ss,axmov sp,ss_pointermov ax,[data_segment]mov ds,axmov bx,init_msg ;显示初始信息 call put_stringmov bx,inst_msg ;显示安装信息 call put_string
这就是程序的入口了。首先,设置栈段,栈段被安排在整个程序的末尾,保留了256字节。
SECTION stack align=16 vstart=0resb 256 ss_pointer:;=============================================================================== SECTION program_trail program_end:
之后,设置好DS,令其指向数据段;然后显示一些信息。
(2)中断初始化和安装
书上158页说:在计算机启动期间,BIOS会初始化中断控制器,将主片的中断号设为从0x08开始,从片的从0x70开始。从上图可以看出来,实时时钟连到了从片的IR0,也就是说实时时钟的中断号是0x70.
mov al,0x70mov bl,4mul bl ;计算0x70号中断在IVT中的偏移mov bx,ax cli ;防止改动期间发生新的0x70号中断
前文已经说过:
中断向量在中断向量表中的位置=中断类型号×4
N*4的字单元存放偏移地址;
N*4+2的字单元存放段基址。
我们已经知道中断类型号是0x70了,下面要计算它在中断向量表中的位置(也就是计算0x70*4):用乘法指令, AX=AL*r8; 前四句执行后,BX中就是0x70号中断向量在向量表中的偏移。
cli这个指令用来清除IF位标志,相当于屏蔽外部中断。因为在修改中断向量表时,如果表项信息只修改了一部分,这时候发生0x70号中断,将会产生不可预料的问题。
push esmov ax,0x0000mov es,axmov word [es:bx],new_int_0x70 ;偏移地址。mov word [es:bx+2],cs ;段地址pop es
将ES压栈(暂时保存),并使它指向中断向量表所在的段,把偏移地址设置为new_int_0x70 ,把段基地址设置为CS。最后恢复ES。
mov al,0x0b ;RTC寄存器Bor al,0x80 ;阻断NMI out 0x70,almov al,0x12 ;设置寄存器B,禁止周期性中断,开放更 out 0x71,al ;新结束后中断,BCD码,24小时制
上面的代码用来设置寄存器B;寄存器B与本实验相关的位有:
[7]: 0表示更新周期每秒都会发生;1表示中止当前的更新周期,此后也不再产生更新周期;
[6]: 0表示禁止周期性中断,1表示允许周期性中断;
[5]: 0表示闹钟中断禁止,1表示闹钟中断允许;
[4]: 0表示禁止更新结束中断,1表示允许更新结束中断;
[3]:该位空着不用;
[2]:数据模式,0表示BCD,1表示2进制;
[1]: 小时格式,0表示12小时制(bit7为0时表示AM,为1表示PM,举例:在BCD模式下,10010001b表示上午11点),1表示24小时制;
[0]:该位空着不用;
从代码可以看出,我们写入寄存器B的值是0x12,也就是:
[7]:0,允许更新周期发生;
[6]:0,禁止周期性中断;
[5]:0,禁止闹钟中断;
[4]:1,允许更新结束中断;
[3]:0
[2]:0,BCD模式
[1]:1,24小时制
[0]:0
mov al,0x0cout 0x70,alin al,0x71 ;读RTC寄存器C,复位未决的中断状态
读寄存器C, 使之开始产生中断信号。注意,在向端口0x70写入al的同时,也打开了NMI,因为这是最后一次在主程序中访问RTC。到此,RTC芯片设置完毕。
in al,0xa1 ;读8259从片的IMR寄存器 and al,0xfe ;清除bit 0(此位连接RTC)out 0xa1,al ;写回此寄存器 sti ;重新开放中断
8259A内部有一个中断屏蔽寄存器,如下图所示:
IMR是一个8位的寄存器,位0-7对应着引脚中断IR0-IR7;如果对应的位为0,则允许中断;为1,则屏蔽中断。
我们通过端口0xa1读取从片的IMR寄存器,用and指令清除bit0(其他位保持原样),然后再写回去。这样,关于中断的初始化就完成了。
最后,sti指令将IF置1,打开中断。从这时候开始,随时发生的中断就可以被处理了。
mov bx,done_msg ;显示安装完成信息 call put_stringmov bx,tips_msg ;显示提示信息call put_string
显示一些信息,表示中断设置和安装已完成。
mov cx,0xb800mov ds,cxmov byte [12*160 + 33*2],'@' ;屏幕第12行,33列
在屏幕12行33列显示一个“@”;
.idle:hlt ;使CPU进入低功耗状态,直到用中断唤醒not byte [12*160 + 33*2+1] ;反转显示属性 jmp .idle
hlt是停机指令,使程序停止运行。这时候处理器进入暂停状态,不执行任何操作。当复位线上有复位信号、CPU响应非屏蔽中断、CPU响应可屏蔽中断3种情况之一发生时,CPU就会脱离暂停状态,执行hlt的下一条指令。
代码分析就到这里吧,下次我们看一下运行结果。
8086实时时钟实验(一)——《x86汇编语言:从实模式到保护模式》05相关推荐
- 8086实时时钟实验(二)——《x86汇编语言:从实模式到保护模式》读书笔记06
上次我们说了代码,这次我们说说怎样看到实验结果. 首先编译源文件(我的源文件就在当前路径下,a盘和c盘在上一级目录下): nasm -f bin c08_mbr.asm -o c08_mbr.bin ...
- 8086键盘输入实验——《x86汇编语言:从实模式到保护模式》读书笔记07
1.BIOS中断 我们可以为所有中断类型自定义中断处理过程,包括内部中断.硬件中断和软中断. BIOS中断,又称BIOS功能调用,主要是为了方便地使用最基本的硬件访问功能.通常,为了区分针对同一硬件的 ...
- 25 linux ndk 头文件_正点原子Linux第二十五章RTC实时时钟实验
1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第二十五章RTC实时时钟实验 实时时钟是很常用的一个外设 ...
- 【正点原子STM32连载】 第二十七章 RTC实时时钟实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- (实验15)单片机,STM32F4学习笔记,代码讲解【RTC实时时钟实验】【正点原子】【原创】
文章目录 其它文章链接,独家吐血整理 实验现象 主程序 RTC初始化程序 代码讲解 其它文章链接,独家吐血整理 (实验3)单片机,STM32F4学习笔记,代码讲解[按键输入实验][正点原子][原创] ...
- STM32学习笔记(十九)RTC实时时钟实验
STM32F103ZET6之RTC实时时钟实验 文章目录 STM32F103ZET6之RTC实时时钟实验 前言 一.简介 二.相关寄存器及配置过程 三.程序源码 1.rtc.h 2.rtc.c 3.m ...
- x86汇编语言从实模式百度云_x86汇编语言:从实模式到保护模式
x86汇编语言:从实模式到保护模式2013年1月由电子工业出版社出版发行,总共6000行的源代码,全方位地向读者展现汇编语言程序设计之美.尽管汇编语言也是一种计算机语言,但却是与众不同的,与它的同类们 ...
- 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16...
一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...
- 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16
一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...
最新文章
- 如何让机器说话更像人?清华和京东的三位大牛给出答案 | CCF C³
- 强化学习(二)马尔科夫决策过程(MDP)
- 2017-5-4 进程
- Python异常:TypeError: a bytes-like object is required, not 'str'
- mysql 异常笔记
- Ubuntu16.04.1安装Caffe(GPU)
- linux下mysql 8.0忘记密码后重置密码
- 数据链路层的介质访问控制协议
- CentOS 6.7 配置JSP运行环境之resin
- 泰然的粒子编辑器~~拿过来玩玩啊
- 【转】Java工程师成神之路
- ps42k20服务器出现问题_天高客户端访问软件出现“服务器操作系统原因”问题解决方法...
- python对excel读写操作
- QT on Android的rtsp播放器demo
- thrift开源项目研究
- elasticsearch 版本区别
- 树莓派3B安装64位操作系统(树莓派无需连接显示器键盘鼠标)
- 我的世界中国版服务器账号封了,Hypixel中国版服务器停止运营公告
- Android中的传感器之---磁场传感器
- 原创 基于微信小程序毕业设计题目选题课题 羽毛球篮球足球乒乓球场地球馆预约小程序的设计与实现(3)我的预约列表