動手寫操作系統9----鍵盤&鼠標中斷實現

鍵盤&鼠標中斷實現

本節主要實現鍵盤中斷和鼠標中斷,鍵盤中斷實現將鍵盤數據顯示到屏幕;鼠標中斷實現鼠標位置的移動。

                                             

 

鍵盤中斷通過主8259A的IRQ1觸發,鼠標中斷通過從8259A的IRQ4觸發

CPU通過中斷向量號來尋址待執行的中斷代碼

中斷向量號 = 起始向量號 + 中斷請求號

起始向量號通過中斷控制字ICW2被初始化  中斷請求號即爲8259A引腳編號

打開主IRQ1和從IRQ4中斷響應引腳

    ;OCW(operation control word)
	;當OCW[i] = 1 時,屏蔽對應的IRQ(i)管線的信號
	;IRQ1對應的是鍵盤產生的中斷
    mov al, 11111001b
    out 021h, al
    call io_delay

    ;CPU忽略所有來自從8259A芯片的信號
	;鼠標是通過從8259A的IRQ4管線向CPU發送信號
    mov al, 11101111b
    out 0A1h, al
    call io_delay

初始化中斷控制字ICW2如下所示:

;向主8259A發送ICW2
	;20h 對應二進制00100000 
	;ICW2[0,1,2] = 0 
	;8259A根據被設置的起始向量號(起始向量號通過中斷控制字ICW2被初始化)加上中斷請求號計算出中斷向量號
	;當主8259A對應的IRQ0管線向CPU發送信號時,CPU根據0x20這個值去查找要執行的代碼,
	;,CPU根據0x21這個值去查找要執行的代碼,依次類推。
    mov al, 020h
    out 021h, al
    call io_delay

	;向從8259A發送ICW2
	;28h 對應二進制00100100
	;8259A根據被設置的起始向量號(起始向量號通過中斷控制字ICW2被初始化)加上中斷請求號計算出中斷向量號
	;當從8259A對應的IRQ0管線向CPU發送信號時,CPU根據0x28這個值去查找要執行的代碼,
	;IRQ1管線向CPU發送信號時,CPU根據0x29這個值去查找要執行的代碼,依次類推。
    mov al, 028h
    out 0A1h, al
    call io_delay

主控制器起始向量號爲0x20 從控制器起始向量號爲0x28  因此

鍵盤中斷中斷向量號爲0x20+0x01=0x21

鼠標中斷中斷向量號爲0x28+0x04=0x2c

因此中斷描述符初始化如下:

;中斷描述符表
LABLE_IDT:
    %rep 0x21
        Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
    %endrep
	
	;鍵盤中斷向量(8259A 鍵盤中斷向量0x20,IRQ1 是鍵盤中斷請求,0x20 + IRQ[n] = 0x21
	.0x21:
		Gate SelectorCode32, KeyboardHandler, 0, DA_386IGate
		
	%rep 10
		Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate
	%endrep
	
	;從中斷控制器8259A 中斷向量0x28,IRQ4 是鼠標中斷請求,0x28 + IRQ[n] = 0x2c
	.0x2c:
		Gate SelectorCode32, MouseHandler, 0, DA_386IGate

相應的鍵盤和鼠標中斷處理函數:

;鍵盤中斷程序
LabelKeyboardHandler:
	KeyboardHandler EQU LabelKeyboardHandler - $$
	; 注意中斷切換過程
	push es
	push ds
	pushad
	mov eax, esp
	push eax
	
	call int_keyboard
	
	pop eax
	mov esp, eax
	popad
	pop ds
	pop es
	iretd
	
;鼠標中斷程序
LabelMouseHandler:
	MouseHandler EQU LabelMouseHandler - $$
	; 注意中斷切換過程
	push es
	push ds
	pushad
	mov eax, esp
	push eax
	
	call int_mouse
	
	pop eax
	mov esp, eax
	popad
	pop ds
	pop es
	iretd

中斷處理函數的處理過程

將當前寄存器值入棧

執行中斷響應函數

將棧中寄存器值恢復,繼續執行中斷前指令

鍵盤中斷響應函數

void int_keyboard(char *index){

        //0x20是8259A控制端口

        //0x21對應的是鍵盤的中斷向量。當鍵盤中斷被CPU執行後,下次鍵盤再向CPU發送信號時,

        //CPU就不會接收,要想讓CPU再次接收信號,必須向主PIC的端口再次發送鍵盤中斷的中斷向量號

        io_out8(0x20, 0x21);

        unsigned char data = io_in8(PORT_KEYDATA);

        fifo8_put(&keybufInfo, data);

}

將鍵盤中斷產生數據存入隊列中,主函數不斷檢測隊列中是否有數據,如果有數據,則將相應數據顯示到屏幕中

    for(;;){
		if (keybufInfo.len > 0) {
			io_cli();
			static char keyval[4] = {'0','x'};
			for(int i=0; i<keybufInfo.len; i++){
				char data = fifo8_get(&keybufInfo);
				static int x = 0;
				char2HexStr(data, keyval);
				showString(keyval, x%SCREEN_WIDTH, x/SCREEN_WIDTH*20, COL8_FFFFFF);
				x += 32;
			}
			io_seti();
		} else if (mousebufInfo.len > 0) {
			io_cli();
			for(int t=0;t<mousebufInfo.len;t++){
				mouseCursorMoved(&mouseDes, COL8_008484);
			}
			io_seti();
		} else {
			io_hlt();
		}  
    }

鼠標電路初始化

內核加載完成,初始化中斷描述符表,要想響應鼠標中斷,需要首先實現鼠標電路的初始化

//初始化鍵盤控制電路,鼠標控制電路是連接在鍵盤控制電路上,通過鍵盤電路實現初始化
void init_mouse(){
	
	waitKBCReady();
	
	//0x60讓鍵盤電路進入數據接受狀態
	io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
	waitKBCReady();
	
	//數據0x47要求鍵盤電路啓動鼠標模式,這樣鼠標硬件所產生的數據信息,通過鍵盤電路端口0x60就可讀到
	io_out8(PORT_KEYDATA, KBC_MODE);
	waitKBCReady();
	
	io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
	waitKBCReady();
	
	//0xf4數據激活鼠標電路,激活後將會給CPU發送中斷信號
	io_out8(PORT_KEYDATA, MOUSECMD_ENABLE);
}

只有當端口0x64端口數據第二個比特爲0時,鼠標電路才能接受來自內核命令。

//鼠標電路對應的一個端口是 0x64, 通過讀取這個端口的數據來檢測鼠標電路的狀態,
//內核會從這個端口讀入一個字節的數據,如果該字節的第二個比特位爲0,那表明鼠標電路可以
//接受來自內核的命令,因此,在給鼠標電路發送數據前,內核需要反覆從0x64端口讀取數據,
//並檢測讀到數據的第二個比特位,直到該比特位爲0時,才發送控制信息
void waitKBCReady(){
	for(;;){
		if((io_in8(PORT_KEYSTA)&0x02)==0){
			break;
		}
	}
}

鼠標中斷響應函數

在鼠標中斷響應函數中,將鼠標中斷產生數據存入隊列中

void int_mouse(char *index){

        //當中斷處理後,要想再次接收中斷信號,就必須向中斷控制器發送一個字節的數據

        io_out8(0x20, 0x20);

        io_out8(0xa0, 0x20);

        //讀取鼠標數據

        unsigned char data = io_in8(PORT_MOUSEDATA);

        fifo8_put(&mousebufInfo, data);

}

主循環不斷檢測鼠標隊列,依次處理每個數據

        for(;;){
		if (keybufInfo.len > 0) {
			io_cli();
			static char keyval[4] = {'0','x'};
			for(int i=0; i<keybufInfo.len; i++){
				char data = fifo8_get(&keybufInfo);
				static int x = 0;
				char2HexStr(data, keyval);
				showString(keyval, x%SCREEN_WIDTH, x/SCREEN_WIDTH*20, COL8_FFFFFF);
				x += 32;
			}
			io_seti();
		} else if (mousebufInfo.len > 0) {
			io_cli();
			for(int t=0;t<mousebufInfo.len;t++){
				mouseCursorMoved(&mouseDes, COL8_008484);
			}
			io_seti();
		} else {
			io_hlt();
		}  
    }

讀取隊列中的數據,每三個爲一組進行解析,每三個數據構建結構體MouseDes來表示當前鼠標的移動信息,然後擦除舊的鼠標信息,計算鼠標新的座標,繪製新的鼠標。

void mouseCursorMoved(MouseDes *mdec, char bc){
	unsigned char data = fifo8_get(&mousebufInfo);
	if(mouse_decode(mdec, data) != 0){
		//擦除之前鼠標位置
		fillRect(mdec->x, mdec->y, 16, 16, bc);
		//計算鼠標新的座標
		mdec->x += mdec->offX;
		mdec->y += mdec->offY;
		if(mdec->x < 0){
			mdec->x = 0;
		}
		if(mdec->x > SCREEN_WIDTH-16/2){
			mdec->x = SCREEN_WIDTH-16/2;
		}
		if(mdec->y < 0){
			mdec->y = 0;
		}
		if(mdec->y > SCREEN_HEIGHT-16){
			mdec->y = SCREEN_HEIGHT-16;
		}
		//繪製鼠標
		init_mouse_cursor((char *)VGA_ADDR, mdec->x, mdec->y, COL8_008484);
	}
}

鼠標移動模型以及處理

//鼠標處理需要連續處理3字節
//phase 表示處理字節階段
//offX, offY 當前鼠標的偏移
//x,y 鼠標當前所在的座標位置
typedef struct _MouseDes{
	char buf[3], phase;
	int offX, offY;
	int x, y, btn;
}MouseDes;

每三個數據即可給出鼠標中斷的數據信息,主要包括鼠標按鍵類型(左 滑輪 右)

鼠標上下移動  左右移動數據。第一個字節0xab, a的數值必須在0-3這個範圍內,由於a對應的是八比特中的高四位,所以這意味着該字節的第7,8兩個比特位必須爲0,b對應着八比特位中的低四位,它的值必須在8-F之間,這意味着該字節數據對應的第4個比特位必須爲1.把第一個字節轉換成二進制,那麼它必須滿足下面格式(X,*代表0或1):

0 0 X X 1 * \ * * 三個*用來表示鼠標按鍵,當鼠標的左鍵,滾輪,右鍵被按下時,對應的比特位會設置爲1.第二個字節用來表示鼠標的左右移動,對該字節進行相應處理後,可以得到鼠標平移的座標變換。第三個字節的數據表示鼠標的上下移動,對該字節進行相應處理後,可以得到鼠標垂直移動時的座標數變化。

具體的處理過程如下所示:

int mouse_decode(MouseDes *mdec, unsigned char dat){
	int flag = -1;
	if(mdec->phase == 0){
		if(dat == 0xfa){
			mdec->phase = 1;
		}
		flag = 0;
	}
	else if(mdec->phase == 1){
		if((dat&0xc8) == 0x08){
			mdec->buf[0] = dat;
			mdec->phase = 2;
		}
		flag = 0;
	}
	else if(mdec->phase == 2){
		mdec->buf[1] = dat;
		mdec->phase = 3;
		flag = 0;
	}
	else if(mdec->phase == 3){
		mdec->buf[2] = dat;
		mdec->phase  =1;
		mdec->btn = mdec->buf[0]&0x07;
		mdec->offX = mdec->buf[1];
		mdec->offY = mdec->buf[2];
		if((mdec->buf[0]&0x10) != 0){
			mdec->offX |= 0xffffff00;
		}
		if((mdec->buf[0]&0x20) != 0){
			mdec->offY |= 0xffffff00;
		}
		mdec->offY = -mdec->offY;
		flag = 1;
	}
	return flag;
}

代碼位置:https://github.com/ChenWenKaiVN/bb_os_core

參考鏈接:

https://www.jianshu.com/p/5f7244fb71c4

https://blog.csdn.net/Zllvincent/article/details/84099179

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章