上一节我们实现了键盘中断事件调用显示所有可显示字符的字体图形显示,对中断处理机制有基本的认识。
1.实现键盘扫描码字体图标显示。当键盘上的一个按键按下时,键盘会发送一个中断信号给CPU,与此同时,键盘会在指定端口(0x60) 输出一个数值,这个数值对应按键的扫描码(make code)叫通码,当按键弹起时,键盘又给端口输出一个数值,这个数值叫断码(break code).我们以按键按键’A’为例,当按键’A’按下时,键盘给端口0x60发出的扫描码是0X1E, 当按键’A’弹起时,键盘会给端口0x60发送断码0x9E。
断码 = 通码 + 0x80
2.修改os.c文件如下:
// !compile method
// clang -m32 -c os.c -o os.o
// objconv -fnasm os.o -o os.s
//
#include "io.h"
#include "ascii_font.h"
//定义调色板颜色
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
//屏幕宽度
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
void initPallet();
/**
*绘制矩形
*x 矩形左上角x坐标
*y 矩形左上角y坐标
*width 宽度
*height 高度
*colIndex pallet_color 类型调色板颜色索引,即矩形颜色
*/
void fillRect(int x,int y,int width,int height,char colIndex);
//绘制桌面背景
void drawBackground();
/**
*绘制字体
*@param addr 绘制的起始显存地址
*@param x 绘制的x坐标
*@param y 绘制的y坐标
*@param col 绘制颜色
*@param pch 绘制的字符数组8*16,每一行共8位,共16行
*@param screenWidth 屏幕宽度
*/
void putChar(char *addr,int x,int y,char col,unsigned char *ch,int screenWidth);
/*
*初始化鼠标指针
*@param vram 绘制的起始显存地址
*@param x 绘制鼠标指针最左上角x坐标
*@param y 绘制鼠标指针最左上角y坐标
*@param bc 绘制的矩形填充颜色,和背景色一样将能看到鼠标指针
*/
void init_mouse_cursor(char *vram,int x,int y,char bc);
int num = 0;
//操作系统C语言入口函数--可以指定为其他
void init_main() {
initPallet();
drawBackground();
init_mouse_cursor((char *)0xa0000,100,100,COL8_008484);
for(; ;){
io_hlt();
}
}
void initPallet(){
//定义调色板
static unsigned char table_rgb[16*3] = {
0x00, 0x00, 0x00, /* 0:黑色*/
0xff, 0x00, 0x00, /* 1:亮红*/
0x00, 0xff, 0x00, /* 2:亮绿*/
0xff, 0xff, 0x00, /* 3:亮黄*/
0x00, 0x00, 0xff, /* 4:亮蓝*/
0xff, 0x00, 0xff, /* 5:亮紫*/
0x00, 0xff, 0xff, /* 6:浅亮蓝*/
0xff, 0xff, 0xff, /* 7:白色*/
0xc6, 0xc6, 0xc6, /* 8:亮灰*/
0x84, 0x00, 0x00, /* 9:暗红*/
0x00, 0x84, 0x00, /* 10:暗绿*/
0x84, 0x84, 0x00, /* 11:暗黄*/
0x00, 0x00, 0x84, /* 12:暗青*/
0x84, 0x00, 0x84, /* 13:暗紫*/
0x00, 0x84, 0x84, /* 14:浅灰蓝*/
0x84, 0x84, 0x84, /* 15:暗灰*/
};
unsigned char *rgb = table_rgb;
int flag = io_readFlag();
io_cli();
io_out8(0x03c8, 0);
for(int i=0;i<16;i++){
io_out8(0x03c9,rgb[0] / 4);
io_out8(0x03c9,rgb[1] / 4);
io_out8(0x03c9,rgb[2] / 4);
rgb += 3;
}
io_writeFlag(flag);
}
void fillRect(int x,int y,int width,int height,char colIndex){
char *vram = (char *)0xa0000;
for(int i=y;i<=y+height;i++){
for(int j=x;j<=x+width;j++){
vram[i*SCREEN_WIDTH+j] = colIndex;
}
}
}
void drawBackground(){
fillRect(0,0,SCREEN_WIDTH-1,SCREEN_HEIGHT-29, COL8_008484);
fillRect(0,SCREEN_HEIGHT-28,SCREEN_WIDTH-1,28, COL8_848484);
fillRect(0,SCREEN_HEIGHT-27,SCREEN_WIDTH,1, COL8_848484);
fillRect(0,SCREEN_HEIGHT-26,SCREEN_WIDTH,25, COL8_C6C6C6);
fillRect(3,SCREEN_HEIGHT-24,56,1, COL8_FFFFFF);
fillRect(2,SCREEN_HEIGHT-24,1,20, COL8_FFFFFF);
fillRect(3,SCREEN_HEIGHT-4,56,1, COL8_848484);
fillRect(59,SCREEN_HEIGHT-23,1,19, COL8_848484);
fillRect(2,SCREEN_HEIGHT-3,57,0, COL8_000000);
fillRect(60,SCREEN_HEIGHT-24,0,19, COL8_000000);
fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-24,43,1, COL8_848484);
fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-23,0,19, COL8_848484);
fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-3,43,0, COL8_FFFFFF);
fillRect(SCREEN_WIDTH-3,SCREEN_HEIGHT-24,0,21, COL8_FFFFFF);
}
void putChar(char *addr,int x,int y,char col,unsigned char *pch,int screenWidth){
for(int i=0;i<16;i++){
char ch = pch[i];
int off = (y+i)*screenWidth;
//显示的字形最左边的是低地址,右侧的是高地址。例如:0x80,则高地址部分显示在内存的低地址,
//最低位的应该偏移7
if((ch & 0x01) != 0){
addr[off+x+7] = col;
}
if((ch & 0x02) != 0){
addr[off+x+6] = col;
}
if((ch & 0x04) != 0){
addr[off+x+5] = col;
}
if((ch & 0x08) != 0){
addr[off+x+4] = col;
}
if((ch & 0x10) != 0){
addr[off+x+3] = col;
}
if((ch & 0x20) != 0){
addr[off+x+2] = col;
}
if((ch & 0x40) != 0){
addr[off+x+1] = col;
}
if((ch & 0x80) != 0){
addr[off+x+0] = col;
}
}
}
void init_mouse_cursor(char *vram,int x,int y,char bc){
//16*16 Mouse
//鼠标指针点阵
static char cursor[16][16] = {
"*...............",
"**..............",
"*O*.............",
"*OO*............",
"*OOO*...........",
"*OOOO*..........",
"*OOOOO*.........",
"*OOOOOO*........",
"*OOOOOOO*.......",
"*OOOO*****......",
"*OO*O*..........",
"*O*.*O*.........",
"**..*O*.........",
"*....*O*........",
".....*O*........",
"......*........."
};
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int off = (i+y)*SCREEN_WIDTH+x+j;
if (cursor[i][j] == '*') {
vram[off] = COL8_000000;
}
if (cursor[i][j] == 'O') {
vram[off] = COL8_FFFFFF;
}
if (cursor[i][j] == '.') {
vram[off] = bc;
}
}
}
}
/*
*char 类型数据转换为16进制字符数据
*@param val 待转化为16进制的数值
*@param arr 保存16进制字符串数据的数组
*/
void char2HexStr(unsigned char val,char *arr) {
unsigned char tmp = val >> 4;
if(tmp>=10){
arr[2] = 'a'+tmp-10;
}
else{
arr[2] = '0'+tmp;
}
tmp = val & 0x0f;
if(tmp>=10){
arr[3] = 'a'+tmp-10;
}
else{
arr[3] = '0'+tmp;
}
}
/*
*8259A中断调用
*
*/
void int_8259A(char *index){
//0x20是8259A控制端口
//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,
//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号
io_out8(0x20,0x21);
//读取8259A 0x60端口键盘扫描码
unsigned char data = io_in8(0x60);
static char arr[4] = {'0','x'};
char2HexStr(data,arr);
unsigned char *ascii = ascii_array;
for(int i=0;i<4;i++){
int x = (num)%32*10;
int y = (num)/32*20;
putChar((char *)0xa0000,x,y,COL8_FFFFFF,ascii+(arr[i]-0x20)*16,SCREEN_WIDTH);
num++;
}
}
4.优化键盘中断服务子程序
中断实际上是将CPU当前正在执行的任务给打断,让CPU先处理中断任务,然后再返回处理原先的任务。处理流程和函数调用有相似之处,如下图所示:
我们要尽可能快的处理,然后把控制器交还给原来的任务。把键盘中断通码和断码数值缓存起来,然后把控制器交换给原来任务,等到CPU稍微空闲时再处理键盘事件。
修改os.c文件如下:
// !compile method
// clang -m32 -c os.c -o os.o
// objconv -fnasm os.o -o os.s
//
#include "io.h"
#include "ascii_font.h"
//定义调色板颜色
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
//屏幕宽度
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
//定义键盘缓冲数据区
#define KEYBUF_LEN 32
struct _KeyBuf{
//缓冲区数据长度
unsigned char buf[KEYBUF_LEN];
//下一数据读/写索引
int next_r, next_w;
//有效数据长度
int len;
}KeyBuf;
struct _KeyBuf keybuf = {{0},0,0,0};
void initPallet();
/**
*绘制矩形
*x 矩形左上角x坐标
*y 矩形左上角y坐标
*width 宽度
*height 高度
*colIndex pallet_color 类型调色板颜色索引,即矩形颜色
*/
void fillRect(int x,int y,int width,int height,char colIndex);
//绘制桌面背景
void drawBackground();
/**
*绘制字体
*@param addr 绘制的起始显存地址
*@param x 绘制的x坐标
*@param y 绘制的y坐标
*@param col 绘制颜色
*@param pch 绘制的字符数组8*16,每一行共8位,共16行
*@param screenWidth 屏幕宽度
*/
void putChar(char *addr,int x,int y,char col,unsigned char *ch,int screenWidth);
/*
*初始化鼠标指针
*@param vram 绘制的起始显存地址
*@param x 绘制鼠标指针最左上角x坐标
*@param y 绘制鼠标指针最左上角y坐标
*@param bc 绘制的矩形填充颜色,和背景色一样将能看到鼠标指针
*/
void init_mouse_cursor(char *vram,int x,int y,char bc);
/*
*char 类型数据转换为16进制字符数据
*@param val 待转化为16进制的数值
*@param arr 保存16进制字符串数据的数组
*/
void char2HexStr(unsigned char val,char *arr);
int num = 0;
//操作系统C语言入口函数--可以指定为其他
void init_main() {
initPallet();
drawBackground();
init_mouse_cursor((char *)0xa0000,100,100,COL8_008484);
for(; ;){
if(keybuf.len==0){
io_hlt();
}
else{
io_cli();
static char arr[4] = {'0','x'};
unsigned char *ascii = ascii_array;
for(int t=0;t> 4;
if(tmp>=10){
arr[2] = 'a'+tmp-10;
}
else{
arr[2] = '0'+tmp;
}
tmp = val & 0x0f;
if(tmp>=10){
arr[3] = 'a'+tmp-10;
}
else{
arr[3] = '0'+tmp;
}
}
/*
*8259A中断调用
*
*/
void int_8259A(char *index){
//0x20是8259A控制端口
//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,
//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号
io_out8(0x20,0x21);
//读取8259A 0x60端口键盘扫描码
char data = io_in8(0x60);
if(keybuf.len
编译加载floppy.img 运行点击键盘 abc 键显示如下:
至此,说明我们的键盘扫描码被存放到数据缓冲区中!