作業要求:
- Write a TSR program which uses the keyboard interrupt.
寫一個以鍵盤interrupt作為啟動條件的TSR(Terminate and Stay Resident)程式。 - When a hot key (using ^ as hot key) is pressed, the TSR program writes your English name and student ID in the middle of the console.
用“^”當作啟動的熱鍵,按下去後在螢幕上顯示你的“英文名字和學號”。 - Note: the TSR program must write your name and ID directly onto the video RAM of the console instead of using INT calls.
提醒:TSR程式必須直接將字串資料寫進“控制畫面的記憶體位址”,而不是使用INT指令。(用了程式會當掉)
心得
這次作業的程序流程跟以往在撰寫的程式不一樣,會保留一部分的程式片段於記憶體內部,並且在執行的時候執行類似於安裝的處理程序,將 DOS 系統的interrupt vector table 修改成跳躍到自己指定的程序片段,因此,所編寫的程序會被當作中繼指令被觸發,而在鍵入鍵盤時,會觸發顯示於螢幕的指令,中間經過剛剛載入的程序進行控制。
對於早期的 DOS 來說有很多 interrupt vector table 的數值是可以被修改的,為了鼓勵早期的開發者在 MS-DOS 上面開發,因此允許使用者進行修改,但由於interrupt vector 會有碰撞的可能,這就產生程序相容性的問題。現在的 vector table 是被部分保護以防止作業系統流程不正確,也是防止病毒的一種方式。
而在處理鍵盤鍵入的 scan key 時,可以參照以下的網址:
http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html
這次的程序流程稍微比較麻煩,有判斷的 AND OR 布林運算,為了不大幅度修改程式碼,採用展開的方式處理每一步。
釐清 push 指令與 call proc 取得參數的觀念,由於丟入的是 reg16 因此參數的是 [bp+4]、[bp+6],又因為是 near call,bp 與 return_address(只有 ip)都是 2 bytes,所以第一參數位置在 [bp+4]。
在音樂處理部分,轉換於以下的網址。
http://www.autoitscript.com/forum/topic/40848-beep-music-mario-bros-theme/
Values for keyboard make/break (scan) code:
01h Esc 31h N
02h 1 ! 32h M
03h 2 @ 33h , < 63h F16
04h 3 # 34h . > 64h F17
05h 4 $ 35h / ? 65h F18
06h 5 % 36h Right Shift 66h F19
07h 6 ^ 37h Grey* 67h F20
08h 7 & 38h Alt 68h F21 (Fn) [*]
09h 8 * 39h SpaceBar 69h F22
0Ah 9 ( 3Ah CapsLock 6Ah F23
0Bh 0 ) 3Bh F1 6Bh F24
0Ch - _ 3Ch F2 6Ch --
0Dh = + 3Dh F3 6Dh EraseEOF
0Eh Backspace 3Eh F4
0Fh Tab 3Fh F5 6Fh Copy/Play
10h Q 40h F6
11h W 41h F7
12h E 42h F8 72h CrSel
13h R 43h F9 73h <delta> [*]
14h T 44h F10 74h ExSel
15h Y 45h NumLock 75h --
16h U 46h ScrollLock 76h Clear
17h I 47h Home 77h [Note2] Joyst But1
18h O 48h UpArrow 78h [Note2] Joyst But2
19h P 49h PgUp 79h [Note2] Joyst Right
1Ah [ { 4Ah Grey- 7Ah [Note2] Joyst Left
1Bh ] } 4Bh LeftArrow 7Bh [Note2] Joyst Up
1Ch Enter 4Ch Keypad 5 7Ch [Note2] Joyst Down
1Dh Ctrl 4Dh RightArrow 7Dh [Note2] right mouse
1Eh A 4Eh Grey+ 7Eh [Note2] left mouse
1Fh S 4Fh End
20h D 50h DownArrow
21h F 51h PgDn
22h G 52h Ins
23h H 53h Del
24h J 54h SysReq ---non-key codes---
25h K 55h [Note1] F11 00h kbd buffer full
26h L 56h left \| (102-key)
27h ; : 57h F11 AAh self-test complete
28h ' " 58h F12 E0h prefix code
29h ` ~ 59h [Note1] F15 E1h prefix code
2Ah Left Shift 5Ah PA1 EEh ECHO
2Bh \ | 5Bh F13 (LWin) F0h prefix code (key break)
2Ch Z 5Ch F14 (RWin) FAh ACK
2Dh X 5Dh F15 (Menu) FCh diag failure (MF-kbd)
2Eh C FDh diag failure (AT-kbd)
2Fh V FEh RESEND
30h B FFh kbd error/buffer full
程式碼說明
基本上都是由 TSR.asm 為主幹下去修改的,必須了解 segment at 的指令是抓記憶體的絕對位置。對於 combination 的鍵入組合,讀取 keyboard flag 利用 bit mask 將數字抓出來判斷。
而在處理輸出的時候,特別要注意記憶體的位址,如果沒有加指定的 segment address,則會用正在執行的程序的 cs, ds, ss,由於這程序會用到另一個 segment address 寫入,因此寫的時候要特別注意。
由於 mov 指令沒有 mem, mem 兩個記憶體的格式,因此需要多一部分去處理。在顯示訊息的部分,定義座標的 base 是左上角,為了不讓 counter 產生負數的情況去做處理。而在記憶體的 RAM 中,是用兩個 byte 去表示一格的顯示(印出字元+顯示屬性),因此每次印出的移動是 2 bytes 為單位。
使用 wasd 上下控制一個方向圖示 (^<)=)
[2013/6/8] 新增快捷 m 鍵撥放音樂,音樂為馬力歐主題曲
字型撰寫 beep function,發現 delay 處理上不方便,沒有 int 指令去直接計算時間差,如果要求是以秒為單位,則可以用系統時間去判斷,但如果在其以下 ms 單位則無法表示,藉此使用一些 nop 去拖延時間。
TITLE TSR homework6 (TSR.asm)
; Write a TSR program which uses the keyboard interrupt.
; When a hot key (using ^ as hot key) is pressed, the TSR
; program writes your English name and student ID in the
; middle of the console.
; Note: the TSR program must write your name and ID directly
; onto the video RAM of the console instead of using INT calls.
intno EQU 09h ; keyboard interrupt number
center EQU (80*13+40)*2 ; screen 80x25, two bytes for each cell
leftup EQU (80*0+0)*2 ; left up side
six_key EQU 07h ; scan code for 6 key
w_key EQU 11h ; scan code for w key
a_key EQU 1eh ; scan code for a key
s_key EQU 1fh ; scan code for s key
d_key EQU 20h ; scan code for d key
m_key EQU 32h ; scan code for m key
rt_shift EQU 01h ; right shift key: bit 0
lt_shift EQU 02h ; left shift key: bit 1
VRamText SEGMENT at 0b800h ; screen ram memory
VRamText ENDS
codeseg SEGMENT PARA
assume cs:codeseg, ds:codeseg, es:VRamText
ORG 100h ; this is a .com program. must be before main
start: ; start ENDS
jmp setup ; jump to TSR installation
; data block
lcounter dw 0 ; pressed counter
scounter dw 0 ; shift counter
ncounter dw 0 ; shift next counter
shiftimg db '>' ; shift image character
old_interrupt dd ? ; DWORD(32bit) es:ip
msg db 'Morris_100502205', '$'
msglen equ $-msg-1
musichz WORD 2600, 796, 796, 796, 1686, 1592, 1592, 1592, 3373, 3183
WORD 3373, 3183, 3183, 6366, 1686, 1592, 1592, 1686, 1592, 1592
WORD 1686, 14857, 1418, 1502, 1418, 1263, 1418, 1592, 1787, 1686
WORD 1592, 1592, 1686, 1592, 1592, 1686, 1592, 1418, 1502, 1418
WORD 1263, 1, 1126, 1062, 843, 796, 1, 1686, 1592, 1592
WORD 1686, 1592, 1592, 1686, 1592, 1418, 1502, 1418, 1263, 1418
WORD 1592, 1787, 1893, 1787, 1592, 1418, 1592, 1787, 1893, 2125
WORD 1893, 1787, 1592, 1787, 1893, 2125, 2385, 2125, 1893, 1787
WORD 1893, 2125, 2527, 2385, 1, 3573, 3183, 3785, 2385, 2527
WORD 2677, 2836, 2527, 2385, 1418, 2527, 1418, 709, 2836, 3183
WORD 2836, 2527, 1592, 2836, 1592, 796, 3183, 3573, 3183, 2836
WORD 1787, 3005, 1787, 893, 3573, 3785, 4011, 3785, 1893, 1787
WORD 1592, 2836, 2527, 2385, 1418, 2527, 1418, 709, 2836, 3183
WORD 2836, 2527, 1592, 2836, 1592, 796, 3183, 3573, 3183, 2836
WORD 1787, 1893, 1787, 1686, 1592, 3183, 3183, 3183, 3183, 6366
WORD 6366, 6366, 6745, 6366, 6745, 6366, 6009, 5672, 5354, 5053
musicde WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 200, 200, 400, 400, 200, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 400, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 400, 200, 10, 200, 10, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 400, 200
WORD 200, 200, 200, 200, 200, 400, 200, 200, 200, 200
WORD 200, 200, 400, 200, 200, 200, 200, 200, 200, 400
WORD 200, 200, 200, 200, 400, 400, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 400
WORD 400, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
; memory-resident code begins here
int_handler:
pushf ; save flags
push ax ; save regs
push dx
push es
push cx
push di
push si
; point ES:DI to the DOS keyboard flag byte:
mov ax, 40h ; DOS data segment is at 40h
mov es, ax
mov di, 17h ; location of keyboard flag
mov ah, es:[di] ; copy keyboard flag into AH
; test for the LEFT_SHIFT and RIGHT_SHIFT keys:
test ah, rt_shift ; right shift key held down??
jnz L1 ; yes: process
test ah, lt_shift ; left shift key held down?
jnz L1 ; yes: process
jmp byPass ; no LEFT_SHIFT or RIGHT_SHIFT: exit
; test if hot-key(^) pressed by (left/right)shift+6
L1: in al, 60h ; 60h keyboard input port
cmp al, six_key ; 6 key pressed?
jnz byPass ; no: exit
; point ES:DI to the DOS screen RAM, DS:SI to the string address
; clear last time print
sub lcounter, 2*msglen ; make clear using
mov ax, cs
mov ds, ax
mov ax, VRamText
mov es, ax
mov ax, center
add ax, lcounter
mov di, ax ; screen buffer offset
lea si, msg ; string address offset
; place the message into the video text buffer
mov cx, msglen
CL1: ; run write into screen buffer
mov al, ' ' ; get character from string[si]
mov BYTE PTR es:[di], al ; write into buffer[di]
inc di
inc di ; skip over the color attribute byte
inc si
add lcounter, 2
loop CL1
sub lcounter, 2*msglen
add lcounter, 2
mov ax, cs
mov ds, ax
mov ax, VRamText
mov es, ax
mov ax, center
add ax, lcounter
mov di, ax ; screen buffer offset
lea si, msg ; string address offset
; place the message into the video text buffer
mov cx, msglen
DP1: ; run write into screen buffer
mov al, BYTE PTR [si] ; get character from string[si]
mov BYTE PTR es:[di], al ; write into buffer[di]
inc di
inc di ; skip over the color attribute byte
inc si
add lcounter, 2
loop DP1
jmp byPassEnd
byPass: ; ignore unneeded input
; check w, a, s, d input
in al, 60h ; 60h keyboard input port
cmp al, w_key ; w key pressed?
jz wshift ; yes: wshift
cmp al, a_key ; a key pressed?
jz ashift ; yes: ashift
cmp al, s_key ; s key pressed?
jz sshift ; yes: sshift
cmp al, d_key ; d key pressed?
jz dshift ; yes: dshift
jmp byPassEnd
wshift:
mov ax, scounter
mov ncounter, ax
sub ncounter, 80
sub ncounter, 80
mov shiftimg, '^'
jmp shiftpro
ashift:
mov ax, scounter
mov ncounter, ax
dec ncounter
dec ncounter
mov shiftimg, '<'
jmp shiftpro
sshift:
mov ax, scounter
mov ncounter, ax
add ncounter, 80
add ncounter, 80
mov shiftimg, '='
jmp shiftpro
dshift:
mov ax, scounter
mov ncounter, ax
add ncounter, 1
add ncounter, 1
mov shiftimg, '>'
jmp shiftpro
; point ES:DI to the DOS screen RAM, DS:SI to the string address
shiftpro:
; clear last time print
mov ax, cs
mov ds, ax
mov ax, VRamText
mov es, ax
mov ax, leftup
add ax, scounter
mov di, ax ; screen buffer offset
lea si, shiftimg ; string address offset
; place the message into the video text buffer
mov cx, 1
CSL1: ; run write into screen buffer
mov al, ' ' ; get character from string[si]
mov BYTE PTR es:[di], al ; write into buffer[di]
inc di
inc di ; skip over the color attribute byte
inc si
add scounter, 2
loop CSL1
sub scounter, 2
;
mov ax, ncounter
mov scounter, ax
mov ax, cs
mov ds, ax
mov ax, VRamText
mov es, ax
mov ax, leftup
add ax, scounter
mov di, ax ; screen buffer offset
lea si, shiftimg ; string address offset
; place the message into the video text buffer
mov cx, 1
SL1: ; run write into screen buffer
mov al, BYTE PTR [si] ; get character from string[si]
mov BYTE PTR es:[di], al ; write into buffer[di]
inc di
inc di ; skip over the color attribute byte
inc si
add scounter, 2
loop SL1
sub scounter, 2
byPassEnd:
in al, 60h ; 60h keyboard input port
cmp al, m_key ; m key pressed?
jnz byPassEnd2 ; no: exit
; music play
mov ax, cs
mov ds, ax
mov cx, 160 ; music size
lea di, musichz
lea si, musicde
loops:
mov ax, [di]
push ax
mov ax, [si]
push ax
call beep
inc di
inc di
inc si
inc si
mov ax, 1 ; make delay between music note
push ax
mov ax, 200
push ax
call beep
loop loops
byPassEnd2:
pop si ; restore regs
pop di
pop cx
pop es
pop dx
pop ax
popf ; restore flags
sti ; set interrupt flag
jmp cs:old_interrupt ; jump to INT routine
; jmp cs:[old_interrupt] is the same, and by this method far call
align dword ; because memory cell bound, DWORD will be faster.
beep PROC ; push hz[bp+6], push delay[bp+4]
push bp
mov bp, sp
push cx
push ax
mov al, 10110110b ; out control byte
out 43h, al ; to port 43h
mov ax, [bp+6] ; Frequency = 1.19 MHz/ ???hz
out 42h,al ; out low-byte of frequency divider
mov al, ah
out 42h, al ; out high-byte of frequence divider
; on-off-on
in al, 61h
or al, 00000011b ;on bit0 and bit 1
out 61h, al
and al, 11111100b ; off bit0 and bit 1
out 61h, al ; of port 61h
or al, 00000011b ;on bit0 and bit 1
out 61h, al
; delay ??? ms
mov cx, [bp+4] ; get delay parameter
delay_loop:
push cx
mov cx, 300h ; this value will response different computer clock rate.
loop2:
and ax, ax ; nop
loop loop2
pop cx
loop delay_loop
; close
in al, 61h
and al, 11111100b ; off bit0 and bit 1
out 61h, al ; of port 61h
pop ax
pop cx
pop bp
ret 4 ; clear stack 2+2 bytes
beep ENDP
end_ISR label byte
setup:
; mov ax, cs ; not necessary for .com program
; mov ds, ax ; code segment already correct.
mov ah, 35h
mov al, intno ; get INT 9 vector
int 21h
mov WORD PTR old_interrupt, bx ; save INT 9 vector
mov WORD PTR old_interrupt+2, es
cli ; clear interrupt flag, disables external interrupts
mov ah, 25h ; set interrupt vector, INT 9 with ds:dx
mov al, intno ;
mov dx, OFFSET int_handler
int 21h
sti ; set interrupt flag
mov ah, 09h ; write a $-terminated string to standard output
mov dx, OFFSET msg
int 21h ; only to show something.
mov ax, 3100h ; terminate and stay resodent
mov dx, OFFSET end_ISR ; point to end of resident code
int 21h ; dx must be the program size in bytes. execute MS-DOS function
codeseg ENDS
END start
文章定位: