14.1、提高分辨率(1)
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
这次调用int 0x10中断,BX=0x4101,AX=0x4f02,显存地址变成了0xe0000000(之前AH=0;AL=画面模式号码;显存地址是0x00a00000)。之前都是通过BIOS设置的画面,现在算是因为显卡的进步,重新设置的设置显示画面的方式。
VBE(VESA-BIOS extension)这是显卡的BIOS的标准。VESA协会为了规范当时显卡各种奇葩设定,专门制定了这个VBE,通过这个标准可以设定各家显卡的画面显示(当然也得是加入了VBE的显卡厂商)。设定方式就是
AX=0x4f02;BX=画面模式号码;
画面模式号码 | 分辨率 |
---|---|
0x101 | 640 x 480 x 8bit 彩色 |
0x103 | 800 x 600 x 8bit 彩色 |
0x105 | 1024 x 768 x 8bit 彩色 |
0x107 | 1280 x 1024 x 8bit 彩色 |
这些号码需要加上0x4000,再赋值到BX中
14.2、提高分辨率(2)
以上当确定主机有VESA合作的显卡时,确实可以使用VBE设置画面。但是如果没有的话将会出错,所以设置高分辨率时需要提前检查是不是有VBE,如果没有只能回去使用320 x 200了。
MOV AX,0x9000
MOV ES,AX
MOV DI,0
MOV AX,0x4f00
INT 0x10
CMP AX,0x004f
JNE scrn320
当AX=0x4f00时发生int 0x10中断,如果存在VBE,AL=4f,AH=status(0x00 成功;0x01 失败),AX=0x004f。
显卡能够利用的VBE信息,将会写入到ES:DI指定的开始地址,共512字节。
在本操作系统中,如果VBE不是2.0版本,就没办法使用更高的分辨率,再来判断以下VBE版本:
MOV AX,[ES:DI+4] ; [ES:DI+4]存放的就是VBE版本
CMP AX,0x0200
JB scrn320 ; if (AX < 0x0200) goto scrn320
接下来查看0x105画面模式能不能使用,因为即使是VBE2.0也不能保证所有画面模式都能使用。
VBEMODE EQU 0x105
MOV CX,VBEMODE
MOV AX,0x4f01
INT 0x10
CMP AX,0x004f
JNE scrn320
AX=0x4f01:获取 SuperVGA 模式信息
CX=VBE模式号码,如果能够设置,AX=4f,并把该画面模式信息写入到ES:DI开始的256字节地址中。这将覆盖之前写入的VBE的信息。
画面模式信息,详细看AX=0x4f01连接:
我们来验证以下三项:
- 颜色数是不是8位
- 是否为调色板模式
- 画面模式号码是否能加上0x4000再进行指定(bit7是1就可以)
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 ; 不行就回到320 x 200
如果以上都验证通过了,那便可以切换到高分辨率了。
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
(略)
keystatus:
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
切换后使用的仍是AX=0x4f01中断的ES:DI地址,查中断手册,如果设置高分辨率AX=0x4f02,VBE3.0+ 会使用ES:DI继续写入信息,覆盖之前写入的256字节信息,但作者就直接使用了之前的256字节内容,可以当时它们没有3.0以上版本吧。
还是行?现在VBE不是3.0吗?不了解了。。。。。
14.3、键盘输入(1)
之前在读入键盘数据时读入的扫描码,可以对应按键,例如:1E ==> A,从缓冲区读取到1E,就输出A,这也是一个方法:
for (;;) {
io_cli();
if (fifo32_status(&fifo) == 0) {
io_stihlt();
} else {
i = fifo32_get(&fifo);
io_sti();
if (256 <= i && i <= 511) { /* 之前缓冲区加了256 */
sprintf(s, "%02X", i - 256);
putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
if (i == 0x1e + 256) {
// 如果i == 0x1e + 256 就输出A
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, "A", 1);
}
按照这个方法也可以打印所有字符了,那再改进以下,把键盘字符都放到数组里:
// static char会被编译成DB指令
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', '.'
};
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 < 256 + 0x54) { // 判断是否超出键盘字符数组范围
if (keytable[i - 256] != 0) { // i - 256就是扫描码,在keytable索引
s[0] = keytable[i - 256];
s[1] = 0;
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1);
}
}
这个存在问题,作者列出的扫描码和现在中国的键盘有不同的地方
1、没有响应组合键
2、‘[’会是‘@’
3、等等
解决办法:重新设计数组就好了
14.4、追记内容
14.4.1、编辑字符
看关键代码注释
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; // cursor_x用来标记显示字符的起始位置(左上角)
}
}
if (i == 256 + 0x0e && cursor_x > 8) { /* 退格键 */
/* 用空格键把光标消去后,后移1次光标。这一部是要把字符后面的黑色光标给覆盖了 */
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); // corsor_c光标颜色
sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
} else if (512 <= i && i <= 767) { /* 鼠标数据*/
if (mouse_decode(&mdec, i - 512) != 0) {
/* 已经收集了3字节的数据,所以显示出来 */
(略)
}
} else if (i == 10) { /* 10秒定时器 */
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
} else if (i == 3) { /* 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); /* 下面设定0 */
cursor_c = COL8_000000;
} else {
timer_init(timer3, &fifo, 1); /* 下面设定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);
}
}
}
14.4.2、移动窗口
看关键代码注释
else if (512 <= i && i <= 767) { /* 鼠标数据*/
if (mouse_decode(&mdec, i - 512) != 0) {
/* 已经收集了3字节的数据,所以显示出来 */
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);/* 包含sheet_refresh含sheet_refresh */
if ((mdec.btn & 0x01) != 0) { /* 按下左键、移动sht_win */
// 鼠标一按,sht_win窗口就移动过去,有点僵硬哈
sheet_slide(sht_win, mx - 80, my - 8); // 窗口的左上角距离鼠标左上角少(80,8)
}
}
}