CAU TRUC AVR

CAU TRUC AVR - AUTO.NLU CÙNG HỌC AVR AVR2 – CẤU...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: AUTO.NLU CÙNG HỌC AVR AVR2 – CẤU TRÚC AVR Thoả thuận: tài liệu này thuộc quyền sở hữu của tác giả, bạn có thể tự do tham khảo tài liệu nhưng không được phép sử dụng để in thành sách báo, đăng lên các diễn đàn hay website, nhưng bạn có thể dùng đường link http://www.dieukhientudong.net để hướng tới tài liệu. Liên hệ tác giả qua email: thanhtam.h@gmail.com. I. Bạn sẽ đi đến đâu. Bài này tiếp tục bài đầu tiên trong loạt bài giới thiệu về AVR, nếu sau bài 1 bạn đã phần nào biết cách lập trình cho AVR bằng AVRStudio thì trong bài này, chúng ta sẽ tìm hiểu kỹ hơn về cấu trúc của AVR. Sau bài này, bạn sẽ: Hiểu được cấu trúc AVR, cấu trúc bộ nhớ và cách thức hoạt động của chip. Hiểu về Stack và cách hoạt động. Biết được một số instruction cơ bản truy xuất bộ nhớ. Học các instruction rẽ nhánh và vòng lặp. Chương trình con (Subroutine) và Macro. Cải tiến ví dụ trong bài 1. Viết 1 ví dụ minh họa cách sử dụng bộ nhớ và vòng lặp. II. Tồ chức của AVR. AVR có cấu trúc Harvard, trong đó đường truyền cho bộ nhớ dữ liệu (data memory bus) và đường truyền cho bộ nhớ chương trình (program memory bus) được tách riêng. Data memory bus chỉ có 8 bit và được kết nối với hầu hết các thiết bị ngoại vi, với register file. Trong khi đó program memory bus có độ rộng 16 bits và chỉ phục vụ cho instruction registers. Hình 1 mô tả cấu trúc bộ nhớ của AVR. Bộ nhớ chương trình (Program memory): Là bộ nhớ Flash lập trình được, trong các chip AVR cũ (như AT90S1200 hay AT()2313…) bộ nhớ chương trình chỉ gồm 1 phần là Application Flash Section nhưng trong các chip AVR mới chúng ta có thêm phần Boot Flash setion. Boot section sẽ được khảo sát trong các phần sau, trong bài này khi nói về bộ nhớ chương trình, chúng ta tự hiểu là Application section. Thực chất, application section bao gồm 2 phần: phần chứa các instruction (mã lệnh cho hoạt động của chip) và phần chứa các vector ngắt (interrupt vectors). Các vector ngắt nằm ở phần đầu của application section (từ địa chỉ 0x0000) và dài đến bao nhiêu tùy thuộc vào loại chip. Phần chứa instruction nằm liền sau đó, chương trình viết cho chip phải được load vào phần này. Xem lại phần đầu của ví dụ trong bài 1: .ORG 0x000 RJMP BATDAU .ORG 0x020 BATDAU: Trong ví dụ này, ngay sau khi set vị trí 0x000 bằng chỉ thị (DIRECTIVE) .ORG 0x000 chúng ta dùng instruction RJMP để nhảy đến vị trí 0x020, như thế phần bộ nhớ chương trình từ 0x00 đến 0x01F không được sử dụng (vì trong ví dụ này chúng ta không sử dụng các vector For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU ngắt). Chương trình chính được bắt đầu từ địa chỉ 0x020, con số 0x020 là do người lập trình chọn, thật ra các vector ngắt của chip ATMEGA8 chỉ kéo dài đến địa chỉ 0x012, vì vậy chương trình chính có thể được bắt đầu từ bất cứ vị trí nào sau đó. Để biết độ dài các vector ngắt của từng chip bạn hãy tham khảo datasheet của chip đó. Vì chức năng chính của bộ nhớ chương trình là chứa instruction, chúng ta không có nhiều cơ hội tác động lên bộ nhớ này khi lập trình cho chip, vì thế đối với người lập trình AVR, bộ nhớ này “không quá quan trọng”. Tất cả các thanh ghi quan trọng cần khảo sát nằm trong bộ nhớ dữ liệu của chip. Hình 1. Cấu trúc bộ nhớ của AVR. Bộ nhớ dữ liệu (data memory): Đây là phần chứa các thanh ghi quan trọng nhất của chip, việc lập trình cho chip phần lớn là truy cập bộ nhớ này. Bộ nhớ dữ liệu trên các chip AVR có độ lớn khác nhau tùy theo mỗi chip, tuy nhiên về cơ bản phần bộ nhớ này được chia thành 5 phần: Phần 1: là phần đầu tiên trong bộ nhớ dữ liệu, như mô tả tronh hình 2, phần này bao gồm 32 thanh ghi có tên gọi là register file (RF), hay General Purpose Rgegister – GPR, hoặc đơn giản là các Thanh ghi. Tất cả các thanh ghi này đều là các thanh ghi 8 bits như trong hình 2. Hình 2. Thanh ghi 8 bits. Tất cả các chip trong họ AVR đều bao gồm 32 thanh ghi Register File có địa chỉ tuyệt đối từ 0x0000 đến 0x001F. Mỗi thanh ghi có thể chứa giá trị dương từ 0 đến 255 hoặc các giá trị có dấu từ -128 đến 127 hoặc mã ASCII của một ký tự nào đó…Các thanh ghi này được đặt tên theo thứ For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU tự là R0 đến R31. Chúng được chia thành 2 phần, phần 1 bao gồm các thanh ghi từ R0 đến R15 và phần 2 là các thanh ghi R16 đến R31. Các thanh ghi này có các đặc điểm sau: - Được truy cập trực tiếp trong các instruction . - Các toán tử, phép toán thực hiện trên các thanh ghi này chỉ cần 1 chu kỳ xung clock. - Register File được kết nối trực tiếp với bộ xử lí trung tâm – CPU của chip. - Chúng là nguồn chứa các số hạng trong các phép toán và cũng là đích chứa kết quả trả lại của phép toán. Để minh họa, hãy xét ví dụ thực hiện phép cộng 2 thanh ghi bằng instruction ADD như sau: ADD R1, R2 Bạn thấy trong dòng lệnh trên, 2 thanh ghi R1 và R2 được sử dụng trực tiếp với tên của chúng, dòng lệnh trên khi được dịch sang opcode để download vào chip sẽ có dạng: 0000110000010010 trong đó 00001=1 tức thanh ghi R1 và 00010 = 2 chỉ thanh ghi R2. Sau phép cộng, kết quả sẽ được lưu vào thanh ghi R1. Tất cả các instruction sử dụng RF làm toán hạng đều có thể truy nhập tất cả các RF một cách trực tiếp trong 1 chu kỳ xung clock, ngoại trừ SBCI, SUBI, CPI, ANDI và LDI, các instruction này chỉ có thể truy nhập các thanh ghi từ R16 đến R31. Thanh ghi R0 là thanh ghi duy nhất được sử dụng trong instruction LPM (Load Program Memory). Các thanh ghi R26, R27, R28, R29, R30 và R31 ngoài chức năng thông thường còn được sử dụng như các con trỏ (Pointer register) trong một số instruction truy xuất gián tiếp. Chúng ta sẽ khảo sát vấn đề con trỏ sau này. Hình 3 mô tả các chức năng phụ của các thanh ghi. Hình 3. Register file. Tóm lại 32 RF của AVR được xem là 1 phần của CPU, vì thế chúng được CPU sử dụng trực tiếp và nhanh chóng, để gọi các thanh ghi này, chúng ta không cần gọi địa chỉ mà chỉ cần gọi trực tiếp tên của chúng. RF thường được sử dụng như các toán hạng (operand) của các phép toán trong lúc lập trình. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Phần 2: là phần nằm ngay sau register file, phần này bao gồm 64 thanh ghi được gọi là 64 thanh ghi nhập/xuất (64 I/O register) hay còn gọi là vùng nhớ I/O (I/O Memory). Vùng nhớ I/O là cửa ngõ giao tiếp giữa CPU và thiết bị ngoại vi. Tất cả các thanh ghi điều khiển, trạng thái…của thiết bị ngoại vi đều nằm ở đây. Xem lại ví dụ trong bài 1, trong đó tôi có đề cập về việc điều khiển các PORT của AVR, mỗi PORT liên quan đến 3 thanh ghi DDRx, PORTx và PINx, tất cả 3 thanh ghi này đều nằm trong vùng nhớ I/O. Xa hơn, nếu muốn truy xuất các thiết bị ngoại vi khác như Timer, chuyển đổi Analog/Digital, giao tiếp USART…đều thực hiện thông qua việc điều khiển các thanh ghi trong vùng nhớ này. Vùng nhớ I/O có thể được truy cập như SRAM hay như các thanh ghi I/O. Nếu sử dụng instruction truy xuất SRAM để truy xuất vùng nhớ này thì địa chỉ của chúng được tính từ 0x0020 đến 0x005F. Nhưng nếu truy xuất như các thanh ghi I/O thì địa chỉ của chúng đựơc tính từ 0x0000 đến 0x003F. Xét ví dụ instruction OUT dùng xuất giá trị ra các thanh ghi I/O, lệnh này sử dụng địa chỉ kiểu thanh ghi, cấu trúc của lệnh như sau: OUT A, Rr, trong đó A là địa chỉ của thanh ghi trong vùng nhớ I/O, Rr là thanh ghi RF, lệnh OUT xuất giá trị từ thanh ghi Rr ra thanh ghi I/O có địa chỉ là A. Giả sử chúng ta muốn xuất giá trị chứa trong R6 ra thanh ghi điều khiển hướng của PORTD, tức thanh ghi DDRD, địa chỉ tính theo vùng I/O của thanh ghi DDRD là 0x0011, như thế câu lệnh của chúng ta sẽ có dạng: OUT 0x0011, R6. Tuy nhiên trong 1 trường hợp khác, nếu muốn truy xuất DDRD theo dạng SRAM, ví dụ lệnh STS hay LDS, thì phải dùng địa chỉ tuyệt đối của thanh ghi này, tức giá trị 0x0031, khi đó lệnh OUT ở trên được viết lại là STS 0x0031, R6. Để thống nhất cách sử dụng từ ngữ, từ bây giờ chúng ta dùng khái niệm “địa chỉ I/O” cho các thanh ghi trong vùng nhớ I/O để nói đến địa chỉ không tính phần Register File, khái niệm “địa chỉ bộ nhớ” của thanh ghi là chỉ địa chỉ tuyệt đối của chúng trong SRAM. Ví dụ thanh ghi DDRD có “địa chỉ I/O” là 0x0011 và “địa chỉ bộ nhớ” của nó là 0x0031, “địa chỉ bộ nhớ” = “địa chỉ thanh ghi” + 0x0020. Vì các thanh ghi trong vùng I/O không được hiểu theo tên gọi như các Register file, khi lập trình cho các thanh ghi này, người lập trình cần nhớ địa chỉ của từng thanh ghi, đây là việc tương đối khó khăn. Tuy nhiên, trong hầu hết các phần mềm lập trình cho AVR, địa chỉ của tất cả các thanh ghi trong vùng I/O đều được định nghĩa trước trong 1 file Definition, bạn chỉ cần đính kèm file này vào chương trình của bạn là có thể truy xuất các thanh ghi với tên gọi của chúng. Giả sử trong ví dụ ở bài 1, để lập trình cho chip Atmega8 bằng AVRStudio, dòng thứ 2 chúng ta sử dụng INCLUDE "M8DEF.INC" để load file định nghĩa cho chip ATMega8, file M8DEF.INC. Vì vậy, trong sau này khi muốn sử dụng thanh ghi DDRD bạn chỉ cần gọi tên của chúng, như: OUT DDRD,R6. Phần 4: RAM ngoại (external SRAM), các chip AVR cho phép người sử dụng gắn thêm các bộ nhớ ngoài để chứa biến, vùng này thực chất chỉ tồn tại khi nào người sử dụng gắn thêm bộ nhớ ngoài vào chip. Phần 5: EEPROM (Electrically Ereasable Programmable ROM) là một phần quan trọng của các chip AVR mới, vì là ROM nên bộ nhớ này không bị xóa ngay cả khi không cung cấp nguồn nuôi cho chip, rất thích hợp cho các ứng dụng lưu trữ dữ liệu. Như trong hình 1, phần bộ nhớ EEPROM được tách riêng và có địa chỉ tính từ 0x0000. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU AVR hoạt động như thế nào? Hình 4 biểu diễn cấu trong bên trong của 1 AVR. Bạn thấy rằng 32 thanh ghi trong Register File được kết nối trực tiếp với Arithmetic Logic Unit -ALU (ALU cũng được xem là CPU của AVR) bằng 2 line, vì thế ALU có thể truy xuất trực tiếp cùng lúc 2 thanh ghi RF chỉ trong 1 chu kỳ xung clock (vùng được khoanh tròn màu đỏ trong hình 4). Hình 4. Cấu trúc bên trong AVR. Các instruction được chứa trong bộ nhớ chương trình Flash memory dưới dạng các thanh ghi 16 bit. Bộ nhớ chương trình được truy cập trong mỗi chu kỳ xung clock và 1 instruction chứa trong program memory sẽ được load vào trong instruction register, instruction register tác động và lựa chọn register file cũng như RAM cho ALU thực thi. Trong lúc thực thi chương trình, địa chỉ của dòng lệnh đang thực thi được quyết định bởi một bộ đếm chương trình – PC (Program counter). Đó chính là cách thức hoạt động của AVR. AVR có ưu điểm là hầu hết các instruction đều được thực thi trong 1 chu kỳ xung clock, vì vậy có thể nguồn clock lớn nhất cho AVR có thể nhỏ hơn 1 số vi điều khiển khác như PIC nhưng thời gian thực thi vẫn nhanh hơn. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU III. Stack. Stack được hiểu như là 1 “tháp” dữ liệu, dữ liệu được chứa vào stack ở đỉnh “tháp” và dữ liệu cũng được lấy ra từ đỉnh. Kiểu truy cập dữ liệu của stack gọi là LIFO (Last In First Out – vào sau ra trước). Hình 5 thể hiện cách truy cập dữ liệu của stack. Hình 5. Stack Khái niệm và cách thức hoạt động của stack có thể được áp dụng cho AVR, bằng cách khai báo một vùng nhớ trong SRAM là stack ta có thể sử dụng vùng nhớ này như một stack thực thụ. Để khai báo một vùng SRAM làm stack chúng ta cần xác lập địa chỉ đầu của stack bằng cách xác lập con trỏ stack-SP (Stack Pointer). SP là 1 con trỏ 16 bit bao gồm 2 thanh ghi 8 bit SPL và SPH (chữ L là LOW chỉ thanh ghi mang giá trị byte thấp của SP, và H = HIGH), SPL và SPH nằm trong vùng nhớ I/O. Giá trị gán cho thanh ghi SP sẽ là địa chỉ khởi động của stack. Quay lại ví dụ ở bài 1, phần khởi tạo các điều kiện đầu. ; KHOI TAO CÁC DIEU KIEN DAU LDI R16, HIGH(RAMEND) LDI R17, LOW(RAMEND) OUT SPH, R16 OUT SPL, R17 Bốn dòng khai báo trên mục đích là gán giá trị của RAMEND cho con trỏ SP, RAMEND (tức End of Ram) là biến chứa địa chỉ lớn nhất của RAM nội trong AVR, biến này được định nghĩa trong file M8DEF.INC. Như thế sau 4 dòng trên, con trỏ SP chứa giá trị cuối cùng của SRAM hay nói cách khác vùng stack bắt đầu từ vị trí cuối cùng của bộ nhớ SRAM. Nhưng tại sao là vị trí cuối cùng mà không là 1 giá trị khác. Có thể giải thích như sau: stack trong AVR hoạt động từ trên xuống, sau khi dữ liệu được đẩy vào stack, SP sẽ giảm giá trị vì thế khởi động SP ở vị trí cuối cùng của SRAM sẽ tránh được việc mất dữ liệu do ghi đè. Bạn có thể khởi động stack với 1 địa chỉ khác, tuy nhiên vì lý do an toàn, nên khởi động stack ở RAMEND. Hai instruction dùng cho truy cập stack là PUSH và POP, trong đó PUSH dùng đẩy dữ liệu vào stack và POP dùng lấy dữ liệu ra khỏi stack. Dữ liệu được đẩy vào và lấy ra khỏi stack tại vị trí mà con trỏ SP trỏ đến. Ví dụ cho chip ATMega8, RAMEND=0x045F, sau khi khởi động, con trỏ SP trỏ đến vị trí 0x045F trong SRAM, nếu ta viết các câu lệnh sau: LDI R1, 1 PUSH R1 LDI R1, 5 PUSH R1 LDI R1, 8 For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU PUSH R1 Khi đó nội dung của stack sẽ như trong hình 6. Hình 6. Nội dung stack trong ví dụ. Sau mỗi lần PUSH dữ liệu, SP sẽ giảm 1 đơn vị và trỏ vào vị trí tiếp theo. Bây giờ nếu ta dùng POP để lấy dữ liệu từ stack, POP R2, thì R2 sẽ mang giá trị của ngăn nhớ 0x045D, tức R2=8. Trước khi instruction POP được thực hiện, con tr SP được tăng lên 1 đơn vị, sau đó dữ liệu sẽ được lấy ra từ vị trí mà SP trỏ đến trong stack. Stack trong AVR không phải là “vô đáy”, nghĩa là chúng ta chỉ có thể PUSH dữ liệu vào stack ở 1 độ sâu nhất định nào đấy (phụ thuộc vào chip). Sử dụng stack không đúng cách đôi khi sẽ làm chương trình thực thi sai hoặc tốn thời gian thực thi vô ích. Vì thế không nên sử dụng stack chỉ để lưu các biến thông thường. Ứng dụng phổ biến nhất của stack là sử dụng trong các chương trình con (Subroutine), khi chúng ta cần “nhảy” từ một vị trí trong chương trình chính đến 1 chương trình con, sau khi thực hiện chương trình con lại muốn quay về vị trí ban đầu trong chương trình chính thì Stack là phương cách tối ưu dùng để chứa bộ đếm chương trình trong trường hợp này. Xem lại ví dụ trong bài 1, trong chương trình chính chúng ta dùng lệnh RCALL DELAY để nhảy đến đoạn chương trình con DELAY, RCALL là lệnh nhảy đến 1 vị trí trong bộ nhớ chương trình, trước khi nhảy, PC được cộng thêm 1 và PUSH một cách tự động vào stack. Cuối chương trình con DELAY, chúng ta dùng instruction RET, instruction này POP dữ liệu từ stack ra PC một cách tự động, bằng cách này chúng ta có thể quay lại vị trí trước đó. Chính vì các lệnh RCALL và RET sử dụng stack một cách tự động nên ta phải khởi động stack ngay từ đầu, nếu không chương trình sẽ thực thi sai chức năng. Tóm lại cần khởi động stack ở đầu chương trình và không nên sử dụng stack một cách tùy thích nếu chưa thật cần thiết. IV. Thanh ghi trạng thái – SREG (STATUS REGISTER). Nằm trong vùng nhớ I/O, thanh ghi SREG có địa chỉ I/O là 0x003F và địa chỉ bộ nhớ là 0x005F (thường đây là vị trí cuối cùng của vùng nhớ I/O) là một trong số các thanh ghi quan trọng nhất của AVR, vì thế mà tôi dành phần này để giới thiệu về thanh ghi này. Thanh ghi SREG chứa 8 bit cờ (flag) chỉ trạng thái của bộ xử lí, tất cả các bit này đều bị xóa sau khi reset, các bit này cũng có thể được đọc và ghi bởi chương trình. Chức năng của từng bit được mô tả như sau: For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Hình 7. Thanh ghi trạng thái. • Bit 0 – C (Carry Flag: Cờ nhớ): là bit nhớ trong các phép đại số hoặc logic, ví dụ thanh ghi R1 chứa giá trị 200, R2 chứa 70, chúng ta thực hiện phép cộng có nhớ: ADC R1, R2, sau phép cộng, kết quả sẽ được lưu lại trong thanh ghi R1, trong khi kết quả thực là 270 mà thanh ghi R1 lại chỉ có khả năng chứa tối đa giá trị 255 (vì có 8 bit) nên trong trường hợp này, giá trị lưu lại trong R1 thực chất chỉ là 14, đồng thời cờ C được set lên 1 (vì 270=100001110, trong đó 8 bit sau 00001110 =14 sẽ được lưu lại trong R1). • Bit 1 – Z (Zero Flag: Cờ 0): cờ này được set nếu kết quả phép toán đại số hay phép Logic bằng 0. • Bit 2 – N (Negative Flag: Cờ âm): cờ này được set nếu kết quả phép toán đại số hay phép Logic là số âm. • Bit 3 – V (Two’s complement Overflow Flag: Cờ tràn của bù 2): hoạt động của cờ này có vẻ sẽ khó hiểu cho bạn vì nó liên quan đến kiến thức số nhị phân (phần bù), chúng ta sẽ đề cập đến khi nào thấy cần thiết. • Bit 4 – S (Sign Bit: Bit dấu): Bit S là kết quả phép XOR giữa 1 cờ N và V, S=N⊕V. • Bit 5 – H (Half Carry Flag: Cờ nhờ nữa): cờ H là cờ nhớ trong 1 vài phép toán đại số và phép Logic, cờ này hiệu qu đối với các phép toán với số BCD. • Bit 6 – T (Bit Copy Storage): được sử dụng trong 2 Instruction BLD (Bit LoaD) và BST (Bit STorage). Tôi sẽ giải thích chức năng Bit T trong phần giới thiệu về BLD và BST. • Bit 7 – I (Global Interrupt Enable : Cho phép ngắt toàn bộ): Bit này phải được set lên 1 nếu trong chương trình có sử dụng ngắt. Sau khi set bit này, bạn muốn kích hoạt loại ngắt nào cần set các bit ngắt riêng của ngắt đó. Hai instruction dùng riêng để Set và Clear bit I là SEI và CLI. Chú ý: tất cả các bit trong thanh ghi SREG đều có thể được xóa thông qua các instruction không toán hạng CLx và set bởi SEx, trong đó x là tên của Bit.Ví dụ CLT là xóa Bit T và SEI là set bit I. Tôi chỉ giải thích ngắn gọn chức năng của các bit trong thanh ghi SREG, cụ thể chức năng và cách sử dụng của từng bit chúng ta sẽ tìm hiểu trong các trường hợp cụ thể sau này, người đọc có thể tự tìm hiểu thêm trong các tài liệu về INSTRUCTION cho AVR. Tôi cung cấp thêm 1 bảng tóm tắt sự ảnh hưởng của các phép toán đại số, logic lên các Bit trong thanh ghi SREG. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Hình 8. Ảnh hưởng của các phép toán lên SREG. V. Giới thiệu một số Imstruction cơ bản. Sau khi tìm hiểu cấu trúc bộ nhớ và phương thức hoạt động của chip, phần này tôi giới thiệu một số instruction mà chúng ta rất hay sử dụng khi lập trình cho AVR. Tôi sẽ chia các instruction này ra thành nhiều nhóm dựa theo phạm vi tác động và chức năng của chúng. Trước hết chúng ta thống nhất một số cách sử dụng ký hiệu trong cách viết cú pháp của các instruction như sau: • Rd: thanh ghi nguồn và cũng là đích thuộc Register File. • Rr: thanh ghi nguồn thuộc Register File. Khái niệm nguồn (Source), đích (Destination) là chỉ các toán hạng và kết quả trong các phép toán đại số và Logic, ví dụ ADD R1, R2 là lệnh cộng 2 giá trị chứa trong 2 thanh ghi R1, R2, trong trường hợp này cả R1 và R2 đều được gọi là nguồn vì chứa giá trị trước khi thực hiện phép cộng. Sau khi phép cộng được thực hiện, kết quả được chứa lại trong R1 và vì thế R1 được gọi là đích trong trường hợp này. R1 vừa là nguồn, vừa là đích trong khi R2 chỉ là nguồn, nếu viết ví dụ này dưới dạng tổng quát sẽ là : ADD Rd, Rr. • R: kết quả sau khi lệnh được thực thi. • K: hằng số. • k: hằng số chỉ địa chỉ tuyệt đối của thanh ghi. • b: (0 đến 7) số thứ tự bit trong các thanh ghi của Register File và vùng nhớ I/O. • s: (0 đến 7) số thứ tự bit trong thanh ghi trạng thái SREG . • X,Y,X: các thanh ghi địa chỉ tương đối (X=R27:R26, X=R29:R28, X=R31:R30). For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU • A: địa chỉ I/O. • q: độ dịch chuyển của địa chỉ tuyệt đối. 1. Instruction chỉ dùng cho Register Files: - LDI (LoaD Immediate). • Cú pháp: LDI Rd, K • Chức năng: Load hằng số K vào thanh ghi Rd. • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: LDI R16, 99 kết quả là thanh ghi R1 mang giá trị 99. - MOV (MOVE). • Cú pháp: MOV Rd, Rr • Chức năng: Copy giá trị trong thanh ghi Rr vào thanh ghi Rd. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: MOV R15, R16 kết quả là R15 có cùng giá trị với R16 (R15=R16=99). - CLR (CLEAR Register). • Cú pháp: CLR Rd • Chức năng: xóa thanh ghi Rd, sau lệnh này thanh ghi Rd=0x00. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: CLR R16 kết quả là R16 = 0x00. - SER (SET Register). • Cú pháp: SER Rd • Chức năng: set tất cả các bit tronh thanh ghi Rd lên 1, sau lệnh này thanh ghi Rd=0xFF • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: SER R16 kết quả là R16 = 0xFF. - CBR (CLEAR Bit in Register). • Cú pháp: CBR Rd, K • Chức năng: xóa các bit trong thanh ghi Rd với “mặt nạ” K, nếu Bit nào trong K là 1 thì Bit tương ứng trong Rd sẽ bị xóa. • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: CBR R16, 0xF0 kết quả là 4 bit cao nhất của R16 bị xóa vì K=11110000 (B). - SBR (SET Bit in Register). • Cú pháp: SBR Rd, K • Chức năng: set các bit trong thanh ghi Rd với “mặt nạ” K, nếu Bit nào trong K là 1 thì Bit tương ứng trong Rd sẽ được set lên 1. • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: SBR R16, 0xF0 kết quả là 4 bit cao nhất của R16 được set lên 1 vì K=11110000 (B). - BLD (Bit LoaD from T Flag). • Cú pháp: BLD Rd, b • Chức năng: Load giá trị trong cờ T của thanh ghi SREG vào bit thứ b trong thanh ghi Rd. Đây cũng chính là chức năng chính của cờ T. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU - - - - - • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: SET ; set bit T lên 1 BLD R16, 4 Kết quả là bit 4 của thanh ghi R16 được set lên 1 vì giá trị của bit T là 1. BST (Bit Storage from T Flag). • Cú pháp: BST Rd, b • Chức năng: Copy bit thứ b trong thanh ghi Rd vào trong cờ T của thanh ghi SREG. Đây cũng chính là chức năng chính của cờ T. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: BST R16, 4 kết quả là cờ T chứa giá trị của bit 4 của thanh ghi R16. CPI (COMPARE with Immediate). • Cú pháp: CPI Rd, K • Chức năng: so sánh thanh ghi Rd với hằng số K, lệnh này làm thay đổi nhiều bit trong thanh ghi SREG trong đó sự thay đổi của cờ Zero là quan trọng nhất, nếu Rd = K cờ Z=1, ngược lại Z=0, sử dụng đặc điểm thay đổi của cờ Z kết hợp với lệnh BRNE hoặc BREQ chúng ta có thể tạo thành một lệnh rẽ nhánh. • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: LDI R16, 10 CPI R16, 10 Kết quả là cờ Z được set thành 1 vì lúc này R16 =10. ANDI (AND with Immediate) • Cú pháp: ANDI Rd, K • Chức năng: thực hiện phép Logic AND giữa thanh ghi Rd với hằng số K và kết quả đặt lại trong Rd. • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: ANDI R17, 0x00 kết quả là R17 có 0x00. AND (Logical AND) • Cú pháp: AND Rd, Rr • Chức năng: thực hiện phép Logic AND giữa 2 thanh ghi Rd và Rr , kết quả đặt lại trong Rd. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R1, 0xFF ;(11111111) LDI R17, 0xAA; (10101010) AND R1, R17 Kết quả là R1=0xAA vì 11111111 & 10101010 =10101010. ORI (Logical OR with Immediate) • Cú pháp: ORI Rd, K • Chức năng: thực hiện phép Logic OR giữa thanh ghi Rd với hằng số K và kết quả đặt lại trong Rd. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: ORI R17, 0xFF kết quả là R17 có 0xFF. - OR (Logical OR) • Cú pháp: OR Rd, Rr • Chức năng: thực hiện phép Logic OR giữa 2 thanh ghi Rd và Rr , kết quả đặt lại trong Rd. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R1, 0xFF ;(11111111) LDI R17, 0xAA; (10101010) AND R1, R17 Kết quả là R1=0xFF vì 11111111 & 10101010 =11111111. - LSL(Logical Shift Left) Cú pháp: LSL Rd • Chức năng: dịch tất thanh ghi Rd sang trái 1 vị trí, Bit 7 (bit lớn nhất) của Rd sẽ được chứa trong cờ nhớ C, bit 0 của Rd bị xóa thành 0. Thực chất LSL tương đương với phép nhân thanh ghi Rd với 2. Bạn xem hình minh họa bên dưới. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R1, 0B11000011 ; (dạng nhị phân của 195) LSL R1 Kết quả là R1=10000110 và cờ C =1 vì thanh ghi R1 đã được dịch sang trái 1 vị trí, trước khi dịch bit 7 của R1 là 1 nên sau khi dịch bit này được chứa trong C, cho nên C=1. - LSR(Logical Shift Right) • Cú pháp: LSR Rd • Chức năng: dịch tất thanh ghi Rd sang phải 1 vị trí, Bit 0 (bit nhỏ nhất) của Rd sẽ được chứa trong cờ nhớ C, bit 7 của Rd bị xóa thành 0. Thực chất LSR tương đương với phép chia thanh ghi Rd cho 2. Bạn xem hình minh họa bên dưới. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R1, 0B11000110 ; (dạng nhị phân của 195) LSR R1 For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU - - - - - Kết quả là R1=01100001 và cờ C =1 vì thanh ghi R1 đã được dịch sang phải 1 vị trí, trước khi dịch bit 0 của R1 là 1 nên sau khi dịch bit này được chứa trong C, cho nên C=1. ADD(ADD without Carry) • Cú pháp: ADD Rd, Rr • Chức năng: thực hiện phép cộng 2 thanh ghi Rd và Rr , kết quả đặt lại trong Rd. Cờ nhớ C không được sử dụng. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R16, 30 LDI R17, 25 ADD R16, R17 Kết quả là R16=55. ADC(ADD with Carry) • Cú pháp: ADC Rd, Rr • Chức năng: thực hiện phép cộng 2 thanh ghi Rd và Rr , kết quả đặt lại trong Rd. Cờ nhớ C được set nếu kết quả vượt quá 255. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R16, 255 LDI R17, 1 ADC R16, R17 Kết quả là R16=0 và cờ C=1vì kết quả phép cộng là 256=100000000. INC (INCrement) • Cú pháp: INC Rd • Chức năng: tăng thanh ghi Rd 1 đơn vị và kết quả đặt lại trong Rd. Lệnh này đặc biệt thích hợp cho các ứng dụng lặp, kết hợp với BREQ hay BRNE có thể tạo thành 1 vòng lặp FOR. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: INC R17 kết quả là R17 được tăng thêm 1 đơn vị. SUB(SUBtract without Carry) • Cú pháp: SUB Rd, Rr • Chức năng: thực hiện phép trừ 2 thanh ghi Rd - Rr , kết quả đặt lại trong Rd. Cờ nhớ C không được sử dụng. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R16, 30 LDI R17, 25 SUB R16, R17 Kết quả là R16=5. SUBI(SUBtract Immediate) • Cú pháp: SUBI Rd, K For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU • Chức năng: thực hiện phép trừ thanh ghi Rd với hằng số K, kết quả đặt lại trong Rd. • Giới hạn: chỉ áp dụng cho các thanh ghi từ R16 đến R31. • Ví dụ: LDI R16, 30 SUBI R16, 20 Kết quả là R16=10. - DEC (DECrement) • Cú pháp: DEC Rd • Chức năng: giảm thanh ghi Rd 1 đơn vị và kết quả đặt lại trong Rd. Lệnh này đặc biệt thích hợp cho các ứng dụng lặp, kết hợp với BREQ hay BRNE có thể tạo thành 1 vòng lặp FOR. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: DEC R17 kết quả là R17 được giảm đi 1 đơn vị. - MUL (MULtiply unsigned) • Cú pháp: MUL Rd, Rr • Chức năng: thực hiện phép nhân không dấu 2 thanh ghi 8 bit Rd, Rr, kết quả là 1 số 16 bit đặt trong 2 thanh ghi R1:R0. Chú ý nếu Rd và Rr là các thanh ghi R1 và R0 thì kết quả sau khi tính được sẽ được viết đè lên. Xem hình minh họa instruction MUL bên dưới. • Giới hạn: áp dụng cho tất cả các thanh ghi trong RF. • Ví dụ: LDI R16, 30 LDI R17, 25 MUL R16, R17 Kết quả là R1=0x2, R0=0xEE, vì 30x25=750=0x02EE. 2. Instruction cho các thanh ghi I/O Bốn instruction sau đây được thiết kế riêng để truy cập vùng nhớ I/O, các instruction này sử dụng địa chỉ I/O của các thanh ghi trong vùng nhớ này. Vì là thiết kế riêng cho vùng nhớ I/O, bạn không thể sử dụng các thanh ghi này để truy cập RF hay SRAM. Trong các cú pháp của instruction này, khái niệm địa chỉ A là địa chỉ I/O, 0 ≤ A ≤ 63, nếu trong ví dụ A=0x00 thì đó là thanh ghi đầu tiên của vùng I/O, không phải là thanh ghi R0. - OUT(OUTPUT Data ) • Cú pháp: OUT A,Rr • Chức năng: xuất giá trị từ thanh ghi Rr ra thanh ghi có địa chỉ A trong vùng nhớ I/O. đây là cách phổ biến nhất để xuất giá trị ra vùng I/O. • Giới hạn: Rr là thanh ghi RF bất kỳ, A bị giới hạn từ 0 đến 63. • Ví dụ: LDI R16, 0xFF For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU OUT 0x11, R16 Kết quả là thanh ghi có địa chỉ 0x11 trong vùng I/O, tức thanh ghi DDRD, có giá trị bằng 0xFF. - IN(INPUT Data ) • Cú pháp: IN Rr, A • Chức năng: Load giá trị từ thanh ghi có địa chỉ A trong vùng nhớ I/O vào thanh ghi Rr. Đây là cách phổ biến nhất để nhận giá trị từ vùng I/O. • Giới hạn: Rr là thanh ghi RF bất kỳ, A bị giới hạn từ 0 đến 63. • Ví dụ: IN R16, 0x10 Kết quả là thanh ghi R16 nhận được giá trị của thanh ghi có địa chỉ 0x11 trong vùng I/O, tức thanh ghi PIND, đây chính là ví dụ đọc giá trị các chân của PORTD vào R16. - SBI(Set Bit in I/O Register) • Cú pháp: SBI A, b • Chức năng: Set bit thứ b trong thanh ghi có địa chỉ A trong vùng nhớ I/O. Tuy nhiên lệnh này không có tác dụng trên toàn bộ vùng I/O mà chỉ có tác đối với 32 thanh ghi đầu (địa chỉ từ 0 đến 31). • Giới hạn: b là số thứ các bit trong thanh ghi, 0≤b≤7; A bị giới hạn từ 0 đến 31. • Ví dụ: SBI 0x12, 2 Kết quả là bit 2 của thanh ghi có địa chỉ 0x12 trong vùng I/O, tức thanh ghi PORTD, được set lên 1. Đây chính là ví dụ set chân PD2 của PORTD. - CBI(Clear Bit in I/O Register) • Cú pháp: CBI A, b • Chức năng: xóa bit thứ b trong thanh ghi có địa chỉ A trong vùng nhớ I/O. Tuy nhiên lệnh này không có tác dụng trên toàn bộ vùng I/O mà chỉ có tác đối với 32 thanh ghi đầu (địa chỉ từ 0 đến 31). • Giới hạn: b là số thứ các bit trong thanh ghi, 0≤b≤7; A bị giới hạn từ 0 đến 31. • Ví dụ: CBI 0x12, 2 Kết quả là bit 2 của thanh ghi có địa chỉ 0x12 trong vùng I/O, tức thanh ghi PORTD, bị xóa thành 0. Đây chính là ví dụ xóa chân PB2 của PORTD. Như tôi trình bày ở phần trên, trong lúc lập trình, sau khi include file định nghĩa cho chip bạn có thể sử dụng tên của các thanh ghi thay cho địa chỉ của chúng, như thế chương trình của các bạn sẽ trở nên dễ hiểu hơn. 3. Các con trỏ X, Y, Z và cách truy cập toàn bộ không gian bộ nhớ Trong Register File của AVR, các thanh ghi từ R26 đến R31ngoài chứa năng thanh ghi thông thường còn có chức năng là con trỏ (Pointer) trong việc truy cập bộ nhớ (cả bộ nhớ data và bộ nhớ Program). Nếu được sử dụng như các Pointer, các thanh ghi trên được biết đến với tên gọi X, Y, Z. Định nghĩa như sau: X=R27:R26, X=R29:R28, X=R31:R30. Chúng là 3 thanh ghi 16 bit được định nghĩa trước cho tất cả các AVR. Ngoài ra trong các file định nghĩa cho chip chúng ta có thêm For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU 6 định nghĩa khác là XL, XH, YL, YH, ZL, ZH cũng chính là tên gọi của R26-> R31. Phần này chúng ta khảo sát một số instruction dùng truy cập toàn bộ khồi nhớ của AVR bằng cách sử dụng địa chỉ trực tiếp và bằng cách sử dụng Pointer. - LDS(LoaD direc from data Space) • Cú pháp: LDS Rd, k • Chức năng: load giá trị 1 byte từ thanh ghi có địa chỉ k trong SRAM vào thanh ghi Rd, k là dạng địa chỉ tuyệt đối có giới hạn từ 0 đến 65535(216-1) . • Giới hạn: Rd là thanh ghi bất kỳ trong RF nhưng giá trị lớn nhất của k là 65535, vì thế với lệnh này ta không thể truy cập vượt quá khoảng không gian 64KB. Nếu muốn truy cập vùng không gian lớn hơn 64KB chúng ta cần một số hỗ trợ, tuy nhiên ở đây tôi giả sử bộ nhớ của chip (thường là bộ nhớ data) không vượt quá 64KB (thực tế chưa có chip AVR nào có SRAM hay EEPROM vượt quá 64KB). • Ví dụ: LDS R2, 0x0060 Kết quả là thanh ghi R2 chứa giá trị của thanh ghi có địa chỉ 0x0060, đây là thanh ghi đầu tiên trong khoảng SRAM (sau RF và vùng I/O) của AVR. - STS(STorage direc to data Space) • Cú pháp: STS k, Rr • Chức năng: instruction này hoàn toàn giống LDS nhưng dùng để xuất dữ liệu từ thanh ghi Rr ra RAM, ngươi đọc có thể tham khảo phần giải thích cho LDS. Sử dụng địa chỉ trực tiếp thì câu lệnh sẽ đơn giản nhưng rất khó nhớ phần địa chỉ, thông thường SRAM là vùng chúng ta hay sử dụng để chứa biến tạm thời, trong các ngôn ngữ cấp cao ta chỉ cần nhớ tên biến nhưng với ASM chúng ta phải nhớ địa chỉ của chúng. Một cách tốt để tránh việc này là dùng chỉ thị (DIRECTIVE, bạn xem lại bài 1) . EQU để gán tên biến cho 1 địa chỉ, ví dụ .EQU bientam = 0x0060 và sau đó sử dụng bientam thay cho 0x0060. Một cách khác được dùng để truy cập bộ nhớ mà không dùng địa chỉ tuyệt đối là sử dụng sử dụng con trỏ. Có 2 instruction hỗ trợ con trỏ là LD (LoaD indirec from data Space), và ST (STorage indirec to data Space), LD đọc dữ liệu từ SRAM vào thanh ghi còn ST lưu dữ liệu từ thanh ghi vào SRAM. Cả 3 con trỏ X, Y và Z đều có thể được dùng nhưng có một số điểm lưu ý: cả 3 đều dùng được trong trường hợp truy xuất thông thường nhưng với cách truy cập có offset, con trỏ X không sử dụng được. Để truy xuất bộ nhớ chương trình bằng con trỏ thì Z là giải pháp duy nhất…Dưới đây là 1 số cách sử dụng LD, ST kết hợp với con trỏ, chúng ta xét thông qua các ví dụ. Ví dụ 1: CLR R27 ; xóa R27, tức xóa byte cao của pointer X LDI R26, 0x60 ; load giá trị 0x60 vào R26, tức byte thấp của pointer X ; sau 2 dòng trên, giá pointer X là 0x0060, sẵn sàng để trỏ đến vị trí đầu tiên trong SRAM. LD R1, X+ ; Load giá trị ở ố nhớ 0x0060 vào R1 (vì X trỏ đến 0x0060), sao đó tăng giá trị ;X lên 1, như thế sau lệnh này X=0x0061 LD R2, X+ ; Load giá trị ở ố nhớ 0x0061 vào R2, sao đó tăng giá trị ;X lên 1, như thế sau lệnh này X=0x0062 For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU LD R3, X ; Load giá trị ở ô nhớ 0x0062 vào R3 và không thay đổi X LD R4, -X ; Giảm giá trị của X trước (X=0x0061), sau đó load giá trị ở ô nhớ 0x0061 vào R4 Từ ví dụ này chúng ta thấy có 3 cách cơ bản để load dữ liệu từ SRAM bằng con trỏ, cách Load trực tiếp trong trường hợp LD R3, X, cách load post-increment (hoặc post-decrement) như trong trường hợp LD R1, X+ và cách load pre-decrement (hoặc pre-increment) trong trường hp LD R4, -X. Chúng ta có thể viết lại ví dụ trên nhưng sử dụng con trỏ Y hoặc Z thay cho X. Ví dụ viết cho instruction ST cũng hoàn toàn tương tự. Tuy nhiên cách truy cập theo cách pre hay post đều làm thay đổi giá trị của con trỏ, điều này có 1 bất lợi là nếu chúng ta muốn quay lại vị trí ô nhớ nào đó, chúng ta phải tiếp tục thay đổi con trỏ. Để tránh việc làm này, 1 cách truy cập khác được hỗ trợ là truy cập “Offset”. Xét ví dụ sau: LD R1, Y+1 Đây chính là cách truy cập Offset dùng con trỏ Y, cách viết trên là tương đương với cách viết LD R1, Y+ Nhưng điểm khác biệt ở đây là cách viết Offset không làm thay đổi giá trị của con trỏ Y. Sử dụng Offset có ưu điểm như sử dụng mảng (array) trong các ngôn ngữ lập trình cấp cao. Cần chú ý là giá trị offset không vượt quá 63 và phương pháp này chỉ dùng cho 2 thanh ghi Y và Z. 4. Rẽ nhánh và vòng lặp Không giống như các ngôn ngữ cấp cao, khi lập trình bằng ASM bạn không được hỗ trợ các cấu trúc điều khiển như If, For, While…người lập trình ASM phải tự xây dựng cho mình các cấu trúc này từ những instruction cơ bản. Nếu bạn có trong tay tài liệu tra cứu instruction cho AVR bạn sẽ thấy có rất nhiều instruction có dạng BRxx, với BR là viết tắt của từ Branch (rẽ nhánh). Đây là các instruction cơ bản giúp bạn xây dựng các cấu trúc điều khiển tương đương If, For, While…cho riêng mình. Trước hết ta sẽ khảo sát instruction BRNE bằng cách xem lại ví dụ trong bài 1, đây là đoạn chương trình con DELAY: DELAY: LDI R20, 0xFF DELAY0: LDI R21, 0xFF DELAY1: DEC R21 BRNE DELAY1 DEC R20 BRNE DELAY0 RET Bạn hãy chú ý 4 dòng lệnh nằm giữa đoạn chương trình trên, dòng đầu tiên thì bạn đã biết load giá trị 255 vào thanh ghi R21, sau đó tôi đặt 1 label DELAY1- xem như là 1 cột mốc, dòng 3, instruction DEC bạn mới được học hôm nay - giảm giá trị thanh ghi R21 đi 1 đơn vị, và cuối cùng BRNE DELAY1, BRNE là viết tắt của BRanch if Not Equal – rẽ nhánh nếu không bằng, thực ra bản chất của lệnh này là rẽ nhánh nếu cờ Zero không bằng 1. Như thế câu lệnh BRNE DELAY1 For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU của chúng ta được AVR thực hiện như sau: kiểm tra cờ Z, nếu Z=1 tiếp tục thực hiện dòng tiếp theo sau mà không quan tâm đến nhãn DELAY1, nhưng nếu Z=0 thì nhảy đến nhãn DELAY1. Bạn thấy rằng ban đầu R21 =255, sau khi giảm 1 bởi DEC, thanh ghi R21=254≠0, cờ Z =0, rẽ nhánh xảy ra, bộ đếm chương trình nhảy về nhãn DELAY1. Quá trình này lặp lại khoảng 255 lần trước khi R21 =0 dẫn đến Z=1. Bao bên ngoài vòng lặp của nhãn DELAY1 là vòng lặp của nhãn DELAY0, cách hiểu hoàn toàn tương tự nhưng trước khi lệnh DEC R20 được thực thi thì phải chờ cho vòng lặp DELAY1 kêt thúc. Bản thân DELAY0 cũng là 1 vòng lặp 255 lần. kết quả cuối cùng là ta thu được 1 vòng lặp khoảng 255x255 lần mà không làm gì cả, đó chính là ý nghĩa và cách hoạt động của đoạn chương trình con DELAY. Bên cạnh BRNE chúng ta có 1 số instruction phục vụ rẽ nhánh khác như: - BREQ(BRanch if EQual ) • Cú pháp: BREQ LABEL • Chức năng: Nhảy đến nhãn LABEL nếu cờ Z =1. Cờ Z chịu tác động của rất nhiều instruction như CP, CPI, SUB, SUBI…vì thế BREQ thường được sử dụng sau các instruction này. • Ví dụ: LDI R16, 0xFF LDI R17, 0xFF CP R16, R17 ; so sanh 2 thanh ghi R16, R17 BREQ RENHANH ….. RENHANH: ; thực hiện những việc khi rẽ nhánh. Kết quả là việc rẽ nhánh xảy ra vì khi so sánh bằng CP, R17=R16 nên cờ Z tự động được set bằng 1, lệnh BREQ được thực thi và nhảy đến nhãn RENHANH. Ví dụ này tương đương cấu trúc if (R16=R17) {thực hiện những việc khi rẽ nhánh} - BRLO(BRanch if LOwer ) • Cú pháp: BRLO LABEL • Chức năng: bản chất của câu lệnh là nhảy đến nhãn LABEL nếu cờ C =1. Tuy nhiên, thông thường lệnh này sử dụng theo sau các instruction như CP, CPI, SUB, SUBI…khi đó việc rẽ nhánh sẽ xảy ra nếu thanh ghi Rd <Rr. • Ví dụ: EOR R16, R16 ;XOR R16 với chính nó, tương đương CLR R16 VONG LAP: INC R16 ;tăng R16 thêm 1 đơn vị CPI R16, $10 ;so sánh R16 với số hexadecimal $10 BRLO VONGLAP ;nhảy về VONGLAP nếu R16 <$10 NOP ;câu lệnh này sẽ được thực thi nếu điều kiện rẽ nhánh ở trên không thỏa, ; NOP là 1 instruction, chức năng là không làm gì cả. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Kết quả là phần lệnh bên trong VONGLAP sẽ được thưc hiện khoảng 16 lần ($10=16) trước khi thực hiện lệnh NOP. - BRSH(BRanch if Same or Higher ) • Cú pháp: BRSH LABEL • Chức năng: bản chất của câu lệnh là nhảy đến nhãn LABEL nếu cờ C =0. Tuy nhiên, thông thường lệnh này sử dụng theo sau các instruction như CP, CPI, SUB, SUBI…khi đó việc rẽ nhánh sẽ xảy ra nếu thanh ghi Rd ≥Rr. • Ví dụ: SUBI R16, 4 ;trừ R16 đi 4 đơn vị BRSH RENHANH ; nhảy đến RENHANH nếu R16 ≥ 4 …. RENHANH: NOP … Còn rất nhiều instruction rẽ nhánh bạn có thể sử dụng để tạo cấu trúc điều khiển, chú ý là các instruction này đều hoạt động dựa trên trạng thái của 1 cờ nào đó, do đó bạn cần lựa chọn 1 lệnh phù hợp để thực thi trước các instruction rẽ nhánh này, để làm được như vậy bạn cần xem kỹ tài liệu hướng dẫn INSTRUCITON cho AVR. Phần này tôi đã giải thích hoạt động và ứng dụng của một số instruction cơ bản, để biết hết các instrcuiton cho AVR bạn cần tham khảo tài liệu liên quan. VI. Macro và chương trình con. Macro là khái niệm chỉ một đoạn code nhỏ để thực hiện một công việc nào đó, nếu có 1 đoạn code nào đó mà bạn rất hay sử dụng khi lập trình thì bạn nên dùng macro để tránh việc phải viết đi viết lại đoạn code đó. Lập trình ASM cho AVR cho phép bạn sử dụng Macro, để tạo 1 Macro bạn sử dụng DIRECTIVE “.MACRO” và “.ENDMACRO”, bạn xem 1 ví dụ: .MACRO delay4 NOP NOP NOP NOP .ENDMACRO Đoạn Macro trên có tên delay4 thực hiện việc delay 4 chu kỳ máy bằng 4 lệnh NOP, nếu trong chương trình bạn cần dùng Macro này thì chỉ cần gọi delay4 ở bất kỳ dòng nào. […] ; code của bạn Delay4 […] ; code của bạn Mỗi lần tên của Macro được gọi, trình biên dịch sẽ tìm đến Macro đó và copy toàn bộ nội dung Macro vào vị trí bạn gọi. Như vậy thực chất con trỏ chương trình không nhảy đến Macro, Macro không làm giảm dung lương chưong trình mà chỉ làm cho việc lập trình nhẹ nhàng hơn. Đây chính là khác biệt lớn nhất của Macro và Subroutine (chương trình con). Chương trình con cũng là 1 đoạn code thực hiện 1 chức năng đặc biệt nào đó. Tuy nhiên khác với Macro, mỗi khi gọi chương trình con, con trỏ chương trình nhảy đến chương trình con đề For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU thực thi chương trình con và sau đó quay về chương trình chính. Như thế chương trình con chỉ được biên dịch 1 lần và có thể sử dụng nhiều lần, nó làm giảm dung lượng chưong trình. Đây l ưu điểm và cũng là điểm khác biệt lớn nhất giữa chương trình con và Macro. Tuy nhiên cần chú ý là việc nhảy đến chương trình con và nhảy về chương trình chính cần vài chu kỳ máy, có thể làm chậm chương trình, đây là nhược điểm của chương trình con so với macro. Chương trình con cho AVR luôn được bắt đầu bằng 1 Label, đó cũng là tên và địa chỉ của chương trình con. Chương trình con thường được kết thúc với câu lệnh RET (Return). Chúng ta đã biết về chương trình con qua ví dụ của bài 1, trong đó DELAY là 1 chương trình con. Để gọi chương trình con từ 1 vị trí nào đó trong chương trình, chúng ta có thể dùng lệnh CALL hoặc RCALL (Relative CALL) (xem lại ví dụ bài 1 về cách sử dụng RCALL). Mỗi khi các lệnh này được gọi, bộ đếm chương trình được tự động được PUSH vào stack và khi chương trình con kết thúc bằng lệnh RET, bộ đếm chương trình được POP trở ra và quay về chương trình chính. Lệnh CALL có thể gọi 1 chương trình con ở bất kỳ vị trí nào trong khi RCALL chỉ gọi trong khoảng bộ nhớ 4KB, nhưng RCALL cần ít chu kỳ xung clock hơn khi thực thi. Hai instruction khác có thể được dùng để gọi chương trình con đó là JMP (Jump) và RJMP (Relative Jump). Khác với các lệnh call, các lệnh jump không cho phép quay lại vì không tự động PUSH bộ đếm chương trình vào Stack, để sử dụng các lệnh này gọi chương trình con bạn cần một số lệnh jump khác ở cuối chương trình con. Tóm lại bạn nên viết 1 chương trình con đúng chuẩn và dùng CALL hoặc RCALL để gọi chương các chương trình này, chỉ những trường hợp đặc biệt hoặc bạn hiểu rất rõ về chúng thì có thể dùng các lệnh jump. VII. Chương trình ví dụ. Nếu bạn đã đọc và hiểu đến thời điểm này thì bạn đã có thể hiểu hết hoạt động của chương trình ví dụ trong bài 1, thật sự ví dụ đó rất đơn giản và dễ hiểu. Tuy nhiên, bạn có thề tối ưu hóa ví dụ đó theo hướng làm giảm dung lượng chương trình và tất nhiên, chương trình sẽ khó hiểu hơn cho người khác. Các phần khởi động vị trí bộ nhớ, stack và chương trình con DELAY chúng ta không thay đổi, chỉ thay đổi phần chương trình chính, 1 trong những cách viết chương trình chính như cách sau: ; CHUONG TRINH CHINH , BAI 1, VI DU 1, VERSION 2/////////////////////////////// LDI R16, $1 ; LOAD GIA TRI KHOI DONG CHO R16 MAIN: OUT PORTB, R16 ; XUAT GIA TRI TRONG R16 RA PORTB RCALL DELAY ; GOI CHUONG TRINH CON DELAY ROL R16 ; XOAY THANH GHI R16 SANG TRAI 1 VI TRI RJMP MAIN ; NEU R16 ≠0, NHAY VE MAIN, TIEP TUC QUET ;////////////////////////////////////////////////////////////////////////////////////////// Có thể không cần giải thích bạn cũng đã có thể hiểu đoạn code trên, đây chỉ là 1 trong những cách có thể, bạn hãy viết lại theo cách của riêng bạn với yêu cầu là chương trình phải thực hiện đúng chức năng và ngắn gọn. Bây giờ chúng ta sẽ thực hiện một ví dụ minh họa cho những gì chúng ta đã học trong bài 2 này. Nội dung của ví dụ thể hiện trong mạch điện hình 9. Hoạt động của mạch điện tử như sau: 1 chip ATMega8 được sử dụng như một counter, có thể dùng để đếm lên và đếm xuống, 2 button For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU trong mạch điện tác động như 2 “kicker”, nhấn button 1 để đếm lên và button để đếm xuống, giá trị đếm nằm trong khoảng từ 0 đến 9. Giá trị đếm được hiển thị trên 1 LED 7 đoạn loại anod chung (dương chung), chip 7447 được dùng để giải mã từ giá trị BCD xuất ra bởi ATMega8 sang tín hiệu cho LED 7 đoạn anod chung, chúng ta cần sử dụng 7447 vì tín hiệu xuất ra từ chip ATMega8 là dạng nhị phân hoặc BCD , tín hiệu này không thể hiển thị trực tiếp trên các LED 7 đoạn, chip 7447 có nhiệm vụ chuyển 1 dữ liệu dạng digit BCD sang mã phù hợp cho LED 7 đoạn. Để thực hiện ví dụ, trước hết bạn hãy vẽ mạch điện như trong hình 9 bằng phần mềm Proteus (xem cách vẽ mạch điện bằng Proteus trong ví dụ bài 1), mạch điện chỉ có 5 loại linh kiện là chip ATMega8 (từ khóa mega8), 1 LED 7 đoạn anod chung với tên đầy đủ trong Proteus là 7SEGCOM-AN-GRN (từ khóa 7SEG), 1 chip 7447 (từ khóa 7447), 1 điện trở 10 Ω và 2 button (từ khóa button). Hình 9. Ví dụ cho bài 2. Sử dụng AVRStudio tạo 1 project mới với tên gọi avr2 (xem lại ví dụ bài 1 để biết cách tạo Project mới trong AVRStudio). Viết lại phần code bên dưới vào vào file avr2.asm .INCLUDE "M8DEF.INC" .CSEG. .ORG 0x0000 RJMP BATDAU .ORG 0x0020 BATDAU: ;KHOI DONG STACK POINTER LDI R17, HIGH(RAMEND) LDI R16, LOW(RAMEND) OUT SPL, R16 OUT SPH,R17 ; KHOI DONG CAC PORT CLR R16 ; XOA R16, R16=0 OUT DDRB, R16; DDRB=0, PORTB LA NGO NHAP LDI R16, 0xFF; SET TAT CA CAC BIT CUA R16 LEN 1 OUT PORTB,R16; DDRB=0, PORTB =0xFF, KEO LEN CAC CHAN PORTB For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU OUT DDRD, R16; DDRD=0xFF, PORTD LA NGO XUAT CLR R25; XOA R25, R25 LA THANH GHI DUNG CHUA SO DEM SER R20 ; R21 LA THANH GHI TAM CHUA GIA TRI TRUOC DO CUA PINB MAIN: IN R21,PINB ; DOC GIA TRI TU PINB, TUC TU CAC BUTTON RCALL SOSANH; GOI CHUONG TRINH CON SOSANH OUT PORTD, R25; XUAT GIA TRI DEM RA PORTD SBRS R21,0; NEU BIT 0 CUA R21 (TUC CHAN PB0) =1 THI BO QUA DONG ;TIEP THEO RCALL TANG ; NHAY DEN CHUONG TRINH CON TANG GIA TRI DEM SBRS R21,1; NEU BIT 1 CUA R21 (TUC CHAN PB1) =1 THI BO QUA DONG ;TIEP THEO RCALL GIAM; NHAY DEN CHUONG TRINH CON GIAM GIA TRI DEM MOV R20,R21; LUU LAI TRANG THAI PINB RJMP MAIN ;**********************CHUONG TRINH CON************************ ; **************subroutine kiem tra gioi hang (tu 0 den 9) cua so dem SOSANH: CPI R25, 10 BREQ RESET0; NEU GIA TRI DEM=10 THI TRA VE 0 CPI R25, 255 BREQ RESET9; NEU GIA TRI DEM =255 THI TRA VE 9 RJMP QUAYVE; NHAY DEN NHAN QUAYVE RESET0: LDI R25,$0; TRA GIA TRI DEM VE 0 RJMP QUAYVE RESET9: LDI R25,$9; GAN 9 CHO GIA TRI DEM QUAYVE: RET ; ************************************************************ ; **************subroutine tang so dem 1 don vi neu dieu kien thoa TANG: SBRS R20,0 RET INC R25 RET ; **************subroutine giam so dem 1 don vi neu dieu kien thoa GIAM: SBRS R20,1 RET DEC R25 RET For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Trong ví này này, chúng ta sử dụng 2 PORT của chip ATMega8, PORTD dùng xuất dữ liệu (số đếm) ra chip 7447 và sau đó hiển thị trên LED 7 đoạn. PORTB dùng như ngõ nhập, tín hiệu từ các button sẽ được chip ATMega8 nhận thông qua 2 chân PB0 và PB1 của PORTB. Hoạt động của cac PORT và việc xác lập 1 PORT như các ngõ xuất chúng ta đã khảo sát trong bài 1. Ở đây chúng ta khảo sát thêm về xác lập PORT như 1 ngõ nhập, trước hết bạn hãy quan sát mạch điện tương đương của 1 chân trong các PORT xuất nhập của AVR trong hình 10. Hình 10. Cấu trúc chân trong PORT của AVR. Trong mạch điện hình 10, các diode và tụ điện chỉ có chức năng bảo vệ chân PORT, nhưng điện trở Rpu (R Pull up) đóng vai trò quan trọng như là điện trở kéo lên khi chân của PORT làm nhiệm vụ nhận tín hiệu (ngõ nhập). Tuy nhiên trong AVR, điện trở kéo lên này không phải luôn kích hoạt, chúng ta biết rằng mỗi PORT của AVR có 3 thanh ghi: DDRx, PORTx và PINx, nếu DDRx=0 thì PORT x là ngõ nhập, lúc này thanh ghi PINx là thanh ghi chứa dữ liệu nhận về, đặc biệt thanh ghi PORTx vẫn được sử dụng trong mode này, đó là thanh ghi xác lập điện trở kéo lên, như thế nếu DDRx=0 và PORTx=0xFF thì các chân PORTx là ngõ nhập và được kéo lên bởi 1 điện trở trong chip, nghĩa là các chân của PORTx luôn ở mức cao, muốn kích để thay đồi trạng thái chân này chúng ta cần nối chân đó trực tiếp với GND, đấy là lý do tại sao các button trong mạch điện của chúng ta có 1 đầu nối với chân của chip còn đầu kia được nối với GND. Đây cũng là ý nghĩa của khái niệm điện trở kéo lên (Pull up resistor) trong kỹ thuật điện tử. Đoạn code trong phần “KHOI DONG CAC PORT” của ví dụ này xác lập PORTD là ngõ xuất (DDRD=0xFF) , PORTB là ngõ nhập có sử dụng điện trở kéo lên (DDRB=0, PORTB=0xFF). Chúng ta sẽ giải thích hoạt động của đoạn chương trình chính và các đoạn chương trình con. Trước hết, trong chương trình này, chúng ta sử dụng 3 thanh ghi chính là R20, R21 và R25, trong đó R25 là thanh ghi chứa số đếm, giá trị của thanh ghi R25 sẽ được xuất ra PORTD của chip, thanh ghi R21 chứa trạng thái của thanh ghi PINB và cũng là trạng thái của các button, thanh ghi R20 kết hợp với thanh ghi R21 tạo thành 1 “bộ đếm cạnh xuống” của các button. Để hiểu thấu đáo hoạt động đếm (cũng là hoạt động chính của ví dụ này) chúng ta xét trạng thái chân PB0 như trong hình 11. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Hình 11. Thay đổi trạng thái ở các chân I/O. Trong trạng thái bình thường (button không được nhấn), chân PB0 ở mức cao (do điện trở kéo lên), bộ đếm không hoạt động, giá trị đếm không thay đổi, bây giờ nếu nhấn button, chân PB0 được nối trực tiếp với GND, chân này sẽ bị kéo xuống mức thấp, bằng cách kiểm tra trạng thái chân PB0, nếu PB0=0 ta tăng giá trị đếm 1 đơn vị. Ý tưởng như thế có vẻ hợp lý, tuy nhiên nếu áp dụng thì chương trình sẽ hoạt động không đúng chức năng, khi bạn nhấn 1 lần giá, trị đếm có thể tăng đến cả trăm hoặc không kiểm soát được, hiệu ứng này tương tự khi bạn nhấn và giữ 1 phím trên bàn phím máy tính, lý do là vì chúng ta sử dụng phương pháp kiểm tra mức để đếm, thời gian quét của chương trình rất ngắn so với thời gian chúng ta giữ button. Để khắc phục, chúng ta dùng phương pháp kiểm tra cạnh xuống, chỉ khi nào phát hiện chân PB0 thay đổi từ 1 xuống 0 thì mới tăng giá trị đếm 1 đơn vị, kết quả là mỗi lần nhấn button thì giá trị đếm chỉ tăng 1 (ngay cả khi ta nhấn và giữ button), thanh ghi R20 được sử dụng để lưu trạng thái trước đó của PINB (cũng là trạng thái của các button). Trong chương trình, tôi sử dụng 2 istruction mới là SBRC và SBRS để kiểm tra trạng thái các chân của PORTB (button). SBRC – Skip if Bit in Register is Clear, lệnh này sẽ bỏ qua 1 dòng lệnh ngay sau đó (chỉ bỏ qua 1 dòng duy nhất) nếu 1 bit trong thanh ghi ở mức 0, SBRC – Skip if Bit in Register is Set- hoạt động tương tự SBRC nhưng skip sẽ xảy ra nếu bit trong thanh ghi ở mức 1. Dựa vào đây chúng ta giải thích 4 dòng sau: SBRS R21,0 RCALL TANG SBRS R21,1 RCALL GIAM Dòng 1 dùng kiểm tra trạng thái bit 0 trong R21 (chú ý R21 chứa giá trị của PINB), nếu bit này bằng 1 (set), tức chân PB0=1 hay button không được nhấn, thì nhảy bỏ qua dòng lệnh tiếp theo để đến dòng 3. Ở dòng 3 chương trình kiểm tra trạng thái chân PB1 (button thứ 2). Quay lại dòng 1, nếu chương trình kiểm tra phát hiện chân PB0=0 (button thứ nhất được nhấn) thì dòng lệnh thứ 2 được thực thi, kết quả là chương trình nhảy đến chương trình con TANG. TANG: SBRS R20,0 RET INC R25 RET Dòng đầu tiên của chương trình con TANG là kiểm tra trạng thái trước đó của chân PB0 (được lưu ở bit 0 trong thanh ghi R20), nếu trạng thái này bằng 0, nghĩa là không có sự chuyển từ 1 xuống 0 ở chân PB0, dòng 2 (lệnh RET) sẽ được thực thi để quay về chương trình chính. Nhưng nếu PB0 trước đó bằng 1, nghĩa là có sự thay đổi từ 1->0 ở chân này, giá trị đếm sẽ được tăng thêm 1 nhờ INC R25, sau đó quay về chương trình chính. Tóm lại muốn tăng giá trị đếm thêm 1 đơn vị cần thỏa mãn 2 điều kiện: chân PB0 hiện tại =0 (button đang được nhấn) và trạng thái trước đó của PB0 phải là 1 (tránh trường hợp tăng liên tục). Phương pháp này có thể áp dụng cho rất nhiều trường hợp đếm dạng đếm xung. Quá trình giảm giá trị đếm được hiểu tương tự, phần còn lại của ví dụ này bạn đọc hãy tự giải thích theo những gợi ý trên. For more details and questions, contact me: thanhtam.h@gmail.com AUTO.NLU Chúng ta kết thúc bài 2 ở đây, sau bài này tôi hy vọng bạn sẽ nắm được một cách chung nhất cấu trúc và hoạt động của chip AVR, đây là cơ sở quan trọng để sử dụng hiệu quả loại vi điều khiển này. Kể từ các bài sau, chúng ta sẽ tìm hiểu hoạt động và ứng dụng của các thiết bị ngoại vi trên AVR như Timer, chuyển đổi analog to digital (ADC), các chuẩn giao tiếp… Các thắc mắc về nội dung và cách sử dụng tài liệu, các bạn có thể liên hệ tác giả qua email: thanhtam.h@gmail.com hoặc sim@mophong.org For more details and questions, contact me: thanhtam.h@gmail.com ...
View Full Document

Ask a homework question - tutors are online