You are on page 1of 37

TRƯỜNG CAO ĐẲNG

KINH TẾ KỸ THUẬT SÀI GÒN


KHOA: ĐIỆN – ĐIỆN TỬ - CNTT

TÀI LIỆU THỰC HÀNH

CẤU TRÚC MÁY TÍNH


Bài thực hành số 1

TỔNG QUAN VỀ PHẦN MỀM MÔ PHỎNG 8086


MICROCESOR EMULATOR (EMU 8086)

1. Mục đích:

Giúp sinh viên khảo sát các vấn đề về sử dụng phần mềm Emu8086 để mô phỏng hoạt động của
vi xử lý 8086.
2. Thiết bị sử dụng:
- Máy vi tính

- Phần mềm Emu 8086

3. Giới thiệu:

Phần mềm Emu8086 là phần mềm cho phép mô phỏng hoạt động của vi xử lý 8086 bao gồm các
câu lệnh cơ bản của 8086, xử lý ngắt mềm, giao tiếp với thiết bị ngoại vi, …… Các phiên bản
mới của Emu 8086 có thể được download tại địa chỉ của trang web: www.emu8086.com
3.1 Các chức năng soạn thảo, dịch và thực hiện chương trình.

Dưới đây màn hình cho phép người sử dụng viết một chương trình hợp ngữ hoặc chạy thử
một ví dụ có sẳn:
New: tạo một chương trình mới, khi đó người dùng sẽ được hỏi xem sẽ tạo file chương trình dạng
nào: COM, EXE, BIN hay BOOT.

Open: mở một file chương trình nguồn hợp ngữ.

Samples: Liệt kê các chương trình mẫu có sẳn do chương trình mô phỏng cung cấp.

Save: Lưu chương trình nguồn

Compile: dịch file chương trình nguồn

Emulate: Cho phép thực hiện chương trình nguồn. các trạng thái của quá trình thực hiện chương
trình được hiển thị trên màn hình mô phỏng dưới đây.

Calculator: người dùng có thể nhập vào một biểu thức với các số là: có dấu, số dạng word
hao85c dạng byte để tính toán. Kết quả tính toán được hiển thị một trong các dạng số thập phân,
nhị phân, hexa hoặc số bát phân (cơ số 8)

Convertor: Bộ chuyển đổi giữa các cơ số.


Hình: Màn hình mô phỏng chương trình

Các chức năng chính:

Load: Tải chương trình. Trước khi thực hiện thì chương trình sẽ được tải vào trong bộ nhớ.
Chương trình có thể ở dạng các file thực hiện được như EXE, COM, BIN, BOOT hoặc dưới dạng
file nguồn ASM.

Reload: người dùng có thể tải lại 1 chương trình.

Single Step: chạy chương trình theo chế độ từng lệnh. Với chế độ này người dùng có thể quan sát
trạng thái các thanh ghi, bộ nhớ trong…

Run: chế độ chạy tất cả các lệnh trong chương trình

Trên màn hình người dùng có thể quan sát trạng thái các thanh ghi và đoạn bộ nhớ sử
dụng cho đoạn mã lệnh của chương trình.
Phần registers mang nội dung của các thanh ghi trong đó các thanh ghi AX, BX, CX, DX
được chia làm 2 nửa : phân cao (H) và phân thấp (L). ngoài ra , ta có thể xem nội dung các thanh
ghi đoạn, con trỏ lệnh, ngăn xếp …
Phần bộ nhớ lưu trữ đoạn mã chương trình. Địa chỉ đoạn ( dạng hexa) được lưu trong
thanh ghi CS. Danh sách địa chỉ offset được hiển thị dưới dạng hexa và thập phân.
Ngoài ra người dùng có thể xem:
• Kết quả hiển thị lên màn hình ( nhắp chuột vào nút Screen)

• Mã nguồn của chương trình ( nhắp chuột vào nút Source)

• Trạng thái ALU (nhắp chuột vào nút ALU)

• Nội dung ngăn xếp (nhắp chuột vào nút Stack)

• Nội dung của thanh ghi cờ (nhắp chuột vào nút FLAG)

3.2 Các chương trình mẫu:

Emu8086 cung cấp cho người dùng 54 chương trình mẫu. chúng rất có ích cho người học lập
trình hợp ngữ. từ các chương trình đơn giản như Hello world cho đến một số chương trình thao
tác với một số thiết bị ngoại vi điển hình như màn hình, máy in… Để chạy thử các chương trình
mẫu này , người dùng nhắp chuột vào nút Samples/ more Samples để chọn ra 1 file chương trình
để chạy thử.

Bài thực hành số 2


Mục đích
 Làm quen với ngôn ngữ lập trình Assembly
 Biết cách viết, dịch, chạy và chẩn lỗi (debug) một vài chương trình đơn giản

Tóm tắt lý thuyết


Hợp ngữ (assembler) là ngôn ngữ bậc thấp, giúp cho người lập trình không phải ghi nhớ mã
máy (opcode) mà sử dụng các từ ngữ gợi nhớ (pseudo-code) gần với ngôn ngữ tự nhiên để miêu
tả công việc cần thực hiện. Tuy vậy, assembler rất gần với ngôn ngữ máy, đòi hỏi người lập trình
phải hiểu biết tương đối đầy đủ về cấu trúc phần cứng máy tính.
Với mỗi kiểu kiến trúc của bộ vi xử lý, có một bộ lệnh riêng, do đó, có một ngôn ngữ
assembler riêng cho nó. Ở đây, chúng ta nghiên cứu assembler cho các bộ vi xử lý Intel thuộc họ
x86. Các chương trình sẽ được viết cho chế độ thực (real mode) trong DOS và được biên dịch
bằng Turbo Assembler.

Cấu trúc thông thường của một chương trình hợp ngũ

.model <Khai báo kiểu chương trình>


.stack <Khai báo kích thước ngăn xếp>
.data
<Khai báo dữ liệu>
.code
<Các lệnh>
end

Ví dụ: Chương trình sau in ra màn hình dòng chữ “Hello !”

.model small
.stack 100h
.data
s DB “Hello !$” ; khai báo xâu kí tự cần in
.code
mov AX,@data ; lấy địa chỉ data segment ghi vào
DS
mov DS,AX ; Vì model small, đây cũng là địa
chỉ
; segment của xâu s

; xuất chuỗi
mov DX, OFFSET s ; lấy địa chỉ offset ghi vào DX
mov AH , 9
int 21h ; gọi hàm 9, ngắt 21h để in

mov AH, 4Ch ; Thoát khỏi chương trình


int 21h
end

Lưu ý:
- Mọi chương trình đều phải có đoạn code thoát khỏi chương trình, nếu không chương
trình sẽ không dừng khi hết chương trình của mình.

Khai báo biến trong hợp ngữ

Cú pháp:
<tên biến> D<Kiểu DL> <giá trị khởi tạo>
hoặc
<tên biến> D<Kiểu DL> <số phần tử> dup(<giá trị khởi tạo>)

Các kiểu dữ liệu: B (1 byte), W (2 bytes), D (4 bytes)


Nếu không khởi tạo, dùng dấu hỏi “?”

Ví dụ:

Khai báo trong C Khai báo trong hợp ngữ


char ch; ch DB ?
char ch = ‘a’; ch DB ‘a’
char ch = 5; ch DB 5
char s[]=”\nhello world!” s DB 10,13,”hello world!$”
int i=100; i DW 100
long l; l DD ?
char a[] = {1,2,3}; a DB 1,2,3
char a[100]; a DB 100 dup(?)
char a[100][50]; a DB 100 dup(50 dup(?))

Dịch, liên kết, chạy và chẩn lỗi chương trình từ dấu nhắc DOS

Cần có các file: tasm.exe (dịch), tlink.exe (liên kết), td.exe (chẩn lỗi). Các bước như sau:
 B1. Thiết lập đường dẫn
path = %path%;<đường dẫn đến thư mục chứa các file kể trên>

 B2. Biên dịch từ file .ASM sang file .OBJ


Tasm <tên file chương trình>.ASM

 B3. Biên dịch từ file .OBJ sang file .EXE


Tlink <tên file>.OBJ

 B4: chạy chương trình:


<tên file>.EXE
 B5: chẩn lỗi (nếu cần thiết)
Td <tên file>.EXE

Để tự động hóa, ta có thể tạo file .BAT chứa các lệnh trên.

Ví dụ:

Tạo file RunASM.bat trong cùng thư mục với tập tin .ASM với nội dung như sau :

tasm %1
tlink %1
%1

(%1 là lấy tham số thứ nhất trong command line)


Sau đó để biên dịch, liên kết và thực thi chương trình hello.ASM ta chỉ cần gõ :

RunASM hello

Công cụ EditPlus

Đây là công cụ soạn thảo văn bản tiện dụng, cho phép tự động đổi màu chữ theo cú pháp.
Ngoài ra còn có thể thiết đặt phím tắt để gọi các tiện ích khác. Để dùng cho soạn thảo chương
trình assembler, cần copy file định nghĩa cú pháp vào thư mục cài đặt và đăng kí sử dụng nó
cho những file có tên mở rộng “.asm”.
 B0. Cấu hình Edit Plus
 B1. Biên dịch file .ASM : nhấn Ctrl + 1 sẽ biên dịch file đang soạn thảo thành
.OBJ
 B2. Liên kết : nhấn Ctrl + 2 sẽ biên dịch file .OBJ thành .EXE
 B3. Chạy chương trình : nhấn Ctrl + 3 sẽ chạy chương trình .EXE
 B4. Chẩn lỗi chương trình : nhấn Ctrl + 4 sẽ debug chương trình .EXE

Lưu ý:
- Để tránh phiền phức khi làm việc với Turbo Assembler, tránh đặt tên thư mục có chứa
khoảng trắng.
- Trước khi nhấn Ctrl + 1 lần đầu tiên, nhớ lưu chương trình với tên cần thiết.

Một số lệnh cơ bản

MOV des,src : chép dữ liệu từ src sang des


INC des : tăng des một đơn vị
DEC des : giảm des một đơn vị
ADD des,src : des = des + src
SUB des,src : des = des – src
INT num : gọi ngắt

Bài tập

Bài 1. Viết CT nhập vào 1 ký tự, xuất ra ký tự đó


Ví dụ:
Moi ban nhap 1 ky tu: b
Ky tu vừa nhập: b

Bài 2. Viết chương trình xuất ra màn hình một số dòng.


Ví dụ:
De chay duoc 1 CT hop ngu ban can thuc hien cac buoc sau:
Dich file ASM thanh file OBJ
Lien ket file OBJ thanh file EXE
Chay file EXE

Bài 3. Viết CT nhập vào 1 ký tự, xuất ra ký tự liền trước và liền sau.
Ví dụ:
Moi ban nhap 1 ky tu: b
Ky tu lien truoc: a
Ky tu lien sau: c
Bài 4. Viết CT nhập vào 1 ký tự thường. In ra ký tự Hoa
Ví dụ:
Moi ban nhap 1 ky tu: b
Ky tu Hoa: B

Bài 5. Viết CT nhập vào 1 ký tự hoa. In ra ký tự thường


Ví dụ:
Moi ban nhap 1 ky tu: B
Ky tu thường: b

Bài 6. Viết chương trình nhập vào 2 số nguyên dương x1, x2 (1 ≤ x2 < x1 < 9). Xuất ra kết quả
các phép tính: x1-1, x1 +2, x1+x2, x1-x2
Ví dụ:
x1 = 5
x2 = 3
x1 – 1 = 4
x1 + 1 = 6
x1 + x2 = 8
x1 – x2 = 7

Mở rộng
1. Tự tìm hiểu xem hàm nào trong ngắt 21h dùng để nhập một xâu kí tự ? Ngoài ngắt 21h,
còn ngắt nào có thể dùng để nhập xuất từ bàn phím ? (dùng NortonGuide hoặc TechHelp).
2. Viết chương trình nhập tên và in ra màn hình câu “Hello ” + tên đã nhập.
3. Tìm hiểu xem tại sao không có lệnh MOV x1, x2 (x1,x2 là hai biến trong bộ nhớ)
4. Hai lệnh “INC AX” và “ADD AX, 1” khác nhau chỗ nào ?

Hướng dẫn
Bài 1. Để nhập 1 một ký tự sử dụng hàm 1 của ngắt 21h, để xuất, sử dụng hàm 2.
Ví dụ:
mov AH,1
int 21h ; kết quả trong AL

mov DL,AL ; kí tự cần xuất trong DL


mov AH,2
int 21h
Bài 2. Cặp kí tự xuống dòng là 10,13. Có thể khai báo nhiều xâu kí tự hoặc chung một xâu.
Ví dụ:
Msg3 DB 10,13,9,“1. Dich file ASM thanh file OBJ.$”
Msg4 DB 10,13,9,“2. Lien ket file OBJ thanh file EXE.$”

Hoặc

Msg34 DB 10,13,9,“1. Dich file ASM thanh file OBJ.”


DB 10,13,9,“2. Lien ket file OBJ thanh file EXE.$”
Bài 4,5. Kí tự hoa và kí tự thường của cùng một chữ cái tiếng Anh cách nhau 20h. Do đó, để
chuyển đổi chữ hoa thành chữ thường và ngược lại, chỉ cần dùng lệnh ADD, SUB.

Bài 6. Để chuyển đổi các kí tự ‘0’ – ‘9’ thành số 0 – 9 chỉ cần thực hiện phép trừ đi 48 (mã của
‘0’). Sau khi thực hiện phép tính, chuyển đổi thành kí tự và in ra màn hình (có thể dùng biểu diễn
Hex).

Bài thực hành số 3


Lệnh so sánh – Lệnh nhảy – Lệnh lặp
Mục đích
 Hiểu cách so sánh hai số trong hợp ngữ
 Hiểu cách thay đổi thứ tự thực hiện các lệnh
 Biết cách sử dụng các lệnh so sánh, nhảy và lặp

Tóm tắt lý thuyết

Lệnh so sánh

Trong hợp ngữ, muốn so sánh hai số, ta phải thực hiện một phép toán số học hoặc logic trên
hai số đó và căn cứ vào các bit trong thanh ghi cờ rồi đưa ra kết luận. Để làm việc này, có thể
dùng lệnh CMP và TEST.
Bản chất của lệnh CMP Des,Src là lệnh SUB Des,Src (thực hiện phép tính Des – Src) nhưng
kết quả của phép tính không được lưu vào Des như trong lệnh SUB.

Ví dụ: so sánh hai số nguyên dương


MOV AH,1
MOV AL,2
CMP AH,AL
Sau khi thực hiện hai lệnh trên, cờ Carry (CF) bật, báo hiệu rằng AH < AL

Bản chất của lệnh TEST Des,Src là lệnh AND Des,Src (thực hiện phép tính Des AND Src)
nhưng kết quả của phép tính không được lưu vào Des như trong lệnh AND.

Ví dụ: kiểm tra hai bit cuối cùng của AL


TEST AL,3 ; 3h = 11b
Nếu cờ Zero (ZF) bật, có nghĩa là cả hai bit 0 và 1 của AL đều bằng 0.

Lệnh nhảy

Thông thường, khi một lệnh (instruction) được thực hiện, giá trị của thanh ghi IP (instruction
pointer) được tự động cập nhật để trỏ đến lệnh kế tiếp. Ngoài ra, nội dung của thanh ghi IP chỉ có
thể bị thay đổi thông qua một số lệnh đặc biệt. Đó là: các lệnh nhảy (J*), lệnh lặp (LOOP*), lệnh
gọi hàm (call, ret), lệnh gọi ngắt (int, iret). Các lệnh này được xếp vào nhóm “Lệnh điều khiển
luồng” (Program flow control instructions). Trong bài thực hành này, chúng ta sẽ học cách sử
dụng các lệnh nhảy và các lệnh lặp.

 Lệnh nhảy không điều kiện

JMP <target>

Có các trường hợp sau:


• JMP SHORT <tên nhãn> (short jump). Khi đó trong mã lệnh lưu 1 byte
khoảng cách (offset) giữa vị trí hiện tại và vị trí cần nhảy đến. Kiểu này chỉ nhảy
trong phạm vi từ –128 đến +127 byte so với vị trí hiện tại.
Ví dụ: JMP SHORT Calculate

• JMP <tên nhãn> (near jump). Khi đó trong mã lệnh lưu 2 byte khoảng cách
(offset) giữa vị trí hiện tại và vị trí cần nhảy đến. Kiểu này nhảy tùy ý trong phạm
vi segment.
Ví dụ: JMP Calculate

• JMP FAR PTR <tên nhãn> (far jump). Khi đó trong mã lệnh lưu offset và
segment của vị trí cần nhảy đến. Kiểu này nhảy đến bất kì chỗ nào.
Ví dụ: JMP FAR PTR Calculate
• JMP <con trỏ 2 byte> (near indirect jump). Khi đó trong mã lệnh lưu địa
chỉ offset của một ô nhớ. Khi thực hiện, IP sẽ được gán bằng giá trị lưu tại địa chỉ
này. Có thể kết hợp dùng với định vị chỉ số.

Ví dụ:
myPointer DW Prepare, Calculate, Check, Output
...
MOV bx,2 ; chỉ số trong mảng con trỏ
SHL bx,1 ; nhân đôi
JMP myPointer[bx]
...
Prepare: ; công việc 0
...
Calculate: ; công việc 1
...
Check: ; công việc 2 – nơi cần nhảy
đến
...
Output: ; công việc 3
...

• JMP <con trỏ 4 byte> (far indirect jump). Tương tự trường hợp trên,
nhưng con trỏ gồm cả segment và offset. Chỉ khác ở khai báo con trỏ

Ví dụ:
myPointer DD Prepare, Calculate, Check, Output
...
MOV bx,1 ; chỉ số trong mảng con trỏ
MOV cl,2
SHL bx,cl ; nhân 4
JMP myPointer[bx]
...
Prepare: ; công việc 0
...
Calculate: ; công việc 1 – nơi cần nhảy
đến
...

• JMP <thanh ghi 2 byte> (indirect jump via regs). Nhảy đến địa chỉ lưu
trong thanh ghi AX.

Ví dụ:
MOV ax, offset Calculate
...
JMP ax ; (IP ← AX)

 Lệnh nhảy có điều kiện

J.... <Label>

Các lệnh nhảy có điều kiện bắt đầu bằng chữ J sau đó là các chữ cái biểu thị điều kiện (ví dụ
JGE ah,5: Jump if Greater than or Equal, nhảy nếu AH lớn hơn hay bằng 5), tiếp sau là một tên
nhãn. Tùy thuộc vào trạng thái các cờ hiệu mà bộ vi xử lý có thực hiện việc nhảy đến nhãn hay
không.

Đối với bộ vi xử lý 80286 trở xuống, lệnh nhảy có điều kiện có độ dài 2 byte, byte đầu tiên
chứa mã lệnh, byte thứ hai chứa khoảng cách tương đối từ lệnh đến nhãn, vì vậy <Label> trong
lệnh nhảy có điều kiện phải nằm trong khoảng từ -128 đến 127 so với vị trí lệnh nhảy. Muốn
nhảy xa hơn ta phải dùng kết hợp lệnh nhảy không điều kiện JMP
Từ 80386 trở lên, bộ lệnh được bổ sung, cho phép sử dụng lệnh nhảy có điều kiện có độ dài 4
byte, do đó <Label> có quyền nằm tùy ý trong cùng phạm vi segment.

Khi sử dụng lệnh nhảy có điều kiện sau khi thực hiện phép so sánh, phải đặc biệt lưu ý toán
hạng trong phép so sánh là số có dấu (signed) hay không có dấu (unsigned) để lựa chọn lệnh cho
phù hợp.
Ví dụ:
MOV AH,AL ; AL hiện bằng 128
CMP AH,1
JGE Greater ; AH > 1 nhưng không nhảy ????
. . .
Greater:

Một số lệnh nhảy có điều kiện thường dùng (tham khảo thêm trong SGK trang 81):
• JE, JZ (nhảy nếu bằng).
• JA (nhảy nếu lớn hơn, không dấu), JG (nhảy nếu lớn hơn, có dấu), JB (nhảy nếu
nhỏ hơn, không dấu), JL (nhảy nếu nhỏ hơn, có dấu).
• JAE (nhảy nếu lớn hơn hay bằng, không dấu), JGE (nhảy nếu lớn hơn hay bằng,
có dấu), JBE (nhảy nếu nhỏ hơn hay bằng, không dấu), JLE (nhảy nếu nhỏ hơn
hay bằng, có dấu).
• JNE, JNZ (nhảy nếu không bằng).

Ví dụ: nếu AL là số nguyên không dấu thì đoạn chương trình ở trên phải sửa lại như sau:
MOV AH,AL
CMP AH,1
JAE Greater
. . .
Greater:

Lệnh lặp

Bằng cách dùng các lệnh nhảy có thể tạo ra vòng lặp. Tuy nhiên, để viết chương trình tiện lợi
và ngắn gọn, có thể dùng thêm các lệnh lặp như LOOP, LOOPZ,…
Lệnh LOOP <Label> tự động giảm CX một đơn vị, sau đó kiểm tra xem CX có bằng 0,
nếu không bằng thì nhảy đến nhãn <Label>
Lệnh LOOPZ <Label> tự động giảm CX một đơn vị, sau đó kiểm tra xem CX có bằng 0
hoặc cờ ZF có bật không, nếu cả hai điều này không xảy ra thì nhảy đến nhãn <Label>

Ví dụ: Nhập mảng A gồm 10 ký tự

MOV SI, 0 ; chỉ số mảng


MOV CX, 10 ; số lần lặp
LAP:
;nhập ký tự
MOV AH, 1
INT 21H
MOV A[SI], AL
INC SI

; xuất ký tự
MOV AH, 2
INT 21H

LOOP LAP

Bài tập
Bài 1. Viết chương trình cho nhập 1 ký tự từ màn hình và xuất câu thông báo chào buổi sáng,
buổi trưa hay buổi chiều tương ứng với ký tự nhậpvào là 'S', 's', 'T', 't', 'C', 'c'.

Bài 2. Nhập 2 số nguyên dương thuộc N,M thuộc [0..9], nhập 1 ký tự Char. Xuất ra màn hình ma
trận gồm N dòng và M cột gồm ký tự Char.
Ví dụ: N=3, M=4, C='*'
⇒ ****
****
****

Bài 3. Nhập 2 số nguyên dương A, B. Tính A/B, A*B (không dùng lệnh DIV, MUL)
Ví dụ: A=18, B=3
Tính A/B: 18 - 3 - 3 - 3 - 3 - 3 - 3 = 0, vậy A/B = 6 (tổng số lần A trừ B cho đến khi A = 0).
Tính A*B = 18 + 18 + 18 = 54

Bài 4. Tìm USCLN của 2 số nguyên dương N, M nhập từ bàn phím. Kiểm tra N,M có là hai số
nguyên tố cùng nhau không?
Ví dụ: N = 15, M = 6 => USCLN(15, 6) = 3
Ví dụ: N = 3, M = 5 => USCLN(3, 5) = 1 => 3, 5 là 2 số nguyên tố cùng nhau.

Bài 5. Dùng lệnh lặp, viết chương trình nhập vào 1 chuỗi ký tự. Sau khi nhập xong đếm xem
chuỗi có bao nhiêu ký tự. Xuất số ký tự có trong chuỗi.
Ví dụ: S = "Hello world !" ==> Số kí tự trong chuỗi là 13.

Bài 6. Nhập vào 2 chuỗi số, đổi 2 chuỗi thành số, sau đó cộng hai số, đổi ra chuỗi và xuất chuỗi
tổng.
Ví dụ: S1 = "123" => N1 = 123
S2 = "456" => N2 = 456
N = N1 + N2 = 123 + 456 = 579 => S = "579" (xuất S ra màn hình)

Bài 7. Viết chương trình cho phép nhập vào một chuỗi S.
Đổi tất cả ký tự thường thành ký tự hoa.
Đổi tất cả ký tự hoa thành ký tự thường.

Bài 8. Nhập và xuất mảng 1 chiều. Tìm phần tử max, min, tính tổng các phần tử trong mảng.
Ví dụ: N=5
A[N] = {3,1,2,7,4}
=> max = 7, min = 1, tổng = 17.

Bài 9. Cài đặt thuật toán Bubble Sort dùng ASM.


Thuật toán Bubble Sort theo ngôn ngữ C như sau:
for (int i = 0; i< N-1; i++)
for(int j=N-1;j > i; j--)
if(a[j] < a[j-1])
Hoan_Vi (a[j], a[j-1]);

Bài 10. Nhập và xuất mảng A hai chiều.


a. Tính tổng các phần tử trên đường chéo chính, đường chéo phụ.
b. Đếm số phần tử 0 và phần tử khác 0 trong mảng.
c. Tìm phần tử max của mỗi dòng, mỗi cột. Tính tổng của mỗi dòng, mỗi cột.
d. Nhập 1 mảng hai chiều B, tạo một mảng hai chiều C có các phần tử trên dòng chẵn
bằng với các phần tử trên dòng chẵn của A, các phần tử trên dòng lẻ bằng các phần tử trên dòng
lẻ của B.

Mở rộng
5. Trong bài tập 5, làm sao để đếm số từ có trong chuỗi kí tự?
6. Trong bài tập 10, làm sao để thể hiện một menu cho phép người dùng chọn trong các kí tự
từ ‘a’ đến ‘d’ sau đó thực hiện công việc ứng với chữ cái đó.

Hướng dẫn
Bài 1. Xem ví dụ:

.MODEL SMALL
.STACK 100H
.DATA
CBS DB "CHAO BUOI SANG$"
CBT DB "CHAO BUOI TRUA$"
CBC DB "CHAO BUOI CHIEU$"
.CODE
MOV AX, @DATA
MOV DS, AX

;nhập 1 ký tự bất kỳ
MOV AH, 1
INT 21H

CMP AL, 'S'


JE CHAO_BUOI_SANG

CMP AL, 's'


JE CHAO_BUOI_SANG

CMP AL, 'T'


JE CHAO_BUOI_TRUA

CMP AL, 't'


JE CHAO_BUOI_TRUA

CMP AL, 'C'


JE CHAO_BUOI_CHIEU

CMP AL, 'c'


JE CHAO_BUOI_CHIEU
CHAO_BUOI_SANG:
LEA DX, CBS
MOV AH,9
INT 21H
JMP THOAT

CHAO_BUOI_TRUA:
LEA DX, CBT
MOV AH,9
INT 21H
JMP THOAT

CHAO_BUOI_CHIEU:
LEA DX, CBC
MOV AH,9
INT 21H
JMP THOAT

THOAT:
MOV AH, 4CH
INT 21H

END

Bài 3. Để nhập một số nguyên, có thể làm như sau: đầu tiên nhập xâu kí tự chứa các số từ 0 đến
9, sau đó đổi từng kí tự ra số và nhân với các lũy thừa tương ứng của 10 và cộng lại.
Bài 9. Xem ví dụ sau: Lặp gồm 2 vòng lặp (xếp mảng A có N phần tử tăng dần)

MOV N, 10 ;giả sử mảng A gồm N ký tự, trong ví dụ này N=10

MOV CX, N
DEC CX
MOV SI, 0

FOR_I:
PUSH CX
MOV CX, N
MOV DI, 0
MOV DL, A[SI]

FOR_J:

CMP DL, A[DI]


JB LAP
MOV BL, A[DI]
MOV A[DI], DL
MOV A[SI], BL
MOV DL, A[SI]

LAP:
INC DI
LOOP FOR_J

INC SI
POP CX
LOOP FOR_I
Bài thực hành số 4
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:


.model small 1. Khai báo
.stack 100h 000h

0FCh
0FCh
.data 100h
msg1 DB 'Nhap vao 1 chuoi: $' SS:SP 
msg2 DB 10,13,'Chuoi nghich
dao la: $' 2. Nhập lần lượt a,b,c đưa vào ngăn xếp:
.code Nhập ký tự ‘a’:
mov ax,@data 000h
mov ds,ax …
0FCh
0FCh
mov ah,9 SS:SP 
00 61 100h
lea dx,msg1
int 21h Nhập ký tự ‘b’:
000h
mov cx,0 …
0FCh
nhap: SS:SP  00 62 0FCh
mov ah,1
00 61 100h
int 21h
cmp al,13
Nhập ký tự ‘c’:
je thongbaoxuat
000h
xor ah,ah SS:SP 
00 63 …
0FCh
push ax
inc cx 00 62 0FCh
jmp nhap 00 61 100h
thongbaoxuat:
mov ah,9 3. Xuất các giá trị trong ngăn xếp
lea dx,msg2 Xuất ký tự ‘c’:
int 21h 000h
SS:SP  …
xuat: 00 63 0FCh
pop ax 00 62 0FCh
mov dl,al 00 61 100h
mov ah,2
int 21h Xuất ký tự ‘b’:
loop xuat 000h

0FCh
mov ah,4ch SS:SP  0FCh
00 62
int 21h 00 61 100h
END
Xuất ký tự ‘a’:
000h

0FCh
0FCh
SS:SP 
00 61 100h
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ập xuất chuỗi kí tự

.model small 1. Khai báo


.stack 100h 000h
.code …
0FCh
CALL Nhap 0FCh
CALL Xuat 100h
SS:SP 
mov ah,4ch 2. Gọi thủ tục Nhap
int 21h 000h
;--------------------------------------------------- …
0FCh
Nhap PROC 0FCh
pop bx SS:SP 
CALL Xuat 100h
mov ah,2
mov dl,’?’ IP = địa chỉ ô lệnh “pop bx”
int 21h 3. Lưu lại địa chỉ quay về
xor cx,cx 000h
nhap: …
0FCh
mov ah,1 0FCh
int 21h
100h
cmp al,13 SS:SP 
je ketthucnhap
BX = địa chỉ lệnh “CALL Xuat”
push ax
4. Nhập ký tự a,b:
inc cx 000h
jmp nhap …
0FCh
ketthucnhap: SS:SP  0FCh
push bx 00 62
00 61 100h
RET
Nhap ENDP
;--------------------------------------------------- 5. Trả lại địa chỉ quay về
Xuat PROC BX = địa chỉ lệnh “CALL Xuat”
pop bx
000h
mov ah,2 SS:SP 
CALL Xuat …
0FCh
mov dl,13
int 21h 00 62 0FCh
mov dl,10 00 61 100h
int 21h
jcxz ketthucxuat 6. Kết thúc thủ tục Nhập:
xuat: 000h
pop dx …
0FCh
SS:SP  0FCh
int 21h 00 62
loop xuat 00 61 100h
ketthucxuat:
push bx IP = địa chỉ ô lệnh “CALL Xuat”
RET
Xuat ENDP Lời gọi thủ tục xuất (CALL Xuat) cũng
END 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

.model small
.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
.model small
.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ộ

.model small
.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ố.

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
7. Có những cách nào để truyền tham số cho thủ tục ? để nhận kết quả trả về ?
8. Thử viết một thủ tục đệ quy.
9. 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ố 5
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:
LOOP TOP ; 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.

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
1. 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.
2. 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ố 6
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
STR1 DB ‘HELLO’
STR2 DB 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ố 7
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 00 2E 2E 00 E0
1E 44 34 53 53 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.

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
10. 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 ?
11. 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.
12. Để 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ì ?
13. 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:
call Insert ; 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
push ax
push bx
push dx
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ố 8
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.

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
14. 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 ?
15. 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….
16. 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.
17. 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
push ax
push bx
mov ax,4f02h
mov bx,103h
int 10h
pop bx
pop ax
ret
endp SVGA_ON

SVGA_OFF proc
push ax
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ụ:
push bx
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
call SetPage
mov pn,dx
Write:
mov di,ax
mov al,cl
stosb

You might also like