VGA 接口支持在屏幕上显示 80*25 的字符,字符可以有 16 种前景和背景色。每个字符用 16 位表示,其结构示意如下:
struct Character {
char bg:4; // 16种背景色
char fg:4; // 16种前景色
char c; // ascii 字符
}
VGA 中字符数据被映射到内存 0xB8000 开始的线性区域,对内存操作即可写入内容(实模式)。
此外,有两个 IO 端口用于控制光标位置等其它操作:
VESA 提供了更丰富的图形支持。
通过 0x3D5 数据端口发送光标位置 y * 80 + x
。
由于光标位置由 uint16 表示,需要分两次传送:
使用黑底白字空格填充整个屏幕数据即可
将屏幕数据内存向左移动 80*2 个字节,并用黑底白字空格填充末行
需要注意一些特殊字符的含义
另外,可见字符在 ascii 码表中,都位于空格(” “)之后
以 toaruos 早期代码(cdff0) vga.c 为例,注释如下
1 #include <system.h>
2
3 /*
4 * Text pointer, background, foreground
5 */
6 unsigned short * textmemptr;
// 黑底白字
7 int attrib = 0x0F;
// 光标位置,从 0 开始计数
8 int csr_x = 0, csr_y = 0;
9
10 /*
11 * scroll
12 * Scroll the screen
13 */
14 void
15 scroll() {
16 unsigned blank, temp;
17 blank = 0x20 | (attrib << 8);
18 if (csr_y >= 25) {
19 /*
20 * Move the current text chunk that makes up the screen
21 * back in the buffer by one line.
22 */
23 temp = csr_y - 25 + 1;
24 memcpy(textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
25 /*
26 * Set the chunk of memory that occupies
27 * the last line of text to the blank character
28 */
// bug???
// memsetw( textmemptr + (csr_y - 1)*80, blank, 80 );
29 memsetw(textmemptr + (25 - temp) * 80, blank, 80);
30 csr_y = 25 - 1;
31 }
32 }
33
// 位置用 16 位整数表示,分两次,先发高字节,后发低字节
// 发数据前在控制端口(0x3D4)上发送 14 和 15 标识
34 /*
35 * move_csr
36 * Update the hardware cursor
37 */
38 void
39 move_csr() {
40 unsigned temp;
41 temp = csr_y * 80 + csr_x;
42
43 /*
44 * Write stuff out.
45 */
46 outportb(0x3D4, 14);
47 outportb(0x3D5, temp >> 8);
48 outportb(0x3D4, 15);
49 outportb(0x3D5, temp);
50 }
51
// 用黑底白字空格填充整个屏幕,并重置光标位置
52 /*
53 * cls
54 * Clear the screen
55 */
56 void
57 cls() {
58 unsigned blank;
59 int i;
60 blank = 0x20 | (attrib << 8);
61 for (i = 0; i < 25; ++i) {
62 memsetw(textmemptr + i * 80, blank, 80);
63 }
64 csr_x = 0;
65 csr_y = 0;
66 move_csr();
67 }
68
69 /*
70 * putch
71 * Puts a character to the screen
72 */
73 void
74 putch(
75 unsigned char c
76 ) {
77 unsigned short *where;
78 unsigned att = attrib << 8;
// 退格
79 if (c == 0x08) {
80 /* Backspace */
81 if (csr_x != 0) csr_x--;
82 } else if (c == 0x09) {
83 /* Tab */
// 经典对齐代码
84 csr_x = (csr_x + 8) & ~(8 - 1);
// \r 回到行首
// \n 加到行首,并切到下一行
85 } else if (c == '\r') {
86 /* Carriage return */
87 csr_x = 0;
88 } else if (c == '\n') {
89 /* New line */
90 csr_x = 0;
91 csr_y++;
92 } else if (c >= ' ') {
93 where = textmemptr + (csr_y * 80 + csr_x);
94 *where = c | att;
95 csr_x++;
96 }
97
98 if (csr_x >= 80) {
99 csr_x = 0;
100 csr_y++;
101 }
102 scroll();
103 move_csr();
104 }
105
106 /*
107 * puts
108 * Put string to screen
109 */
110 void
111 puts(
112 unsigned char * text
113 ){
114 int i;
115 int len = strlen(text);
116 for (i = 0; i < len; ++i) {
117 putch(text[i]);
118 }
119 }
120
121 /*
122 * settextcolor
123 * Sets the foreground and background color
124 */
125 void
126 settextcolor(
127 unsigned char forecolor,
128 unsigned char backcolor
129 ) {
130 attrib = (backcolor << 4) | (forecolor & 0x0F);
131 }
132
133 /*
134 * init_video
135 * Initialize the VGA driver.
136 */
137 void init_video() {
// framebuffer 的地址被映射到 0xB8000 开始的线性区域
138 textmemptr = (unsigned short *)0xB8000;
139 cls();
140 }