Assembly tiếp (TT)

Màu nền
Font chữ
Font size
Chiều cao dòng

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” (LIFOLast 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: Truyen2U.Pro