深入探索BIOS级编程:从键盘输入到视频编程
1. 引言
在计算机编程的历史长河中,早期的程序员们热衷于深入了解计算机硬件,直接与之交互。如今,我们也能加入这个行列,在BIOS(基本输入 - 输出系统)层面开展工作。这些知识并非过时,对于嵌入式系统应用开发或者想要了解计算机BIOS设计的人来说,具有重要价值。
为确保与本章程序完全兼容,建议安装早期版本的Windows,如Windows 95或Windows 98。也可以使用软件工具在计算机上创建虚拟机进行实验。本章的所有程序均为16位实模式应用程序,可在Windows XP及更早版本的Microsoft Windows中开发和运行。通过学习,你将掌握以下实用技能:
- 了解按下键盘按键时的具体情况以及字符的最终去向。
- 学会检查键盘缓冲区是否有字符等待,并清除旧的按键记录。
- 掌握读取非ASCII键盘按键(如功能键和光标箭头)的方法。
- 学会显示彩色文本,并理解颜色基于视频显示的RGB颜色混合系统的原理。
- 掌握将屏幕划分为彩色面板并分别滚动的方法。
- 学会绘制256色的位图图形。
- 掌握检测鼠标移动和鼠标点击的方法。
2. BIOS数据区
BIOS数据区包含了ROM BIOS服务例程使用的系统数据。部分BIOS数据区内容如下表所示:
| 十六进制偏移量 | 描述 |
| ---- | ---- |
| 0000 – 0007 | 端口地址,COM1 – COM4 |
| 0008 – 000F | 端口地址,LPT1 – LPT4 |
| 0010 – 0011 | 已安装的硬件列表 |
| 0012 | 初始化标志 |
| 0013 – 0014 | 内存大小(以千字节为单位) |
| 0015 – 0016 | I/O通道中的内存 |
| 0017 – 0018 | 键盘状态标志 |
| 0019 | 备用键输入存储 |
| 001A – 001B | 键盘缓冲区指针(头) |
| 001C – 001D | 键盘缓冲区指针(尾) |
| 001E – 003D | 键盘预输入缓冲区 |
| 003E – 0048 | 软盘数据区 |
| 0049 | 当前视频模式 |
| 004A – 004B | 屏幕列数 |
| 004C – 004D | 再生(视频)缓冲区长度(以字节为单位) |
| 004E – 004F | 再生(视频)缓冲区起始偏移量 |
| 0050 – 005F | 视频页面1 - 8的光标位置 |
| 0060 | 光标结束行 |
| 0061 | 光标起始行 |
| 0062 | 当前显示的视频页面编号 |
| 0063 – 0064 | 活动显示基地址 |
| 0065 | CRT模式寄存器 |
| 0066 | 彩色图形适配器寄存器 |
| 0067 – 006B | 磁带数据区 |
| 006C – 0070 | 定时器数据区 |
例如,键盘预输入缓冲区(偏移量为001Eh)包含等待BIOS处理的按键的ASCII码和键盘扫描码。
3. 使用INT 16h进行键盘输入
3.1 键盘工作原理
键盘输入从键盘控制器芯片开始,最终将字符放入键盘预输入缓冲区。由于每个按键产生2个字节(ASCII码 + 扫描码),缓冲区最多可容纳15个按键。当用户按下按键时,会发生以下事件:
1. 键盘控制器芯片向PC的键盘输入端口发送8位数字扫描码(sc)。
2. 输入端口触发中断,向CPU发送预定义信号,表明输入 - 输出设备需要关注。CPU响应并执行INT 9h服务例程。
3. INT 9h服务例程从输入端口获取键盘扫描码(sc),查找对应的ASCII码(ac)(如果有),并将扫描码和ASCII码插入键盘预输入缓冲区。如果扫描码没有匹配的ASCII码,预输入缓冲区中该键的ASCII码为零或E0h。
在MS - Windows下,部分扩展键按下时的ASCII码如下表所示:
| 按键 | ASCII码 |
| ---- | ---- |
| Ins, Del, PageUp, PageDown, Home, End, Up arrow, Down arrow, Left arrow, Right arrow | E0h |
| 功能键(F1 – F12) | 00h |
一旦扫描码和ASCII码安全地存入预输入缓冲区,它们将一直保留,直到当前运行的程序将其取出。在实模式应用程序中,有两种方法可以实现这一点:
- 调用BIOS级函数INT 16h,从键盘预输入缓冲区中获取扫描码和ASCII码。这在处理无ASCII码的扩展键(如功能键和光标箭头)时非常有用。
- 调用MS - DOS级函数INT 21h,从输入缓冲区中获取ASCII码。如果按下了扩展键,需要再次调用INT 21h以获取扫描码。
3.2 INT 16h函数
INT 16h在键盘处理方面比INT 21h具有明显优势。它可以一步获取扫描码和ASCII码,并且具有设置键盘重复率和获取键盘标志状态等额外操作。以下是一些常用的INT 16h函数:
设置键盘重复率(03h)
该函数可设置键盘按键重复率。当按住按键时,按键开始重复前会有250到1000毫秒的延迟,重复率范围为1Fh(最慢)到0(最快)。
| 描述 | 接收参数 | 返回值 | 示例调用 |
| ---- | ---- | ---- | ---- |
| 设置键盘重复率 | AH = 3,AL = 5,BH = 重复延迟(0 = 250 ms;1 = 500 ms;2 = 750 ms;3 = 1000 ms),BL = 重复率:0 = 最快(30/秒),1Fh = 最慢(2/秒) | 无 | mov ax,0305h
mov bh,1 ; 500 ms重复延迟
mov bl,0Fh ; 重复率
int 16h |
将按键推入键盘缓冲区(05h)
该函数可将按键(由8位ASCII码和8位键盘扫描码组成)推入键盘预输入缓冲区。
| 描述 | 接收参数 | 返回值 | 示例调用 |
| ---- | ---- | ---- | ---- |
| 将按键推入键盘缓冲区 | AH = 5,CH = 扫描码,CL = ASCII码 | 如果预输入缓冲区已满,CF = 1且AL = 1;否则,CF = 0,AL = 0。 | mov ah,5
mov ch,3Bh ; F1键的扫描码
mov cl,0 ; ASCII码
int 16h |
等待按键(10h)
该函数从键盘预输入缓冲区中移除下一个可用按键。如果没有按键等待,键盘处理程序将等待用户按下按键。
| 描述 | 接收参数 | 返回值 | 示例调用 |
| ---- | ---- | ---- | ---- |
| 等待按键并扫描键盘 | AH = 10h | AH = 键盘扫描码,AL = ASCII码 | mov ah,10h
int 16h
mov scanCode,ah
mov ASCIICode,al |
以下是一个使用INT 16h输入按键并显示每个按键的ASCII码和扫描码的示例程序,当按下Esc键时程序终止:
TITLE Keyboard Display (Keybd.asm)
; This program displays keyboard scan codes
; and ASCII codes, using INT 16h.
INCLUDE Irvine16.inc
.code
main PROC
mov
ax,@data
mov
ds,ax
call
ClrScr
; clear screen
L1:
mov
ah,10h
; keyboard input
int
16h
; using BIOS
call
DumpRegs
; AH = scan, AL = ASCII
cmp
al,1Bh
; ESC key pressed?
jne
L1
; no: repeat the loop
call
ClrScr
; clear screen
exit
main ENDP
END main
检查键盘缓冲区(11h)
该函数可查看键盘预输入缓冲区中是否有按键等待,返回下一个可用按键的ASCII码和扫描码(如果有),但不会从缓冲区中移除该按键。
| 描述 | 接收参数 | 返回值 | 示例调用 |
| ---- | ---- | ---- | ---- |
| 检查键盘缓冲区 | AH = 11h | 如果有按键等待,ZF = 0,AH = 扫描码,AL = ASCII码;否则,ZF = 1。 | mov ah,11h
int 16h
jz NoKeyWaiting ; 缓冲区无按键
mov scanCode,ah
mov ASCIICode,al |
获取键盘标志(12h)
该函数返回当前键盘标志的有价值信息。键盘标志位于BIOS数据区的00417h - 00418h地址处。
| 描述 | 接收参数 | 返回值 | 示例调用 |
| ---- | ---- | ---- | ---- |
| 获取键盘标志 | AH = 12h | AX = 键盘标志的副本 | mov ah,12h
int 16h
mov keyFlags,ax |
键盘标志能反映用户使用键盘的情况,例如用户是否按下了左Shift键、右Shift键或Alt键等。每个位为1表示对应的键当前被按下或处于切换开启状态(如Caps lock、Scroll lock、Num lock和Insert)。在Windows 95和98中,也可以通过直接读取段地址为0040h、偏移量为17h - 18h的内存来获取键盘标志字节。
清除键盘缓冲区
程序通常有一个处理循环,只能被预先安排的按键中断。例如,基于DOS的游戏程序在显示图形图像的同时,会检查键盘缓冲区是否按下了箭头键和其他特殊键。用户可能按下许多无关按键,这些按键只会填满键盘预输入缓冲区,但当按下正确的按键时,程序应立即响应命令。
以下是一个名为ClearKeyboard的程序示例,使用循环清除键盘缓冲区,同时检查特定的键盘扫描码:
TITLE Testing ClearKeyboard (ClearKbd.asm)
; This program shows how to clear the keyboard
; buffer while waiting for a particular key.
; To test it, rapidly press random keys to fill
; up the buffer. When you press Esc, the program
; ends immediately.
INCLUDE Irvine16.inc
ClearKeyboard PROTO, scanCode:BYTE
ESC_key = 1
; scan code
.code
main PROC
L1:
; Display a dot, to show program's progress
mov
ah,2
mov
dl,'.'
int
21h
mov
eax,300
; delay for 300 ms
call
Delay
INVOKE ClearKeyboard, ESC_key
; check for Esc key
jnz
L1
; continue loop if ZF=0
quit:
call
Clrscr
exit
main ENDP
;---------------------------------------------------
ClearKeyboard PROC,
scanCode:BYTE
;
; Clears the keyboard while checking for a
; particular scan code.
; Receives: keyboard scan code
; Returns: Zero flag set if the ASCII code is
; found; otherwise, Zero flag is clear.
;---------------------------------------------------
push
ax
L1:
mov
ah,11h
; check keyboard buffer
int
16h
; any key pressed?
jz
noKey
; no: exit now
mov
ah,10h
; yes: remove from buffer
int
16h
cmp
ah,scanCode
; was it the exit key?
je
quit
; yes: exit now (ZF=1)
jmp
L1
; no: check buffer again
noKey:
; no key pressed
or
al,1
; clear zero flag
quit:
pop
ax
ret
ClearKeyboard ENDP
END main
该程序每300毫秒在屏幕上显示一个点。测试时,快速按下随机按键填充缓冲区,当按下Esc键时,程序立即结束。
3.3 键盘输入部分小结
通过以上内容,我们了解了键盘的工作原理、INT 16h函数的使用方法,包括设置键盘重复率、将按键推入缓冲区、等待按键、检查缓冲区和获取键盘标志等。同时,还学习了如何清除键盘缓冲区以确保程序能及时响应特定按键。这些知识为我们在BIOS层面处理键盘输入提供了强大的工具。
4. 使用INT 10h进行视频编程
4.1 基本背景
当应用程序需要在文本模式下在屏幕上写入字符时,可以选择以下三种输出方式:
-
MS - DOS级访问
:任何运行或模拟MS - DOS的计算机都可以使用INT 21h将文本写入视频显示器。输入/输出可以轻松重定向到其他设备(如打印机或磁盘),但输出速度较慢,且无法控制文本颜色。
-
BIOS级访问
:使用INT 10h函数输出字符,即BIOS服务。执行速度比INT 21h快,并且可以指定文本颜色。在填充大的屏幕区域时,通常会检测到轻微延迟,输出无法重定向。
-
直接视频内存访问
:将字符直接移动到视频RAM,执行瞬间完成,输出无法重定向。在MS - DOS时代,文字处理器和电子表格程序都使用这种方法。在Windows NT、2000、XP及更高版本中,此方法仅限于全屏模式。
应用程序根据自身需求选择不同的访问级别。对性能要求最高的程序选择直接视频访问;其他程序选择BIOS级访问。当输出可能需要重定向或屏幕需要与其他程序共享时,使用MS - DOS级访问。需要注意的是,MS - DOS中断使用BIOS级例程来完成工作,而BIOS例程使用直接视频访问来产生输出。
4.2 全屏模式运行程序
使用视频BIOS绘制图形的程序应在以下环境之一中执行:
- 纯MS - DOS
- Linux下的DOS模拟器
- MS - Windows的全屏模式
在MS - Windows中,有几种方法可以切换到全屏模式:
- 在Windows XP中,为程序的EXE文件创建快捷方式,打开快捷方式的属性对话框,选择“选项”,在“显示选项”组中选择“全屏模式”。(注意:Windows Vista不支持以全屏模式运行16位EXE程序。)
- 从“开始”菜单打开命令窗口,按Alt - Enter切换到全屏模式。使用CD(更改目录)命令导航到EXE文件所在目录,输入程序名称运行。Alt - Enter是一个切换键,再次按下将程序返回窗口模式。
4.3 理解视频文本
在基于Intel的系统中,有两种基本的视频模式:文本模式和图形模式。程序只能在一种模式下运行,不能同时运行两种模式:
-
文本模式
:程序向屏幕写入ASCII字符,BIOS中的内置字符生成器为每个字符生成位图图像。在文本模式下,程序无法绘制任意线条和形状。
-
图形模式
:程序控制每个屏幕像素的外观。操作相对原始,因为没有内置的线条和形状绘制函数。可以使用内置函数在图形模式下向屏幕写入文本,也可以用不同的字体替换内置字体。MS - Windows提供了一组用于在图形模式下绘制形状和线条的函数。
当计算机在MS - DOS下启动时,视频控制器设置为视频模式3(彩色文本,默认80列×25行)。在文本模式下,行从屏幕顶部开始编号,从0开始;列从屏幕左侧开始编号,从0开始。每行的高度为当前活动字体的字符单元格高度,每列的宽度为字符单元格宽度。
4.4 字体
字符由内存中的字符字体表生成。BIOS允许程序在运行时重写字符表,从而显示自定义字体。
通过以上内容,我们对使用INT 10h进行视频编程有了初步了解,包括不同的访问级别、全屏模式的运行方法、视频文本模式以及字体相关知识。这些知识为进一步深入学习视频编程奠定了基础。
深入探索BIOS级编程:从键盘输入到视频编程
5. INT 10h视频编程的具体功能
5.1 控制颜色
在BIOS级访问中,使用INT 10h可以控制文本的颜色。这是基于视频显示的RGB颜色混合系统。通过设置相应的参数,可以指定文本的前景色和背景色。例如,某些INT 10h的功能可以接收颜色参数,从而实现不同颜色文本的显示。
5.2 INT 10h视频函数
INT 10h有许多强大的视频函数,以下是一些常见的函数及其功能:
| 功能 | 描述 | 接收参数 | 返回值 |
| ---- | ---- | ---- | ---- |
| 设置光标位置 | 将光标移动到指定的屏幕位置 | AH = 02h,BH = 视频页面号,DH = 行号,DL = 列号 | 无 |
| 显示字符 | 在当前光标位置显示字符 | AH = 0Eh,AL = 字符的ASCII码,BH = 视频页面号,BL = 前景色 | 无 |
| 设置视频模式 | 切换到指定的视频模式 | AH = 00h,AL = 视频模式编号 | 无 |
以下是一个使用INT 10h显示字符的示例代码:
TITLE Display Character with INT 10h (DisplayChar.asm)
INCLUDE Irvine16.inc
.code
main PROC
mov ax, @data
mov ds, ax
mov ah, 00h ; 设置视频模式为彩色文本模式
mov al, 03h
int 10h
mov ah, 02h ; 设置光标位置到第10行,第20列
mov bh, 0
mov dh, 10
mov dl, 20
int 10h
mov ah, 0Eh ; 显示字符 'A'
mov al, 'A'
mov bh, 0
mov bl, 0Fh ; 白色前景色
int 10h
exit
main ENDP
END main
5.3 库过程示例
在实际编程中,我们可以创建一些库过程来简化INT 10h的使用。例如,创建一个过程用于在指定位置显示字符串:
; 显示字符串过程
DisplayString PROC,
stringPtr: PTR BYTE,
row: BYTE,
col: BYTE
push ax
push bx
push dx
mov ah, 02h ; 设置光标位置
mov bh, 0
mov dh, row
mov dl, col
int 10h
mov si, stringPtr
ShowCharLoop:
lodsb
cmp al, 0
je EndShowChar
mov ah, 0Eh ; 显示字符
mov bh, 0
mov bl, 0Fh ; 白色前景色
int 10h
jmp ShowCharLoop
EndShowChar:
pop dx
pop bx
pop ax
ret
DisplayString ENDP
6. 使用INT 10h绘制图形
6.1 INT 10h像素相关函数
INT 10h提供了一些与像素相关的函数,用于在图形模式下绘制图形。例如,设置像素颜色和读取像素颜色的函数:
| 功能 | 描述 | 接收参数 | 返回值 |
| ---- | ---- | ---- | ---- |
| 设置像素颜色 | 在指定位置设置像素的颜色 | AH = 0Ch,AL = 颜色值,CX = 列号,DX = 行号 | 无 |
| 读取像素颜色 | 读取指定位置像素的颜色 | AH = 0Dh,CX = 列号,DX = 行号 | AL = 像素颜色值 |
以下是一个使用INT 10h设置像素颜色的示例代码:
TITLE Set Pixel Color with INT 10h (SetPixel.asm)
INCLUDE Irvine16.inc
.code
main PROC
mov ax, @data
mov ds, ax
mov ah, 00h ; 设置视频模式为图形模式
mov al, 13h ; 320x200, 256色模式
int 10h
mov ah, 0Ch ; 设置像素颜色
mov al, 0Fh ; 白色
mov cx, 100
mov dx, 100
int 10h
mov ah, 00h ; 恢复到文本模式
mov al, 03h
int 10h
exit
main ENDP
END main
6.2 DrawLine程序
为了绘制线条,我们可以编写一个DrawLine程序。以下是一个简单的绘制直线的算法示例:
; DrawLine过程
DrawLine PROC,
x1: WORD,
y1: WORD,
x2: WORD,
y2: WORD,
color: BYTE
push ax
push bx
push cx
push dx
mov ax, x1
mov bx, y1
mov cx, x2
mov dx, y2
mov al, color
DrawLoop:
mov ah, 0Ch ; 设置像素颜色
int 10h
; 简单的直线绘制算法,这里只是示例,可根据需要优化
cmp ax, cx
je CheckY
inc ax
jmp DrawLoop
CheckY:
cmp bx, dx
je EndDraw
inc bx
jmp DrawLoop
EndDraw:
pop dx
pop cx
pop bx
pop ax
ret
DrawLine ENDP
6.3 笛卡尔坐标程序
在图形编程中,笛卡尔坐标是常用的坐标系统。我们可以编写一个程序将笛卡尔坐标转换为屏幕坐标。以下是一个简单的转换示例:
; 笛卡尔坐标转换为屏幕坐标
CartesianToScreen PROC,
x: WORD,
y: WORD,
screenX: PTR WORD,
screenY: PTR WORD
push ax
push bx
mov ax, x
add ax, 160 ; 假设屏幕中心为原点
mov bx, y
add bx, 100
mov [screenX], ax
mov [screenY], bx
pop bx
pop ax
ret
CartesianToScreen ENDP
7. 内存映射图形
7.1 模式13h:320x200,256色
模式13h是一种常见的图形模式,它提供了320x200的分辨率和256种颜色。在这种模式下,可以直接访问视频内存来绘制图形。视频内存的起始地址为A000:0000。
7.2 内存映射图形程序
以下是一个简单的内存映射图形程序示例,用于在模式13h下绘制一个矩形:
TITLE Memory-Mapped Graphics (MemMapGraphics.asm)
INCLUDE Irvine16.inc
.code
main PROC
mov ax, @data
mov ds, ax
mov ah, 00h ; 设置视频模式为模式13h
mov al, 13h
int 10h
mov ax, 0A000h ; 视频内存段地址
mov es, ax
mov di, 0 ; 视频内存偏移地址
mov cx, 320 * 200 ; 总像素数
mov al, 0Fh ; 白色
FillLoop:
stosb
loop FillLoop
mov ah, 00h ; 恢复到文本模式
mov al, 03h
int 10h
exit
main ENDP
END main
8. 鼠标编程
8.1 鼠标INT 33h函数
INT 33h提供了一系列用于鼠标编程的函数。以下是一些常见的函数及其功能:
| 功能 | 描述 | 接收参数 | 返回值 |
| ---- | ---- | ---- | ---- |
| 初始化鼠标 | 启用鼠标并设置鼠标指针的初始位置 | AX = 00h | CX = 水平分辨率,DX = 垂直分辨率 |
| 显示鼠标指针 | 在屏幕上显示鼠标指针 | AX = 01h | 无 |
| 隐藏鼠标指针 | 隐藏鼠标指针 | AX = 02h | 无 |
| 获取鼠标位置和按钮状态 | 获取鼠标的当前位置和按钮状态 | AX = 03h | BX = 按钮状态,CX = 水平位置,DX = 垂直位置 |
8.2 鼠标跟踪程序
以下是一个简单的鼠标跟踪程序示例:
TITLE Mouse Tracking (MouseTrack.asm)
INCLUDE Irvine16.inc
.code
main PROC
mov ax, @data
mov ds, ax
mov ax, 00h ; 初始化鼠标
int 33h
mov ax, 01h ; 显示鼠标指针
int 33h
TrackLoop:
mov ax, 03h ; 获取鼠标位置和按钮状态
int 33h
mov bx, 0 ; 隐藏鼠标指针
int 33h
mov ah, 02h ; 设置光标位置
mov dh, dl
mov dl, cl
int 10h
mov ah, 0Eh ; 显示字符 'M'
mov al, 'M'
mov bh, 0
mov bl, 0Fh
int 10h
mov ax, 01h ; 显示鼠标指针
int 33h
jmp TrackLoop
mov ax, 02h ; 隐藏鼠标指针
int 33h
mov ah, 00h ; 恢复到文本模式
mov al, 03h
int 10h
exit
main ENDP
END main
9. 总结
通过以上内容,我们深入学习了BIOS级编程的多个方面,包括键盘输入、视频编程、图形绘制、内存映射图形和鼠标编程。掌握这些知识可以让我们更深入地了解计算机硬件的工作原理,并且能够开发出高性能的应用程序。在实际应用中,我们可以根据具体需求选择合适的编程方法和函数,以实现各种功能。同时,我们还学习了许多实用的代码示例,这些示例可以作为我们进一步开发的基础。希望这些内容对大家在计算机编程领域的学习和实践有所帮助。
BIOS级编程:键盘与视频控制
超级会员免费看
54

被折叠的 条评论
为什么被折叠?



