【读书笔记-《30天自制操作系统》-13】Day14

相比前几篇的内容,本篇不仅内容更为简单,而且与显示相关,更为有趣。首先通过调用VBE的显示模式提高显示画面的分辨率,然后分别实现按下键盘按键显示对应的字符,以及通过鼠标移动窗口。因为是以前面讲过的很多内容为基础,程序代码很简单,而且能切实看到成果,也更有趣。

1. 提高画面分辨率

现在要把显示画面的分辨率提高到640x480,就又要修改BIOS的画面模式设定所用的汇编语言代码了。

c 复制代码
; 设定画面模式

		MOV		BX,0x4101		; VBE的640x480x8bit彩色
		MOV		AX,0x4f02
		INT		0x10
		MOV		BYTE [VMODE],8	; 记录下画面模式
		MOV		WORD [SCRNX],640
		MOV		WORD [SCRNY],480
		MOV		DWORD [VRAM],0xe0000000

记得之前切换画面模式用的汇编语言指令是"AH = 0, AL = 画面模式号码",但是上面这段代码却有所不同,用的是AX = 0x4f02。这是什么原因呢?

从前电脑的规格是以IBM公司为中心决定的,IBM规定了画面显示模式的规格,各家显卡公司也按照这一规格来进行实现。而后来各个显卡公司开发出了更多性能更好的显卡,具有各种画面显示模式,设置和使用的方法也各不相同,导致程序员比较无所适从。于是多家显卡公司进行协商,成立了VESA协会。这个协会制定了几乎可以通用的设置方法,制作了专门的BIOS,这个追加的BIOS被称为VESA BIOS extension,简称为VBE。利用它就可以扩展显卡的显示模式了。

VBE的画面模式号码如下:

  • 0x101: 640x480x8bit 彩色
  • 0x103: 800x600x8bit 彩色
  • 0x105: 1024x768x8bit 彩色
  • 0x107: 1280x1024x8bit 彩色

为了在真机上使用VBE的显示模式,我们还需要进行一些确认。

(1) 确认是否支持VBE?

有些公司的产品仍然不支持VBE,因此无法使用VBE的显示模式。我们通过如下操作来进行确认:

c 复制代码
; 确认VBE是否存在

		MOV		AX,0x9000
		MOV		ES,AX
		MOV		DI,0
		MOV		AX,0x4f00
		INT		0x10
		CMP		AX,0x004f
		JNE		scrn320

这里给ES赋值0x9000,给DI赋值为0,给AX赋值为0x4f00,再执行INT 0x10,如果有VBE的话,AX就会变成0x004f。否则说明没有VBE,仍然只能使用之前320x200的画面。而为ES和DI进行赋值,是因为此显卡能利用的VBE信息将要写入内存中以ES:DI开始的512字节中。

(2) 确认VBE的版本是否在2.0以上

使用高分辨率,也需要VBE的版本在2.0以上。

c 复制代码
; VBE版本确认

		MOV		AX,[ES:DI+4]
		CMP		AX,0x0200
		JB		scrn320			; if (AX < 0x0200) goto scrn320

(3) 即使VBE版本在2.0以上,也还是需要确认画面模式0x105是否能够使用:

c 复制代码
; 获取画面模式信息

		MOV		CX,VBEMODE
		MOV		AX,0x4f01
		INT		0x10
		CMP		AX,0x004f
		JNE		scrn320

这里对AX的值进行了确认,如果是0x004f以外的值,所指定的画面模式就不能使用。

此次取得的画面模式信息也被写入内存从ES:DI开始的256字节中,这样刚才VBE的版本信息会被覆盖。但确认VBE版本之后这个信息就不需要了,因此没什么影响。

(4) 其他画面模式信息确认

这还没完,还有最后的几项信息需要确认:

  • 颜色数是否为8
  • 是否为调色板模式
  • 画面模式号码是否可以加上0x4000再进行指定
c 复制代码
; 画面模式信息确认

		CMP		BYTE [ES:DI+0x19],8
		JNE		scrn320
		CMP		BYTE [ES:DI+0x1b],4
		JNE		scrn320
		MOV		AX,[ES:DI+0x00]
		AND		AX,0x0080
		JZ		scrn320			; 模式属性的bit7是0,放弃

以上这些信息如果都确认OK,那么就可以使用VBE的画面模式了。如果确认不通过,则还是只能使用之前的分辨率。

c 复制代码
; 画面模式切换

		MOV		BX,VBEMODE+0x4000
		MOV		AX,0x4f02
		INT		0x10
		MOV		BYTE [VMODE],8	; 
		MOV		AX,[ES:DI+0x12]
		MOV		[SCRNX],AX
		MOV		AX,[ES:DI+0x14]
		MOV		[SCRNY],AX
		MOV		EAX,[ES:DI+0x28]
		MOV		[VRAM],EAX
		JMP		keystatus

scrn320:
		MOV		AL,0x13			; VGA图,320x200x8bit彩色
		MOV		AH,0x00
		INT		0x10
		MOV		BYTE [VMODE],8	; 
		MOV		WORD [SCRNX],320
		MOV		WORD [SCRNY],200
		MOV		DWORD [VRAM],0x000a0000

调整分辨率后,画面明显变大了。

2. 键盘输入与鼠标移动窗口

前面已经讲过关于键盘和鼠标的内容了。但是键盘只是实现了按下按键显示按键码,而鼠标也只是实现了移动鼠标箭头,其实还没有什么实用的功能。本篇中来实现按下键盘按键显示对应的字符,以及通过鼠标移动窗口的功能。

2.1 键盘信息输入

前面已经讲过,按下一个键盘按键,会显示一个按键码,松开的时候也会显示一个按键码。利用这一点,我们来实现按键显示对应字符的功能。

比如按下按键A时,显示的按键码是1E,按下B的时候,显示的按键码是30,......,关于每个按键的按键码形成了一张对照表。根据表中的按键码数值,可以编写keytable对照表,根据按键码来显示按键的字符。

c 复制代码
static char keytable[0x54] = {
		0,   0,   '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0,   0,
		'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0,   0,   'A', 'S',
		'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0,   0,   ']', 'Z', 'X', 'C', 'V',
		'B', 'N', 'M', ',', '.', '/', 0,   '*', 0,   ' ', 0,   0,   0,   0,   0,   0,
		0,   0,   0,   0,   0,   0,   0,   '7', '8', '9', '-', '4', '5', '6', '+', '1',
		'2', '3', '0', '.'
	};
	
		if (256 <= i && i <= 511) { /* 键盘数据 */
			sprintf(s, "%02X", i - 256);
			putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
			if (i < 256 + 0x54) {
				if (keytable[i - 256] != 0) {
					s[0] = keytable[i - 256];
					s[1] = 0;
					putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1);
				}
			}
		}

目前的程序还只能显示一个字符,再对程序进行修改,使其可以显示多个字符,并且修改了显示画面。

c 复制代码
int mx, my, i, cursor_x, cursor_c;

make_textbox8(sht_win, 8, 28, 144, 16, COL8_FFFFFF);
cursor_x = 8;
cursor_c = COL8_FFFFFF;
......
for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			io_stihlt();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { /* 键盘数据 */
				sprintf(s, "%02X", i - 256);
				putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
				if (i < 0x54 + 256) {
					if (keytable[i - 256] != 0 && cursor_x < 144) { /* 一般字符 */
						/* 显示一个字符就前移一次光标 */
						s[0] = keytable[i - 256];
						s[1] = 0;
						putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
						cursor_x += 8;
					}
				}
				if (i == 256 + 0x0e && cursor_x > 8) { /* backspace */
					/* 用空格键把光标消去后,后移一次光标 */
					putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
					cursor_x -= 8;
				}
				/* 光标再显示 */
				boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
			} else if (512 <= i && i <= 767) { 
				if (mouse_decode(&mdec, i - 512) != 0) {
					sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
					if ((mdec.btn & 0x01) != 0) {
						s[1] = 'L';
					}
					if ((mdec.btn & 0x02) != 0) {
						s[3] = 'R';
					}
					if ((mdec.btn & 0x04) != 0) {
						s[2] = 'C';
					}
					putfonts8_asc_sht(sht_back, 32, 16, COL8_FFFFFF, COL8_008484, s, 15);
					mx += mdec.x;
					my += mdec.y;
					if (mx < 0) {
						mx = 0;
					}
					if (my < 0) {
						my = 0;
					}
					if (mx > binfo->scrnx - 1) {
						mx = binfo->scrnx - 1;
					}
					if (my > binfo->scrny - 1) {
						my = binfo->scrny - 1;
					}
					sprintf(s, "(%3d, %3d)", mx, my);
					putfonts8_asc_sht(sht_back, 0, 0, COL8_FFFFFF, COL8_008484, s, 10);
					sheet_slide(sht_mouse, mx, my);
				}
			} else if (i == 10) { 
				putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
			} else if (i == 3) { 
				putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6);
			} else if (i <= 1) {
				if (i != 0) {
					timer_init(timer3, &fifo, 0);
					cursor_c = COL8_000000;
				} else {
					timer_init(timer3, &fifo, 1);
					cursor_c = COL8_FFFFFF;
				}
				timer_settime(timer3, 50);
				boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
			}
		}
	}

cursor_x用来记住光标显示的位置,输入一个字符后,变量就递增8,cursor_c则表示光标的颜色,并且每0.5s变化一次,产生闪烁效果。显示效果如下:

不过我这里遇到一个问题,按下一个按键松开之后,输入框中会一直重复输入这个字符,把输入框占满,而此时再按backspace键也没有反应。暂时还没找到原因,先继续往下学,看看后续能否解决这个问题

键盘显示字符做完了,鼠标也修改一下,程序也很简单,只需要在主程序鼠标处理的部分增加几行代码:

c 复制代码
	if ((mdec.btn & 0x01) != 0) {
			/* 按下左键之后,就移动sht_win窗口 */
			sheet_slide(sht_win, mx - 80, my - 8);
		}

这样只要点击鼠标,窗口就会移动到鼠标点击的位置。

这一篇的内容可以说是非常简单了,而且实现效果立竿见影。不过从下一篇开始就要进入多任务的学习了,这部分内容是个难点,还是提前做好准备吧。

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495645 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
克里斯蒂亚诺更新5 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a1117768 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
我的xiaodoujiao8 小时前
API 接口自动化测试详细图文教程学习系列32--Allure测试报告2
python·学习·测试工具·pytest
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言