[書]x86彙編語言:從實模式到保護模式 -- 第九章 硬中斷,使用RTC芯片實現實時時間的顯示;軟中斷,使用BIOS中斷實現鍵盤輸入的讀取和顯示

PART 1 >> 使用BIOS中斷實現鍵盤輸入的讀取和顯示

; File: c09_2.asm
; Date: 20191222

; ===============================================================================
SECTION head vstart=0                       ; 定義用戶程序頭部段 
    ; 用戶程序可能很大,16位可能不夠
    program_length  dd program_end          ; 程序總長度[0x00]
    
    ; 程序入口點(Entry Point)
    program_entry   dw beginning            ; 偏移地址[0x04]
                    ; 只是編譯階段確定的彙編地址。程序加載到內存後,需要根據加載的實際位置重新計算
                    ; 儘管在16位的環境中,一個段最長爲64KB,但它卻可以起始於任何20位的物理地址處。
                    ; 不可能用16位來保存20位的地址,所以需要32位
                    dd section.code.start ; 彙編地址[0x06] 
                    
    realloc_tbl_size dw (head_end-segment_code_1)/4     ; 段重定位表項個數[0x0a]
    
    segment_code_1  dd section.code.start   ; [0x0c]    
    segment_data_1  dd section.data.start   ; [0x10]
    segment_stack   dd section.stack.start  ; [0x14]    ; 這裏section 和 start 不能用大寫 ???
                    
    head_end:
    

; ===============================================================================    
SECTION code align=16 vstart=0

beginning:
    ; 設置用戶程序自己的堆棧段
    ; ds和es依然指向着用戶程序頭部head段
    mov ax, [segment_stack]
    mov ss, ax
    mov sp, stack_end
    
    ; 設置用戶程序自己的數據段
    ; 如果先初始化數據段ds和附加段es,那麼頭部head段中的數據將無法訪問
    mov ax, [segment_data_1]
    mov ds, ax
    
    mov cx, msg_end-message
    mov bx, message
    
 .show_char:
    ; 在頻幕上寫字符
    ; 中斷0x10的0x0e號功能。在屏幕光標位置處寫一個字符,並推進光標位置。
    ; Input: al, 字符
    mov ah, 0x0e        ; ah中指定0x0e號功能
    mov al, [bx]
    int 0x10
    inc bx
    loop .show_char
    
 .rw_keyboard:
    ; 從鍵盤讀字符
    ; 中斷0x10的0x00號功能。
    ; Output: al, 字符
    mov ah, 0x00        ; ah中指定0x00號功能
    int 0x16
    
    mov ah, 0x0e
    mov bl, 0x07
    int 0x10
    
    jmp .rw_keyboard
    

; ===============================================================================    
SECTION data align=16 vstart=0    

    message       db 'Hello, friend!',0x0d,0x0a
                  db 'This simple procedure used to demonstrate '
                  db 'the BIOS interrupt.',0x0d,0x0a
                  db 'Please press the keys on the keyboard ->'
    msg_end:
    
    
; ===============================================================================    
SECTION stack align=16 vstart=0   
    resb 256
stack_end:

    
; ===============================================================================    
SECTION program_tail
program_end:

 

 

===================================================================================================

PART 2 >> 使用RTC芯片實現實時時間的顯示

; FILE: c09_1.asm
; DATE: 20191211

; ===============================================================================
SECTION head vstart=0                       ; 定義用戶程序頭部段 
    ; 用戶程序可能很大,16位可能不夠
    program_length  dd program_end          ; 程序總長度[0x00]
    
    ; 程序入口點(Entry Point)
    program_entry   dw beginning            ; 偏移地址[0x04]
                    ; 只是編譯階段確定的彙編地址。程序加載到內存後,需要根據加載的實際位置重新計算
                    ; 儘管在16位的環境中,一個段最長爲64KB,但它卻可以起始於任何20位的物理地址處。
                    ; 不可能用16位來保存20位的地址,所以需要32位
                    dd section.code.start ; 彙編地址[0x06] 
                    
    realloc_tbl_size dw (head_end-segment_code_1)/4     ; 段重定位表項個數[0x0a]
    
    segment_code_1  dd section.code.start   ; [0x0c]    
    segment_data_1  dd section.data.start   ; [0x10]
    segment_stack   dd section.stack.start  ; [0x14]    ; 這裏section 和 start 不能用大寫 ???
                    
    head_end:

; =============================================================================== 
SECTION code align=16 vstart=0

beginning:
    ; 設置用戶程序自己的堆棧段
    ; ds和es依然指向着用戶程序頭部head段
    mov ax, [segment_stack]
    mov ss, ax
    mov sp, stack_end
    
    ; 設置用戶程序自己的數據段
    ; 如果先初始化數據段ds和附加段es,那麼頭部head段中的數據將無法訪問
    mov ax, [segment_data_1]
    mov ds, ax
    
    mov bx, msg_init            ; 顯示初識信息
    call show_string
    
    mov bx, msg_install         ; 顯示安裝信息
    call show_string
    
    ; 計算RTC芯片中斷處理過程的段地址和偏移地址
    ; RTC芯片的中斷信號,通向中斷控制器8259從片的第1箇中斷引腳IR0。
    ; 計算機啓動期間,BIOS會初始化中斷控制器8259,將主片的中斷號設爲從0x08開始,從片的中斷號從0x70開始。
    ; 所以,計算機啓動後,RTC芯片的中斷號默認是0x70(可通過對8259編程來修改默認中斷號)
    xor ax, ax
    mov al, 0x70                ; RTC芯片的默認中斷號0x70
    mov bl, 4                   ; 每個中斷向量表項佔4字節(段地址:偏移地址), 乘4,得中斷像量表內的偏移
    mul bl
    mov bx, ax
    
    cli                         ; cli 清楚IF標誌位,禁止中斷,防止改動期間發生新的0x70號中斷

    ; 設置中斷向量表0x70號表項內容
    ; 實模式下,256箇中斷程序的入口點集中存放在內存0x00000~0x003FF共1KB的空間內,即中斷向量表
    push es
    xor ax, ax
    mov es, ax                  ; 將es指向中斷向量表所在的段    
    mov word [es:bx], my_int_0x70    ; 中斷處理過程的偏移地址    
    mov word [es:bx+2], cs           ; 段地址
    pop es

    ; 不懂 ……
    ; 設置RTC的工作狀態,使它能夠產生中斷信號給8259中斷控制器
    mov al,0x0b                        ;RTC寄存器B
    or al,0x80                         ;阻斷NMI 
    out 0x70,al
    mov al,0x12                        ;設置寄存器B,禁止週期性中斷,開放更 
    out 0x71,al                        ;新結束後中斷,BCD碼,24小時制 
    mov al,0x0c
    out 0x70,al
    in al,0x71                         ;讀RTC寄存器C,復位未決的中斷狀態
    in al,0xa1                         ;讀8259從片的IMR寄存器 
    and al,0xfe                        ;清除bit 0(此位連接RTC)
    out 0xa1,al                        ;寫回此寄存器 
    
    sti                         ; sti 放開中斷,與cli相對應
    
    mov bx, msg_done
    call show_string            ; 顯示中斷安裝完成信息
    mov bx, msg_tips
    call show_string            ; 顯示提示信息
    
    ; 屏幕中心顯示字符@
    mov ax, 0xb800
    mov ds, ax
    mov byte [12*160 + 33*2], '@'       ; 25row*80col    
    
    ; hlt 使處理器處於停機狀態,停止執行指令,
    ; 可以被外部中斷喚醒並恢復執行
 .idle:
    hlt
    not byte [12*160 + 33*2 + 1] ; 反轉上面@字符的顯示屬性
    jmp .idle

    
; Function: 頻幕上顯示文本
; Input: ds:bx 字符串起始地址,以0結尾
show_string:
    mov cl, [bx]
    or cl, cl
    jz .exit
    call show_char
    inc bx
    jmp show_string
    
 .exit:
    ret

; Function: 
; Input: cl 字符
show_char:

    push ax
    push bx
    push cx
    push dx
    push ds
    push es
    
    ; 讀取當前光標位置
    ; 索引寄存器端口0x3d4,其索引值14(0x0e)和15(0x0f)分別用於提供光標位置的高和低8位
    ; 數據端口0x3d5
    mov dx, 0x3d4   
    mov al, 0x0e   
    out dx, al
    mov dx, 0x3d5
    in al, dx
    mov ah, al
    
    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x3d5
    in al, dx
    mov bx, ax      ; 此處用bx存放光標位置的16位數
    
 ; 判斷是否爲回車符0x0d
    cmp cl, 0x0d    ; 0x0d 爲回車符
    jnz .show_0a    ; 不是回車符0x0d,再判斷是否換行符0x0a
    mov ax, bx      ; 是回車符,則將光標置位到行首
    mov bl, 80
    div bl
    mul bl
    mov bx, ax
    jmp .set_cursor
    
    ; ; 將光標位置移到行首,可以直接減去當前行嗎??
    ; mov ax, bx
    ; mov dl, 80
    ; div dl
    ; sub bx, ah
    ; jmp .set_cursor
    
 
 ; 判斷是否爲換行符0x0a
 .show_0a:
    cmp cl, 0x0a    ; 0x0a 爲換行符    
    jnz .show_normal; 不是換行符,則正常顯示字符
    add bx, 80      ; 是換行符,再判斷是否需要滾屏
    jmp .roll_screen
 
 ; 正常顯示字符
 ; 在寫入其它內容之前,顯存裏全是黑底白字的空白字符0x0720,所以可以不重寫黑底白字的屬性
 .show_normal:
    mov ax, 0xb800  ; 顯存映射在 0xb8000~0xbffff
    mov es, ax
    shl bx, 1       ; 光標指示字符位置,顯存中一個字符佔2字節,光標位置乘2得到該字符在顯存中得偏移地址    
    mov [es:bx], cl
    shr bx, 1       ; 恢復bx
    inc bx          ; 將光標推進到下一個位置
    
 ; 判斷是否需要向上滾動一行屏幕
 .roll_screen:
    cmp bx, 2000    ; 25行x80列
    jl .set_cursor
    
    mov ax, 0xb800    
    mov ds, ax      ; movsw的源地址ds:si
    mov es, ax      ; movsw的目的地址es:di
    mov si, 0xa0
    mov di, 0
    cld             ; 傳送方向cls std
    mov cx, 1920    ; rep次數 24行*每行80個字符*每個字符加顯示屬性佔2字節 / 一個字爲2字節
    rep movsw
    
    ; 清除屏幕最底一行,即寫入黑底白字的空白字符0x0720
    mov bx, 3840    ; 24行*每行80個字符*每個字符加顯示屬性佔2字節
    mov cx, 80
 .cls:
    mov word [es:bx], 0x0720
    add bx, 2
    loop .cls
    
    mov bx, 1920    ; 重置光標位置爲最底一行行首
 
 ; 根據bx重置光標位置
 ; 索引寄存器端口0x3d4,其索引值14(0x0e)和15(0x0f)分別用於提供光標位置的高和低8位
 ; 數據端口0x3d5
 .set_cursor:
    mov dx, 0x3d4   
    mov al, 0x0e   
    out dx, al
    mov dx, 0x3d5
    mov al, bh      ; in和out 只能用al或者ax
    out dx, al
    
    mov dx, 0x3d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x3d5
    mov al, bl
    out dx, al
    
    pop es
    pop ds
    pop dx
    pop cx
    pop bx
    pop ax

    ret
    

my_int_0x70:
    push ax
    push bx
    push cx
    push dx
    push es

 .w0:                                    
    mov al,0x0a                        ;阻斷NMI。當然,通常是不必要的
    or al,0x80                          
    out 0x70,al
    in al,0x71                         ;讀寄存器A
    test al,0x80                       ;測試第7位UIP 
    jnz .w0                            ;以上代碼對於更新週期結束中斷來說 
                                       ;是不必要的 
    xor al,al
    or al,0x80
    out 0x70,al
    in al,0x71                         ;讀RTC當前時間(秒)
    push ax

    mov al,2
    or al,0x80
    out 0x70,al
    in al,0x71                         ;讀RTC當前時間(分)
    push ax

    mov al,4
    or al,0x80
    out 0x70,al
    in al,0x71                         ;讀RTC當前時間(時)
    push ax

    ; 讀一下RTC的寄存器C,使得所有中斷標誌復位。相當於,告訴RTC,中斷已得到處理,可以繼續下一次中斷。
    ; 否則,RTC看到中斷未被處理,將不再產生中斷信號。
    ; RTC產生中斷的原因有多種,可以在程序中通過讀寄存器C來判斷。不過,這裏不需要,因爲除了更新週期結束中斷外,其他中斷都被關閉了。
    mov al,0x0c                        ;寄存器C的索引。且開放NMI 
    out 0x70,al
    in al,0x71                         ;讀一下RTC的寄存器C,否則只發生一次中斷
                                       ;此處不考慮鬧鐘和週期性中斷的情況 

    ; 屏幕是黑的,默認的顯示屬性是0x07,即黑底白字
    mov ax,0xb800
    mov es,ax                          ; es指向顯示緩衝區
    mov bx,12*160 + 36*2               ;從屏幕上的12行36列開始顯示    

    ; 小時
    pop ax
    call bcd_to_ascii

    mov [es:bx],ah
    mov [es:bx+2],al                   ;顯示兩位小時數字

    mov byte [es:bx+4],':'             ;顯示分隔符':'
    not byte [es:bx+5]                 ;反轉顯示屬性 

    ; 分鐘
    pop ax
    call bcd_to_ascii
    
    mov [es:bx+6],ah
    mov [es:bx+8],al                   ;顯示兩位分鐘數字

    mov al,':'
    mov [es:bx+10],al                  ;顯示分隔符':'
    not byte [es:bx+11]                ;反轉顯示屬性

    ; 秒
    pop ax
    call bcd_to_ascii

    mov [es:bx+12],ah
    mov [es:bx+14],al                  ;顯示兩位小時數字

    ; 向8259芯片發送中斷結束命令(End Of Interrupt, EOI)
    mov al,0x20                        ;中斷結束命令EOI 
    out 0xa0,al                        ;向從片發送 
    out 0x20,al                        ;向主片發送
    
    pop es
    pop dx
    pop cx
    pop bx
    pop ax
    iret        ; iret 中斷返回指令,Interrupt Return,回到中斷之前的地方繼續執行
                ; 這裏如果用ret,顯示的時間將不會更新 ? ? ?

    
; Function: BCD碼轉ASCII    
; Input: AL, BCD碼
; Output: AX, ascii碼
bcd_to_ascii:
    mov ah, al      ; 先複製到ah,用於後面處理十位數,al用於處理個位數
    and al, 0x0f    ; 僅保留低4位
    add al, 0x30    ; 轉換成ASCII
    
    shr ah, 4
    and ah, 0x0f
    add ah, 0x30
    
    ret
    




    
;===============================================================================
SECTION data align=16 vstart=0

msg_init       db 'Starting...',0x0d,0x0a,0
               
msg_install    db 'Installing a new interrupt 70H...',0

msg_done       db 'Done.',0x0d,0x0a,0

msg_tips       db 'Clock is now working.',0


    
; ===============================================================================     
SECTION stack align=16 vstart=0
    resb 256
stack_end:



; ===============================================================================  
SECTION program_tail
program_end:

 

發佈了120 篇原創文章 · 獲贊 43 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章