遇到的瓶颈问题:如何能做到随时获取键盘中断,而不是一直等着按F1或ESC键。(int 16H的0号功能与1号功能的配合):
我们知道BIOS键盘缓冲区 15字节数据,高8位保存扫描码,低8位保存ASCII码。缓冲区为一个环形队列。int 16例程的0号功能可以从键盘缓冲区中阻塞式读取数据,获取到的数据会存放到AX中,高八位存放扫描码,低八位存放对应的ASCII码。
int 9H则可以用来向缓冲区中写数据,每写一次,缓冲区队尾指针就后移一次。int 16H读取一次(0号功能读)则队头指针就后移一次(即读取某字节后,该字节就被跳过,下次不会再读)。当队列为空时(队头队尾指针重合时),int 16H的0号功能再去读,读取不到则阻塞等待。
但是我们有时候需要非阻塞式读取,因为不能说读取不到键盘输入程序就不执行了(有时候键盘输入只是影响当前状态,并不能停止当前状态),那么我们就需要用int 16H的1号功能非阻塞读取缓冲区,但是非阻塞读取时,即使数据已经读取走,缓冲区也不会发生变化(只负责读取不负责修改队列,下次读的还是上一次的字节位置),所以需要每次在处理非阻塞读取到的内容的子程序中阻塞读取一次,将非阻塞读取但没有删除(即没有移动队头指针)的数据清除,也就是将队头指针后移一次。
对于阻塞读取非阻塞的残留数据,使用的自实现方法为:clear_keyboard_buffer
几个核心点:
①阻塞与非阻塞读取键盘缓冲区:clear_keyboard_buffer
②显示信息的组织与存储(不采用data段,而直接放在code段中)
③CMOS RAM的读取与修改
④在时间显示时,按键只F1、ESC键有效,其它键无效,也不影响键盘缓冲区与下次读取(程序中解决的基本问题,但没有解决个别键的影响问题)
;2017年5月13日16:18:14
;Mail:mailbox_krj@163.com
;Author:KangRuoJin
;Version:v1.2
assume cs:code,ss:stack
stack segment
db 128 dup (0)
stack ends
code segment
start:
mov bx,stack
mov ss,bx
mov sp,128
call copy_boot
mov bx,0
push bx
mov bx,7E00H
push bx
retf ;跳转到0:7E00H开始执行
mov ax,4C00H
int 21H
;================================================================
boot:
jmp boot_start
;*******************************************************************************************
MENU_1 db 'Weclome to System of Mr.Kang',0
MENU_2 db '(1) Reset PC',0
MENU_3 db '(2) Start System',0
MENU_4 db '(3) ShowClock',0
MENU_5 db '(4) Set Clock',0
MENU_6 db 'Please Enter Your Choose(1~4):',0
MENU_ADDRESS dw offset MENU_1 - offset boot + 7E00H
dw offset MENU_2 - offset boot + 7E00H
dw offset MENU_3 - offset boot + 7E00H
dw offset MENU_4 - offset boot + 7E00H
dw offset MENU_5 - offset boot + 7E00H
dw offset MENU_6 - offset boot + 7E00H
time db 'YY/MM/DD hh:mm:ss',0
cmos db 9,8,7,4,2,0
clockone db 'press F1 to change the color, press ESC to return',0
clock2 db 'Please input Date and Time,(YY MM DD hh mm ss):',0
change db 12 dup (0),0
;*******************************************************************************************
boot_start:
call init_reg ;ds、es寄存器初始化
call clear_screen ;清屏与屏幕初始化(包括边框显示)
call show_menu ;显示menu菜单
jmp choose_option ;开始进入选择的循环中去
mov ax,4C00H
int 21H
;================================================================
choose_option:
call clear_keyboard_buffer ;清除键盘缓冲区
mov ah,0
int 16H
cmp al,'1'
je choose_option_one
cmp al,'2'
je choose_option_two
cmp al,'3'
je choose_option_three
cmp al,'4'
je choose_option_four
jmp choose_option
;================================
choose_option_one:
jmp choose_option
choose_option_two:
jmp choose_option
choose_option_three:
call ShowClock
jmp boot_start ;跳回到boot_start清屏,并重新显示
choose_option_four:
call SetClock
jmp boot_start
;=================================================================
SetClock:
call init_reg ;寄存器初始化
call clear_screen ;清屏并重新画边框
call clear_change_stack ;清空存放修改内容的栈缓冲区
;设置显示提示信息的位置为10行15列开始
mov di,160*10+15*2
mov si,offset clock2 - offset boot + 7E00H
call show_line
;显示修改缓冲区中的内容
mov di,160*12+30*2
mov si,offset change - offset boot + 7E00H
call show_line
call get_string
call set_CMOSRAM
ret
;=================================================================
set_CMOSRAM:
mov bx,offset cmos - offset boot + 7E00H
mov si,offset change - offset boot + 7E00H
mov cx,6
ChangeMessage: ;GetMessage的逆过程
push cx
mov dx,ds:[si]
sub dx,3030H
mov cl,4
shl dl,cl
and dh,00001111B
or dl,dh
mov al,ds:[bx]
out 70H,al ;将al送入地址端口70h
mov al,dl
out 71H,al ; 将数据写入CMOSRAM时钟
inc bx
add si,2
pop cx
loop ChangeMessage
ret
;=================================================================
get_string:
mov si,offset change - offset boot + 07E00H
mov bx,0
getSting:
mov ah,0
int 16H
cmp al,'0' ;判断是否是小于‘0’
jb noNumber
cmp al,'9' ;判断是否大于'9'
ja noNumber
call char_push
cmp bx,12
ja EnterKey ;超过输入范围就不允许再输,直接结束
mov di,160*12+30*2 ;与SetClock中,显示修改缓冲区中的内容位置相对应
call show_line
jmp getSting
noNumber:
cmp ah,0EH ;退格通码
je BackspaceKey
cmp ah,1CH ;回车通码
je EnterKey
jmp getSting
BackspaceKey:
call char_pop
mov di,160*12+30*2 ;与SetClock中,显示修改缓冲区中的内容位置相对应
call show_line
jmp getSting
EnterKey:
ret
;=================================================================
char_push:
cmp bx,12
ja char_push_over ;大于12则不再入栈,直接返回
mov ds:[si+bx],al ;入栈
inc bx
char_push_over:
ret
char_pop:
cmp bx,0
je char_pop_over
dec bx
mov byte ptr ds:[si+bx],'*'
char_pop_over:
ret
;=================================================================
clear_change_stack:
push bx
push cx
mov bx,offset change - offset boot + 7E00H
mov cx,6
ClearChangeStack:
mov word ptr ds:[bx],'**'
add bx,2
loop ClearChangeStack
pop cx
pop bx
ret
;=================================================================
ShowClock:
call init_reg ;寄存器初始化
call clear_screen ;清屏并重新画边框
mov di,14*160+15*2 ;设置显示提示信息的位置为14行15列开始
mov si,offset clockone - offset boot + 7E00H
call show_line ;显示
ShowClockStart:
call GetTimeMessage ;获取时间信息放到time中去
mov di,10*160+30*2 ;设置显示时间与日期的位置为10行30列开始
mov si,offset time - offset boot + 7E00H
call show_line ;按规则显示
;这里的读取键盘缓冲区是自己完成,我们亦可以在
mov ah,1 ;非阻塞式读取,由于是非阻塞读取,当读取到之后,缓冲区不会清空
int 16H
jz ShowClockStart
cmp ah,3BH
je ChangeColor
cmp al,1BH ;ESC扫描码为1,与设定的功能号有冲突,所以用ASCII码
je ReturnToMainMenu
cmp al,0 ;能到达这一步说明不为F1也不为ESC
jne ClearKeyBuffer ;但是不为0说明一定是有键盘中断产生,就要清除无用的键盘中断
jmp ShowClockStart
;================================
ChangeColor: ;改变颜色
call ChangeShowColor
ClearKeyBuffer:
call clear_keyboard_buffer ;清除非阻塞读取时未清空的缓冲区
jmp ShowClockStart
ReturnToMainMenu: ;退回到choose_option时会清除缓冲区,所以这里不需要清除缓冲区
ret
;================================================================
GetTimeMessage:
mov cx,6
mov bx,offset cmos - offset boot + 7E00H
mov si,offset time - offset boot + 7E00H
GetMessage:
push cx
mov al,ds:[bx]
out 70H,al ;将al送入地址端口70h
in al,71H ;从数据端口71h处读出单元内容
mov ah,al
mov cl,4
shr al,cl ;右移4位
and ah,0FH ;al分成两个表示BCD码值的数据
add ax,3030H ;BCD码+30h=10进制数对应的ASCII码
mov word ptr ds:[si],ax ;ASCII码写入time段
inc bx
add si,3
pop cx
loop GetMessage
ret
;================================================================
ChangeShowColor:
push bx
push cx
mov cx,2000
mov bx,1
loopChangeShowColor:
add byte ptr es:[bx],1
or byte ptr es:[bx],00001000B ;高亮不闪烁设置
and byte ptr es:[bx],10001111B ;背景一直为黑色
add bx,2
loop loopChangeShowColor
pop cx
pop bx
ret
;================================================================
clear_keyboard_buffer:
mov ah,1
int 16H ;修改了ax,所以ZF可能会改变
jz ClearKeyboardBufferOver ;如果非阻塞读取的数据为0则结束
mov ah,0 ;否则,则继续用阻塞的方式清除一次缓冲区字节
int 16H
jmp clear_keyboard_buffer
ClearKeyboardBufferOver:
ret
;================================================================
init_reg:
mov bx,0B800H
mov es,bx
mov bx,0
mov ds,bx
ret
;================================================================
clear_screen:
mov bx,0
mov cx,2000
mov dx,1F00H ;黑屏白字
ClearScreen:
mov es:[bx],dx
add bx,2
loop ClearScreen
mov bx,6*160+10*2
mov cx,60
clear_showframe_UpandDown:
mov byte ptr es:[bx],'#'
mov byte ptr es:[bx+160*9],'#'
add bx,2
loop clear_showframe_UpandDown
mov bx,7*160+10*2
mov cx,8
clear_showframe_LeftandRight:
mov byte ptr es:[bx],'#'
mov byte ptr es:[bx+59*2],'#'
mov byte ptr es:[bx+2],'#'
mov byte ptr es:[bx+59*2-2],'#'
add bx,160
loop clear_showframe_LeftandRight
ret
;================================================================
show_menu:
mov bx,offset MENU_ADDRESS - offset boot + 7E00H
mov di,160*8 + 25*2
mov cx,6
ShowMenu:
mov si,ds:[bx] ;获取输出行信息的偏移地址
call show_line ;给定显示的位置显示一行
add bx,2 ;获取下一条内容的地址
add di,160 ;输出到下一行
loop ShowMenu
ret
;================================================================
show_line:
push ax
push si
push di
ShowLineStart:
mov al,ds:[si] ;获取一个字符
cmp al,0 ;遇到0则输出结束
je ShowLineOver
mov es:[di],al ;遇到的不是0则输出到显示屏上
add di,2
inc si
jmp ShowLineStart
ShowLineOver:
pop di
pop si
pop ax
ret
;================================================================
boot_end:nop
;================================================================
copy_boot: ;将代码段复制到0:7E00H处(正式的写法都需要将代码放到内存中去)
mov bx,cs
mov ds,bx
mov si,offset boot
mov bx,0
mov es,bx
mov di,7E00H
mov cx,offset boot_end - offset boot
cld
rep movsb
ret
;================================================================
code ends
end start
测试环境:VMWare Workstation 下的WINOWS XP(WINDOWS 8.1下的DOSBOX中去修改CMOS RAM的内容会无效,即选项4测试不成功)。
测试截图:
(1)、主界面:
(2)、时间显示:
(3)、F1键改变颜色:
上图也是按F1键改变颜色后的截图(总共有000~111的八种颜色切换)
(4)、设置日期与时间:
可以看到,我们修改的时间成功了: