在屏幕中间显示,按ESC键后改变字符的颜色

本文介绍了一个程序,该程序能在屏幕中间显示从'a'到'z'的所有字符,并利用CPU空循环实现延迟效果。当按下Esc键时,程序会改变当前显示字符的颜色。此外,还详细解释了如何通过编写自定义的int9中断例程来实现这一功能。

程序功能:编写程序在屏幕中间显示“a”~“z”,并可以让人看清,这个任务比较好实现。
(1)在b800:[ 160*12+40*2]处存入a的ASCII码、(2)在循环中使用一个100000000000H次的循环空转达到延迟效果、(3)按键盘引发int9中断改变颜色
那么如何实现,按下Esc 键后,改变显示的颜色呢?
键盘输入到达60h 端口后,就会引发9号中断,CPU 则转去执行int 9中断例程。
我们可以编写int 9中断例程,功能如下:
(1)从60h 端口读出键盘的输入;
(2)调用BIOS 的int 9 中断例程,处理其他硬件细节;
(3)判断是否为Esc的扫描码,如果是,改变显示的颜色后返回;如果不是则直接返回。
我们对这些功能的实现一一进行分析
1、从端口60h读出键盘的输入使用:in al,60h
2、调用BIOS的int 9中断例程
有一点要注意的是,我们写的中断处理程序要成为新的int 9中断例程,主程序必须要将中断向量表中的int 9中断例程的入口地址改为我们写的中断处理程序的入口地址。那么在新的中断处理程序中调用原来的int 9中断例程时,中断向量表中的int 9中断例程的入口地址却不是原来的int 9 中断例程的地址。所以我们不能使用int 指令直接调用。要在我们写的新中断例程中调用原来的中断例程,就必须在将中断向量表中的中断例程的入口地址改为新地址之前,将原来的入口地址保存起来。这样,在需要调用的时候,我们才能找到原来的中断例程的入口。对于我们现在的问题,假设我们将原来int 9中断例程的偏移地址和段地址保存在ds:[0]和ds:[2]单元中。那么我们在需要调用原来的int 9中断例程时候,就可以在ds:[0]、ds:[2] 单元中找到它的入口地址。那么,有了入口地址后,我们如何进行调用呢?
当然不能使用指令int 9来调用。我们可以用别的指令来对int指令进行一些模拟,从而实现对中断例程的调用。我们来看,int 指令在执行的时候,CPU 进行下面的工作:
(1)取中断类型码n;
(2)标志寄存器入栈;
(3)IF=0,TF=0;
(4)CS 、IP 入栈;
(5)设置(IP)=(n*4),(CS=(n*4+2)。
取中断类型码是为了定位中断例程的入口地址,在我们的问题中,中断例程的入口地址已经知道。所以,我们用别的指令模拟int指令时候,不需要做第(1)步。在假设要调用的中断例程的入口地址在ds:0和ds:2单元中的前提下,我们将int 过程用下面几步模拟:
(1)标志寄存器入栈;
(2)IF=0,TF=0;
(3)CS、IP入栈;
(4)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
可以注意到第(3)、(4)步和call dword ptr ds:[0]的功能一样。call dword ptr ds:[0]的功能也是:(1)CS 、IP 入栈;(2)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
说明:如果还有疑问,复习10.6节的内容。
所以int 过程的模拟过程变为:
(1)标志寄存器入栈;
(2)IF=0,TF=0;
(3)call dword ptr ds:[0]
对于(1),可用pushf来实现。
对于(2),可用and和popf实现,如下面的指令实现。
实现IF=0,TF=0步骤:
pushf
pop ax
and ah,11111100b ;IF和OF为标志寄存器的第9位和第8位
push ax
popf
这样,模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序如下
pushf ;标志寄存器入栈
pushf;实现IF=0,TF=0的功能
pop ax
and ah,11111100b ;IF和OF为标志寄存器的第9位和第8位
push ax
popf ;IF=0、TF=0
call dword ptr ds:[0];call功能: ①CS、IP入栈,②;(IP)=((ds)*16+0),③;(CS)=((ds)*16+2)
3、如果是Esc键的扫描码,改变显示的颜色后返回,如何改变显示的颜色?
显示的位置是屏幕的中问,即第12行40列,显存中的偏移地址为:160*12+40* 2。所以字符的ASCII码要送入b800:160*12+40*2处。而b800:160*12+40*2+1处是字符的属性,我们只要改变此处的数据就可以改变在b800:160*12+40*2处显示的字符的颜色了。
该程序的最后一个问题是,要在程序返回前,将中断向量表中的ini 9中断例程的入口地址恢复为原来的地址。否则程序返回后,别的程序将无法使用键盘。
注意,本章中所有关于键盘的程序,因要直接访问真实的硬件,则必须在DOS实模式下运行。在Windows 2000 的DOS 方式下运行,会出现一些和硬件工作原理不符合的现象。
开发int9中断例程架构:
①在主程序中把原来的int9的原始程序入口保存到data段中,并把自己写的int9中断例程入口地址替换到中断向量表的9号中断地址,对应的是IP是0: [9*4]和CS是0[9*4+2]
②等待外部中断自动调用int9
③程序运行完后还原int9原来的中断例程入口
在int9中断例程内部结构
1.保存用到的通用寄存器
2.接收60h端口的数据
3.修改IF和TF的值为0
4.处理数据
5.使用call模拟调用int9的系统中断例程
6.还原通用寄存器
说明:由于中断例程使用的是iret返回,而iret的过程是①从栈中还原IP和CS,②从栈中还原寄存器状态;这里使用了call的远跳转(地址在data段中),而用call过程是先把CS和IP保存进栈,跳转到指定地址执行完再通过retf返回,调用完成后再从栈中还原IP和CS;而这里我们调用的是中断例程,是用iret返回的,retf和iret返回的IP和CS顺序相同,而iret比retf多一步还原寄存器状态,所以我们要构造供iret的返回的栈数据:就是在call前先保存寄存器状态,然后就可以使用iret的形式还原程序的IP,CS,标志寄存器。我们只要在自己编写的中断例程中处理完自己的数据后再调用BIOS 的int 9中断例程就可以了。

;程序功能:在屏幕中间同一点显示a-z的所有字符
;        1.使用cpu循环空运行实现延迟
;        2.按ESC键改变正在循环显示的字符的颜色
;        3.程序完成时,再次还原int9中断向量表
assume cs:code
data segment
db 100 dup(0)

data ends

code segment
start: 
    ;init ds,es
    mov ax,data
    mov ds,ax
    mov ax,0
    mov es,ax
    
    ; save int9 IP and CS
    mov ax,es:[9*4]
    mov ds:[0],ax
    mov ax,es:[9*4+2]
    mov ds:[2],ax
    mov es:[9*4],offset int9
    mov es:[9*4+2],cs
    
    ;循环显示a-z的所有字符
    mov ax,0b800h
    mov es,ax
    mov ah,'a'
    show: mov es:[160*12+40*2],ah
    call delay    ;调用延迟
    inc ah
    cmp ah,'z'
    jna show
    

    ;还原int9中断向量表
    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


;使用cup循环空运行,实现延迟时间的作用
;使用32位的借位减法实现 1000 0000 0000h 次数的循环
delay:
    push ax
    push dx
    mov dx,1000h
    mov ax,0
    s: sub ax,1
    sbb dx,0
    cmp ax,0
    jne s
    cmp dx,0
    jne s
    pop dx
    pop ax
    ret

;实现int9中断例程
;pushf是16位,pop是16位
int9:
    
    push ax    ;保存临时变量
    in al,60h
    
    pushf    ;用于call模拟int9后的popf操作
    pushf    ;用于修改IF=0、TF=0 操作
    pop bx
    and bh,11111100b
    push bx
    popf
    call dword ptr ds:[0]    
    ;调用完这个模拟的int9后,会执行iret操作
    ;会先popf出第一次pushf的值
    
    cmp al,1
    jne int9ret
    add byte ptr es:[160*12+40*2+1],11h
    int9ret:
    pop ax
    iret


code ends
end start

程序简化:

1)在键盘按键的时候触发int9中断例程,这时候IF和TF都已经设置成0了,使用我们就没必要设置了

2)原始的int9可以保存到0:200中,在程序中使用中断调用

存在的问题:

在上面的程序,如果设置中断向量表的时候没有全部完成,就是重定位中断向量表地址的时候出现了键盘的按键中断,在CUP会跳到一个错误的地址执行,如何解决呢?                            提示:使用sti和cli命令

转载于:https://www.cnblogs.com/mq0036/p/5150801.html

#include <graphics.h> #include <conio.h> #include <stdio.h> #define BUTTON_TOP 100 void main() { initgraph(800,600); float H=220; float S=1; float L=0.7f; for (int y=0;y<600;y++) { L+=0.0005f; setlinecolor(HSLtoRGB(H,S,L)); line(0,y,799,y); } H=0; S=1; L=0.6f; setlinestyle(PS_SOLID,10); for (int r=400;r>344;r--) { H+=5; setlinecolor(HSLtoRGB(H,S,L)); circle(700,600,r); } settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLUE); outtextxy(10, 550, "Esc: exit");//指定位置输出字符串 settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLUE); outtextxy(10, 50, "班级:电子信息工程一班");//指定位置输出字符串settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLUE); outtextxy(10, 80, "姓名:林佳豪");//指定位置输出字符串 settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLUE); outtextxy(10, 110, "学号:2201020116");//指定位置输出字符串 RECT rStr1={300, BUTTON_TOP, 500,BUTTON_TOP+50}; char str1[]="HDMI 1"; char inputChar; setfillcolor(YELLOW);//设置当前的填充颜色 solidroundrect(300, BUTTON_TOP, 500,BUTTON_TOP+50,20,20);//画填充圆角矩形,前2个参数左顶点坐标,中间两个参数右下点的坐标,后2个参数为椭圆的宽高 settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLACK);//设置当前背景色 settextstyle(24, 0, "宋体");//设置字体大小 参数24为高度,参数0为宽度自适应 drawtext(str1, &rStr1, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在指定区域内以指定格式输出字符串,水平、垂直居中(单行有效) RECT rStr2={300, BUTTON_TOP+200, 500,BUTTON_TOP+50}; char str2[]="HDMI 2"; char inputChar2; setfillcolor(YELLOW);//设置当前的填充颜色 solidroundrect(300, BUTTON_TOP+100, 500,BUTTON_TOP+150,20,20);//画填充圆角矩形,前2个参数左顶点坐标,中间两个参数右下点的坐标,后2个参数为椭圆的宽高 settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLACK);//设置当前背景色 settextstyle(24, 0, "宋体");//设置字体大小 参数24为高度,参数0为宽度自适应 drawtext(str2, &rStr2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在指定区域内以指定格式输出字符串,水平、垂直居中(单行有效) RECT rStr3={300, BUTTON_TOP+400, 500,BUTTON_TOP+50}; char str3[]="HDMI 3"; char inputChar3; setfillcolor(YELLOW);//设置当前的填充颜色 solidroundrect(300, BUTTON_TOP+200, 500,BUTTON_TOP+250,20,20);//画填充圆角矩形,前2个参数左顶点坐标,中间两个参数右下点的坐标,后2个参数为椭圆的宽高 settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLACK);//设置当前背景色 settextstyle(24, 0, "宋体");//设置字体大小 参数24为高度,参数0为宽度自适应 drawtext(str3, &rStr3, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在指定区域内以指定格式输出字符串,水平、垂直居中(单行有效) RECT rStr4={300, BUTTON_TOP+600, 500,BUTTON_TOP+50}; char str4[]="HDMI 4"; char inputChar4; setfillcolor(YELLOW);//设置当前的填充颜色 solidroundrect(300, BUTTON_TOP+300, 500,BUTTON_TOP+350,20,20);//画填充圆角矩形,前2个参数左顶点坐标,中间两个参数右下点的坐标,后2个参数为椭圆的宽高 settextcolor(WHITE);//设置当前文字颜色 setbkcolor(BLACK);//设置当前背景色 settextstyle(24, 0, "宋体");//设置字体大小 参数24为高度,参数0为宽度自适应 drawtext(str4, &rStr4, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在指定区域内以指定格式输出字符串,水平、垂直居中(单行有效) while((inputChar=getch())!=27)//当输入不是Esc时,一直执行while循环 { switch(inputChar) { case 72: printf("input up\n"); break; case 80: printf("input down\n"); break; case 75: printf("input left\n"); break; case 77: printf("input right\n"); break; case 13: printf("input Enter\n"); break; } } closegraph(); // 关闭图形界面 }怎么让上下控制选项HDMI 1到HDMI 4 和esc退出程序
03-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值