第25天
这本书的这一章一开始就讲如果控制主板上的蜂鸣发专声器发声,看到这个我很兴奋。因为到目前为止我还没有用windows api或者自己写程序让电脑发出声音,终于可以尝试一下了,虽然不是声卡发声,只是最低级的蜂鸣发声器,但是还是很兴奋。
我们控制主板上的发声器也和控制中断处理器是一样的,也是要使用in和out指令进行操作。
蜂鸣发声器的打开和关闭:
- 使用端口0x61控制
- 打开:IN(AL, 0X61); AL |= 0X03; AL &= 0X0F; OUT(0X61, AL);
- 关闭:IN(AL, 0X61); AL &= 0X0D; AL &= 0X0D; OUT(0X61, AL);
如果打开之后控制发声器的发声频率:
- AL = 0XB6; OUT(0X43, AL);
- AL = 设定值的低8位; OUT(0X42, AL);
- AL = 设定值的高8位; OUT(0X42, AL);
- 当设定值为0时当作65536来处理
- 发声的频率为时钟除以设定值,也就是说设定值为1000时相当于发也1.19318KHZ的声音
api设计
- edx = 20
- eax = 声音频率(单位是mHz, 即毫HZ),当频率为0时表示停止发声
else if (edx == 20) {
if (eax == 0) {
i = io_in8(0x61);
io_out8(0x61, i & 0x0d);
} else {
i = 1193180000 / eax;
io_out8(0x43, 0xb6);
io_out8(0x42, i & 0xff);
io_out8(0x42, i >> 8);
i = io_in8(0x61);
io_out8(0x61, (i | 0x03) & 0x0f);
}
}
_api_beep: ; void api_beep(int tone);
MOV EDX,20
MOV EAX,[ESP+4] ; tone
INT 0x40
RET
看一下应用程序如何写:
void api_end(void);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_beep(int tone);
void HariMain(void)
{
int i, timer;
timer = api_alloctimer();
api_inittimer(timer, 128);
for (i = 20000000; i >= 20000; i -= i / 100) {
/* 20KHz�~20Hz :人类可以听到的声音范围 */
/* i以1%的速度递减 */
api_beep(i);
api_settimer(timer, 1); /* 0.01秒 */
if (api_getkey(1) != 128) {
break;
}
}
api_beep(0);
api_end();
}
关于声音就到这里,接下来看看如何增加更多的颜色。
到目前为止我们只用了16程模式。我们使用的是显卡的调色板模式,理论上有256种颜色,我们就想办法让操作系统支持更多的颜色。
剩下的240个颜色我们设置么颜色呢?调色板对应的是RGB,一共24位,我们给每个三原色中的一种分6个,那么一共就可以定义6 * 6 * 6 = 216种颜色
unsigned char table2[216 * 3];
int r, g, b;
set_palette(0, 15, table_rgb);
for (b = 0; b < 6; b++) {
for (g = 0; g < 6; g++) {
for (r = 0; r < 6; r++) {
table2[(r + g * 6 + b * 36) * 3 + 0] = r * 51;
table2[(r + g * 6 + b * 36) * 3 + 1] = g * 51;
table2[(r + g * 6 + b * 36) * 3 + 2] = b * 51;
}
}
}
set_palette(16, 231, table2);
RGB取值分别为0, 51, 102, 153, 204, 255。这样调色板设置好了之后如果要取(51, 102, 153)这个颜色的话就是137号。
将下来我们写一个应用程序测试一下。
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
int api_getkey(int mode);
void api_end(void);
void HariMain(void)
{
char *buf;
int win, x, y, r, g, b;
api_initmalloc();
buf = api_malloc(144 * 164);
win = api_openwin(buf, 144, 164, -1, "color");
for (y = 0; y < 128; y++) {
for (x = 0; x < 128; x++) {
r = x * 2;
g = y * 2;
b = 0;
buf[(x + 8) + (y + 28) * 144] = 16 + (r / 43) + (g / 43) * 6 + (b / 43) * 36;
}
}
api_refreshwin(win, 8, 28, 136, 156);
api_getkey(1);
api_end();
}
我们现在只能在命令行窗口运行应用程序,但是命令行窗口只有一个,所以我们同时只能运行一个应用程序,那么多任务对于应用程序来说没有任何意义。我们应用让操作系统可以同时运行多个命令行以支行多个应用程序。
如果想要支持多个命令行窗口,那么就需要改造操作系统的程序结构。我们之前把每个命令行窗口的数据段和窗口数据存到操作系统的内存中。
int ds_base = *((int *) 0xfe8);
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
如果有多个命令行窗口的话这样的方式就不能用了,反正要为每个命令行窗口创建一个任务,而每个任务有会维护一个TASK结构的数据,我们可以改造这个结构将这两个值保存到这个变量中。
struct TASK {
int sel, flags; /* sel‚ÍGDT‚Ì”Ô�†‚Ì‚±‚Æ */
int level, priority;
struct FIFO32 fifo;
struct TSS32 tss;
struct CONSOLE *cons;
int ds_base;
};
之前我们在运行应用程序的时候,直接给应用程序分配段号,现在由于存在多个命令行窗口,可能会运行多个应用程序就不能用这样的方式分配段号了。
set_segmdesc(gdt + task->sel / 8 + 1000, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
set_segmdesc(gdt + task->sel / 8 + 2000, segsiz - 1, (int) q, AR_DATA32_RW + 0x60);
接下来处理关闭应用程序时候的问题了。
if (i == 256 + 0x3b && key_shift != 0) {
task = key_win->task;
if (task != 0 && task->tss.ss0 != 0) { /* Shift+F1 */
cons_putstr0(task->cons, "\nBreak(key) :\n");
io_cli(); /* 强制结束任务时禁止任务切换 */
task->tss.eax = (int) &(task->tss.esp0);
task->tss.eip = (int) asm_end_app;
io_sti();
}
}
以上是按下键盘时关闭应用程序。
if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
/* �点击关闭按扭 */
if ((sht->flags & 0x10) != 0) { /* 是否为应用程序窗口 */
task = sht->task;
cons_putstr0(task->cons, "\nBreak(mouse) :\n");
io_cli(); /* 强制结束时禁止任务切换 */
task->tss.eax = (int) &(task->tss.esp0);
task->tss.eip = (int) asm_end_app;
io_sti();
}
}
以上是点击关闭按钮时关闭应用程序。这样处理完之后就可以支持多个应用程序窗口了,为了写程序方便,这本书就使用了操作系统运行时打开2个命令行窗口,估计接下来还会通过输入命令运行命令行窗口的功能。
这个操作系统还有一个和其他操作系统不一样的地方。一开机时候自动行打开一个窗口也就是task_a窗口。为了使操作系统更像个操作系统应该把这个窗口给取消了。
如果要取消这个窗口其实只要删除跟运行这个窗口有关的代码就行了,可以删好多东西,删除之后看看结果怎么样结果是操作系统出错了。原来的操作系统运行流程是这样的:首先运行主程序,打开task_a窗口,然后把操作系统的焦点放在这个窗口,光标也在task_a窗口闪烁,直到用户按下了tab或者用鼠标切换窗口之后才切换。但是现在不是了,现在我们想要让光标直接在第一个打开的命令行窗口闪烁就出问题了。因了task_a的优先级要高,当task_a运行完之前就向命令行窗口发送光标闪烁的消息时候,命令行窗口连消息队列都还没有建立,所以出错了。解决方法也很简单,只要把命令行窗口建立消息队列的程序语句放在发送消息之前就可以了。