Tải bản đầy đủ (.docx) (51 trang)

bai2.cau truc avr

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (704.34 KB, 51 trang )

Bài 2 - Cấu Trúc AVR


1

2

3

4

5
( 262 Votes )
Nội dung Các bài cần tham khảo trước
1. Giới thiệu.
2. Tổ chức AVR.
3. Stack.
4. Thanh ghi trạng thái.
5. Ví dụ.
Download ví dụ
Làm quen AVR.
Assembly cho AVR.
AVR Studio .
Mô phỏng với Proteus .

I. Giới thiệ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 "Làm
quen AVR" 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 AT90S2313…) 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ụ trongbài 1:
.ORG 0x000
RJMP BATDAU
.ORG 0x020
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 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. Tổ chứ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ả trong hình 1, 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ứ 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.
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ỉ I/O” + 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 3: RAM tĩnh, nội (internal SRAM), là vùng không gian cho
chứa các biến (tạm thời hoặc toàn cục) trong lúc thực thi chương trình,
vùng này tương tự các thanh RAM trong máy tính nhưng có dung
lượng khá nhỏ (khoảng vài KB, tùy thuộc vào loại chip).
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.
Câu hỏi bây giờ là 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.
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 R16, 1
PUSH R16
LDI R16, 5
PUSH R16
LDI R16, 8
PUSH R16
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 REGISTRY).
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:
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 xor 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.
Hình 8. Ảnh hưởng của các phép toán lên SREG.
IV. 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 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 đề 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.
V. Ví dụ minh họa.
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 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), 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à
7SEG-COM-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
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
List 1. Ví dụ cấu trúc AVR
1
2
3
4
5

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.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
OUT DDRD, R16 ;DDRD=0xFF, PORTD LA NGO XUAT
CLR R25 ;XOA R25, R25 LA THANH GHI DUNG CHUA SO DEM
SER R20 ; R20 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
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.
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 ;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
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.

Assembly cho AVR


1


2

3

4

5
( 16 Votes )
Nội dung Các bài cần tham khảo trước
1. Instruction chỉ dùng cho Register Files.
2. Instruction cho các thanh ghi I/O.
3. Các con trỏ X, Y, Z và cách truy cập toàn bộ không gian bộ nhớ.
4. Rẽ nhánh và vòng lặp.
Làm quen AVR.
Cấu trúc AVR
WinAVR

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).
• A: địa chỉ I/O.
• q: độ dịch chuyển của địa chỉ tuyệt đối.
I. 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: 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ụ: áp dụng cho tất cả các thanh ghi trong RF.
- 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.

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×