Giáo trình Assembly
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Assembly", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Tài liệu đính kèm:
- giao_trinh_assembly.pdf
Nội dung text: Giáo trình Assembly
- Giáo trình Assembly
- CHƯƠNG 1 : SỐ NHỊ PHÂN Trong Computer dữ liệu được ghi nhớ bằng các mạch điện có khả năng thể hiện một số trạng thái xác định . Mỗi mạch điện như vậy là một phần tử nhớ căn bản . Số giá trị một phần tử nhớ căn bản có thể biểu diễn cũng chính là số trạng thái mạch điện tạo nên nó có thể xác định. Do khả năng của kỹ thuật chế tạo mạch, và cũng do giá thành của mạch điện, các phần tử nhớ trên các Computer hiện nay đều được thiết lập từ các mạch điện có hai trạng thái xác định bởi mức điện thế cao và thấp. Tương ứng với hai trạng thái của mạch điện, mỗi phần tử nhớ mang một trong hai giá trị 0 và 1. Một phần tử nhớ như vậy được gọi là một bit . * Các toán tử trên bit : A B NOT A A AND B A OR B A XOR B A XOR (A XOR B) 0 0 1 0 0 0 0 0 1 1 0 1 1 1 1 0 0 0 1 1 0 1 1 0 1 1 0 1 1.1_ ĐƠN VỊ LƯU CHỨA THÔNG TIN: Để có thể biểu diễn được nhiều giá trị hơn, thông thường người ta lưu chứa thông tin dưới dạng từng nhóm bit, mỗi nhóm biểu thị cho một phần tử của thông tin. - Một nhóm 4 bit được gọi là nibble. - Một nhóm 8 bit được gọi là byte. Byte là đơn vị lưu chứa thông tin cơ sở trên các Computer hiện nay. Các bội số của byte là : 1 KB = 1024 byte ( 2 lũy thừa 10 ) 1 MB = 1024 KB 1 GB = 1024 MB - Một nhóm 16 bit được gọi là word. - Một nhóm 32 bit được gọi là double word (dword). Computer truy nhập đến các thông tin theo đơn vị nhỏ nhất là byte . 1.2_HỆ ĐẾM: Các giá trị số lưu chứa trong Computer dưới dạng từng nhóm các bit với cách biểu diễn 0 và 1 khác hẳn cách biểu diễn số thông thường của chúng ta (dùng các ký số 0,1 9). Cách biểu diễn số của chúng ta dựa trên cơ sở mười ký số 0,1 9 được gọi là hệ đếm thập phân, cách biểu diễn số trong Computer dựa trên cơ sở hai ký số 0 và 1 được gọi là hệ đếm nhị phân. 1.2.1_Quy tắc của một hệ đếm theo vị trí: Hệ đếm của chúng ta hiện nay là một hệ đếm theo vị trí, dựa trên cơ sở mười ký số 0,1 9. Giá trị của một số được biểu diễn bằng một dãy các ký số, trong đó giá trị của mỗi ký số tùy thuộc vào vị trí của nó ở trong dãy. Thí dụ : 1 9 7 4 3 5 1*100000 + 9*10000 + 7*1000 + 4*100 + 3*10 + 5 105 104 103 102 101 10 1
- Gọi số các ký hiệu để biểu diễn một hệ đếm theo vị trí gọi là cơ số C của hệ đếm ta có công thức tổng quát : i x = Σ C * Ki (Ki là các ký số của hệ đếm) 1.2.2_Các hệ đếm thông dụng : Hệ nhị phân : sử dụng các ký số 0 và 1 (bin : binary ) Hệ bát phân : sử dụng các ký số 0,1 7 (Oct: ) Hệ thập phân: sử dụng các ký số 0,1 9 (dec : decimal) Hệ thập lục : sử dụng các ký số 0,1 9,A F (hex : hexa ) Bảng đối chiếu các hệ đếm: Dec Hex Bin Dec Hex Bin 0 0 0000 8 8 1000 1 1 0001 9 9 1001 2 2 0010 10 A 1010 3 3 0011 11 B 1011 4 4 0100 12 C 1100 5 5 0101 13 D 1101 6 6 0110 14 E 1110 7 7 0111 15 F 1111 1.2.3_Quy ước biểu diễn các hệ đếm : · Các số dạng bin kết thúc bằng b. · Các số dạng hex kết thúc bằng h. · Các số hex nên bắt đầu bởi ký số 0 (bắt buộc khi ký số đầu tiên là A F). Thí dụ : 1991, 01000101b, 0F1h, 078h. Việc sử dụng hệ nhị phân trên Computer gặp một số khó khăn : để biểu diễn một giá trị cần quá nhiều ký số làm cho người sử dụng phải ghi chép nhiều và rất khó nhớ. Từ nhận xét : hệ nhị phân là một hệ con hoàn toàn của hệ thập lục: tập hợp 4 bit nhị phân có thể biểu diễn 16 giá trị số khác nhau ( 24 ), người ta thường dùng các ký số thập lục (0,1 9,Ạ.Z) để biểu diễn thay cho từng nhóm 4 bit trong một số nhị phân. 1 1 1 1 1 0 1 0 0 1 0 0 1 1 0 1 F A 4 D 1.3_ CÁC PHÉP TÍNH TRÊN HỆ NHỊ PHÂN Các phép tính cơ bản + - * / trên số nhị phân được thực hiện với các quy tắc như trên hệ thập phân . Tuy vậy, phải chú ý rằng các số nhị phân lưu trong Computer được ghi nhận bằng một số các bit xác định : có số các ký số xác định. Do đó các phép tính chỉ có thể thực hiện trên khuôn khổ các là các bit tràn . Các số nhị phân chúng ta đề cập kế tiếp là các số nhị phân được lưu trong một số bit xác định (8,16,32), tức là có chiều dài không đổi . 1.3.1_Phép cộng nhị phân: 2
- Quy tắc : 0 + 0 = 0 0 + 1 = 1 1 + 1 = 0 nhớ 1 ( 10 ) Thí dụ : 1 0 0 1 1 0 1 1 1 1 0 1 0 1 1 1 + + 0 0 1 0 1 0 1 1 0 0 1 1 1 0 1 1 ___ ___ 1 1 0 0 0 1 1 0 1 0 0 0 1 0 0 1 0 Bít tràn 1.3.2_Phép trừ nhị phân: Quy tắc : 0 - 0 = 0 1 - 1 = 0 1 - 0 = 1 0 - 1 = 1 nhớ 1 Thí dụ : 1 0 0 1 1 0 1 1 1 1 0 1 0 1 1 1 - - 0 0 1 0 1 0 1 1 0 0 1 1 1 0 1 1 ___ ___ 0 1 1 1 0 0 0 0 1 0 0 1 1 1 0 0 1.3.3_Phép nhân nhị phân: Quy tắc : 0 * 0 = 0 0 * 1 = 0 1 * 1 = 1 Thí dụ : 1 1 0 1 0 1 1 1 x 1 0 1 ___ 1 1 0 1 0 1 1 1 0 0 0 0 0 0 0 0 1 1 0 1 0 1 1 1 ___ 1 0 0 0 0 1 1 0 0 0 1 1.3.4_Phần bù nhị phân: Phần bù của một số nhị phân được định nghĩa là một số nhị phân được tạo thành từ số ban đầu theo quy tắc các bit đều được đảo ngược (NOT). Thí dụ : phần bù của 01001101 là 10110010 3
- 0 1 0 0 1 1 0 1 1 0 1 1 0 0 1 0 số đối = phần bù + 1 ( bù 2 ) Trong quá trình xác định cách ký hiệu cho các số âm người ta nhận thấy các số âm tạo thành bao giờ cũng có bit cao nhất là 1, và các số dương tương ứng có bit cao nhất là 0 . Các nhận xét trên đưa đến các hệ quả về số nhị phân có dấu như sau : 〈 Các số âm bao giờ cũng có bit cao nhất là 1 . 〈 Một số âm cộng số đối tương ứng của nó sẽ cho kết quả là 0 . 〈 Các số có dấu cộng với nhau sẽ cho kết quả đúng như phép cộng các số nguyên ( Hãy chứng minh -3+2 = -1 ) . 4
- CHƯƠNG 2 : CẤU TRÚC MỘT MÁY VI TÍNH IBM PC INTERRUPT TIMER CLOCK CPU CONTROLLER DMA MEMORY SYSTEM BUS KEYBOARD VIDEO DISKETTE HARD DISK IO CONTROLLER CONTROLLER CONTROLLER CONTROLLER INTERFACE 2.1_BỘ XỬ LÝ TRUNG ƯƠNG Trung tâm của một máy vi tính IBM PC là bộ xử lý trung ương (CPU) 8088 do Intel sản xuất . 8088 là một vi mạch đặc biệt, có khả năng tự thực hiện một loạt các mệnh lệnh được liệt kê trước,có khả năng tự phán đoán để chọn cách đáp ứng với các tình huống trong quá trình thực hiện lệnh. Mỗi mệnh lệnh 8088 được biểu thị bằng một nhóm các byte. Các byte ấy được gọi là mã lệnh (opcode) của bộ xử lý . Khi thực hiện công việc của mình CPU đọc các mã lệnh được lưu chứa trong bộ nhớ của máy tính và thực hiện chúng theo thứ tự liên tiếp nhau. 2.2_BỘ NHỚ Bộ nhớ là nơi lưu chứa các mệnh lệnh mà CPU sẽ phải thực hiện,cũng là nơi ghi nhớ các thông tin, các giá trị trung gian, các kết quả của quá trình làm việc Bộ nhớ được tạo thành từ hai loại chính : - Bộ nhớ chỉ đọc - ROM (Read Only Memory) - Bộ nhớ đọc/ghi - RAM (Random Access Memory) Các thông tin ghi trên ROM là cố định và không xóa được: mang tính chất “vĩnh cửu” Các thông tin trên RAM có thể được sửa đổi tùy thích, nhưng những gì đang được lưu giữ trong RAM sẽ mất đi khi tắt máy. Trong một máy tính, mỗi phần tử nhớ thuộc ROM hoặc RAM được ghi nhận bởi một số thứ tự (gọi là địa chỉ). 2.3_CÁC MẠCH HỖ TRỢ Để có thể hoạt động được, CPU cần đến sự hỗ trợ của nhiều vi mạch khác. Đồng thời, CPU cũng không thể giao tiếp với người sử dụng bằng các giá trị nhị phân biểu diễn dưới dạng các xung điện . Nó phải nhờ đến các mạch giao diện (Interface) như keyboard controller,video controller Các mạch giao diện này sẽ điều động trực tiếp đến các phương tiện giao tiếp cụ thể với người sử dụng (như màn hình,bàn phím,máy in ) theo các yêu cầu,các thông tin được gởi đến từ CPU. 2.4_PORT Để liên lạc với các vi mạch hỗ trợ,các mạch giao diện, CPU dùng một hệ thống địa chỉ riêng ( khác với hệ thống địa chỉ định vị của bộ nhớ ) để xác định đối tượng cần đối thoại . Mỗi địa chỉ trong hệ thống này được gọi là một cổng (port) . Mỗi vi mạch hỗ trợ, mỗi mạch giao diện sẽ chiếm dụng một số cổng xác định . Khi cần giao tiếp, CPU chỉ cần sử dụng đến các cổng tương ứng. 5
- 2.5_CLOCK Các mạch điện ít khi nào có thời gian thực hiện giống nhau. Do đó, để các phần tử trong một máy tính hoạt động nhịp nhàng ăn khớp với nhau phải có một bộ phận đếm nhịp . Đảm nhiệm công việc này là một bộ phận tạo xung nhịp cho tất cả các mạch điện trong máy . Bộ phận này được gọi là đồng hồ của máy . Máy có đồng hồ với tần số càng cao thì sẽ chạy càng nhanh. 2.6_BUS CPU và các mạch có liên quan được nối song song với một dải các kênh liên lạc, nguồn cung cấp điện cấu trúc này được gọi là bus. Bus được chia thành 4 phần chính: 1. Command bus 2. Address bus 3. Data bus 4. Power bus Hệ thống quản lý bus gọi là Bus controller Giao tiếp thông qua bus được thực hiện như sau : Giả sử CPU cần tăng giá trị từ một phần tử nhớ tại địa chỉ X lên 1 đơn vị, nó phải trải qua các bước sau : 1. Đặt địa chỉ X vào address bus 2. Đưa yêu cầu đọc memo vào command bus 3. Đợi Bus thông báo sẵn sàng : bus controller lấy giá trị tại địa chỉ X đặt vào Data bus 4. Tiếp nhận giá trị cần đọc từ data bus 5. Tăng giá trị ấy lên 1 ( phép toán được thực hiện trong CPU ) 6. Đặt giá trị mới trở lại data bus 7. Đưa yêu cầu ghi memo vào command bus (address bus đang giữ giá trị cũ) 6
- 8. CHƯƠNG 3 : BỘ VI XỬ LÝ 8088 3.1_CÁC THANH GHI CỦA BỘ VI XỬ LÝ 8088 Bộ vi xử lý 8088 có một số phần tử dùng để ghi nhớ các giá trị trung gian trong quá trình làm việc,tạo ra các địa chỉ,làm các phép tính Các phần tử này gọi là các thanh ghi (Register). 8088 là bộ vi xử lý 16 bit, các tác vụ của 8088 thường dược thực hiện trên 16 bit dồng thời,các thanh ghi của 8088 đều là các phần tử nhớ 16 bit.Các thanh ghi hoàn toàn có thể sử dụng như các phần tử nhớ thuộc về RAM. Điểm khác nhau giữa thanh ghi và các phần tử nhớ thuộc RAM là Thanh ghi là các phần tử thuộc bộ vi xử lý (về mặt vật lý). Một số tác vụ đặc biệt như nhân hay chia, kết quả bắt buộc phải đặt trong thanh ghi Một số thanh ghi chỉ dùng để xác định địa chỉ cho 8088 Các tác vụ trên thanh ghi nhanh hơn nhiều so với các tác vụ trên RAM. Bởi vì để thực hiện các tác vụ trên RAM,8088 phải đi qua một quá trình như sau : a) Đọc thông tin từ RAM (thông qua Bus) b) Thực hiện tác vụ c) Ghi trả lại thông tin vào RAM (thông qua Bus) Bộ vi xử lý 8088 có 14 thanh ghi 16 bit,được chia thành nhóm theo chức năng như sau: 3.1.1_Nhóm thanh ghi đa dụng : AX,BX,CX,DX : Mỗi thanh ghi có thể sử dụng như hai thanh ghi 8 bit với các tên AH,AL,BH,BL,CH,CL,DH,DL F E D C B A 9 8 7 6 5 4 3 2 1 AH AL AX BH BL BX CH CL CX DH DL DX 3.1.2_Nhóm thanh ghi con trỏ và chỉ mục : SP,BP,SI,DI : thường dùng để tạo các địa chỉ, cũng có thể dùng để lưu chứa các số liệu như nhóm thanh ghi đa dụng 3.1.3Nhóm thanh ghi phân đoạn : CS,DS,SS,ES : Chỉ dùng để xác định Segment, không cho phép thực hiện các phép tính số học,logic 3.1.4Thanh ghi con trỏ lệnh IP : Chỉ dùng để xác định offset của mã lệnh 3.1.5Thanh ghi cờ hiệu Flag : Ghi lại trạng thái của các phép tính số học, kết quả của các phép so sánh F E D C B A 9 8 7 6 5 4 3 2 1 0 OF DF IF TF SF ZF AF PF CF 3.2CÁCH XÁC ĐỊNH ĐỊA CHỈ CỦA 8088 7
- Bộ vi xử lý 8088 dùng một số 20 bit để xác định địa chỉ cho các phần tử trong vùng nhớ (memory), tương đương với 1MB vị trí. Bằng các giá trị 16 bit,ta chỉ có thể tạo ra địa chỉ cho 0FFFFh phần tử ( 64 KB hay 65536 byte ). Do đó để có thể định vị 1 MB địa chỉ bằng các thanh ghi 16 bit, 8088 sử dụng nguyên tắc truy nhập bộ nhớ theo từng segment,mỗi segment có kích thước 64 KB, điểm bắt đầu của segment phải là một địa chỉ chia hết cho 010h ( 16 thập phân ) . Địa chỉ của segment được tính bằng paragraph ( 1 paragraph = 16 byte ). Địa chỉ một phần tử nào đó thuộc đoạn sẽ được xác định thông qua địa chỉ tương đối (offset) của phần tử đó trong đoạn và vị trí của đoạn . Từ nguyên tắc trên, ta có các kết quả sau : Địa chỉ 20 bit = Segment*16 + Offset Cách biểu diễn : Segment:offset Cùng một địa chỉ, có thể biểu diễn dưới nhiều hình thức khác nhau. Thí dụ: 0040:006C và 0000:046C là hai cách biểu diễn một địa chỉ Segment chỉ là khái niệm logic, do đó không có vị trí cố định cho từng segment . Ta hoàn toàn có thể mô tả các segment phủ lấp lên nhau. Bộ vi xử lý 8088 luôn xác định segment thông qua giá trị của các thanhghi segment. Do đó để xác định segment ta phải gán các giá trị tương ứng cho các thanh ghi segment. Thanh ghi CS chỉ định segment của đoạn mã chương trình đang thực hiện,IP mang giá trị offset. Vì vậy CS:IP luôn là địa chỉ của lệnh đang được thực hiện. Thanh ghi DS chỉ định segment của dữ liệu,thông thường các địa chỉ đều được căn cứ theo segment DS . Thanh ghi SS chỉ định segment của STACK ( sẽ nói rõ hơn trong phần sau). Thanh ghi ES dùng trong các tác vụ về chuỗi hoặc theo yêu cầu riêng. 3.3CÁC XÁC ĐỊNH OFFSET CỦA 8088 Offset của bộ vi xử lý 8088 được xác định theo nguyên tắc sau : Offset là một hằng số TD : [046Ch] Offset là giá trị của một trong các thanh ghi chỉ mục BX,SI,DI,BP TD : [BX] Offset là giá trị của một tổng được tạo thành từ các hằng số,các thanh ghi chỉ mục Nguyên tắc này có một số hạn chế : - SI và DI không được cùng có mặt trong tổng - BX và BP không được cùng có mặt trong tổng TD : [SI+BX] [DI+BX+020h] TD : [SI+BP+041h] Segment mặc nhiên : khi xác định một địa chỉ, không phải lúc nào ta cũng phải chỉ ra thanh ghi segment nào sẽ được sử dụng . Nếu không có chỉ định cụ thể thanh ghi segment, các offset tạo thành từ các hằng số và các thanh ghi chỉ mục BX,SI,DI luôn được hiểu là đi kèm với thanh ghi segment DS . Offset được tạo thành từ BP được hiểu là đi kèm với thanh ghi SS Nếu chúng ta chỉ định rõ ràng thanh ghi segment đi kèm với offset, các giá trị mặc nhiên về segment sẽ bị vô hiệu hoá . 3.4 BỘ 8088 THỰC HIỆN MỘT CHƯƠNG TRÌNH RA SAO ? 3.4.1Thế nào là một chương trình : Chương trình là một tập hợp các mã lệnh và các dữ liệu . Mã lệnh (code) : là các lệnh 8088 sẽ thực hiện Dữ liệu (data) : là các thông tin chương trình sẽ sử dụng đến .Thí dụ : chương trình của bạn sẽ in 8
- dòng chữ “Do you like me ?” ra màn hình thì dòng chữ ấy phải có mặt trong chương trình của bạn. Vùng lưu chứa dòng chữ này trong chương trình được gọi là một vùng dữ liệu (data) . Chương trình của bạn cũng cần đến một số vùng nhớ để lưu chứa các giá trị trong quá trình chương trình được thực hiện, các vùng nhớ này cũng được gọi là vùng dữ liệu . Phải chú ý, bộ 8088 không thể phân biệt đâu là code, đâu là data ! 3.4.2Cách thức 8088 thực hiện một chương trình : Giả sử đã có chương trình trong RAM và CS:IP chỉ đến lệnh đầu tiên của chương trình . 8088 đọc byte xác định bởi CS:IP,nhận diện lệnh, từ đó suy ra chiều dài của lệnh . 8088 cộng thêm vào IP chiều dài của lệnh, rồi bắt đầu thực hiện lệnh . Sau khi thực hiện lệnh, 8088 đọc tiếp byte đang được xác định bởi CS:IP và cứ thế quá trình này lặp đi lặp lại mãi . Chương trình của chúng ta được thực hiện từ lệnh đầu tiên đến lệnh cuối cùng theo phương cách này . Nhưng làm cách nào để đưa một chương trình vào RAM và làm cách nào để ấn định CS:IP chỉ đến lệnh đầu tiên của chương trình ấy ? Tất cả các tác vụ này đều do hệ điều hành thực hiện . Chương trình của chúng ta được lưu chứa trên đĩa thành một file có phần mở rộng (đuôi đặc tính) là COM hay EXE . Khi ta gõ từ bàn phím tên của file này và nhấn ENTER, hệ điều hành sẽ đọc file ấy vào RAM, sau đó đưa vào CS và IP các giá trị để CS:IP chỉ vào lệnh đầu tiên của chương trình vừa được nạp vào. Sau đó là quá trình thực hiện chương trình của chúng ta như đã nêu trên. Làm cách nào để kết thúc chương trình của chúng ta ? Như đã nêu trên, 8088 không ngừng đọc và phân giải các lệnh theo địa chỉ được CS và IP xác định,do đó kết thúc một chương trình nào đó không có nghĩa là 8088 ngừng hoạt động (!) . Quá trình kết thúc một chương trình được thực hiện bằng cách làm cho CS:IP chỉ vào một điểm xác định trước trong vùng mã của hệ điều hành ( còn gọi là chuyển điều khiển cho hệ điều hành ) . Sau đó là quá trình đợi lệnh của hệ điều hành . Quá trình đợi lệnh của hệ điều hành như chúng ta nhìn thấy, có vẻ như “máy tính đang ngừng làm việc,chờ chúng ta ra lệnh tiếp” ! Đây là điều lầm tưởng của nhiều người mới bắt đầu làm quen với Computer. Chúng ta phải luôn nhớ rằng máy tính liên tục phân giải và thực hiện các lệnh được CS:IP chỉ đến và chỉ ngừng khi chúng ta tắt máy ! 3.4.3Chúng ta tạo ra chương trình bằng cách nào : Các mã lệnh trong chương trình của chúng ta là các số nhị phân biểu diễn thành từng byte . Chúng ta sẽ rất khó khăn nếu phải viết một chương trình bằng từng số nhị phân như vậy . Trong thực tế chúng ta sẽ viết một chương trình nguồn, đó là một file với các đặc điểm : - Sự trình bày tuân thủ theo một số quy tắc chung - Mỗi lệnh của 8088 sẽ được gọi bằng một cái tên gợi nhớ . - Các địa chỉ được xác định bởi các tên riêng do người viết quy định - Chương trình nguồn sẽ được một chương trình đặc biệt biên dịch (compile) thành một file với đầy đủ các mã lệnh và các dữ liệu đã mô tả trong chương trình nguồn . Vì vậy chúng ta không cần phải thắc mắc lệnh này hoặc lệnh kia có mã máy là gì ! 3.4.4Một chương trình nguồn sẽ được biên dịch bằng Assembler 86 : Code Segment Assume CS:Code,DS:Code org 0100h Main proc near M1: mov dx,offset chuoi1 ; in chuoi1 ra màn hình mov ah,9 ; int 021h ; mov ah,1 ; Read Keyboard 9
- int 021h ; cmp al,’Y’ ; Kiểm tra ký tự dọc vào je KT ; nếu là ‘Y’ hay ‘y’ thì nhảy dến KT cmp al,’y’ je KT mov ah,02 ; Nếu không, Beep một tiếng rồi quay lại M1 mov dl,7 int 021h ; jmp M1 KT: mov dx,offset chuoi2 ; In chuoi2 mov ah,9 ; int 021h ; int 020h ; Kết thúc chương trình Main endp Chuoi1 db 13,’Do you like me ? $’ ; Mã 13 để trở về đầu dòng Chuoi2 db ‘ Thank you !$’ Code ends end main 10
- CHƯƠNG 4 : ASSEMBLER 86 Ngữ pháp của các trình hợp dịch thông dụng hiện nay như MASM,TASM khá phức tạp, do đó tài liệu này sẽ chỉ trình bày các phần căn bản mà hầu hết trình hợp dịch thông dụng đều chấp nhận. Những phần căn bản nhất của ngữ pháp của assembler cho bộ xử lý 8088/86 sẽ được trình bày trong chương này. Các phần khác sẽ được đề cập đến trong các chương sau. 4.1NGỮ PHÁP CỦA ASSEMBLER 86 Offset Var_X nếu không khai SEGMENT-A báo Segment Offset Var_X nếu có khai báo Segment Var_X SEGMENT-B SEGMENT-C 4.1.1Khai báo segment Chương trình của chúng ta có thể sử dụng đến nhiều segment khi được thực hiện : Để truy nhập đến mỗi segment, ta phải gán địa chỉ của segment tương ứng cho các thanh ghi segment . Sau đó, các phần tử thuộc về segment sẽ được truy nhập thông qua offset của nó trong segment ấy. Lưu ý là việc gán giá trị cho các thanh ghi segment chỉ được thực hiện trong lúc chương trình đang thực hiện . Trong quá trình hợp dịch, trình hợp dịch không thể kiểm soát được giá trị thực của thanh ghi segment . Do đó sự phân chia chương trình thành các segment hoàn toàn là các ý niệm của người lập trình . Một trong các nhiệm vụ quan trọng của trình hợp dịch là xác định chính xác các offset theo các segment của chương trình chúng ta . Nếu không có một sự lưu ý nào về segment sẽ được sử dụng, trình hợp dịch sẽ cho rằng toàn bộ chương trình ở trong cùng một segment . Khi ấy các phần tử thuộc chương trình đều được trình hợp dịch xác định địa chỉ tương đối (offset) theo điểm đầu của chương trình . Thí dụ : một vị trí thuộc SEGMENT-B sẽ được tính offset theo cự ly từ đầu SEGMENT-A cho đến vị trí của nó trong toàn bộ đoạn mã chương trình được tạo ra .Khai báo về Segment sẽ lưu ý trình biên dịch về phạm vi của các segment cùng các phần tử thuộc về chúng.Theo đó trình biên dịch sẽ xác định đúng các offset tương ứng theo nguyên tắc : phần tử được xét thuộc segment nào sẽ được xác định cự ly theo điểm khởi đầu của segment ấy . 11
- Một Segment được khai báo với trình hợp dịch theo dạng thức sau : Tên-segment Segment [word/byte] [Public] . . ( Phạm vi của segment ) . . Tên-segment ends Các phần tử thuộc về segment nào phải được mô tả bên trong cấu trúc segment đó. Để xác định thanh ghi segment sẽ dùng cho segment được mô tả ta dùng khai báo sau : Assume Thanh-ghi:Tên-segment[,Thanh-ghi:Tên-segment [ ] ] TD : Assume CS:Code,DS:Code ;xác định với trình hợp dịch CS và DS sẽ cùng mang giá trị của Code segment . Lưu ý : Chỉ thị Assume chỉ là sự khai báo cho trình hợp dịch biết các thanh ghi segment sẽ được sử dụng như thế nào chứ không tạo ra phép gán giá trị cho các thanh ghi ấy ! 4.1.2Khai báo thủ tục Chương trình của chúng ta được viết thành các thủ tục (procedure) . Một procedure có cấu trúc như sau : Tên-procedure proc [near/far] . . Tên-procedure endp Bên trong thủ tục là các lệnh, các khai báo về dữ liệu Mỗi một lệnh, mỗi một khai báo đều được viết dưới dạng một dòng lệnh . Khai báo thủ tục trong hợp ngữ chỉ là hình thức . Chúng ta có thể bỏ qua các khai báo ấy mà vẫn tạo lập được một số chương trình đơn giản . Tuy vậy viết chương trình dưới dạng các thủ tục sẽ làm chương trình của chúng ta trở nên rõ ràng và dễ quản lý hơn . Khi chương trình được viết dưới dạng các thủ tục, việc khai báo thủ tục rất quan trọng. Trình hợp dịch sẽ căn cứ trên các khai báo ấy mà xác định chính xác các mã lệnh tương ứng với các chỉ thị JMP, CALL và RET . 4.1.3Cấu trúc một dòng lệnh A. Dòng lệnh mô tả : Mô tả hằng ( không tạo ra mã lệnh ) Tên EQU Giá trị Mô tả hằng dùng để định nghĩa một số giá trị sẽ được dùng đến trong chương trình . Sau khi được mô tả, mỗi khi sử dụng đến giá trị nào chúng ta chỉ cần dùng tên tương ứng . Trình hợp dịch sẽ tự động thay thế giá trị thực của hằng vào đoạn mã tương ứng . Mô tả biến Tên Loại Danh sách giá trị ( Trong đó Loại có thể là [Byte/Word/Dword] ) Mô tả biến dùng để xác định một địa chỉ,từ đó bắt đầu lưu chứa các thông tin với tính chất nêu trong “Loại” và chi tiết được liệt kê trong “Danh sách giá trị”. Khi cần truy nhập đến các thông tin này chúng ta dùng tên biến như một mốc địa chỉ. 12
- Thí dụ : Biến-A địa chỉ tại nơi khai báo Biến-A Biến-A[1] địa chỉ Biến-A + 1 Biến-A[2] địa chỉ Biến-A + 2 Biến-A[-1] địa chỉ Biến-A - 1 Trình hợp dịch sẽ tự động đổi ra địa chỉ thực của biến trong các chỉ thị có dùng đến biến. Mô tả nhãn Tên LABEL Loại ( Trong đó Loại có thể là [Byte/Word/Dword/Near/Far] ) Mô tả nhãn dùng để xác định một vị trí trong chương trình,tính chất của địa chỉ ấy tùy thuộc vào “Loại” được khai báo . Trình hợp dịch sẽ tạo ra các địa chỉ,các cự ly thực trong các chỉ thị có liên hệ đến tên nhãn. Mô tả biến và nhãn cho phép chúng ta chỉ định các vị trí trong chương trình theo vị trí logic của chúng chứ không theo địa chỉ thực của chúng . Khi hợp dịch, trình hợp dịch sẽ tính các cự ly thực sự và thay thế chúng vào các mã lệnh tương ứng . Để lấy địa chỉ một phần tử dùng cấu trúc : Offset Tên-phần-tử Thí dụ : Mov dx,offset Biến-A Các quy định chung : Tên : là một chuỗi tạo bởi các ký tự, các ký số và - (dấu gạch dưới). Tên không được bắt đầu bằng ký số. Trình hợp dịch quy định sau dấu chấm phẩy (;) là các lời chú thích. Ta có thể đặt bất cứ ghi chú gì ở đấy . Mỗi giá trị trong mô tả hằng hoặc biến có thể là : - Một hằng số, một tên hằng đã khai báo - Một biểu thức gồm các hằng số,tên hằng tạo thành từ các phép toán: - Số học : + - * / - Logic : NOT AND OR XOR - và dấu () Thí dụ : GT-A equ 020h ; Có giá trị là 00100000b GT-B equ 040h ; Có giá trị là 01000000b Biến-A db NOT 010h ; Có giá trị là 11101111b Biến-B db GT-A OR GT-B ; Có giá trị là 01100000b B. Dòng lệnh thực hiện [Nhãn:] Lệnh Đối tượng Tên : là một chuỗi tạo bởi các ký tự, các ký số và - (dấu gạch dưới). Tên không được bắt đầu bằng ký số. Sau dấu chấm phẩy (;) là các lời chú thích . Ta có thể đặt bất cứ ghi chú gì ở đấy . 4.1.4Các loại (kiểu) cơ bản Loại : Loại byte : khai báo bằng từ khóa DB. Mỗi phần tử trong danh sách giá trị chiếm 1 byte 13
- Thí dụ: a/ Số-học-viên DB 0,0,0 ;khai báo Số-học-viên là một giá trị kiểu byte, chiếm tất cả 3 byte, giá trị ban đầu của cả 3 byte này là 0 b/ File-name DB ‘Thidu1.asm’,0 ;khai báo File-name là một giá trị kiểu byte, chiếm tất cả 11 byte, giá trị ban đầu của các byte này lần lượt là mã của từng ký tự trong dấu nháy và cuối cùng là 0 c/ Buffer DB 20 dup (0) ;khai báo Buffer là một giá trị kiểu byte, chiếm tất cả 20 byte, các byte mang giá trị được mô tả trong cặp dấu ngoặc đơn ( 0 ). Chú ý : - Chỉ có kiểu byte mới chấp nhận chuỗi ký tự - Để biểu diễn một byte,ta có thể ghi giá trị của byte đó dưới dạng số thập phân, số thập lục, số nhị phân hoặc là một ký tự (có mã tương ứng) đặt giữa hai dấu nháy đơn . TD : 65, 041h, ‘A’ là các cách biểu diễn khác nhau của cùng một giá trị . - Các vùng nhớ lưu chứa giá trị được khai báo được gọi là các biến của chương trình - Kiểu được khai báo chỉ dùng làm giá trị ngầm định để xác định loại dữ liệu được thao tác trong các lệnh . Ta hoàn toàn có thể chỉ định kiểu bất kỳ cho một biến trong các lệnh một cách tường minh. Loại word : khai báo bằng từ khóa DW Mỗi phần tử trong danh sách giá trị chiếm 2 byte (1 word) Loại double word : khai báo bằng từ khóa DD Mỗi phần tử trong danh sách giá trị chiếm 4 byte (2 word) Chú ý : Dữ liệu lưu chứa trong RAM có một số điểm khác với cách biểu diễn thông thường của chúng ta : những số biểu diễn bằng nhiều byte như loại word, double word được ghi nhớ trong RAM theo quy tắc byte thấp đứng trước, byte cao đứng sau. Thí dụ : giá trị hai byte 01234h được ghi nhớ tại địa chỉ 0462:0180 như sau : 0462:0180 034h 0462:0181 012h Chú ý : Khi nói một giá trị được ghi nhớ tại một địa chỉ nào đó ta phải hiểu là “được ghi nhớ bắt đầu từ địa chỉ đã chỉ ra” ! 4.1.5Phân loại chương trình Đối với hệ điều hành DOS, chương trình của chúng ta được chia thành hai loại chính : - Loại COM : chỉ sử dụng 1 segment, có kích thước tối đa gần 64 KB khi được thực thi, các thanh ghi segment đều được gán giá trị của segment hiện lưu chứa chương trình . Do đó trong một file COM, nếu không có nhu cầu truy nhập đến các vùng nhớ ngoài segment của chương trình, không cần phải chú ý đến việc xác định giá trị các thanh ghi segment. Giá trị của IP khi chương trình được thực thi luôn luôn bằng 0100h . - Loại EXE : sử dụng nhiều segment, không hạn chế về kích thước khi được thực thi, các thanh ghi segment cần được gán giá trị của segment có liên quan . Giá trị của IP khi chương trình được thực thi tùy thuộc từng chương trình cụ thể . Trong tài liệu này, các chương trình thí dụ đều được viết dưới dạng COM . 4.2CÁC LỆNH CĂN BẢN 4.2.1Lệnh MOV Dạng lệnh : MOV đích,nguồn 14
- Kết quả : dữ liệu sẽ được đem từ “nguồn” vào “đích” (giá trị của nguồn vẫn giữ nguyên) Trong đó “nguồn” và “đích” có thể là : - Thanh ghi - Số - Offset . Nếu là offset thì phải chỉ ra sẽ sử dụng byte hay word bằng cách sử dụng các khai báo : Byte PTR offset ( 8 bit ) Word PTR offset ( 16 bit ) Thí dụ : MOV AX,BX MOV AX,word PTR [SI+3] MOV AH,0Fh MOV word PTR [SI+BX],BX MOV AH,byte PTR [BX] MOV byte PTR [DI+4],9 Để lấy địa chỉ của một tên ( nhãn hoặc biến ) ta thực hiện như sau : MOV DX,offset chuoi1 hoặc LEA DX,chuoi1 Lệnh trên sẽ đưa vào DX offset của chuoi1 Chú ý : - Đích và nguồn phải có kích thước bằng nhau - Bộ vi xử lý 8088 không thể thực hiện các tác vụ với cả hai đối tượng cùng được định vị bởi offset . Do đó lệnh sau là không hợp lệ : Mov byte ptr [SI],byte ptr [DI+2] 4.2.2Lệnh XCHG Dạng lệnh : XCHG đích,nguồn Kết quả : dữ liệu giữa “nguồn” vào “đích” sẽ được đổi chỗ cho nhau 4.2.3Các lệnh cộng,trừ Dạng lệnh : ADD đích,nguồn ADC đích,nguồn (cộng) SUB đích,nguồn SBB đích,nguồn (trừ) Kết quả : giá trị của “đích” được cộng (hay trừ) với nguồn, kết quả được đặt vào “đích” .Giá trị của “nguồn” không đổi Nếu là ADC hay SBB, kết quả được cộng thêm hay trừ bớt 1 khi bit CF = 1. 4.2.4Các lệnh nhân,chia *Lệnh nhân : MUL giá trị 8 bit (thanh ghi/offset) Thanh ghi AL được nhân với giá trị 8 bit,kết quả là giá trị 16 bit đặt trong AX Thí dụ : Mov AL,3 Mov CL,2 Mul CL kết quả AX=6 MUL giá trị 16 bit (thanh ghi/offset) Thanh ghi AX nhân với giá trị 16 bit, kết quả là giá trị 32 bit đặt trong DX và AX 15
- Thí dụ : Mov AX,3 Mov CX,0100h Mul CL kết quả DX=0 AX=0300h *Lệnh chia : DIV giá trị 8 bit (thanh ghi/offset) Thanh ghi AX được chia cho giá trị 8 bit, kết quả là giá trị 8 bit đặt trong AL,số dư đặt trong AH Thí dụ : Mov AX,035h Mov CL,010h Div CL Kết quả AL=3 AH=5 DIV giá trị 16 bit (thanh ghi/offset) Giá trị 32 bit đặt trong thanh ghi DX và AX (giống như kết quả phép nhân 16 bit) chia cho giá trị 16 bit, kết quả là giá trị 16 bit đặt trong AX,số dư đặt trong DX Thí dụ : Mov DX,038h ; giá trị 32 bit trong DX và Mov AX,02147h ; AX là 0382147h Mov CX,01000h Div CX kết quả AX=0382h (thương số) DX=0147h (số dư) 4.2.5Lệnh CMP và lệnh TEST Dạng lệnh : CMP đích,nguồn TEST dích,nguồn Cách thực hiện : CMP thực hiện phép tính : nguồn – đích, TEST lấy nguồn AND với đích. Kết quả : Không làm thay đổi giá trị của đích và nguồn, kết quả thực hiện được ghi nhận trong thanh ghi cờ hiệu Flag,để nhận định kết quả phương pháp thông thường là dùng nhóm lệnh chuyển điều khiển có điều kiện. 4.2.6Lệnh JMP (chuyển điều khiển không điều kiện) Dạng lệnh : JMP Nhãn 4.2.7Nhóm lệnh chuyển điều khiển có điều kiện . Dạng lệnh : Các giá trị không dấu Các giá trị có dấu Ý nghĩa JB Nhãn JL Nhãn = JA Nhãn JG Nhãn > 16
- JNB Nhãn JNL Nhãn >= JNE Nhãn JE Nhãn = BX thì gán Max = AX JMP M-2 ; Rồi chuyển điều khiển đến M-2 M-1: MOV Max,BX ; Gán Max = BX, sau đó thực hiện tiếp tại M-2 M-2: ; Kết quả : Max là giá trị lớn nhất Max dw 0 ; giữa AX và BX 4.2.8Nhóm lệnh logic Dạng lệnh : AND đích,nguồn XOR đích,nguồn, OR đích,nguồn NOT đích 4.2.9Nhóm lệnh shift Dạng lệnh : SHL đích,1 hay SHL đích,CL SHR đích,1 hay SHR đích,CL Tác dụng : “đẩy” các bit của đích theo chiều từ bit thấp đến bit cao (sang trái/SHL) hay theo chiều từ bit cao qua bit thấp(sang phải/SHR) với số bit là 1 hay là giá trị trong CL. Bit bị đẩy “lọt ra ngoài” được đặt trong CF . Các khoảng trống do lệnh shift tạo ra được điền bằng giá trị 0 Trước khi SHL Sau khi SHL 1 Sau khi SHL 2 1 0 0 1 1 0 1 1 0 0 1 1 0 1 1 0 0 1 1 0 1 1 0 0 CF = 0 CF = 1 CF = 0 Trước khi SHR Sau khi SHR 1 Sau khi SHR 2 1 0 0 1 1 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 1 1 0 CF = 0 CF = 1 CF = 1 Dạng lệnh : ROL đích,1 hay ROL đích,CL ROR đích,1 hay ROR đích,CL 17
- Tác dụng : Bit cao nhất và bit thấp nhất của đích được xem như đứng cạnh nhau trong một vòng tròn . ROL và ROR sẽ đẩy các bit theo chiều từ bit thấp đến bit cao (ROL) hoặc theo chiều từ bit cao sang bit thấp (ROR) trong vòng tròn ấy với số bit là 1 hay là giá trị trong CL . Trước khi ROL Sau khi ROL 1 Sau khi ROL 2 1 0 0 1 1 0 1 1 0 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 Trước khi ROR Sau khi ROR 1 Sau khi ROR 2 1 0 0 1 1 0 1 1 1 1 0 0 1 1 0 1 1 1 1 0 0 1 1 0 Dạng lệnh : RCL đích,1 hay RCL đích,CL RLR đích,1 hay RCR đích,CL Tác dụng : tương tự ROL và ROR, nhưng bit giá trị của CF được đặt chèn vào giữa điểm nối của bit cao nhất và bit thấp nhất.Vòng quay gồm 9 ( hay 17 ) bit . Trước khi RCL Sau khi RCL 1 Sau khi RCL 2 1 0 0 1 1 0 1 1 0 0 1 1 0 1 1 0 0 1 1 0 1 1 0 1 Trước khi RCR Sau khi RCR 1 Sau khi RCR 2 1 0 0 1 1 0 1 1 0 1 0 0 1 1 0 1 1 0 1 0 0 1 1 0 CF = 0 CF = 1 F = 1 18
- CHƯƠNG 5 : STACK - PROCEDURE - INTERRUPT 5.1Khái niệm về STACK Bộ vi xử lý 8088 dành riêng các thanh ghi SS và SP cho việc quản lý STACK, đó là một cấu trúc đặc biệt được quy định như sau : STACK là một vùng nhớ thuộc về segment do SS xác định Thanh ghi SP đóng vai trò một con trỏ xác định điểm làm việc của STACK Có hai tác vụ chuẩn trên STACK : - PUSH thanhghi/offset (16 bit) :Cất giá trị 16 bit trong thanhghi/offset vào stack - POP thanhghi/offset (16 bit) :Lấy giá trị 16 bit trong stack đặt vào thanhghi/offset Cách thực hiện : Tác vụ Push : trừ SP cho 2, sau đó đặt một giá trị 16 bit vào địa chỉ SS:SP Tác vụ Pop : lấy giá trị 16 bit tại địa chỉ SS:SP rồi cộng SP với 2 Nếu ta dùng tác vụ Push để cất nhiều giá trị vào STACK, các giá trị này được xếp thành một hàng, giá trị nào được push vào trước đứng đầu hàng, giá trị nào push vào sau đứng cuối hàng .Tác vụ Pop sẽ lấy các giá trị từ STACK theo thứ tự từ cuối hàng . Do đó thứ tự lấy các giá trị từ STACK bằng Pop phải ngược lại thứ tự đã cất chúng bằng Push . Trong quá trình thực hiện chương trình để tránh hiện tượng các quá trình trung gian làm sai lạc giá trị các thanh ghi, biện pháp thông thường là cất giá trị các thanh ghi này vào STACK trước khi thực hiện quá trình trung gian . Thực hiện quá trình trung gian xong, lấy lại giá trị đầu cho các thanh ghi từ STACK. Thí dụ : push DX ; cất DX push AX ; cất AX mov ah,2 ; mov dl,7 ; beep int 021h ; pop AX ; lấy lại AX pop DX ; lấy lại DX Chú ý : - Số phần tử có thể cất vào stack chỉ tùy thuộc vào kích thước của stack. - Đã cất giá trị nào vào stack thì phải nhớ lấy ra ! 5.2THỦ TỤC Chương trình của chúng ta có một số đoạn được sử dụng nhiều lần . Đối với các đoạn chương trình này, tốt nhất là tổ chức thành các thủ tục (procedure) Mỗi lần cần sử dụng ta chỉ cần gọi đến chúng theo các tên đã được đặt . Việc tổ chức thành thủ tục sẽ giúp cho chương trình ngắn gọn và rõ ràng hơn nhiều. Bộ vi xử lý 8088 cung cấp các lệnh có liên quan đến thủ tục như sau : 5.2.1Chỉ thị CALL 19
- CALL Tên-procedure Tác vụ CALL sẽ chuyển điều khiển chương trình đến procedure được chỉ định bằng cách cất địa chỉ của lệnh kế tiếp vào stack,rồi chỉ CS:IP đến địa chỉ của procedure được chỉ định. CALL word ptr Chuyển điều khiển bằng cách push IP vào stack,sau đó nạp cho IP word lưu chứa tại địa-chỉ CALL dword ptr Chuyển điều khiển bằng cách push CS, push IP, sau đó nạp cho IP word ptr địa-chỉ[0], nạp cho CS word ptr địa-chỉ[2] 5.2.2Chỉ thị RET RET [n] Quay trở lại lệnh kế tiếp lệnh CALL đã gọi procedure.Một procedure thông thường sẽ kết thúc bằng lệnh RET. Thực hiện bằng cách lấy từ stack địa chỉ đã được lưu nhớ trong lệnh CALL rồi gán lại cho CS:IP. Nếu chỉ thị RET đi kèm với một hằng số thì sau khi CS và IP được phục hồi, SP sẽ được cộng thêm hằng số ấy ( tương đương với việc loại bỏ khỏi stack ngần ấy byte ). 5.2.3NEAR và FAR : Nếu procedure được gọi bởi lệnh CALL ở trong cùng segment,thì chỉ cần thay đổi IP để chuyển điều khiển.Do đó chỉ có IP được cất vào stack, và lệnh RET tương ứng cũng chỉ lấy lại giá trị cho IP từ stack . Kết quả là CS không thay đổi trong khi thực hiện các lệnh trên. Trong trường hợp này lệnh CALL và RET tương ứng được gọi là NEAR . Khi thực hiện một CALL near, IP được cất vào stack (push) Khi thực hiện một RET near, IP được lấy ra từ stack (pop) Ngược lại với NEAR, là FAR. Khi thực hiện một CALL far, trước tiên CS được cất vào stack, kế tiếp là IP . Sau đó cả IP lẫn CS đều được gán giá trị mới . Khi thực hiện một RET far, trước tiên IP được lấy ra từ stack, kế tiếp là CS . 5.2.3Quy định của trình hợp dịch về NEAR và FAR Nếu procedure-A là NEAR thì mặc nhiên các lệnh RET thuộc procedure-A là NEAR và các lời gọi tới procedure-A (CALL procedure-A) là NEAR Nếu procedure-B là FAR thì mặc nhiên các lệnh RET thuộc procedure-B là FAR và các lời gọi tới procedure-B (CALL procedure-B) là FAR 5.3INTERRUPT 5.3.1Interrupt trên 8088/8086 Bộ vi xử lý 8088 dành riêng vùng nhớ từ 0000:0000 đến 0000:03FF để tạo thành một bảng ngắt (interrupt table). Bảng ngắt có 256 phần tử liên tiếp được đánh số từ 0-255 (0-0FFh). Mỗi phần tử của bảng ngắt chiếm 4 byte tạo thành địa chỉ (hai byte đầu chứa offset và hai byte sau chứa segment) của một procedure đặc biệt gọi là một interrupt. Interrupt là một procedure loại FAR, nhưng chỉ được gọi bằng lệnh : INT số-hiệu. Khi thực hiện lệnh INT, Flag được cất vào stack kế tiếp là CS và IP. Điều khiển được chuyển hướng bằng cách nạp cho CS và IP các giá trị tương ứng trong bảng ngắt (lấy từ phần tử có thứ tự bằng số hiệu đi kèm với chỉ thị INT ) Interrupt kết thúc bằng lệnh IRET, khi thực hiện IRET, IP, CS và Flag được trả lại giá trị cũ. Một interrupt có thể được phát động bằng hai cách : do phần cứng hay do phần mềm . 20
- 5.3.2Interrupt cứng (hard interrupt) Khi một thiết bị ngoại vi cần thông báo một đôi điều gì đó với CPU thông thường một interrupt sẽ được phát động.Chương trình đang được thực hiện bởi CPU sẽ tạm dừng lại, chỉ thị INT Int-no được thực hiện ngay tức khắc với Int-no là số hiệu của interrupt được phát động.Các interrupt được phát động theo kiểu này được gọi là các interrupt cứng . Các interrupt cứng luôn luôn cất tất cả các thanh ghi được sử dụng đến vào stack trước khi thực hiện công việc của mình. Công việc một interrupt cứng thực hiện thường chiếm một thời gian rất ngắn.Sau đó các thanh ghi sẽ được hoàn trả từ stack và interrupt kết thúc bằng tác vụ IRET . Như vậy, các interrupt cứng đã tạm ngắt đoạn chương trình hiện thời để thực hiện một số tác vụ nào đó đáp ứng với nguyên nhân đã tạo ra interrupt, sau đó lại cho đoạn chương trình ấy tiếp tục . Người sử dụng và bản thân chương trình đang chạy vẫn cứ tưởng mình đang làm chủ hoàn toàn Computer ! Các interrupt cứng được điều khiển bởi interrupt controller . Trên IBM PC đó là vi mạch 8259A của Intel . 8258A cho phép quản lý 8 interrup cứng . Trên IBM AT, hai vi mạch 8259A được sử dụng cung cấp 16 interrupt cứng . Mỗi interrupt cứng được ký hiệu : IRQ ( n = 0 7 hoặc 0 15 ) Mỗi IRQ sẽ được chỉ định tương ứng với một số hiệu từ 0 đến 255 . Đó chính là số hiệu của interrupt được phát động. Để tránh trường hợp interrupt cứng bị lồng vào nhau ( một interrupt cứng được phát động trở lại trong khi đoạn chương trình của nó chưa kết thúc) Interrupt controller sẽ không cho phép phát động thêm bất cứ interrupt cứng nào khác sau khi một interrupt cứng được gọi . Do đó trước khi kết thúc, một interrupt cứng phải cho phép interrupt controller hoạt động trở lại (enable interrupt) bằng cách gởi ra port 020h giá trị 020h. mov al,020h out 020h,al iret 5.3.2 Interrupt mềm (soft interrupt) Một interrupt được phát động bởi một chỉ thị INT ngay bên trong đoạn chương trình đang được thực hiện gọi là một interrupt mềm . Nói một cách khác, interrupt mềm là một interrupt được chương trình sử dụng chủ động gọi đến . Lẽ dĩ nhiên, một chương trình cũng có thể cố ý gọi đến một interrupt cứng . Nhưng hành động ấy sẽ chẳng làm interrupt ấy trở thành “mềm” ! Thông thường việc gọi thẳng đến một interrupt cứng từ chương trình sẽ tạo ra các hậu quả không tốt đẹp gì cho lắm : đoạn chương trình của interrupt cứ đinh ninh rằng một thiết bị ngoại vi, hay một tác nhân ngoài nào đó đang chờ đợi nó trả lời ! Số lượng interrupt cứng rất ít so với số các phần tử của bảng ngắt,do đó hầu hết các interrupt còn lại đều được sử dụng như các interrupt mềm Các tác vụ điều khiển các thiết bị ngoại vi như màn hình, bàn phím, máy in,đĩa cứng,đĩa mềm cần phải có các procedure phức tạp đảm nhận.Các procedure này một phần được cung cấp bởi ROM BIOS,một phần thuộc về hệ điều hành . Chương trình của chúng ta khi có yêu cầu sử dụng đến các thiết bị ngoại vi chỉ cần gọi đến procedure tương ứng . Sử dụng lệnh CALL để truy nhập đến các procedure thuộc về ROM BIOS hay hệ điều hành đòi hỏi địa chỉ của các thủ tục ấy phải cố định.Yêu cầu ấy hầu như không thể thực hiện được bởi vì với mỗi version mới của ROM BIOS hay hệ điều hành, ta lại có một bộ các địa chỉ mới ! Và như vậy các chương trình viết theo cách này chỉ có thể chạy được với một ROM BIOS và một hệ điều hành xác định . Để giải quyết vấn đề trên, ta có thể viết các procedure ấy dưới dạng các interrupt và để ROM BIOS hay hệ điều hành tự cài đặt địa chỉ của chúng vào bảng ngắt theo từng số hiệu đã quy định trước . Khi cần sử dụng đến các procedure ấy, chương trình của chúng ta chỉ cần thực hiện chỉ thị INT tương ứng . 21
- Các interrupt theo nghĩa nêu trên chính là các interrupt mềm . Thông thường một interrupt mềm được gọi kèm theo một số tham số, các tham số ấy được đặt trong các thanh ghi theo một quy tắc đã định trước . Mỗi interrupt có thể cung cấp nhiều chức năng khác nhau, các chức năng này được gọi là các service hoặc các function . Chức năng được chỉ định bằng số hiệu đặt trong AH khi thực hiện lệnh INT . 5.3.3Các interrupt trên IBM AT ( Theo Norton Utilities ) Int-no Interrup Name Int-no Interrup Name 00 Divide by Zero 23 Ctrl-Break Handler Address 01 Single Step 24 Critical Error Handler 02 Nonmaskable 25 Absolute Disk Read 03 Breakpoint 26 Absolute Disk Write 04 Overflow 27 Terminate and Stay Resident 05 Print Screen 28 DOS Idle 06 Reserved 29 DOS Internal - FAST PUTCHAR 07 Reserved 2A-2D Reserved for DOS 08 System Timer IRQ 0 Timer 2E DOS - Execute Command Output 0 2F Multiplex (process interface) 09 Keyboard IRQ 1 Keyboard 30-32 Reserved for DOS 0A Reserved IRQ 2 [Cascade] 33 Microsoft Mouse Driver 0B Reserved IRQ 3 COM2 34-3E Reserved for DOS 0C Reserved IRQ 4 COM1 3F Overlay Manager 0D Reserved IRQ 5 LPT2 40 Diskette BIOS Revector 0E Diskette IRQ 6 Floppy Disk 41 Fixed Disk Parameters 0F Reserved IRQ 7 LPT1 42 Relocated Video Handler 10 Video 43 Reserved 11 Equipment Determination 44 Novell Netware API 12 Memory Size Determination 45 Reserved 13 Fixed Disk/Diskette 46 Fixed Disk Parameters 14 Asynchronous Communication 47-49 Reserved 15 System services 4A User Alarm 16 Keyboard 4B-5F Reserved 17 Printer 60-66 Reserved for User Programs 18 Resident BASIC 67 Expanded Memory 19 Bootstrap Loader 68-6F Reserved 1A Real-Time Clock Services 70 Real-Time Clock 1B Keyboard Break 71-74 Reserved 1C User Timer Tick 75 Redirect to NMI Interrupt 1D Video Parameters 76-79 Reserved 1E Diskette Parameters 7A Novell Netware (API) 1F Video Graphics Characters 7B-7F Reserved 20 Program Terminate 80-85 Reserved for BASIC 21 General DOS functions 86-F0 Reserved for BASIC interpreter 22 Terminate Address F1-FF Reserved for User Programs 22
- 5.3.4Lấy địa chỉ một interrupt Truy nhập trực tiếp bảng ngắt : xor ax,ax mov es,ax mov ax,word ptr es:[Int-no*4] mov Offs-Int,ax mov ax,word ptr es:[Int-no*4+2] mov Seg-Int,ax Offs-Int dw 0 Seg-Int dw 0 Dùng chức năng 035h của interrupt 021h mov ah,035h ; AH = 035h : get int-addr mov al,Int-no ; AL = Int-no int 021h ; Return : mov Offs-Int,bx ; BX = Offset mov Seg-Int,es ; ES = Segment 5.3.5Thay địa chỉ một interrupt Truy nhập trực tiếp bảng ngắt : CLI ; Cấm interrupt xor ax,ax mov es,ax mov word ptr es:[Int-no*4],Offset-New-Int mov word ptr es:[Int-no*4+2],Segment-new-Int STI ; Cho phép interrupt Dùng chức năng 025h của interrupt 021h mov ah,025h ; AH = 025h : set int-addr mov al,Int-no ; AL = Int-no mov dx,Offset-new-Int ; ; DS:DX = New-Int address int 021h 5.3.6Thay thế một interrupt Một vài chương trình trong quá trình thực hiện đã thay thế một số interrupt của hệ thống bằng các đoạn chương trình của mình . Nếu interrupt này không thuộc về ROM BIOS lẫn hệ điều hành thì đây chỉ là một cách cài đặt các thủ tục dưới dạng interrupt.Khi cần gọi đến thủ tục nào chỉ cần gọi interrupt tương ứng . 5.3.7“Xỏ mũi” interrupt ( Hook interrupt vector ) Nếu interrupt bị thay thế thuộc về ROM BIOS hay hệ điều hành, thông thường đoạn chương trình thay thế phải đảm nhiệm tất cả các khả năng của interrupt cũ : phải viết lại những gì đã có . Trong trường hợp này người ta thường chọn một giải pháp khác : mỗi khi interrupt được phát động chương trình thay thế sẽ được chuyển quyền, interrupt cũ sẽ được gọi thực hiện từ chương trình thay thế nếu có yêu cầu . Giải pháp này cho phép chúng ta bổ sung thêm một số chức năng, hay tạo một số hiệu ứng phụ cho một interrupt mà vẫn giữ được tất cả các khả năng của nó. 23
- Nguyên tắc này có thể được thực hiện như sau : Lấy địa chỉ interrupt cất vào Old-Int mov ah,035h mov al,Int-no int 021h mov word ptr Old-Int[0],bx mov word ptr Old-Int[2],es Old-Int dd 0 Thay địa chỉ interrupt bằng địa chỉ của New-Int mov ah,025h mov al,Int-no mov dx,offset New-Int int 021h Khi New-Int được gọi : New-Int sẽ làm một số tác vụ cần thiết như kiểm tra,thay đổi tham số rồi chuyển quyền cho Old- Int bằng cách nhảy thẳng đến đầu vào của Old-Int. New-Int proc near ; Thực hiện các tác vụ cần thiết jmp dword ptr cs:Old-Int New-Int endp Hoặc Old-Int sẽ được gọi như một thủ tục, khi quyền điều khiển được trả về, New-Int bắt đầu kiểm tra và biến đổi kết quả trả về từ Old-Int . Cuối cùng kết thúc bằng IRET New-Int proc near pushf call dword ptr cs:Old-Int ; Thực hiện các tác vụ cần thiết iret New-Int endp 5.4MỘT SỐ CHỨC NĂNG CỦA INTERRUPT 021H Interrupt 021h trên IBM PC được dành riêng cho hệ điều hành DOS. Hầu hết các chức năng của DOS được cung cấp qua interrupt này . Trong các thí dụ kế tiếp chúng ta sẽ sử dụng đến một số các chức năng căn bản sau : 5.4.1Read keyboard and echo Input : AH = 1 Output : AL = ASCII code Chức năng : đọc một ký tự từ bàn phím, nếu chưa có thì chờ cho đến khi một phím được gõ . Phím được đọc cũng đồng thời được in ra màn hình tại vị trí con trỏ hiện thời. 5.4.2Print character Input : AH = 2 DL = ASCII code 24
- Output : None Chức năng : in một ký tự ra màn hình tại vị trí con trỏ hiện thời. Các ký tự điều khiển sẽ ảnh hưởng lên chức năng này. Một số ký tự điều khiển trên IBM PC : 7 : Bell, có tác dụng làm loa beep một tiếng 8 : Backspace, có tác dụng kéo lùi con trỏ sang trái một ký tự 10 : Line feed (LF), có tác dụng dời con trỏ xuống hàng kế tiếp 13 : Carry return (CR), có tác dụng đưa con trỏ về đầu dòng hiện tại 5.4.3Print string Input : AH = 9 DS:DX = string terminated by ‘$’ Output : None Chức năng : in một chuỗi ra màn hình bắt đầu từ vị trí con trỏ hiện thời . Chuỗi được quy định kết thúc bởi ký tự ‘$’ . Trong chuỗi chấp nhận các ký tự điều khiển. 25
- CHƯƠNG 6 : MỘT SỐ CẤU TRÚC VÀ GIẢI THUẬT CĂN BẢN 6.1CẤU TRÚC IF 6.1.1IF Biến-A <= 5 THEN Biến-B:=1 ELSE Biến-B:=0 cmp Biến-A,5 ; So sánh Biến-A với 5 ja L1 mov Biến-B,1 ; Nhỏ hơn hay bằng jmp L2 L1: mov Biến-B,0 ; Lớn hơn L2: 6.1.2IF Biến-A <= 5 THEN ELSE Biến-B:=0 cmp Biến-A,5 ; So sánh Biến-A với 5 jbe L1 jmp L2 L1: ; Nhỏ hơn hay bằng . ; . ; Kích thước đoạn mã này lớn hơn . ; 128 byte . jmp L3 L2: ; Lớn hơn mov Biến-B,0 L3: 6.2CẤU TRÚC CASE 6.2.1Case Biến-A of 0 : Loại-A:=Loại-A+1; 1 : Loại-B:=Loại-B+1; 2 : Loại-C:=Loại-C+1; 3 : Loại-D:=Loại-D+1; end; cmp Biến-A,0 jne L1 inc Loại-A ; Loại-A:=Loại-A+1 jmp L4 L1: cmp Biến-A,1 jne L2 inc Loại-B ; Loại-B:=Loại-B+1 jmp L4 L2: 26
- cmp Biến-A,2 jne L3 inc Loại-C ; Loại-C:=Loại-C+1 jmp L4 L3: cmp Biến-A,3 jne L4 inc Loại-D ; Loại-D:=Loại-D+1 jmp L4 L4: ; End-case 6.2.2Case Biến-A of ‘0’ ’9’ : Loại:=0; ‘A’ ’Z’ : Loại:=1; end; cmp Biến-A,’0’ jae L1 jmp L2 L1: ; Biến-A >=’0’ cmp Biến-A,’9’ ja L2 Mov Loại,0 ; Biến-A =’A’ cmp Biến-A,’Z’ ja L4 Mov Loại,0 ; Biến-A CF = 1 Chỉ thị JB được thực hiện nếu CF = 1 Chỉ thị JAE được thực hiện nếu CF = 0 27
- 6.2.3Chuyển điều khiển theo nhiều giá trị và cự ly chuyển điều khiển lớn hơn 128 cmp ah,0 je L1 cmp ah,1 je L2 cmp ah,2 je L3 cmp ah,010h je L4 cmp ah,011h je L5 cmp ah,012h je L6 jmp End-case L1: jmp Case-0 L2: jmp Case-1 L3: jmp Case-2 L4: jmp Case-10 L5: jmp Case-11 L6: jmp Case-12 Case-0: jmp End-case Case-1: jmp End-case Case-2: jmp End-case Case-10: jmp End-case Case-11: jmp End-case Case-12: End-case: 28
- 6.3CẤU TRÚC FOR 6.3.1FOR Biến-A:=0 TO 100 DO mov Biến-A,0 L1: inc Biến-A cmp Biến-A,100 ja L2 jmp L1 L2: 6.3.2Chỉ thị LOOP Chỉ thị LOOP thực hiện như sau : trừ cx 1,kiểm tra cx đã bằng 0 chưa, nếu chưa sẽ chuyển quyền điều khiển đến . Nếu cx = 0 thì tiếp tục làm các lệnh kế tiếp . Dùng chỉ thị LOOP để điều khiển các vòng lặp làm chương trình của chúng ta trở nên ngắn gọn và thực hiện nhanh hơn. Trong khi dùng chỉ thị LOOP để điều khiển vòng lặp hãy cẩn thận khi làm thay đổi giá trị của CX . Thí dụ : mov cx,10 ; Lặp 10 lần L1: push cx ; Bảo toàn CX mov ah,2 ; Chức năng 02h interrupt 021h mov dl,7 ; In mã số 7 : beep một tiếng int 021h pop cx ; Phục hồi CX loop L1 Chú ý : Khởi đầu quá trình LOOP nếu CX=0 thì vòng lặp sẽ thực hiện 65536 lần trước khi kết thúc ! Để kiểm tra CX có bằng 0 hay không, ta có thể dùng chỉ thị JCXZ .Đây là một lệnh chuyển điều khiển có điều kiện.Chuyển điều khiển được thực hiện nếu CX=0 Mỗi lần “loop” CX bị giảm 1 nhưng đây là phép toán nội của 8088, thanh ghi cờ hiệu hoàn toàn không bị ảnh hưởng . Do đó khi kết thúc quá trình lặp mặc dù CX luôn bằng 0 nhưng hiện tượng này không được ghi nhận bởi cờ hiệu ZF. 6.4MỘT SỐ KỸ THUẬT CĂN BẢN 6.4.1Xóa/Dựng/Đảo một bit Xóa bit 2 của AL AND AL,NOT 4 ; 11111011b Dựng bit 2 của AL OR AL,4 ; 00000100b NOT bit 2 của AL XOR AL,4 ; 00000100b 29
- 6.4.2Kiểm tra một bit test al,8 ; kiểm tra bit 3 của AL jz L2 L1: ; Bằng 1 jmp L3 L2: ; Bằng 0 L3: 6.4.3Kiểm tra nhiều bit đồng thời khác không and al,8 OR 4 ; Xóa các bit khác bit 3 và bit 2 cmp al,8 OR 4 ; Kiểm tra bit 3 và bit 2 jne L2 L1: ; Đồng thời bằng 1 jmp L3 L2: ; Có ít nhất 1 bit bằng 0 L3: 6.4.4Kỹ thuật nhân và chia trên số nhị phân Tác vụ nhân và chia thường chiếm nhiều thời gian thực hiện hơn các tác vụ cộng trừ, shift Do đó các chương trình assembler thường cố gắng chuyển các tác vụ nhân chia thành những đoạn chương trình chỉ gồm các tác vụ cộng trừ và shift, rol Ta có các số tính chất sau : - Shift left 1 bit trên một giá trị nhị phân cho kết quả tương tự nhân giá trị nhị phân đó cho 2 - Shift right 1 bit trên một giá trị nhị phân cho kết quả tương tự chia giá trị nhị phân đó cho 2 Thí dụ 1 : Nhân một giá trị với 10 . Dùng quy tắc A*10 = A*8+A*2 mov ax,giá-trị-đầu ; shl ax,1 ; AX = giá-trị-đầu*2 mov bx,ax ; BX = giá-trị-đầu*2 shl ax,1 ; AX = giá-trị-đầu*2*2 shl ax,1 ; AX = giá-trị-đầu*2*2*2 add ax,bx ; AX = giá-trị-đầu*(2*2*2+2) ; AX = giá-trị-đầu*10 Thí dụ 2 : Nhân một giá trị với 14. Dùng quy tắc A*14 = A*16-A*2 mov ax,giá-trị-đầu ; shl ax,1 ; AX = giá-trị-đầu*2 mov bx,ax ; BX = giá-trị-đầu*2 shl ax,1 ; AX = giá-trị-đầu*2*2 shl ax,1 ; AX = giá-trị-đầu*2*2*2 30
- shl ax,1 ; AX = giá-trị-đầu*2*2*2*2 add ax,bx ; AX = giá-trị-đầu*(2*2*2*2-2) ; AX = giá-trị-đầu*14 6.5MỘT SỐ GIẢI THUẬT CHUYỂN ĐỔI 6.5.1Tạo chuỗi số dec từ một giá trị nhị phân 2 byte (Không định dạng) Num-to-Dec proc near ; ; Input : ; AX = giá trị cần chuyển đổi ( 2 byte ) ; DS:SI = Buffer (Kích thước 5 byte,xóa trống bằng 020h) ; Output : ; Dec-string được đặt vào buffer và canh theo lề phải ; push ax push bx push cx mov bx,5 mov cl,10 NTD1: div cl ; add ah,’0’ ; Chuyển số dư thành ký số ; Chú ý : ‘0’ ’9’ là các mã liên tục mov byte ptr [si+bx],ah dec bx ; bx = bx-1 xor ah,ah ; Chia al cho 10,số dư trong ah cmp al,0 jne NTD1 ; Nếu AL # 0 thì lặp lại pop cx pop bx pop ax ret Num-to-Dec endp 6.5.2Tạo chuỗi số dạng dec từ một giá trị nhị phân 4 byte (Có định dạng) Num-to-Dec proc near ; ; Input : ; DX,AX = giá trị cần chuyển đổi ( 4 byte ) ; DS:SI = Buffer ( Không cần phải xóa trống trước ) ; CX = Kích thước chuỗi số ( format/picture ) ; Cho CX = 0 sẽ tạo ra lỗi ; Output : ; Dec-string được đặt vào buffer và canh theo lề phải ; 31
- push ax push bx push cx push dx push si push di mov di,10 add si,cx NTD1: mov bx,ax ; BX = 2 byte thấp mov ax,dx ; Chia 2 byte cao cho 10 xor dx,dx div di xchg ax,bx ; (số-dư*65536 + 2 byte thấp)/10 div di add dl,’0’ ; Số dư chính là ký số cần tìm dec cx jcxz NTD3 NTD2: mov byte ptr ds:[si],dl dec si mov dx,bx or dx,ax jnz NTD1 mov dl,’ ‘ loop NTD2 NTD3: pop di pop si pop dx pop cx pop bx pop ax ret Num-to-Dec endp 6.5.3Tạo chuỗi số dạng dec từ một giá trị nhị phân 4 byte (Có định dạng) NUM-TO-DEC proc near ; ; Input : ; ; DX,AX = Giá trị cần chuyển đổi ; (DX : 2 byte cao, AX : 2 byte thấp) ; ES:DI = Địa chỉ của buffer ; CH = Kích thước chuỗi số ( format/picture ) ; ; Output : 32
- ; Dec-string được đặt vào buffer và canh theo lề phải cld mov bx,ax ; DX,BX chứa giá trị cần đổi mov al,’ ‘ mov cl,9 ; Chuỗi số có chiều dài tối mov si,offset NTD-table ; đa 9 ký số NTD1: cmp dx,word ptr cs:[si+2] ; So sánh DX,BX với dword jb NTD2 ; đang được DS:SI chỉ đến ja NTD3 ; ( Bắt đầu từ hàng tỉ ) cmp bx,word ptr cs:[si] ; So sánh 2 byte cao trước jae NTD3 ; 2 byte thấp sau NTD2: add si,4 ; Dời đến hàng kế tiếp cmp ch,cl jbe NTD21 ; Xóa trống phần đầu buffer stosb ; NTD21: dec cl jnz NTD1 NTD3: inc cl NTD4: mov al,’0’-1 NTD5: inc al sub bx,word ptr cs:[si] ; Trừ hai giá trị 4 byte sbb dx,word ptr cs:[si+2] ; jnc NTD5 ; Kết quả vẫn chưa âm add bx,word ptr cs:[si] ; Nếu kết quả âm, trả lại adc dx,word ptr cs:[si+2] ; kết quả dương nhỏ nhất ; > chuyển xuống hàng ; kế tiếp stosb ; AL = ký số hàng đang xét dec cl ; CL = hàng đang xét add si,4 ; Dời đến hàng kế tiếp jnz NTD4 ret NUM-TO-DEC endp NTD-table dd 1000000000 ; hàng tỉ dd 100000000 ; hàng trăm triệu 33
- dd 10000000 ; hàng chục triệu dd 1000000 ; hàng triệu dd 100000 ; hàng trăm ngàn dd 10000 ; hàng chục ngàn dd 1000 ; hàng ngàn dd 100 ; hàng trăm dd 10 ; hàng chục dd 1 ; hàng đơn vị Chú ý : Giải thuật của 6.5.1 và 6.5.2 : chia số cần đổi cho 10, suy ra ký số tương ứng từ số dư của phép toán chia . Quá trình tạo ra chuỗi số thập phân theo chiều từ phải sang trái. Để tránh trường hợp kết quả chia bị tràn,thí dụ 6.5.2 đã dùng đến hai lần chia trung gian, trong đó kết quả của mỗi lần chia luôn không tràn. Giải thuật của 6.5.3 : dùng một bảng liệt kê giá trị các hàng đơn vị của hệ thập phân theo thứ tự từ lớn đến nhỏ . Khi chuyển đổi sẽ tiến hành dò bảng từ trên xuống cho đến khi gặp giá trị nhỏ hơn giá trị cần chuyển đổi . Thí dụ : giá trị cần chuyển đổi là 170900 thì giá trị tìm thấy là 100000 (tiêu biểu cho hàng trăm ngàn) Lấy giá trị cần chuyển đổi trừ cho giá trị tìm thấy và lặp lại cho đến khi hiệu số nhỏ hơn số trừ . Số lần lặp chính là giá trị của ký số tương ứng. Trong thí dụ trên, số lần lặp là 1, hiệu số là 70900 Cho số trừ là phần tử kế tiếp trong bảng, lặp lại quá trình trên cho đến khi dò hết bảng . Giải thuật của 6.5.3 chỉ dùng các tác vụ cộng trừ và có thể mở rộng cho các số có kích thước tùy ý . 6.5.4Tạo chuỗi số dạng hex từ một giá trị nhị phân 1 byte Num-To-Hex proc near ; ; Input : ; AL = giá trị cần chuyển đổi ( 1 byte ) ; DS:SI = Buffer ; Output : ; Hex-string được đặt vào buffer ( 2 ký số hex ) ; push ax push cx mov ah,al ; AH = AL mov cl,4 shl ah,4 ; Xóa 4 bit cao (nibble cao) shr ah,4 ; AH = nibble thấp shr al,4 ; AL = nibble cao add ax,03030h ; AH = AH+’0’, AL = AL +’0’ cmp ah,’9’ jbe NTH1 add ah,’A’-‘9’-1 NTH1: 34
- cmp al,’9’ ; Nếu AL >’9’ > ‘A’ ’F’ jbe NTH2 add al,’A’-‘9’-1 ; Cộng thêm khoảng cách từ ‘A’ NTH2: ; đến ‘9’ trừ 1 . mov word ptr [si],ax ; ký số tương ứng với nibble pop cx ; cao sẽ đặt trước . pop ax ret Num-To-Hex endp 6.5.5Tạo chuỗi số dạng bin từ một giá trị nhị phân 1 byte Num-to-Bin proc near ; ; Input : ; AL = giá trị cần chuyển đổi ( 1 byte ) ; DS:SI = Buffer ( 8 byte ) ; Output : ; Bin-string được đặt vào buffer ( 8 ký số bin ) ; push ax push bx xor bx,bx ; BX = 0 NTB1: mov ah,’0’ shl al,1 ; CF = bit bị đẩy lọt ra ngoài adc ah,0 ; CF = 0 > AH = ‘0’+0 = ‘0’ ; CF = 1 > AH = ‘0’+1 = ‘1’ mov byte ptr [si+bx],ah inc bx ; bx = bx+1 cmp bx,8 ; Đã hết 8 bit chưa ? jne NTB1 pop bx pop ax ret Num-to-Bin endp 6.5.6Đổi chuỗi số dec thành giá trị nhị phân 4 byte DTN proc near ; Input : ; DS:SI = Buffer chứa num-str dạng decimal ; CL = chiều dài của num-str ; Output: ; CF=1 > lỗi chuyển đổi ; CF=0 > DX:AX = giá trị nhị phân tương ứng push bx push cx 35
- push si xor ch,ch xor ax,ax xor dx,dx DTN0: cmp byte ptr [si],’ ‘ ; Bỏ qua các khoảng trống jne DTN1 ; bên trái chuỗi số inc si loop DTN0 jmp DTN3 ; Chuỗi số “rỗng” DTN1: mov bl,byte ptr [si] inc si sub bl,03Ah ; kiểm tra ‘0’ lỗi loop DTN1 DTN2: clc jmp DTN4 DTN3: ; Nếu gặp lỗi, CF=1, kết quả stc ; chuyển đổi dở dang trong DTN4: ; DX|AX pop si pop cx pop bx 36
- ret DTN endp 6.5.7Đổi chuỗi số hex thành giá trị nhị phân 4 byte HTN proc near ; Input : ; DS:SI = Buffer chứa num-str dạng hexa ; CL = chiều dài của num-str ; Output: ; CF=1 > lỗi chuyển đổi ; CF=0 > DX:AX = giá trị nhị phân tương ứng ; push bx push cx push si xor ch,ch xor bx,bx ; DX|BX chứa kết quả trung xor dx,dx ; gian mov ah,0Fh HTN0: cmp byte ptr [si],’ ‘ ; Bỏ qua các khoảng trống jne HTN1 ; bên trái chuỗi số inc si loop HTN0 jmp HTN4 ; Chuỗi số “rỗng” HTN1: mov al,byte ptr [si] inc si sub al,’0’ jc HTN4 ; AL lỗi cmp al,9 jb HTN2 sub al,’A’-‘9’-1 cmp ah,al jc HTN4 ; AL > ‘F’ > lỗi HTN2: push cx mov cl,4 HTN3: shl bx,1 ; Nhân DX|BX với 16 rcl dx,1 ; loop HTN3 ; or bl,al ; Cộng dồn giá trị mới pop cx 37
- loop HTN1 ; Cho đến khi hết chuỗi số HTN4: mov ax,bx pop si pop cx pop bx ret HTN endp 6.5.8Đổi chuỗi số bin thành giá trị nhị phân 4 byte BTN proc near ; Input : ; DS:SI = Buffer chứa num-str dạng binary ; CL = chiều dài của num-str ; Output: ; CF=1 > lỗi chuyển đổi ; CF=0 > DX:AX = giá trị nhị phân tương ứng ; push bx push cx push si xor ch,ch xor bx,bx xor dx,dx BTN0: cmp byte ptr [si],’ ‘ ; Bỏ qua các khoảng trống jne BTN1 ; bên trái chuỗi số inc si loop BTN0 jmp BTN2 ; Chuỗi số “rỗng” BTN1: mov al,byte ptr [si] inc si sub al,’0’ jc BTN2 ; AL lỗi cmp al,1 ja BTN2 ; AL > 1 > lỗi shr al,1 ; Bit giá trị đặt trong CF rcl bx,1 ; rcl dx,1 ; Chuyển vào DX|BX loop BTN1 BTN2: 38
- mov ax,bx pop si pop cx pop bx ret BTN endp 39
- CHƯƠNG 7 : CÁC TÁC VỤ VỀ CHUỖI 7.1LODSB/LODSW Tác dụng : nạp vào AL (đối với LODSB) hay AX (đối với LODSW) giá trị tại địa chỉ xác định bởi DS:SI . Sau đó: Nếu DF = 0 tăng SI 1 đơn vị đối với LODSB, 2 đơn vị đối với LODSW Nếu DF = 1 giảm SI 1 đơn vị đối với LODSB, 2 đơn vị đối với LODSW 7.2STOSB/STOSW Tác dụng : lấy giá trị của AL (đối với LODSB) hay AX (đối với LODSW) đặt vào byte hay word tại địa chỉ xác định bởi ES:DI . Sau đó Nếu DF = 0 tăng DI 1 đơn vị đối với STOSB, 2 đơn vị đối với STOSW Nếu DF = 1 giảm DI 1 đơn vị đối với STOSB, 2 đơn vị đối với STOSW 7.3SCASB/SCASW Tác dụng : So sánh giá trị của AL (đối với SCASB) hay AX (đối với SCASW) với byte hay word tại địa chỉ xác định bởi ES:DI . Sau đó Nếu DF = 0 tăng DI 1 đơn vị đối với SCASB, 2 đơn vị đối với SCASW Nếu DF = 1 giảm DI 1 đơn vị đối với SCASB, 2 đơn vị đối với SCASW 7.4CMPSB/CMPSW Tác dụng : so sánh 1 byte (đối với CMPSB) hay 1 word (đối với CMPSW) tại DS:SI với giá trị tương ứng tại ES:DI . Sau đó Nếu DF = 0 tăng SI và DI 1 đối với CMPSB, 2 đối với CMPSW Nếu DF = 1 giảm SI và DI 1 đối với CMPSB, 2 đối với CMPSW 7.5MOVSB/MOVSW Tác dụng : lấy 1 byte (đối với MOVSB) hay 1 word (đối với MOVSW) tại địa chỉ xác định bởi DS:SI đặt vào địa chỉ xác định bởi ES:DI . Sau đó Nếu DF = 0 tăng SI và DI 1 đối với MOVSB, 2 đối với MOVSW Nếu DF = 1 giảm SI và DI 1 đối với MOVSB, 2 đối với MOVSW Để thay đổi cờ hiệu DF bộ vi xử lý 8088/8086 có các lệnh sau : - STD : dựng cờ hiệu DF > DF = 1 - CLD : xóa cờ hiệu DF > DF = 0 7.6TIỀN TỐ REPEAT (REP) Các tác vụ về chuỗi thường được sử dụng trên nhiều byte/word liên tiếp . Do đó cần một vòng lặp để thực hiện nhiều lần một tác vụ về chuỗi . Thí dụ : mov al,020h ; dùng mã ASCII 020h (khoảng trống) mov cx,30 ; để xóa trống biến buffer1 có chiều lea di,Buffer1 ; dài 30 byte cld L1: ; stosb ; Có thể thay đoạn chương trình lặp loop L1 ; này bằng chỉ thị 40
- ; REP STOSB Các tiền tố repeat thường được sử dụng chung với các tác vụ về chuỗi để thay thế các vòng lặp nói trên . Tiền tố REP khi đi kèm với một tác vụ chuỗi sẽ có tác dụng lặp đi lặp lại tác vụ chuỗi đó cho đến khi CX = 0 . Mỗi lần lặp, CX tự động giảm 1 . Chú ý : Việc CX bị trừ 1 sau mỗi lần lặp khi sử dụng tiền tố REP không ảnh hưởng đến thanh ghi cờ hiệu như các tác vụ số học và logic. Như vậy để thực hiện n lần liên tiếp một tác vụ chuỗi ta cần làm : mov cx,n rep Tiền tố REPE (repeat while equal) : giống REP nhưng sẽ kết thúc quá trình lặp bất cứ khi nào phép so sánh có kết quả không bằng xảy ra. Tiền tố REPNE (repeat while not equal) : giống REP nhưng sẽ kết thúc quá trình lặp bất cứ khi nào phép so sánh có kết quả bằng xảy ra. REPE và REPNE thường được dùng với SCASB/SCASW hoặc CMPSB/CMPSW 7.7CHUỖI TRONG PASCAL ( KIỂU STRING ) Đối với PASCAL,chuỗi là một đoạn dữ liệu gồm nhiều byte liên tiếp nhau, và thường có ý nghĩa là một dãy các ký tự hơn là một dãy các byte. Nói một cách khác, các thông tin lưu chứa trong chuỗi thường dùng để mô tả một đoạn văn theo ngôn ngữ của con người . Chuỗi chỉ là một khái niệm của người lập trình . Mỗi chuỗi của PASCAL chiếm 256 byte liên tiếp với cấu trúc : Byte đầu tiên trong một chuỗi của PASCAL được dùng để ghi nhớ độ dài hiện tại của thông tin đang được chuỗi lưu chứa . Giá trị này được gọi là str-len . Các byte kế tiếp dùng để lưu chứa thông tin của chuỗi. Thông tin của chuỗi chỉ tính từ byte đầu tiên của vùng lưu chứa thông tin đến byte thứ str-len . TURBO PASCAL đưa ra khái niệm chuỗi với kích thước tối đa bé hơn 256 byte Các chuỗi được khai báo như : string[30] có kích thước 31 byte string[50] có kích thước 51 byte 7.7CÁC THÍ DỤ ỨNG DỤNG Chuỗi được nhắc đến trong các thí dụ sau là chuỗi của PASCAL 7.7.1Đổi các mẫu tự của một chuỗi thành các chữ cái in hoa Up-string proc near ; Input ; DS:SI = Địa chỉ của chuỗi cần đổi push ds pop es mov di,si mov cl,byte ptr es:[di] ; Chiều dài chuỗi xor ch,ch inc si inc di US-1: 41
- cld lodsb call Up-char ; stosb loop US-1 ret Up-string endp Up-char proc near ; Input ; AL = ký tự cần đổi ; ; Output ; AL = ký tự đã được đổi cmp al,’a’ jb UC-end ; Kiểm tra AL có thuộc về cmp al,’z’ ; ‘a’ ’z’ hay không ja UC-end sub al,’a’-‘A’ UC-end: ret Up-char endp 7.7.2Tìm một byte trong một chuỗi Find-char proc near ; Input ; ES:DI = Địa chỉ của chuỗi ; AL = Byte cần tìm ; Output ; ZF = 0 > không tìm thấy ; ZF = 1 > ES:DI là địa chỉ của byte cần tìm mov cl,byte ptr es:[di] xor ch,ch ; CX = Str-len inc di cld repne scasb ret Find-char endp 7.7.3Tìm một chuỗi con trong một chuỗi mẹ Find-string proc near ; Input ; ES:DI = Địa chỉ của chuỗi mẹ 42
- ; DS:SI = Địa chỉ của chuỗi con cần tìm ; Output ; ZF = 0 > không tìm thấy ; ZF = 1 > DX = vị trí tìm thấy trong chuỗi mẹ mov cl,byte ptr es:[di] xor ch,ch ; CX = Chiều dài của chuỗi mẹ mov bl,byte ptr ds:[si] xor bh,bh ; BX = Chiều dài của chuỗi con inc si inc di mov dx,cx cld lodsb ; Lấy ký tự đầu của chuỗi con đặt vào AL dec si ; Định lại SI FS-begin: repne scasb ; so sánh các ký tự của chuỗi mẹ với AL jne FS-end ; ZF = 0 > JNE = OK dec di ; Định lại DI inc cx ; Định lại CX cmp cx,bx ; Kiểm tra chiều dài còn lại của chuỗi mẹ jb FS-end ; nếu bé hơn chuỗi con thì kết thúc ; ZF = 0 > JNE = OK push si ; DS:SI = địa chỉ chuỗi con push di ; ES:DI = địa chỉ ký tự bằng với ký tự ; đầu của chuỗi con push cx mov cx,bx ; CX = chiều dài chuỗi con repe cmpsb ; Bắt đầu so sánh chuỗi con với chuỗi mẹ pop cx pop di pop si je FS-end ; ZF = 1 > JE = OK inc di dec cx jmp FS-begin FS-end: pushf sub dx,cx inc dx ; DX = Vị trí tìm thấy trong chuỗi popf 43
- ret Find-string endp Có thể sửa đổi bài trên thành một thủ tục có thể tìm một chuỗi con trong cả một segment ( 64 Kb ). 7.7.4Insert một byte vào một chuỗi Insert-Char proc near ; ; DS:SI = địa chỉ chuỗi mẹ ; AL = byte cần chèn vào ; DX = vị trí cần chèn ( tính từ 1 ) ; push ds pop es ; ES = DS mov cl,byte ptr ds:[si] cmp cl,255 je IC-end ; Không thể chèn thêm inc byte ptr ds:[si] ; Tăng kích thước chuỗi mẹ lên 1 xor ch,ch add si,cx ; DS:SI = địa chỉ byte cuối chuỗi mẹ mov di,si inc di sub cx,dx inc cx ; CX = tổng số ký tự cần dịch chuyển std ; SI,DI sẽ giảm sau mỗi lần MOVSB rep movsb mov byte ptr ds:[si+1],al ; DS:[SI+1] = địa chỉ cần chèn IC-end: ret Insert-Char endp 7.7.5Delete một byte từ một chuỗi Delete-Char proc near ; ; DS:SI = địa chỉ chuỗi mẹ ; DX = vị trí cần xóa ( tính từ 1 ) ; push ds pop es ; ES = DS mov cl,byte ptr ds:[si] cmp cl,0 je DC-end ; Không thể delete dec byte ptr ds:[si] ; Giảm kích thước chuỗi mẹ xor ch,ch add si,dx ; DS:SI = địa chỉ byte sẽ được xóa 44
- mov di,si inc si sub cx,dx ; CX = tổng số ký tự cần dịch chuyển cld rep movsb DC-end: ret Delete-Char endp 7.7.6Trích một chuỗi con từ một chuỗi mẹ Sub-string proc near ; ; Input ; DS:SI = địa chỉ chuỗi mẹ ; ES:DI = địa chỉ chuỗi con ; BX = số byte cần chép ; DX = vị trí bắt đầu ( tính từ 1 ) ; ; Output ; BX = chiều dài chuỗi con mov cl,byte ptr ds:[si] xor ch,ch ; CX = chiều dài chuỗi mẹ add si,dx ; DS:SI = địa chỉ điểm bắt đầu chép mov byte ptr es:[di],0 ; Nếu điểm bắt đầu vượt quá chiều dài cmp cx,dx ; hiện tại của chuỗi mẹ thì cho lại jb SS-end ; chuỗi rỗng sub cx,dx inc cx ; CX = số byte cho đến hết chuỗi mẹ cmp cx,bx ; Nếu số byte cần chép vượt quá kích jbe SS-1 ; thước chuỗi mẹ thì chỉ chép đến mov cx,bx ; cuối chuỗi mẹ. SS-1: mov byte ptr es:[di],cl ; Gán chiều dài cho chuỗi con cld inc di rep movsb SS-end: ret Sub-string endp 45
- CHƯƠNG 8 : KEYBOARD Bàn phím của máy IBM PC là một thiết bị bao gồm : Các phím : mỗi phím xác định bởi một số thứ tự gọi là scan code (scan code là một giá trị 8 bit) Có 3 loại bàn phím chính : - bàn phím 83 phím của IBM PC - bàn phím 84 phím của IBM PC/AT - bàn phím 101 phím của IBM PC/AT Các đèn hiệu báo trạng thái cho các phím đặc biệt Bộ vi xử lý riêng với nhiệm vụ tiếp nhận thông tin từ các phím và chuyển các thông tin này đến computer. 8.1NGUYÊN LÝ HOẠT ĐỘNG CỦA BÀN PHÍM 8.1.1Scan code Khi một phím được nhấn xuống, bàn phím thông báo và chuyển scan code của phím ấy cho computer Nếu phím đã được nhấn xuống và giữ im (không buông tay) sau một thời gian (initial delay), bàn phím sẽ tiếp tục chuyển scan code của phím ấy cho computer theo một tốc độ xác định (repeat rate) . Ta gọi scan code trong trường hợp này là Pressed-key Khi phím được ngưng nhấn (buông tay), bàn phím chuyển scan code của phím ấy cho computer sau khi cộng thêm 128 (dựng bit số 7) Ta gọi scan code trong trường hợp này là Released-key Bảng B.8.1 : Scan code của các phím trên bàn phím 83/84 Hex Key Hex Key Hex Key Hex Key Hex Key 01 Esc 12 E 23 H 34 . > 45 NumLock 02 1 ! 13 R 24 J 35 / ? 46 ScrollLock 03 2 Ỵ 14 T 25 K 36 Shft(Rt) 47 Home [7] 04 3 # 15 Y 26 L 37 * PrtSc 48 Up [8] 05 4 $ 16 U 27 ; : 38 Alt 49 PgUp [9] 06 5 % 17 I 28 “ ‘ 39 Spacebar 4A K - 07 6 Á 18 O 29 Ạ À 3A CapsLock 4B Left [4] 08 7 & 19 P 2A Shft(L) 3B F1 4C [5] 09 8 * 1A [ A 2B \ 3C F2 4D Right[6] 0A 9 ( 1B ] Ã 2C Z 3D F3 4E K + 0B 0 1C Enter 2D X 3E F4 4F End [1] 0C - - 1D Ctrl 2E C 3F F5 50 Down [2] 0D + = 1E A 2F V 40 F6 51 PgDn [3] 0E bksp 1F 30 B 41 F7 52 Ins [0] 0F Tap 20 T,T 31 N 42 F8 53 Del [.] 10 Q 21 F 32 M 43 F9 54 SysReq 11 W 22 33 33 , < 44 F10 Đối với bàn phím 101 phím, scan code tương ứng với các phím mở rộng (Ctrl,Alt,Enter thứ hai, các phím Ins,Delete,Home,End,PgUp,PgDn và các phím mũi tên thứ hai ) luôn được tạo thành một cặp giá trị 46
- với byte đầu bằng 0E0h và byte kế tiếp là scan code tương tự với scan code của bàn phím 83/84 . Tổ hợp các phím mở rộng sẽ tạo thành một chuỗi các scan code với ý nghĩa tương tự trên bàn phím 83/84 Bảng B.8.2 : Scan code của các phím mở rộng trên bàn phím 101/102 Ú Key Hex Sequence Key Hex Sequence| F11 57 Home e0 47 | F12 58 Shft-Home e0 aa e0 47 | Right-Alt e0 38 End e0 4f | Right-Ctrl e0 1d Shft-End e0 aa e0 4f | PrintScreen e0 2a e0 37 Up e0 48 | Shft-PrintScreen (SysReq) e0 37 Shft-Up e0 aa e0 48 | Ctrl-PrintScreen (SysReq) e0 37 Down e0 50 | Alt-PrintScreen 54 Shft-Down e0 aa e0 50 | Pause e1 1d 45 e1 9d c5 PageUp e0 49 | Ctrl-Pause (Break) e0 46 e0 c6 Shft-PageUp e0 aa e0 49 | Insert e0 53 PageDown e0 51 | Shft-Insert e0 aa e0 52 Shft-PageDown e0 aa e0 51 | Delete e0 53 Right e0 4d | Shft-Delete e0 aa e0 53 Shft-Right e0 aa e0 4d | Left e0 4b K Enter e0 1c | Shft-Left e0 aa e0 4b K / e0 35 | Shft- K / e0 aa e0 35 Chú ý : các tên phím trong bảng B.8.1 và B.8.2 có ký hiệu K đi trước là các phím thuộc phần Key Pad của bàn phím (bảng số bên tay phải) 8.1.2 I/O control Liên lạc giữa bàn phím và computer được thực hiện thông qua keyboard controller . Giao tiếp với bàn phím có thể được chia thành hai loại chính : a.Tiếp nhận scan code Khi nhận được thông tin từ bàn phím,keyboard controller sẽ phát động interrupt 09h. Scan code gởi đến được đặt tại port 060h. Đoạn chương trình của interrupt 09h có nhiệm vụ đọc giá trị ấy từ port 060h vàdiễn giải thành các thông tin hữu dụng . b.Thiết lập các thông số cho bàn phím. Có thể thiết lập các trạng thái làm việc khác nhau của bàn phím bằng cách gởi một control code ra port 060h và kế tiếp là các thông số tương ứng. Các control code hợp lệ là : v 0FFh : Reset the keyboard and start internal diagnostics. v 0FEh : Resend the last transmission. v 0FDh-0F7h : No operator (NOP). v 0F6h : Set keyboard to defaults and continue scanning. v 0F5h : Set keyboard to defaults and disable keyboard scanning. v 0F4h : Enable the keyboard. KBD sends ‘ACK’, clears buffer, and starts scanning. v 0F3h : Set typematic rate and delay. First send 0F3h, then send data byte: 7 6 5 4 3 2 1 0 0 delay Rept rate bits 0-4 set the repeat rate 47
- bits 5-6 set initial delay before first repeat: 00=250ms;01=500ms; 10=750ms; 11=1000ms bit 7 is always 0 Repeat rate setting (bits 0-4) Value Character/sec Value Character/sec 0 30.0 0Ah 10.0 1 26.7 0Dh 9.2 2 24.0 10h 7.5 4 20.0 14h 5.0 8 15.0 1Fh 2.0 v 0F2h-0EFh : No operator (NOP). v 0EEh : Echo. Diagnostics aid. Simply sends 0EEh right back. v 0EDh Turn LED ‘lock key’ lights on or off. First send 0EDh, then send data byte: 7 6 5 4 3 2 1 0 Not used c n s ScrollLock LED 0=OFF, 1=ON NumLock LED 0=OFF, 1=ON CapsLock LED 0=OFF, 1=ON Lưu ý : sau khi gởi control code ra port 060h,phải có một khoảng thời gian trễ (nên lớn hơn 10ms) trước khi gởi tiếp data byte. Thí dụ : mov al,Ctrl-code out 060h,al xor cx,cx ; Delay: loop Delay mov al,Data-byte out 060h,al 8.2INTERRUPT 09H 8.2.1Shift và Toggle key Interrupt 09h quy dịnh hai loại phím đặc biệt : 1. Shift key : bao gồm Ctrl, Shift và Alt . Trạng thái của các phím này quyết định giá trị của các phím khác . Thí dụ : khi nhấn phím C ta nhận được ký tự ‘c’, nếu nhấn Shift-C ta nhận được ký tự ‘C’ . Một phím shift khi được nhấn xuống sẽ bắt đầu ảnh hưởng đến các phím gõ sau đó cho đến khi phím shift được buông ra . 2. Toggle key : gồm Caps Lock và Num Lock . Đây là các công tắc hai chiều . Khi Caps Lock được bật lên, trạng thái shift của các phím ký tự sẽ đảo ngược . Khi Num Lock được bật lên, các phím mũi tên trên bảng số bên phải sẽ mất tác dụng. Tình trạng của các phím shift và toggle được interrupt 09h ghi nhớ tại địa chỉ 040:017. Mỗi lần nhận được một scan code, interrupt 09h đối chiếu với tình trạng hiện thời của các phím shift và toggle để suy ra mã ASCII đích thực của phím được gõ vào .ý nghĩa các bit của byte tại địa chỉ 040:017 ( hay 0000:0417 ) 7 6 5 4 3 2 1 0 X Insert state : 1 = ON ; 0 = OFF 48
- X Caps Lock : 1 = ON ; 0 = OFF X Num Lock : 1 = ON ; 0 = OFF X Scroll Lock : 1 = ON ; 0 = OFF X 1 = Alt pressed X 1 = Crtl pressed X 1 = Left shift pressed X 1 = Right shift pressed 8.2.2Thủ tục cập nhật keyboard status byte (040:017) ; Thủ tục sau được thực hiện ngay sau khi interrupt 09h được gọi Update-KBD-Flag proc near push ds mov ax,040h mov ds,ax in al,060h ; Port 060h : keyboard data mov ah,al and al,07Fh ; Xóa bit 7 push cs pop es ; ES = CS lea di,KBD-Table-1 ; mov cx,8 cld repne scansb ; Dò KBD-Table-1 jne UKF-end ; Không phải là shift key hay toggle key mov al,1 ; CX = bit-no của Keyboard status byte shl al,cl cmp cx,3 ; Nếu bit-no > 3 > Toggle key jbe UKF-Shift ; > Không xét released-key UKF-Toggle: test ah,080h ; jnz UKF-end ; Released-key xor byte ptr ds:[017h],al ; switch bit jmp UKF-end UKF-Shift: test ah,080h ; jnz UKF-Shift-Clear ; Released-key UKF-Shift-Set: or byte ptr ds:[017h],al ; set bit jmp UKF-end UKF-Shift-Clear: not al and byte ptr ds:[017h],al ; clear bit 49
- UKF-end: pop ds ret Update-KBD-Flag endp KBD-Table-1 db 052h ; Insert scan-code ; 7 db 03Ah ; Caps Lock scan-code ; 6 db 045h ; Num Lock scan-code ; 5 db 046h ; Scroll Lock scan-code ; 4 db 038h ; Alt scan-code ; 3 db 01Dh ; Ctrl scan-code ; 2 db 02Ah ; Left shift scan-code ; 1 db 036h ; Right shift scan-code ; 0 db 0 8.2.3Keyboard buffer Scan code của các phím khác với hai loại phím trên (Shift key và Togglekey),ngoại trừ Insert,được chuyển thành hai byte theo hai trường hợp : Trường hợp phím được gõ vào là một phím bình thường, byte thấp chứa mã ASCII của phím được gõ,byte cao chứa scan code. Trường hợp phím gõ vào là một phím đặc biệt như các phím F1 đến F10 các phím mũi tên (cursor key), các phím Home,End,PgUp,PgDn,Delete, Insert thì byte thấp sẽ bằng 0, byte cao chứa scan code. Cặp giá trị trên được interrupt 09h đặt vào một “hàng đợi” tại địa chỉ040:01Eh.Hàng đợi này là một buffer có kích thước 020h byte được đánh dấu bằng hai vị trí : đầu (KBD-head) và cuối (KBD-tail) . Các giá trị này đặt tại : 040:01Ah : offset của điểm đầu ( 1 word ), Segment=040h 040:01Ch : offset của điểm cuối ( 1 word ), Segment=040h Khởi đầu, khi keyboard buffer còn trống, điểm đầu được cho trùng với điểm cuối . Khi một phím được gõ, scan code và ascii code được đặt vào vị trí điểm cuối đang chỉ đến, sau đó điểm cuối được tăng thêm 2 . Nếu điểm cuối đang chỉ đến vị trí cuối cùng của buffer (về mặt vật lý), nó sẽ được đặt lại cho trùng với vị trí đầu tiên của buffer (về mặt vật lý) Nếu như vị trí kế tiếp của điểm cuối bằng với vị trí hiện tại của điểm đầu : keyboard buffer đã đầy, các phím được gõ kế tiếp sẽ không được đặt vào keyboard buffer . Khi có yêu cầu đọc bàn phím, cặp giá trị đang được điểm đầu chỉ đến sẽ được lấy ra (xem như là mới do bàn phím gởi đến), sau đó điểm đầu được tăng thêm 2 . Nếu điểm đầu chỉ đến vị trí cuối cùng của buffer (về mặt vật lý), nó sẽ được đặt lại cho trùng với vị trí đầu tiên của buffer (về mặt vật lý) . Lẽ dĩ nhiên nếu điểm đầu đang trùng với điểm cuối,sẽ không có phím nào được đọc. Tóm lại bàn phím được khai thác theo nguyên lý : giá trị của các phím được gởi đến từ bàn phím được interrupt 09h ghi nhớ và sắp xếp theo thứ tự trước sau trong một buffer riêng,khi các chương trình ứng dụng “đọc bàn phím”, giá trị cũ nhất trong buffer sẽ được chuyển giao. Minh họa về cách quản lý keyboard buffer : a.Khởi đầu KBD-head và KBD-tail đang trùng nhau : Keyboard buffer đang trống KBD-head KBD-tail b.Phím ‘T’ được gõ : 50
- KBD-head T KBD-tail c.Phím ‘E’ được gõ : KBD-head T E KBD-tail d.Phím ‘S’ được gõ : KBD-head T E S KBD-tail e. Phím ‘T’ được gõ : KBD-head T T E S KBD-tail f. Có yêu cầu đọc bàn phím > Phím ‘T’ được đọc KBD-head T T E S KBD-tail 8.2.4: Thủ tục cập nhật keyboard buffer ( thêm một phím vào hàng đợi ) Insert-key proc near ; Input ; AH : Scan code ; AL : ASCII code ; Output ; AX = 0FF00h > buffer đã đầy push bx push si push ds mov bx,040h mov ds,bx mov bx,word ptr ds:[01Ch] ; BX = điểm cuối hàng đợi mov si,bx add bx,2 ; cmp bx,03Eh ; Cuối buffer hay chưa jb IK-1 mov bx,1Eh ; Nếu rồi thì quay trở về đầu buffer IK-1: cmp bx,word ptr ds:[01Ah] ; Kiểm tra xem đã đầy buffer chưa jne IK-2 mov ax,0FF00h 51
- jmp IK-3 IK-2: mov word ptr ds:[si],ax ; Đặt scan-code và Ascii-code vào ; hàng đợi mov word ptr ds:[01Ch],bx ; Cập nhật điểm cuối IK-3: pop ds pop si pop bx ret Insert-key endp 8.2.5Thủ tục đọc một phím từ keyboard buffer Read-key proc near ; Input ; AH = 0 > nếu chưa có phím trong hàng đợi thì chờ ; AH = 1 > nếu chưa có phím trong hàng đợi thì AX = 0FF00h ; Output ; AH : Scan code ; AL : ASCII code push bx push ds mov bx,040h mov ds,bx RK-1: mov bx,word ptr ds:[01Ah] ; BX = điểm đầu hàng đợi cmp bx,word ptr ds:[01Ch] ; Có bằng điểm cuối không ? jne RK-2 ; Nếu bằng > chưa có phím trong ; hàng đợi . or ah,ah jz RK-1 ; AH = 0 > chờ mov ax,0FF00h ; AH # 0 > cho AX=0FF00h rồi kết thúc jmp RK-4 RK-2: mov ax,word ptr ds:[bx] ; Đặt scan-code và Ascii-code vào AX add bx,2 cmp bx,03Eh jne RK-3 ; Cuối buffer hay chưa ? mov bx,01Eh ; Nếu rồi thì quay trở về đầu buffer RK-3: mov word ptr ds:[01Ah],bx ; Cập nhật điểm đầu . RK-4: pop ds pop bx ret 52
- Read-key endp 8.3INTERRUPT 016H Để giúp các chương trình có thể truy nhập đến bàn phím một cách dễ dàng,BIOS cung cấp một số các chức năng đơn giản và thuận tiện thông qua interrupt 016h. Interrupt 016h cung cấp các chức năng căn bản sau : 8.3.1Đọc keyboard buffer Input : AH = 0 Output : AH = scan code AL = ASCII code (0 nếu là phím đặc biệt) Đọc một phím từ keyboard buffer,nếu chưa có phím trong keyboard buffer sẽ chờ cho đến khi có phím gõ vào ( do interrupt 09h cập nhật ).Khi đọc sẽ “xóa” luôn mã phím trong keyboard buffer ( Chỉ cập nhật KBD-head chứ không thay đổi thông tin trong keyboard buffer). 8.3.2Kiểm tra keyboard buffer Input : AH = 01 Output : ZF = 1 : không có phím nào trong keyboard buffer. ZF = 0 : đã có phím trong keyboard buffer, AH = scan code, AL = ASCII code Kiểm tra keyboard buffer, nếu keyboard buffer không trống thì đọc như chức năng 0 nhưng không cập nhật lại KBD-head. 8.3.3Đọc keyboard status byte Input : AH = 02 Output : AL = status byte ( thông tin như nêu ở phần trên ) Đọc status byte tại địa chỉ 0000:0417h Chú ý rằng chức năng 01 của interrupt 021h đã sử dụng interrupt 016h để đọc bàn phím, và chỉ cho lại byte thấp của phím gõ vào. Vì vậy, sử dụng interrupt 016h để đọc bàn phím là phương pháp tốt nhất cho các chương trình Assembler. 8.3.4Mã của một số các phím đặc biệt : (giá trị AX sau khi đọc bằng chức năng 0 hay 1 của interrupt 016h) Cursor-up equ 04800h F1 equ 03B00h Cursor-down equ 05000h F2 equ 03C00h Cursor-left equ 04B00h F3 equ 03D00h Cursor-right equ 04D00h F4 equ 03E00h ESC-key equ 0011Bh F5 equ 03F00h Enter-key equ 01C0Dh F6 equ 04000h Ins-key equ 05200h F7 equ 04100h Del-key equ 05300h F8 equ 04200h Backspace equ 00E08h F9 equ 04300h Home-key equ 04700h F10 equ 04400h End-key equ 04F00h PgUp-key equ 04900h Ctrl-left equ 07300h PgDn-key equ 05100h Ctrl-right equ 07400h Ctrl-Home equ 07700h Ctrl-PgUp equ 08400h Ctrl-End equ 07500h 53
- Ctrl-PgDn equ 07600h Ctrl-F1 equ 05E00h Alt-F1 equ 06800h Ctrl-F2 equ 05F00h Alt-F2 equ 06900h Ctrl-F3 equ 06000h Alt-F3 equ 06A00h Ctrl-F4 equ 06100h Alt-F4 equ 06B00h Ctrl-F5 equ 06200h Alt-F5 equ 06C00h Ctrl-F6 equ 06300h Alt-F6 equ 06D00h Ctrl-F7 equ 06400h Alt-F7 equ 06E00h Ctrl-F8 equ 06500h Alt-F8 equ 06F00h Ctrl-F9 equ 06600h Alt-F9 equ 07000h Ctrl-F10 equ 06700h Alt-F10 equ 07100h 8.4MỘT CHƯƠNG TRÌNH VỀ BÀN PHÍM Các thủ tục Num-to-dec và Num-to-hex sử dụng trong thí dụ này là các bài tập mẫu của chương 6. main proc near lea dx,Mess1 ; In Mess1 mov ah,09h int 021h M1: lea di,Key-name ; Xóa Key-name của phím trước mov cx,10 ; cld ; mov al,’ ‘ ; rep stosb ; xor ax,ax ; Đọc keyboard int 016h push ax mov key-name,al ; Đặt ASCII code vừa nhận được vào ; Key-name lea si,Function-table mov cx,7 or al,al ; AL = 0 > Function key jz M2 lea si,Control-table ; AL Test Control key xchg ah,al mov cx,10 M2: call Get-name ; Dò tên Function/Control key pop ax push ax xor ah,ah lea si,Ascii-code-hex+1 54
- call Num-to-hex ; Tạo ASCII code (hex) lea si,Ascii-code-dec mov bx,3 call Num-to-dec ; Tạo ASCII code (dec) pop ax push ax mov al,ah xor ah,ah lea si,Scan-code-hex+1 call Num-to-hex ; Tạo SCAN code (hex) lea si,Scan-code-dec call Num-to-dec ; Tạo SCAN code (dec) lea dx,Mess2 mov ah,09h int 021h pop ax ; AX = read key cmp al,27 ; ( 01Bh ) ESC-key > Kết thúc je M3 jmp M1 M3: int 020h main endp Mess1 db ‘ Test-key program ‘,10,10,13 db ‘Press ESC to abort, another key to continue’ db 10,10,13,’$’ Mess2 db ‘ Key name is : ‘ Key-name db 10 dup (‘ ‘) db ‘ ASCII CODE : ‘ ascii-code-hex db ‘000h (‘ ascii-code-dec db ‘000) ‘ db ‘SCAN CODE : ‘ scan-code-hex db ‘000h (‘ scan-code-dec db ‘000) ‘,13,’$’ Get-Name proc near ; DS:SI = Key-table addr ; AL = ID code ; CX = Element size ; Key-table elements structure : ; 1 byte for ID code ( ID-code=0 > end table ) ; cx-6 byte for name GN1: cmp ah,byte ptr [si] 55
- je GN2 cmp byte ptr [si],0 jz GN3 add si,cx jmp GN1 GN2: inc si dec cx lea di,Key-name cld rep movsb GN3: ret Get-Name endp Control-table db 00Dh, ‘CR(Enter)’ db 01Bh, ‘ESC ‘ db 00Ah, ‘LF ‘ db 009h, ‘Tab ‘ db 020h, ‘Space bar’ db 008h, ‘Backspace’ db 000h Function-table db 048h, ‘Up ‘ db 050h, ‘Down ‘ db 04Bh, ‘Left ‘ db 04Dh, ‘Right ‘ db 052h, ‘Insert’ db 053h, ‘Delete’ db 047h, ‘Home ‘ db 04Fh, ‘End ‘ db 049h, ‘PgUp ‘ db 051h, ‘PgDn ‘ db 03Bh, ‘F1 ‘ db 03Ch, ‘F2 ‘ db 03Dh, ‘F3 ‘ db 03Eh, ‘F4 ‘ db 03Fh, ‘F5 ‘ db 040h, ‘F6 ‘ db 041h, ‘F7 ‘ db 042h, ‘F8 ‘ db 043h, ‘F9 ‘ db 044h, ‘F10 ‘ db 000h, ‘ ‘ 56
- CHƯƠNG 9 : CLOCK - TIMER - SOUND 9.1CLOCK Để thực hiện một lệnh, CPU phải trải qua nhiều giai đoạn, mỗi giai đoạn được một nhóm các mạch điện thực hiện, kết quả của giai đoạn trước được chuyển cho giai đoạn sau thực hiện tiếp cho đến khi lệnh được hoàn tất. Trong quá trình thục hiện, để các mạch điện hoạt động nhịp nhàng đồng bộ với nhau, người ta đã dùng các xung điện với tần số xác định để đều khiển các mạch điện ấy.Các mạch điện sẽ chỉ hoạt động khi xuất hiện xung,và sẽ thực hiện một tác vụ nào đó (tương ứng với một giai đoạn) trong quá trình xuất hiện xung.Các xung đóng vai trò của người cầm cờ hiệu ra lệnh cho các mạch điện làm việc một cách nhịp nhàng,đúng lúc.Các xung điện này được gọi là nhịp đồng hồ của máy điện toán (CLOCKS) . Đối với máy IBM PC/XT chuẩn nhịp đồng hồ là 4772727 Hz (4.77 MHz) . Nhịp đồng hồ này được tạo ra từ nguồn dao động gốc ( có tần số 14.31818 MHz ) bằng cách chia tần số gốc cho 3. Các máy IBM AT sử dụng các bộ vi xử lý 80286 và 80386 đã dùng nhịp đồng hồ với tần số cao hơn : 6,8,12,16,25,33 MHz nên đã có tốc độ lớn hơn. Mặt khác số chu kỳ để thực hiện một lệnh của các bộ vi xử lý 80286,80386 thường ít hơn 8088/8086, đây cũng là lý do làm tốc độ của chúng cao hơn. 9.2TIMER Một số tác vụ trong máy tính cần được thực hiện theo một chu kỳ xác định hoặc trong những thời gian xác định . Thí dụ : Đếm giờ Chờ motor của ổ đĩa đạt đến tốc độ xác định . Các yêu cầu này đòi hỏi phải có một hệ thống “đồng hồ báo thức” được gọi là TIMER . Đối với các máy vi tính thuộc họ nhà IBM PC/XT/AT chip 8253-5 hoặc các chip tương đương đảm nhận nhiệm vụ trên . TIMER chip cung cấp nhiều kênh (CHANNEL) tín hiệu đồng hồ, mỗi kênh cho một mục đích riêng . Tần số làm việc của mỗi kênh đều có thể xác định bằng cách cung cấp các tham số thích hợp cho TIMER chip . Những chip có khả năng thay đổi tình trạng làm việc thông qua các mệnh lệnh và các tham số được gọi là các chip có thể thảo chương dược (programmable chips). 8253-5 cung cấp 3 kênh : - Kênh 0 cung cấp tín hiệu đồng hồ hệ thống được xác định tần số chuẩn khoảng 18.206 Hz sau khi bật máy . - Kênh 1 dùng để điều khiển quá trình làm tươi bộ nhớ (RAM refresh) - Kênh 2 dùng để điều khiển loa. 9.3THẢO CHƯƠNG CHO 8253-5 Trên IBM PC/XT/AT 8253-5 được điều khiển thông qua các port : - Port 040h : data port of channel 0 - Port 041h : data port of channel 1 - Port 042h : data port of channel 2 - Port 043h : control port of 8253-5 9.3.1Nguyên lý hoạt động của 8253-5 57
- Chip 8253-5 được cung cấp một dao động tại ngõ vào có tần số 1.19318 MHz. Dao động này được tạo ra bằng cách chia dao động gốc cho một giá trị thích hợp ( 12 trên PC/XT ) . Mỗi kênh của 8253-5 là một bộ đếm (Counter) và một thanh ghi 16 bit lưu giữ số đếm (selected count register) gọi tắt là SCR. Để thiết lập trạng thái làm việc cho mỗi kênh ta cần phải gán giá trị cho SCR của mỗi bộ đếm cùng các tham số bằng cách gởi một byte điều khiển ra control port của 8253-5 để chỉ định kênh và mode làm việc,kế tiếp là các tham số ra data port tương ứng . Byte điều khiển có dạng thức : SC1 SC0 RL1 RL0 M2 M1 M0 BCD Với chi tiết về các thành phần như sau : SC1 SC0 RL1 RL0 0 0 Select Counter 0 0 0 Counter Latching operator 0 1 Select Counter 1 0 1 Read/Load high byte only 1 0 Select Counter 2 1 0 Read/Load low byte only 1 1 Illegal 1 1 Read/Load low byte first and then high byte M2 M1 M0 0 0 0 Mode 0 0 0 1 Mode 1 0 Binary Counter 16-bits X 1 0 Mode 2 1 Binary Code Decimal (BCD) Counter X 1 1 Mode 3 (4 decades) 1 0 0 Mode 4 1 0 1 Mode 5 9.3.2Các mode của 8253-5 Mode 0 : Interrupt on terminal Count Tín hiệu ra ở mức thấp sau khi mode được chỉ định . Sau khi SCR được nạp giá trị, tín hiệu ra tiếp tục ở mức thấp và bộ đếm bắt đầu làm việc . Khởi đầu bộ đếm được nạp giá trị của SCR, sau đó giảm 1 khi có tín hiệu xuất hiện ở ngõ vào . Khi bộ đếm giảm đến 0 (terminal count),tín hiệu ra sẽ chuyển lên mức cao và giữ luôn trạng thái này cho đến khi mode được định lại hoặc SCR được nạp lại . Nạp lại SCR trong khi bộ đếm đang làm việc, sẽ tạo ra kết quả : - Nạp byte thứ nhất : bộ đếm ngừng hoạt động - Nạp byte thứ hai : bộ đếm làm việc trở lại với số đếm mới Mode 1 : Programmable One-Shot Giống mode 0,nhưng việc nạp lại SCR trong khi tín hiệu ngõ ra ở mức thấp sẽ không có tác dụng . Mode 2 : Rate generator Tín hiệu ra sẽ ở mức thấp trong một thời hằng và sẽ ở mức cao trong thời hằng kế tiếp,quá trình này được lặp đi lặp lại cho đến khi định lại mode. Thời hằng có giá trị bằng khoảng thời gian thực hiện N ( N là giá trị trong SCR ) chu kỳ của tín hiệu vào.Như vậy tín hiệu ra sẽ là dạng xung vuông với tần số bằng tần số ngõ vào chia cho 2*N . Mode 3 : Square Wave Rate generator Giống như mode 2, nhưng tín hiệu sẽ ở mức thấp trong ½ thời hằng và ở mức cao trong ½ thời hằng kế tiếp.Do đó tín hiệu ra sẽ là xung vuông với tần số bằng tần số ngõ vào chia cho N . Như vậy tần số của tín hiệu của ngõ ra trong mode 3 sẽ bằng 1.19318 MHz chia cho giá trị của SCR. Để tạo ra một dao động có tần số f ta cần cho số đếm của kênh tương ứng một giá trị xác định bởi : 1193180 58
- n = f Mode 4 : Software Triggered Strobe Sau khi mode 4 được thiết lập, tín hiệu ngõ ra sẽ ở mức cao . Khi SCR được nạp, bộ đếm bắt đầu làm việc . Khi bộ đếm giảm đến 0, tín hiệu ngõ ra sẽ chuyển sang mức thấp trong khoảng thời gian bằng 1 chu kỳ của tín hiệu xung vào,sau đó trở lại mức cao . Nếu SCR được nạp lại trong lúc bộ đếm đang hoạt động, giá trị này sẽ được nạp vào bộ đếm cho chu kỳ kế tiếp. Mode 5 : Hardware Triggered Strobe Sau khi mode 5 được thiết lập, tín hiệu ngõ ra sẽ ở mức cao . Khi SCR được nạp, bộ đếm bắt đầu làm việc . Khi bộ đếm giảm đến 0, tín hiệu ngõ ra sẽ chuyển sang mức thấp trong khoảng thời gian bằng 1 chu kỳ của tín hiệu xung vào,sau đó trở lại mức cao . 9.3.3Thảo chương cho Chanel-2 Đọc SCR của Channel-2 vào CX mov al,10110110b ; Yêu cầu đọc 2 byte của SCR từ Channel-2 out 043h,al ; Đưa ra control port in al,042h ; Lấy byte thứ nhất (Low byte) mov cl,al ; Cất vào CL in al,042h ; Lấy byte thứ hai (High byte) mov ch,al ; Cất vào CH > CX = SCR value Chỉ định tần số của Channel-2 là 512 Hz dạng xung vuông mode 3 mov dx,012h ; Double word trong DX,AX có giá trị 1193180 mov ax,034DCh mov cx,512 div cx ; mov bx,ax ; BX = 1193180/512 mov al,10110110b ; Yêu cầu định mode 3 cho channel-2 out 043h,al ; đồng thời nạp giá trị mới cho SCR mov al,bl out 042h,al ; Load low byte mov al,bh out 042h,al ; Load high byte 9.4NGUYÊN LÝ HOẠT ĐỘNG CỦA ĐỒNG HỒ HỆ THỐNG (SYSTEM CLOCK) Ngõ ra của channel-0 của 8253-5 được nối liền với chip điều khiển interrupt 8259 . Mỗi xung nhịp của channel-0 sẽ làm phát động interrupt cứng 08h . Mỗi lần được gọi đến, interrupt 08h sẽ thực hiện các công việc chính sau : - Tăng giá trị tại double word 0000:046C (Count tick) lên 1 ( giá trị này được cho bằng 0 lúc 0 giờ ) - Gọi interrupt 01Ch - Cho phép phát động interrupt cứng (Enable hard interrupt) bằng cách gởi ra cổng 020h giá trị 020h . SCR của channel-0 được nạp giá trị 0FFFFh (65535) trong quá trình khởi động máy, do đó tần số của ngõ ra sẽ là 1193180/65536 = 18.206 Hz . Hệ điều hành sẽ căn cứ theo giá trị của Count tick để tính ra thời gian cho hệ thống . Make-time proc near ; Make-time sẽ lấy giá trị 59
- ; hiện thời của Count tick xor ax,ax ; nhân với 65535 rồi chia mov es,ax ; cho 1193180 . Giá trị mov ax,word ptr es:[046Ch] ; thu được chính là tổng mov dx,word ptr es:[046Ch+2] ; số giây tính từ 0 giờ ; DX:AX = Time-count ; Từ đó tính ra giờ,phút mov bx,ax ; giây và phần trăm giây . mov cx,dx ; Time-count = tổng số giây tính từ 0 giờ *(1193180/65535) ; Nhân Time-count với 5 rồi đặt kết quả vào DX:AX shl ax,1 rcl dx,1 ; Nhân 2 shl ax,1 rcl dx,1 ; Nhân 2 add ax,bx adc dx,cx ; Cộng dồn 1+2*2=5 ; Gọi giá trị thu được tại đây là (1) ; Chia kết quả cho 59659 ;. Chú ý : 59659=1193180/20 mov cx,59659 div cx mov bx,ax ; Cất kết quả vào BX xor ax,ax ; DX:AX = số dư * 65535 div cx ; mov dx,bx ; ; DX:AX = (1)*65535/59659 mov cx,200 div cx ; AX = (Time-count*65535/1193180)/2 = (số giây từ 0 giờ)/2 mov bl,100 sub bl,dl ; Kiểm tra số dư ; Nếu DL > 100 > BL CF=1 ; Nhân kết quả với 2 > số giây tính từ 0 giờ rcl ax,1 ; Nếu số dư của phép chia cho 200 mov dx,0 ; lớn hơn 100 thì cộng thêm 1 vào rcl dx,1 ; kết quả mov cx,60 ; giây = (tổng số giây) mod 60 div cx ; Kết quả là tổng số phút mov s-var,dl ; Số dư là giây ; phút = (tổng số phút) mod 60 div cl mov m-var,ah ; Số dư là phút mov h-var,al ; Kết quả là giờ ret 60
- Make-time endp s-var db 0 m-var db 0 h-var db 0 9.5NGUYÊN LÝ ĐIỀU KHIỂN LOA TRONG IBM PC/XT/AT Mỗi máy IBM PC/XT/AT được trang bị một cái loa nhỏ công suất khoảng 0,2W . Loa được gắn vào ngõ ra của một mạch khuếch đại có đầu vào là ngõ ra của Channel-2 của 8253-5 . Mạch khuếch đại của loa được điều khiển bởi hai bit thấp của port 061h : Nếu cả hai bit thấp của port 061h đều bị xóa thành 0 thì mạch khuếch đại không làm việc : loa sẽ im . Nếu cả hai bit thấp của port 061h đều được dựng lên thì mạch khuếch đại sẽ làm việc : loa sẽ dao động với tín hiệu của ngõ ra Channel-2. Thí dụ : Bật loa: in al,061h or al,00000011b out 061h,al Tắt loa: in al,061h and al,11111100b out 061h,al 9.6NGUYÊN LÝ TẠO ÂM NHẠC TRÊN IBM PC/XT/AT Để máy tính chơi được một đoạn nhạc, chúng ta cần phải tạo ra các note nhạc và duy trì chúng theo từng trường độ riêng . Để tạo ra một note nhạc chúng ta chỉ cần đưa tần số tương ứng của note ấy ra loa . Như vậy, điều cần thiết là chúng ta xác định được tần số của từng note nhạc trong các cung bậc (octave) quen thuộc. Tần số của các note thuộc octave 1 và octave 7 Octave 1 Octave 7 C = 32.70 C = 2093.00 C# = 34.65 C# = 2217.46 D = 36.71 D = 2349.32 D# = 38.89 D# = 2489.02 E = 41.20 E = 2637.02 F = 43.65 F = 2793.83 F# = 46.25 F# = 2959.96 G = 49 . G = 3135.96 G# = 51.91 G# = 3322.44 A = 55.00 A = 3520.00 A# = 58.27 A# = 3729.31 B = 61.74 B = 3951.07 Tần số của một note thuộc một octave gấp đôi tần số của note tương ứng thuộc octave ngay trước nó. Thí dụ : Tần số của note DO ở octave 5 sẽ bằng tần số của note DO ở octave 6 chia 2 và bằng tần số của note DO ở octave 4 nhân 2 . 61