目前制作的操作系统还不能发声呢,由于调用声卡的过程比较复杂,这里我们就先实现蜂鸣器发声好了。蜂鸣器发出的声音,是那种哔哔哔哔的,听起来有些奇怪。这里设置声音的频率随着时间以100Hz的速度降低,当声音频率降到20Hz以下或键盘按下任意键时结束。
beepdown.c
void HariMain(void)
{
int i, timer;
timer = api_alloctimer();
api_inittimer(timer, 128);
for (i = 20000000; i >= 20000; i -= 100000) {
/* 20KHz~20Hz : 人类可以听到的声音范围 */
api_beep(i);
api_settimer(timer, 1);
if (api_getkey(1) != 128) {
break;
}
}
api_beep(0);
api_end();
}
这里没有调用声卡,所以虚拟机里不用设置声卡也可以听到声音。
make run 一下——
将 i 改为递增的,以 i %的速度递增则会听到声音频率升高的声音,发出的声音要比之前感觉起来好多了。
beepup.c
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 = 20000; i <= 20000000; i += i / 100) {
api_beep(i);
api_settimer(timer, 1);
if (api_getkey(1) != 128) {
break;
}
}
api_beep(0);
api_end();
}
现在来实现可以显示多种颜色的应用程序,将光的三原色RGB(红、绿、蓝)中每个颜色赋予6个色阶,这样可以定义出6×6×6=216种颜色。
graphic.c节选:
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {
(略)
};
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);
return;
}
这里RGB颜色与色号的对应关系为:色号 = 16 + r * 1 + g * 6 + b * 36 。
实现显示多种颜色的应用程序color,这里将红色通道关闭,仅显示绿色和蓝色的色阶变化。
color.c节选
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 = 0;
g = x * 2;
b = y * 2;
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();
}
make run——
让色彩更加丰富,可以用两种颜色交替排列,看上去就像是这两种颜色混合在一起一样。纯色中间可以产生3个中间色,这样6级色阶能够可以显示出21级色阶。(6 + 5 × 3 = 21)
color2.hrb节选:
unsigned char rgb2pal(int r, int g, int b, int x, int y);
void HariMain(void)
{
char *buf;
int win, x, y;
api_initmalloc();
buf = api_malloc(144 * 164);
win = api_openwin(buf, 144, 164, -1, "color2");
for (y = 0; y < 128; y++) {
for (x = 0; x < 128; x++) {
buf[(x + 8) + (y + 28) * 144] = rgb2pal(0, x * 2, y * 2, x, y);
}
}
api_refreshwin(win, 8, 28, 136, 156);
api_getkey(1);
api_end();
}
unsigned char rgb2pal(int r, int g, int b, int x, int y)
{
static int table[4] = {
3, 1, 0, 2 };
int i;
x &= 1; /* 判断是偶数还是奇数 */
y &= 1;
i = table[x + y * 2]; /* 用来生成中间色的常量 */
r = (r * 21) / 256; /* r为0~20 */
g = (g * 21) / 256;
b = (b * 21) / 256;
r = (r + i) / 4; /* r为0~5 */
g = (g + i) / 4;
b = (b + i) / 4;
return 16 + r + g * 6 + b * 36;
}
执行make run,有点渐变色效果——
我们熟知的操作系统都是可以同时运行多个应用程序的,目前制作的操作系统只有一个命令行窗口,且命令行窗口只能执行一个应用程序。我们想要同时运行两个应用程序,最简单的方法就是同时启动两个命令行窗口。
实现这个功能,需要修改的地方包括:
1.将与命令行窗口相关的变量定义为数组,通过循环进行相同的处理,这样不管增加多少个命令行窗口都很方便。
2.将用于判断向哪个命令行窗口输出字符的变量cons,保存到每个任务各自的TASK结构中,这样就可以由不同的任务读取不同的值了。
bootpack.h节选:
struct TASK {
int sel, flags; /* sel代表GDT编号 */
int level, priority;
struct FIFO32 fifo;
struct TSS32 tss;
struct CONSOLE *cons; /* 变量cons保存到这里 */
int ds_base;
};
3.在启动应用程序时,指定了段号,当启动下一个应用程序时,之前的使用的段会被覆盖。比如color.hrb 指定了1003号 和 1004号,当运行color2.hrb时,这两个段会被覆盖,因此需要为color.hrb和color2.hrb分配编号不同的段。
console.c节选:
(略)
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);
(略)
start_app(0x1b, task->sel + 1000 * 8, esp, task->sel + 2000 * 8, &(task->tss.esp0));
(略)
4.实现正常关闭窗口,当使用Shift +F1 键盘强制结束 和用鼠标点击 × 按钮会以当前点击的窗口为对象。
bootpack.c节选,添加键盘强制结束代码:
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();
}
}
bootpack.c节选,添加鼠标点击×按钮结束代码:
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();
}
}
task_a窗口不是应用程序,而是操作系统的一部分,放在这里有些碍眼了,把它删除掉。
删除相关代码后,执行一下make run——