Assembly tiếp (TT)
Bài thực hành số 3
Ngăn xếp – Thủ tục – Macro
Mục đích
Hiểu được cơ chế hoạt động của ngăn xếp, quá trình gọi một thủ tục.
Biết cách sử dụng ngăn xếp, khai báo và gọi thủ tục.
Biết cách tạo và sử dụng macro.
Tóm tắt lý thuyết
Ngăn xếp
1. Một số lưu ý:
- Ngăn xếp (Stack) là vùng nhớ đặc biệt được truy cập theo cơ chế “vào trước ra sau” (LIFO – Last In First Out), nghĩa là dữ liệu nào đưa vào sau sẽ được lấy ra trước.
- Ngăn xếp gồm nhiều phần tử, mỗi phần tử là một từ (2 bytes).
- Vị trí của ngăn xếp trong bộ nhớ được xác định bởi cặp thanh ghi SS:SP (SS chứa địa chỉ đoạn, SP chứa địa chỉ ô của đỉnh ngăn xếp). Khi chưa sử dụng, ngăn xếp rỗng, vị trí được xác định bởi SP lúc đó là đáy ngăn xếp.
2. Khai báo:
.STACK <kích thước của ngăn xếp>
Ví dụ: khai báo một vùng ngăn xếp có kích thước 256 bytes:
.STACK 100h
3. Các thao tác:
· Đưa trị vào (đỉnh) ngăn xếp:
PUSH <nguồn> ; đưa nguồn (thanh ghi hay từ nhớ
; 16 bit) vào đỉnh ngăn xếp
PUSHW <hằng> ; đưa trực tiếp một hằng16 bit vào
; đỉnh ngăn xếp
PUSHF ; đưa nội dung thanh ghi cờ vào đỉnh ; ngăn xếp
· Lấy trị (ở đỉnh) ra khỏi ngăn xếp:
POP <đích> ; lấy giá trị (2 bytes) ở đỉnh ngăn xếp
; đưa vào đích (thanh ghi (trừ thanh
; ghi IP) hay từ nhớ 16 bit)
POPF ; lấy giá trị (2 bytes) ở đỉnh ngăn xếp
; đưa vào thanh ghi cờ
Chú ý : Các lệnh PUSH, PUSHF, POP và POPF không ảnh hưởng tới các cờ.
Ví dụ: Chương trình xuất chuỗi ngược dùng stack:
000h
…
0FCh
0FCh
SS:SP à
…
0FCh
0FCh
SS:SP à
000h
…
0FCh
0FCh
SS:SP à
000h
100h
000h
…
0FCh
0FCh
100h
SS:SP à
…
0FCh
0FCh
SS:SP à
000h
100h
…
0FCh
0FCh
SS:SP à
000h
100h
…
0FCh
0FCh
SS:SP à
000h
100h
100h
100h
.model small
.stack 100h
.data
msg1 DB 'Nhap vao 1 chuoi: $'
msg2 DB 10,13,'Chuoi nghich dao la: $'
.code
mov ax,@data
mov ds,ax
mov ah,9
lea dx,msg1
int 21h
mov cx,0
nhap:
mov ah,1
int 21h
cmp al,13
je thongbaoxuat
xor ah,ah
push ax
inc cx
jmp nhap
thongbaoxuat:
mov ah,9
lea dx,msg2
int 21h
xuat:
pop ax
mov dl,al
mov ah,2
int 21h
loop xuat
mov ah,4ch
int 21h
END
1. Khai báo
2. Nhập lần lượt a,b,c đưa vào ngăn xếp:
Nhập ký tự ‘a’:
00
61
Nhập ký tự ‘b’:
00
62
00
61
Nhập ký tự ‘c’:
00
63
00
62
00
61
3. Xuất các giá trị trong ngăn xếp
Xuất ký tự ‘c’:
00
63
00
62
00
61
Xuất ký tự ‘b’:
00
62
00
61
Xuất ký tự ‘a’:
00
61
Thủ tục
1. Khai báo:
<Tên thủ tục>PROC<Kiểu>;kiểu là NEAR(mặc định) hay FAR
; thân thủ tục
……………
RET
<Tên thủ tục> ENDP
Thủ tục thường được viết ở cuối chương trình.
2. Gọi thủ tục:
CALL <Tên thủ tục>
CALL <Địa chỉ> ; địa chỉ là thanh ghi hoặc vùng nhớ chứa địa chỉ
; thủ tục
3. Hoạt động của lời gọi thủ tục:
Khi thực hiện lời gọi thủ tục (CALL) thì:
- Địa chỉ ô của lệnh kế lệnh CALL (*) sẽ được cất vào ngăn xếp
- Địa chỉ ô của lệnh đầu tiên trong thủ tục được đưa vào IP
Khi thực hiện lệnh RET để quay về trình gọi thì:
- Địa chỉ trong ngăn xếp được lấy ra và được vào IP.
Do đó, nếu trong thủ tục có thao tác với ngăn xếp thì trong thủ tục, trước khi thao tác với ngăn xếp ta nên lưu lại địa chỉ (*) ở trên (chính là giá trị hiện thời trong ngăn xếp) để quay trở về trình gọi. Xem mô tả trong ví dụ sau.
Ví dụ: Nhậpxuấtchuỗikí tự
000h
…
0FCh
0FCh
100h
SS:SP à
…
0FCh
0FCh
SS:SP à
000h
100h
000h
…
0FCh
0FCh
100h
SS:SP à
…
0FCh
0FCh
SS:SP à
000h
100h
000h
…
0FCh
0FCh
100h
SS:SP à
000h
…
0FCh
100h
0FCh
SS:SP à
.model small
.stack 100h
.code
CALL Nhap
CALL Xuat
mov ah,4ch
int 21h
;---------------------------------------------------
Nhap PROC
pop bx
mov ah,2
mov dl,’?’
int 21h
xor cx,cx
nhap:
mov ah,1
int 21h
cmp al,13
je ketthucnhap
push ax
inc cx
jmp nhap
ketthucnhap:
push bx
RET
Nhap ENDP
;---------------------------------------------------
Xuat PROC
pop bx
mov ah,2
mov dl,13
int 21h
mov dl,10
int 21h
jcxz ketthucxuat
xuat:
pop dx
int 21h
loop xuat
ketthucxuat:
push bx
RET
Xuat ENDP
END
1. Khai báo
2. Gọi thủ tục Nhap
CALL Xuat
IP = địa chỉ ô lệnh “pop bx”
3. Lưu lại địa chỉ quay về
BX = địa chỉ lệnh “CALL Xuat”
4. Nhập ký tự a,b:
00
62
00
61
5. Trả lại địa chỉ quay về
BX = địa chỉ lệnh “CALL Xuat”
CALL Xuat
00
62
00
61
6. Kết thúc thủ tục Nhập:
00
62
00
61
IP = địa chỉ ô lệnh “CALL Xuat”
Lời gọi thủ tục xuất (CALL Xuat) cũng hoạt động tương tự như trên.
Macro
1. Một số lưu ý:
- Khi chúng ta có nhiều đoạn code giống nhau, chúng ta có thể sử dụng macro để thay thế, giống như chúng ta dùng define ở trong C.
- Bản chất là thay thế lời gọi macro bằng các lệnh trong thân macro.
- Các macro nên phục hồi những thanh ghi mà nó sử dụng trừ những thanh ghi chứa kết quả.
2. Khai báo:
<tên macro> MACRO <các đối số>
; thân macro
……………
ENDM
3. Hai cách sử dụng macro
· Tạo macro trực tiếp trong chươnng trình:
- Các macro thường được khai báo ở đầu chương trình trước phần .code.
- Ví dụ: Xuất một chuỗi ra màn hình sử dụng macro
.modelsmall
.stack 100h
.data
chuoi1 db “hello”,10,13,’$’
chuoi2 db “bye”,10,13,’$’
@xuatchuoi macro chuoi
lea dx,chuoi
mov ah,9
int 21h
endm
.code
…
@xuatchuoi chuoi1
@xuatchuoi chuoi2
…
end
· Xây dựng thư viện các macro:
- Tạo 1 thư viện (tập tin) chứa các macro
- include vào chương trình (thường trước phần .code) bằng lệnh include
- Ví dụ: Xuất một chuỗi ra màn hình sử dụng thư viện macro
THUVIEN.INC
@xuatchuoi macro chuoi
lea dx,chuoi
mov ah,9
int 21h
endm
TestMacro.asm
.modelsmall
.stack 100h
.data
chuoi1 db “hello”,10,13,’$’
chuoi2 db “bye”,10,13,’$’
INCLUDE THUVIEN.INC
.code
…
@xuatchuoi chuoi1
@xuatchuoi chuoi2
…
end
4. Các thành phần cục bộ của macro:
- Trong macro, ta cũng có thể khai báo các biến, nhãn cục bộ để tránh gây ra lỗi khi gọi macro nhiều lần.
- Cú pháp :
LOCAL <danh sách các nhãn, các biến cục bộ>
- Ví dụ: Xuất một chuỗi hằng ra màn hình sử dụng macro với biến cục bộ
.modelsmall
.stack 100h
@xuatchuoi macro chuoi
LOCAL chuoicucbo, nhancucbo
.data
chuoicucbo db chuoi,’$’
.code
lea dx,chuoicucbo
mov ah,9
int 21h
endm
.code
…
nhancucbo:
…
@xuatchuoi <“hello”,10,13>
@xuatchuoi ”bye”
…
end
Lưu ý: nếu cần truyền chuỗi phức tạp thì ta cần sử dụng <…> để báo cho trình biên dịch biết đây là một đối số.
Tài liệu tham khảo
Nguyễn Minh Tuấn, Giáo trình hợp ngữ - Chương 6, ĐHKHTN, 2002
Randal Hyde, The art of assembly language programming – Chapter 11,12.
Norton Guide
Dan Rollins, TechHelp v.6.0
Bài tập
Bài 1: Viết chương trình kiểm tra một biểu thức đại số có chứa các dấu ngoặc (như (), [] và {}) là hợp lệ hay không hợp lệ .
Ví dụ:
(a + [b – { c * ( d – e ) } ] + f)
là hợp lệ nhưng
(a + [b – { c * ( d – e )] } + f)
là không hợp lệ.
Bài 2: Tính giá trị biểu thức đã nhập ở bài tập 2 theo thứ tự từ trái sang phải.
Bài 3: Viết lại các bài tập tuần trước dưới dạng các thủ tục
Bài 4: Xây dựng một thư viện các macro
Mở rộng
Có những cách nào để truyền tham số cho thủ tục ? để nhận kết quả trả về ?
Thử viết một thủ tục đệ quy.
Tìm hiểu cách phân chia chương trình thành nhiều file và cách biên dịch, liên kểt chúng.
Hướng dẫn
Bài 1. dùng ngăn xếp để PUSH các dấu ngoặc trái ( ‘(‘, ’{‘, ‘[‘ ) vào ngăn xếp. Nếu gặp dấu ngoặc phải ( ‘)’, ‘}’, ‘]’ ) thì POP từ stack ra. Nếu không POP được, hoặc POP ra không đúng loại với dấu ngoặc phải -> không hợp lệ . Ngược lại là biểu thức hợp lệ.
Bài thực hành số 4
Làm việc với số nguyên
Mục đích
Biết sử dụng các phép toán logic, số học
Biết cách đổi giữa các cơ số nhị phân, thập phân và thập lục phân
Tóm tắt lý thuyết
Phép toán trên bit
1. NOT : lệnh này đổi tác tố đích thành số bù. Không có cờ nào bị ảnh hưởng
2. AND (OR hoặc XOR) : AND (OR, XOR) Đích, nguồn
Tất cả các cờ đều bị ảnh hưởng
Chú ý : AND dùng để xóa các bit. OR dùng để bật các bit. XOR dùng để đảo bit.
3. Các lệnh dịch bit SHL và SHR : dịch các bit của toán hạng đích sang trái (hoặc phải) một hay nhiều bit.
SHL (SHR) Đích, 1 hoặc SHL (SHR) Đích, CL
CL là số lần dịch bit.Việc dịch bit trái (phải) tương ứng với phép nhân (chia) cho
lũy thừa 2.
Chú ý : Hiện tượng tràn số có thể xảy ra và cờ CF chứa bit cuối cùng bị dịch ra
khỏi toán hạng.Để dịch bit với các số âm ta nên dùng SAL hoặc SAR tương ứng.
4. Các lệnh quay ROL và ROR : dịch các bit của toán hạng đích sang trái (phải)
một hay nhiều bit theo vòng tròn.
ROL (ROR) Đích, 1 hoặc ROL (ROR) Đích, CL
CL là số lần quay bit, cờ CF sẽ chứa giá trị bit bị dịch ra khỏi toán hạng.
Chú ý : Để dịch bit qua cờ nhớ ta dùng RCL hoặc RCR tương ứng.
Ví dụ : Sử dụng lệnh ROL để đếm số bit 1 trong thanh ghi BX
XOR AX,AX
MOV CX,16
TOP :
ROL BX, 1
JNC NEXT ; kiểm tra có phải là bit 0 không
INC AX ; nếu không phải thì tăng số bit 1
NEXT:
LOOPTOP ; lặp cho đến khi làm xong
Lệnh số học
1. Cộng ADD, ADC : ADD (ADC) đích , nguồn
Ví dụ : ADD AL , 10H -> AL = AL + 10H
2. Trừ SUB, SBB : SUB (SBB) đích , nguồn
Ví dụ : SUB BL, 10H -> BL = BL – 10H
Chú ý : Các phép toán cộng trừ trực tiếp giữa các ô nhớ là không hợp lệ. Ngoài ra
ta cũng có thể sử dụng INC hoặc DEC để cộng hoặc trừ 1 đơn vị vào nội dung
một ô nhớ hoặc một thanh ghi.
3. Nhân MUL, IMUL: MUL (IMUL) nguồn
Lệnh MUL thực hiện phép nhân không dấu, còn IMUL là lệnh nhân có dấu. Nếu nguồn là byte (8 bit) thì kết quả chứa trong AX và AX = AL * nguồn. Nếu nguồn là word (16 bit) thì kết quả chứa trong DX:AX và DX:AX = AX * nguồn. Nếu nguồn là double (32 bit) thì kết quà chứa trong EDX:EAX và EDX:EAX = EAX * nguồn.
4. Chia DIV, IDIV : DIV (IDIV) số chia
Lệnh DIV thực hiện chia không dấu, còn IDIV là lệnh chia có dấu. Nếu số chia là byte (8 bit) thì số bị chia là AX và kết quả gồm: phần dư = AH, phần thương = AL. Nếu số chia là word (16 bit) thì số bị chia là DX:AX và kết quả gồm phần dư = DX, phần thương = AX. Nếu số chia là double thì sô bị chia là EDX:EAX và kết quả gồm phần dư = EDX, phần thương = EAX.
Chú ý : phải xoá giá trị DX hoặc EDX trước khi nhân, hoặc chia.
Tài liệu tham khảo
Nguyễn Minh Tuấn, Giáo trình hợp ngữ - Chương 7, ĐHKHTN, 2002
Randal Hyde, The art of assembly language programming – Chapter 9.
Norton Guide
Dan Rollins, TechHelp v.6.0
Bài tập
1. Viết chương trình (VCT) đổi một số dạng thập phân sang thập lục phân.
Ví dụ: Nhập một số hệ 10 : 26
Dạng thập lục phân: 1A
2. VCT nhập một số hệ thập phân rồi xuất ra biểu diễn dạng nhị phân của nó.
Ví dụ: Nhập số hệ 10: 26
Dạng nhị phân: 11010
3. VCT đổi một số dạng thập lục phân sang sang thập phân.
Ví dụ: Nhập số hệ thập lục phân: 1a (hoặc 1A)
Dạng thập phân của nó là: 26
4. VCT đổi một số dạng thập lục phân sang nhị phân
Ví dụ: Nhập số hệ thập lục phân: 1a (hoặc 1A)
Dạng biểu diễn nhị phân là : 00011010
5. VCT đổi một số dạng nhị phân sang thập phân
Ví dụ: Nhập một số nhị phân: 11010
Dạng thập phân là: 26
6. VCT đổi một số dạng nhị phân sang thập lục phân
Ví dụ: Nhập một số nhị phân: 11010
Dạng thập lục phân là: 1A
7. VCT “echo” với yêu cầu: nhập vào số nguyên dương n và một kí tự bất kì, sau đó trên màn hình xuất hiện n lần kí tự đó.
Ví dụ: Nhập một kí tự: k Nhập số lần n : 5 à Kết quả : kkkkk.
8. VCT nhập vào hai số nguyên dương. Tính tổng, hiệu, tích, thương (phép div) và phần dư khi chia 2 số nguyên (phép mod)
Ví dụ: Nhập số thứ nhất : 14 Nhập số thứ hai : 16
Tổng hai số là : 30 Hiệu: -2 Tích: 224 Thương: 0 Phần dư: 14
Mở rộng
Tìm hiểu về BCD. Viết chương trình nhập 2 số nguyên ở hệ 10, chuyển sang BCD, tính tổng, hiệu và in kết quả ở hệ 10.
Liệu có thể viết chương trình tính được 20!, 30!, kết quả in ra ở dạng hex ? dạng cơ số 10 ?
Bài thực hành số 5
Làm việc với xâu kí tự
Mục đích
Biết sử dụng các phép toán trên chuỗi
Biết làm một số thao tác với xâu kí tự (tìm kiếm, đếm từ, chuyển hoa / thường …. )
Tóm tắt lý thuyết
Ø Cờ hướng DF (Direction Flag) : xác định hướng xử lí chuỗi. Khi DF = 0 (dùng lệnh CLD) chuỗi được xử lí tăng dần, ngược lại DF = 1 (lệnh STD) chuỗi được xử lí giảm dần.
Ø Con trỏ chuỗi: DS:SI – địa chỉ nguồn và ES:DI – địa chỉ đích
Ø Các lệnh trên chuỗi :
1. MOVSB (MOVSW) : chuyển nội dung của byte (word) được định bởi DS:SI đến byte (word) được chỉ bởi ES: DI. Sau đó SI và DI tự động tăng lên 1 (hoặc 2) nếu cờ DF = 0 hay giảm 1 (hoặc 2) nếu DF = 1
Ví dụ: giả sử cần chép nội dung chuỗi thứ nhất : ‘HELLO’ vào chuỗi
thứ hai theo thứ tự ngược lại ta làm như sau :
.DATA
STR1DB ‘HELLO’
STR2DB 5 DUP(‘?’)
.CODE
MOV AX, @DATA
MOV DS, AX
MOV ES, AX
LEA SI, STR1+4 ; cuối STR1
LEA DI, STR2 ; đầu STR2
STD ; định hướng xử lí giảm
MOV CX, 5
move :
MOVSB
ADD DI,2 ; + 2 do DI bị giảm
; 1 sau lệnh MOVSB
LOOP move
2. STOSB (STOSW): chuyển nội dung của thanh ghi AL (AX) đến byte
(word) được định bởi ES:DI. Sau đó DI tự động tăng lên 1 (hoặc 2) nếu
cờ DF = 0 hay giảm 1 (hoặc 2) nếu DF = 1.
Ví dụ: Đọc và lưu một chuỗi kí tự bằng chức năng AH = 1, ngắt 21H
NhapChuoi PROC
;Vào: DI = chứa offset của chuỗi
;Ra: DI = nội dung chuỗi vừa nhập
; BX = kích thước chuỗi
CLD ; đặt cờ DF theo hướng tăng
XOR BX, BX ; gán BX = 0
MOV AH, 1
INT 21H
while1 :
CMP AL, 13 ; nếu gõ ENTER
JE end_while1 ; kết thúc nhập
CMP AL, 8 ; nếu gõ BS
JNE else1 ;không phải lưu chuỗi
DEC DI ;ngược lại lùi 1 kí tự
DEC BX ;giảm kích thước chuỗi
JMP read ; đọc kí tự khác
else1:
STOSB
INC BX
read:
INT 21H
JMP while1
end_while1: ; thoát khỏi vòng lặp
4. LODSB (LODSW) : chuyển nội dung của byte (word) được định bởi
DS:SI vào AL (hoặc AX) sau đó tăng (hoặc giảm) SI 1 (hoặc 2) đơn vị.
5. SCASB (SCASW): tìm nội dung chứa trong AL (hoặc AX) có trong chuỗi
định bởi ES:DI hay không. Nếu tìm thấy thì cờ ZF sẽ được bật. Sau mỗi
lần thực hiện con trỏ DI sẽ tăng hoặc giảm 1 (hoặc 2) đơn vị.
6. CMPSB (CMPSW) : so sánh byte tại DS:SI và byte tại ES:DI, sau đó tăng
(hoặc giảm) SI và DI 1 (hoặc 2) đơn vị.
Bài tập
9. VCT nhập một chuỗi kí tự và in ra chuỗi theo thứ tự ngược lại. In chiều dài chuỗi.
Ví dụ : Nhập chuỗi : abcd Chuỗi kết quả: dcba Chiều dài chuỗi: 4
10. VCT nhập họ tên .Sau đó biến tất cả thành chữ hoa rồi in ra. Biến tất cả thành chữ thường rồi in ra.
Ví dụ: Nhập vào chuỗi : Thanh cHi khanG Chuỗi Hoa : THANH CHI KHANG
Chuỗi kết quả thường: thanh chi khang
11. Nhập một chuỗi kí tự tính tần số xuất hiện của các nguyên âm.
Ví dụ : Nhập chuỗi : Thanh Chi Khang Số lần xuất hiện của các nguyên âm là: 3
12. VCT nhập hai chuỗi, liệt kê các kí tự có mặt trong hai chuỗi.
Ví dụ: Nhập chuỗi: computer và chuỗi : informatic
Các kí tự có mặt trong hai chuỗi : o, m, t, r
13. Nhập vào hai chuỗi kí tự, so sánh hai chuỗi (= > < ).
Ví dụ: Chuỗi thứ nhất: forn Chuỗi thứ hai : form
Kết quả : Chuỗi thứ nhất > chuỗi thứ hai.
14. Nhập vào hai chuỗi kí tự, kiểm tra chuỗi thứ nhất là chuỗi con chuỗi tthứ hai không, không phân biệt hoa thường.
Ví dụ: Chuỗi thứ nhất : form Chuỗi thứ hai: inFoRMatic
Kết quả : Chuỗi thứ nhất là con chuỗi thư hai
Bài thực hành số 6
Lập trình bàn phím
Mục đích
Hiểu được cách thức hoạt động của bàn phím
Biết cách sử dụng một số hàm liên quan đến bàn phím của ngắt 16h (BIOS ) và ngắt 21h (DOS)
Tóm tắt lý thuyết
Nguyên tắc hoạt động của bàn phím
Bàn phím cho máy PC có nhiều loại: 83 phím, 84 phím, 101 phím,… Bên trong mỗi bàn phím là chip điều khiển 8049 và 8042. Khi một phím được nhấn (up-to-down) hay được thả (down-to-up), chip điều khiển ghi nhận phím đó bằng một (hoặc một vài) mã số (gọi là mã quét, scan code) và gửi mã này ra cổng 60h, đồng thời tạo tín hiệu ngắt IRQ1.
Ví dụ:
- Khi phím chữ ‘a’ được nhấn rồi thả ra, ta nhận được 2 mã quét tương ứng là: 1E và 9E. Thông thường, mã thả (up-code) bằng mã nhấn (down-code) cộng thêm 80h.
- Tương tự, đối với Left-Control, 2 mã quét là 1D và 9D
- Tuy nhiên, với Right-Control, ta nhận được 4 mã quét: 0E 1D (khi nhấn) và 0E 9D (khi thả).
Tín hiệu IRQ1 gây ra ngắt 09h. Ngắt 09h này có nhiệm vụ chuyển đổi mã quét thành mã ASCII và lưu trữ vào bộ đệm bàn phím. Các chương trình có nhu cầu nhận thông tin từ bàn phím có thể sử dụng các hàm của ngắt 21h hoặc 16h để đọc bộ đệm này mà không cần quan tâm đến giá trị của mã quét.
Ví dụ: một chương trình nào đó chỉ cần dùng ngắt 16h, hàm 01 để kiểm tra xem người sử dụng có gõ dấu chấm câu (nhấn phím ‘.’) hay không mà không quan tâm đến đó là phím dấu chấm ở phần keypad (scan code = 53) hay là ở phần các phím cơ bản (scan code = 34).
Khi được gọi, trình phục vụ ngắt 09h sẽ đọc cổng 60h để lấy mã quét. Nếu phím được nhấn thuộc loại phím thường (ví dụ như các phím chữ a, b,…) mã quét sẽ được dịch ra mã ASCII tương ứng. Sau đó, giá trị của mã quét và mã ASCII được lưu vào bộ đệm bàn phím. Bộ đệm này có địa chỉ 0040h:001Eh, kích thước 16 word, được tổ chức như một mảng vòng với con trỏ đầu (head) lưu tại địa chỉ 0040h:001Ah, con trỏ cuối (tail) lưu tại địa chỉ 0040h:001Ch. Nếu phím được nhấn là loại phím mở rộng (ví dụ như F1, F2,…), trong bộ đệm sẽ lưu giữ số 0 và mã mở rộng của phím đó.
Ví dụ: Giả sử NumLock đang là OFF, bộ đệm bàn phím đang trống (head = tail = 0041Eh), khi lần lượt ấn các phím ‘a’, F10, ‘·’, ‘NumLock’, ‘·’keypad, ‘NumLock’, ‘·’keypad, ‘Delete’ bộ đệm sẽ có nội dung như sau:
↓ 0041Ch
a F10 · · (kp) · (kp) Delete
61 1E
00 44
2E 34
2E 53
00 53
E0 53
head ↑ tail ↑
Lưu ý rằng, việc nhấn phím NumLock không sinh ra một thông tin nào trong bộ đệm. Hai phím dấu chấm cho cùng một mã ASCII là 2Eh. Phím Delete cho cùng một mã mở rộng dù được nhấn trong chế độ NumLock là ON hay OFF.
Một số hàm của ngắt 16h (BIOS)
AH = 00h. Lấy một phím từ bộ đệm bàn phím. Nếu bộ đệm trống, sẽ chờ cho đến khi một phím được nhấn. Trả về mã quét trong AH, mã ASCII (hoặc mã mở rộng) trong AL.
AH = 01h. Kiểm tra bộ đệm bàn phím. Nếu trống, bật cờ ZF. Nếu không trống, tắt cờ ZF, đọc phím đầu tiên trong bộ đệm (trỏ đến bởi con trỏ head), trả về mã quét trong AH, mã ASCII (hoặc mã mở rộng) trong AL. Tuy nhiên, phím này không bị lấy ra khỏi bộ đệm.
AH = 02h. Kiểm tra tình trạng các phím đặc biệt. Hàm này trả về byte ở địa chỉ 0040h:0017h. Các bit (I,C,N,S,A,O,L,R) của byte này, tính từ cao xuống thấp, ứng với các phím:
Insert CapsLock NumLock ScrollLock Alt Control LeftShift RightShift.
Phím nào ở trạng thái ON thì bit tương ứng sẽ bật.
AH = 03h. Thay đổi tốc độ nhận phím. AL = 05h, BH = thời gian đợi trước khi lặp, BL = tần số lặp. BH có thể nhận các giá trị từ 0 (250ms) đến 3 (1000 ms). BL có thể nhận các giá trị từ 0 (30 lần/giây) đến 1Fh (2 lần/giây).
AH = 05h. Giả lập thao tác nhấn phím. CH = mã quét, CL = mã ASCII (hoặc mã mở rộng). Hàm này ghi giá trị của CH và CL vào bộ đệm bàn phím và trả về AL = 0, nếu bộ đệm còn chỗ trống. Trả về AL = 1 nếu không còn chỗ trống.
Một số hàm của ngắt 21h (DOS)
AH = 01h. Đợi một phím được nhấn và trả lại mã ASCII của phím đó trong thanh ghi AL, đồng thời hiển thị kí tự lên màn hình. Nếu đây là phím không có mã ASCII mà chỉ có mã mở rộng thì AL trả về 0. Để nhận được mã mở rộng, cần phải gọi hàm này một lần nữa. Nếu Ctrl-Break được nhấn thì ngắt 23h sẽ được gọi.
AH = 08h. Hàm này chỉ khác hàm 01h ở chỗ không thể hiện lên màn hình kí tự ứng với phím được nhấn.
AH = 07h. Hàm này khác hàm 08h ở chỗ không kiểm tra Ctrl-Break.
AH = 0Ah. Nhập từ bàn phím một xâu kí tự có độ dài không quá N kí tự, kết thúc bởi mã 13h (phím Enter). Vùng bộ nhớ để lưu trữ xâu kí tự phải được chuẩn bị trước ở địa chỉ DS:DX. Byte đầu tiên ở địa chỉ này phải lưu giá trị N. Khi trả về, byte thứ hai lưu độ dài xâu nhận được (không kể kí tự kết thúc 13h, mặc dù kí tự này vẫn được lưu vào vùng nhớ).
AH = 0Ch. Xóa sạch bộ đệm bàn phím và gọi một trong các hàm 01h, 07h, 08h, 0Ah. Trong AL lưu số hiệu của hàm cần gọi.
Tài liệu tham khảo
Nguyễn Minh Tuấn, Giáo trình hợp ngữ - Chương 10, ĐHKHTN, 2002
Randal Hyde, The art of assembly language programming – Chapter 20.
Dan Rollins, TechHelp v.6.0
Bài tập
Bài 1. KeyDetection. Sử dụng các hàm liên quan đến bàn phím của ngắt 16h. Viết chương trình kiểm tra xem có phím chữ cái nào được nhấn không, nếu có thì dùng chữ đó để in đầy màn hình. Nếu không thì tiếp tục in đầy màn hình bằng chữ cái được nhấn ở lần trước. Nhấn Esc để kết thúc.
Bài 2. Phím gõ tắt. Sử dụng các hàm liên quan đến bàn phím của ngắt 21h, viết chương trình cho phép nhập từ bàn phím một xâu kí tự độ dài không quá 79. Trong quá trình nhập, nếu người dùng nhấn phím F1, chương trình sẽ tự động chèn vào cụm từ “DH KHTN Tp.HCM”, nếu nhấn phím F2 chương trình sẽ tự động chèn vào cụm từ “Khoa CNTT – BM MMT&VT”. Cho phép dùng BackSpace để sửa lỗi. Khi nhập xong, in ra độ dài của xâu kí tự đó.
Mở rộng
Trong bài tập 1, khi người dùng nhấn một chữ cái nào đó, thì chữ cái đó có lập tức xuất hiện trên màn hình không ? Có thể giải thích như thế nào về khoảng thời gian trễ này ?
Trong bài tập 2, làm sao để cho phép ngay sau khi nhấn F1 để thêm cụm từ, có thể nhấn Esc để bỏ đi cụm từ vừa thêm.
Để vượt qua giới hạn 79 kí tự trong bài tập 2, cần biết thêm kĩ thuật gì ?
Viết một chương trình cho phép xem nội dung của bộ đệm bàn phím. Dùng chương trình đó để quan sát sự thay đổi của bộ đệm khi bấm phím.
Hướng dẫn
Bài 1. Dùng hàm 01 của ngắt 16h để kiểm tra bộ đệm. Tuy nhiên phải nhớ rằng hàm này không lấy phím được nhấn ra khỏi bộ đệm bàn phím. Vì vậy, sau khi phát hiện có phím được nhấn, có thể gọi hàm 00 để lấy phím ra khỏi bộ đệm.
Ví dụ:
NextKey:
;
; trong khi chưa có phím nào được nhấn,
; ta xử lí những việc khác ở đây
;
mov ah,1 ; kiểm tra bộ đệm
int 16h
jz NextKey ; vẫn không có gì, quay lại
mov ah,0
int 16h ; lấy ra khỏi bộ đệm
;
; xử lí phím vừa nhận ở đây
jmp NextKey
Bài 2. Tạo một mảng 80 kí tự. Dùng hàm 8 của ngắt 21h để kiểm tra phím nào được nhấn. Nếu là phím có ASCII code khác 0, lưu vào mảng đồng thời in ra màn hình. Nếu là phím đặc biệt, gọi hàm 8 lần nữa để lấy mã mở rộng. Sau đó kiểm tra F1 hay F2 được nhấn để chèn cụm từ cần thiết vào mảng.
Ví dụ: Để xử lí nhập xâu và chèn macro, tham khảo đoạn chương trình sau
mac1 db 'DH KHTN Tp.HCM$'
mac2 db 'Khoa CNTT - BM MMT&VT$'
...................
NextKey:
mov ah,8 ; chờ nhấn phím, không hiển thị
int 21h
cmp al,0
jnz NotSpec ; nếu là phím thường
int 21h
cmp al,3bh
jz InsMac1
cmp al,3ch
jz InsMac2
jmp NextKey
InsMac1:
mov bx,offset mac1
jmp InsMac
InsMac2:
mov bx,offset mac2
jmp InsMac
; thêm các macro khác ở đây
; ........
InsMac:
callInsert ; chèn macro ở DS:BX vào mảng
jmp NextKey
NotSpec:
;
; lưu kí tự vào mảng
;
Để cho phép sửa chữa bằng Esc, có thể kiểm tra mã ASCII, nếu là 8, viết ra 3 kí tự có mã ASCII lần lượt là 8,32,8. (3 kí tự này có nghĩa là: lùi con trỏ, viết khoảng trắng để xóa, lùi con trỏ lần nữa). Đồng thời phải giảm giá trị của biến lưu trữ độ dài xâu hiện thời.
Ví dụ: Để bổ sung tính năng dùng BckSpc, tham khảo đoạn chương trình sau:
BckSpc db 8,32,8,'$'
...............
cmp al,8
jnz InsChar ; nếu không phải BckSpc, lưu
cmp si,0 ; kiểm tra độ dài xâu hiện thời
jz NextKey
mov dx,offset BckSpc ; xóa kí tự trên màn hình
printSt
dec si ; xóa trong mảng
jmp NextKey
InsChar:
cmp si,maxLen ; dài quá 79 ?
jz NextKey
mov buffer[si],al ; lưu vào mảng
inc si
jmp NextKey
Ví dụ: Để in ra độ dài xâu vừa nhập (<80, là số nguyên có hai chữ số), có thể viết như sau:
printUInt macro
pushax
pushbx
pushdx
mov bh,10
div bh
mov bx,ax
mov dl,bl
add dl,48
mov ah,2
int 21h
mov dl,bh
add dl,48
mov ah,2
int 21h
pop dx
pop bx
pop ax
endm
Không quên kiểm tra độ dài xâu hiện thời trước mỗi thao tác thêm, bớt kí tự trong mảng !
Bài thực hành số 7
Lập trình màn hình
Mục đích
Hiểu được cách tổ chức bộ nhớ màn hình cho chế độ text 80x25 16 màu và chế độ graphic SVGA 800x600 256 màu
Biết truy xuất bộ nhớ bằng ngắt 10h và bằng cách đọc/ghi vào vùng nhớ màn hình
Tóm tắt lý thuyết
Tổ chức bộ nhớ
Thông tin thể hiện trên màn hình được quy định bởi dữ liệu ghi trong vùng nhớ màn hình. Dữ liệu này được tổ chức khác nhau tùy vào chế độ thể hiện (display mode).
Trong chế độ 03h (text 16 màu, 80x25) vùng nhớ màn hình bắt đầu từ địa chỉ B800h:0000. Mỗi màn hình có 80x25 = 2000 kí tự. Mỗi kí tự được lưu trữ bởi 2 byte, byte thứ nhất lưu mã ASCII, byte thứ hai lưu thuộc tính thể hiện (bit 7 : nhấp nháy, bit 6-4 : màu nền, bit 3-0 : màu chữ). Như vậy mỗi màn hình ứng với 4000 byte. Trong chế độ này (03h) ta có thể sử dụng 4 trang màn hình khác nhau, đánh số từ 0 đến 3. Tại mỗi thời điểm chỉ có một trang được hiển thị, các trang khác ẩn nhưng vẫn có thể ghi dữ liệu lên đó. Địa chỉ của trang thứ k là B8000h + k x 1000h, nghĩa là mỗi trang chiếm 4096 byte, mặc dù chỉ 4000 byte là được sử dụng.
Trong chế độ 103h (SVGA, graphic 256 màu, 800x600) vùng nhớ màn hình bắt đầu từ địa chỉ A000h:0000. Mỗi điểm ảnh ứng với 1 byte lưu chỉ số màu. Như vậy, vùng nhớ màn hình trải dài 480000 byte, chia làm nhiều trang. Mỗi trang có kích thước bằng một segment 64KB. Chỉ số màu của điểm ảnh chính là số thứ tự của màu trong bảng màu. Mỗi màu trong bảng màu được xác định bởi 18 bit đại diện cho tỉ lệ 3 thành phần màu (R,G,B), mỗi thành phần 6 bit nhận giá trị từ 0 đến 63. Ở chế độ này, màn hình có thể biểu diễn được 218 màu khác nhau, nhưng tại một thời điểm thì chỉ thể hiện 256 màu khác nhau.
Một số hàm của ngắt 10h (BIOS)
AH = 00h. Thay đổi chế độ hiển thị
AL : chế độ hiển thị. Nếu bit 7 bật thì màn hình không bị xóa khi thay đổi chế độ hiển thị.
AL = 03h. Chọn chế độ đồ họa 80x25, 16 màu.
AH = 0Fh. Lấy chế độ hiển thị hiện thời. Kết quả:
AH : số cột
AL : chế độ hiển thị
BH : trang hiện thời
AH = 01h. Thay đổi kích thước con trỏ.
CH : dòng quét đầu
CL : dòng quét cuối
AH = 02h. Thay đổi vị trí con trỏ.
DH : dòng
DL : cột
BH : trang
AH = 05h. Thay đổi trang thể hiện.
AL : trang
AH = 0Ah. In ra kí tự tại vị trí con trỏ.
BH : trang
AL : mã ASCII
CX : lặp bao nhiêu lần
AH = 09h. In ra kí tự tại vị trí con trỏ, nhưng cho phép đặt thuộc tính cho kí tự.
BH : trang
BL : thuộc tính
AL : mã ASCII
CX : lặp bao nhiêu lần
AH = 0Eh. In ra kí tự tại vị trí con trỏ, dịch con trỏ sang vị trí tiếp theo.
BH : trang
AL : mã ASCII
AX = 4F02h. In ra kí tự tại vị trí con trỏ, dịch con trỏ sang vị trí tiếp theo.
BX : chế độ đồ họa ( = 103h : chể độ SVGA 256 màu 800x600)
Nếu kết quả trả về trong AX khác với 004Fh thì hệ thống không thể chuyển sang SVGA.
Tài liệu tham khảo
Nguyễn Minh Tuấn, Giáo trình hợp ngữ - Chương 10, ĐHKHTN, 2002
Randal Hyde, The art of assembly language programming – Chapter 23.
Dương Anh Đức, Giáo trình đồ họa máy tính, ĐHKHTN, 1998
Dan Rollins, TechHelp v.6.0
Bài tập
Bài 1. ChangePage. Chọn chế độ 03. Trên trang 0, ở tọa độ (8,10) viết dòng chữ “VIET NAM” màu đỏ trên nền trắng. Trên trang 1, ở tọa độ (12,7) viết dòng chữ “QUE HUONG TOI” màu vàng trên nền xanh. Làm biến mất con trỏ. Tạo vòng lặp chuyển qua lại giữa trang 0 và 1 cho đến khi một phím được nhấn.
Bài 2. Vạch màu. Khởi động chế độ 103h (SVGA). Bằng cách truy xuất trực tiếp vùng nhớ màn hình, hãy thể hiện 256 màu trong bảng màu mặc định bằng các vạch màu nằm cạnh nhau, mỗi vạch độ rộng 3 điểm ảnh. Sau khi nhấn phím bất kì, trở về chế độ 03 và kết thúc chương trình.
Mở rộng
Trong bài tập 1, có nhận xét gì về tốc độ chuyển giữa hai trang ? Có cách nào để đạt được kết quả tương tự (hai dòng chữ thay nhau xuất hiện) mà tốc độ chuyển đổi nhanh hơn ?
Thay vì hai dòng chữ chớp tắt như bài tập 1, làm sao để thể hiện hai dòng chữ luân phiên tiến lại gần nhau, rồi lại lùi xa nhau, rồi lại gần nhau….
Tìm cách pha lại (định nghĩa lại) bảng màu, để sau khi nhấn Enter các vạch thể hiện trong bài tập 2 đột nhiên biến thành một dải màu thay đổi từ sáng trắng đến xám, đến đen. Nhấn Enter lần nữa sẽ phục hồi lại dải màu như trước.
Nghiên cứu một thuật toán vẽ đường thẳng và viết chương trình vẽ những đoạn thẳng tùy ý trên màn hình.
Hướng dẫn
Bài 1.
Dùng hàm 05 của ngắt 10h để thay đổi trang.
Ví dụ:
mov ah,05h ; set page to view
mov al,0
int 10h
Dùng hàm 02 để nhảy đến vị trí cần thiết.
Ví dụ:
mov ah,02h ; goto (8,10) on page 0
mov dh,8
mov dl,10
mov bh,0
int 10h
Để viết chữ có màu và dịch chuyển con trỏ, có thể gọi ngắt 2 lần như sau
Ví dụ:
printStA proc
mov al,[si]
cmp al,'$'
jz Done
mov ah,09h
mov cx,1
int 10h
mov ah,0eh
int 10h
inc si
jmp printStA
Done:
ret
endp printStA
Để làm biến mất con trỏ, có thể dùng cách sau
Ví dụ:
mov ch,20h ; Hide cursor
mov cl,20h
mov ah,1
int 10h
Để chuyển đổi giữa hai màn hình, có thể viết:
Ví dụ:
mov dl,0
mov dh,5
next:
xor dl,1 ; switch page number 0/1
mov ax,dx
int 10h ; change page
mov ah,1 ; check key pressed
int 16h
jz next
Bài 2.
Để chuyển giữa chế độ text và graphic, có thể dùng đoạn chương trình sau
Ví dụ:
SVGA_ON proc
pushax
pushbx
mov ax,4f02h
mov bx,103h
int 10h
pop bx
pop ax
ret
endp SVGA_ON
SVGA_OFF proc
pushax
mov ax,0003h
int 10h
pop ax
ret
endp SVGA_OFF
Để in một điểm ảnh, có thể dùng cách sau:
Ví dụ:
pushbx
mov bx,800
xor dx,dx
mul bx ; dx:ax = y * 800
pop bx
add ax,bx ; ax = ax + x
adc dx,0 ; dx = 0 + carry
cmp dx,pn
jz Write
callSetPage
mov pn,dx
Write:
mov di,ax
mov al,cl
stosb
Bạn đang đọc truyện trên: Truyen247.Pro