Tải bản đầy đủ (.pdf) (27 trang)

CÙNG HỌC AVR AVR3– LẬP TRÌNH C CHO AVR: NGẮT VÀ TIMER/COUNTER docx

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 (1.49 MB, 27 trang )

AUTO.NLU

For more details and questions, contact me:

CÙNG HỌC AVR
AVR3– LẬP TRÌNH C CHO AVR: NGẮT VÀ TIMER/COUNTER

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 để
hướng tới tài liệu. Liên hệ tác giả qua email:

I. Bạn sẽ đi đến đâu.
Đây là thứ 3 trong loạt bài cùng học AVR. Nếu bài 1 và bài 2 giới thiệu một cách chung nhất
về AVR thì kể từ bài 3 tôi sẽ giới thiệu lập trình điều khiển peripheral (thiết bị ngoại vi hay các
chức năng mở rộng tích hợp sẵn trên các chip AVR). Do là các thiết bị ngoại vi mở rộng nên
không phải tất cả các chip AVR đều có tất cả các thiết bị này, ví dụ
chip AVR AT90S2313,
AT90S8515 không có bộ chuyển đổi Analog-Digital (ADC) và giao tiếp TWI (I
2
C)…Tuy nhiên,
các chip thuộc dòng Mega có khá nhiều thiết bị ngoại vi phục vụ cho các ứng dụng đo lường, điều
khiển…Chúng ta sẽ tiếp tục sử dụng chip Atmega8 để khảo sát, trong quá trình khảo sát, nếu có
sự khác biệt giữa Atmega8 và các chip mới hơn (như Atmega16, Atmega32, Atmega64,
Atmega128…) tôi sẽ nói rõ để bạn rõ.
Một sự khác biệt lớn giữa AVR3 so với các bài viết trước là tôi sẽ hướng dẫn lập trình bằng
C mà cụ thể
là bằng AVR-GCC (được tích hợp trong WinAVR). Các bạn xem phần II để biết rõ
hơn.
Sau bài này, tôi hy vọng bạn sẽ:
- Cài đặt và sử dụng WinAVR, Programmer Notepad.


- Hiểu được một số hàm cơ bản trong thư viện avr-libc.
- Hiểu về INTERRUPT (ngắt), cách sử dụng ngắt bằng C và cả ASM.
- Cải tiến ví dụ trong bài 2 bằng Interrupt.
II. WinAVR.
Tại sao C: như tôi đã trình bày ở các bài học trước, khi bạ
n đã hiểu về AVR, để thực hiện
các ứng dụng, bạn có thể không nhất thiết phải luôn lập trình bằng ASM. Ngôn ngữ cấp cao như C
sẽ giúp cho bạn xây dựng các ứng dụng nhanh chóng và dễ dàng hơn, tuy nhiên không vì thế mà
bạn “quên” ASM, lập trình bằng C kết hợp ASM là giải pháp hay nhất. Một chú ý là chúng ta chỉ
sử dụng C để đơn giản hóa lập trình tính toán, cấu trúc điều khiển…lập trình C cho AVR không
có nghĩa là bạ
n không cần biết cấu trúc và cách thức hoạt động của chip!!!
Tại sao WinAVR: WinAVR (đọc là Whenever: theo tác giả của WinAVR) là một bộ phần
mềm mã nguồn mở bao gồm các công cụ cho dòng vi điều khiển AVR . WinAVR chạy trên nền
hệ điều hành Windows, nó bao gồm các công cụ sau:
- Trình biên dịch avr-gcc, GNU GCC là trình biên dịch C, C++ phát triển bởi cộng
đồng mã nguồn mở GNU, avr-gcc phát triển riêng cho AVR.
- Chương trình nạp chip avrdude.
- Chương trình debugger avr-gdb.
AUTO.NLU

For more details and questions, contact me:

- Programmer Notepad: trình biên tập code hỗ trợ nhiều ngôn ngữ như C, C++,
CSS, HTML, Java,…
- MFile: tiện ích tạo các file Makefile dùng trong quá trình biên dịch code…
Cốt lõi của WinAVR là trình biên dịch GNU GCC và thư viện avr-libc, đây là bộ công cụ
lập trình C miễn phí hoàn chỉnh duy nhất cho AVR. Có thể nói bộ công cụ này góp phần không
nhỏ giúp cho chip AVR ngày càng trở nên phổ biến. WinAVR liên tục được cập nhật và hoàn
thiện bởi rất nhiều người, nguồn tài liệu và chương trình mẫu viết bằng công c

ụ này là rất
lớn…Đây là những lí do chính khiến tôi chọn WinAVR để giới thiệu với bạn.
Download và cài đặt: có 2 cách để bạn cài đặt bộ công cụ C cho AVR, cách thứ nhất, bạn
download từng phần gồm binutils, gnu-gcc, avr-libc, và avrdude…rồi cài đặt (cách này thường
được sử dụng trên hệ điều hành Linux…tôi sẽ giới thiệu trong 1 tài liệu khác) và cách thứ 2 là
dùng WinAVR (dành cho windows). Bạn có thể download trực tiếp phiên bản mới nhất tại
website chính thức c
ủa WinAVR: Quá trình cài đặt
tương đối dễ dàng vì bạn có thể chỉ cần làm theo các lựa chọn mặc định. Sau khi cài đặt WinAVR
vào máy (tôi giả sử thư mục cài đặt của bạn là C:\WinAVR) bạn sẽ có đầy đủ bộ công cụ từ IDE
(Integrated Development Environment) để biên tập code, trình biên dịch, linker, chương trình nạp
chip, tiện ích tạo Makefile…
Programmer Notepad (pn): Programmer Notpad là phần mềm môi trường phát triển tích
hợp (IDE) miễn phí cho việc lập trình các ngôn ngữ như C, C++, CSS, HTML, Java,…Tuy giao
diện của pn khá
đơn giản nhưng đây là công cụ rất hoàn hảo và được tích hợp sẵn trong WinAVR,
avr-gcc lugin được tích hợp sẵn trong pn cho WinAVR nên chúng ta có thể biên dịch code,
download chương trình vào chip trực tiếp với pn. (có thể tham khảo thêm về pn tại website
/> ) (hey, bạn có thể viết code cho avr-gcc bằng AVRStudio, Eclipse IDE
hay ngay cả với Windows Notepad…makes sense).
MFile: để biên dịch 1 chương trình bằng trình biên dịch gnu gcc, bạn cần 1 file tên là
Makefile không có phần mở rộng, file này chứa thông tin cần thiết như thông tin về trình biên
dịch, target (là các chip AVR trong trường hợp của chúng ta), trình nạp chip…MFile là tiện ích
giúp chúng ta tạo các Makefile nhanh chóng và chinh xác. MFile được tích hợp sẵn trong
WinAVR.
III. Khởi động cùng Programmer Notepad (pn).
Sau khi cài đặt WinAVR, trên desktop của bạn có thể sẽ xuất hiện 2 icon của pn và MFile
như trong hình 1.

Hình 1

Phần này chúng ta tìm hiểu cách viết một chương trình C trong pn thông qua 1 ví dụ đơn
giản. Từ Desktop, hãy khởi động pn, lần đầu chạy pn bạn sẽ thấy giao diện của chương trình như
trong hình 2.

AUTO.NLU

For more details and questions, contact me:


Hình 2: giao diện pn.
Trước hết chúng ta hãy cài đặt thêm một số tính năng vào pn bao gồm chức năng tạo ra file
coff (dùng mô phỏng với AVR Studio Simulator) và chức năng vừa biên dịch vừa nạp chip. Từ
menu chính của pn, chọn menu “Tool > Option”, bạn sẽ thấy hộp thoại Option xuất hiện, hãy
chọn mục Tool trong hộp thoại Option rồi nhấn button “Add” như trong hình 3.
Vùng Project
File Editor
Kết quả biên dịch
AUTO.NLU

For more details and questions, contact me:


Hình 3: Option cho pn.
Trong hộp thoại “New Tool Properties” hãy đặt các thông số như trong hình 4:

Hình 4: Add chức năng tạo file COFF cho pn.
AUTO.NLU

For more details and questions, contact me:


Lặp lại các bước trên để add thêm chức năng vừa biên dịch vừa đổ chương trình vào pn (xem
hình 5).

Hình 5: Add chức năng Make và Program cho pn.
Nhấn “OK” để kết thúc cài đặt, click vào menu Tools bạn sẽ thấy có 2 lệnh mới được add
vào, chúng ta sẽ sử dụng chúng sau này.

Hình 6: Tools menu.
Khi add các chức năng mới vào pn, bạn chú ý 2 mục là command và parameters, command
thì luôn là “make” vì đây chính là lệnh “đa năng” khi làm việc với trình biên dịch gnu-gcc.
AUTO.NLU

For more details and questions, contact me:

Parameters là các thông số gởi kèm với lệnh make, như vậy khi bạn click vào 1 dòng lệnh mới tạo
ra, pn sẽ yêu cầu trình dịch gcc thực hiện lệnh make với thông số mà bạn đã tạo.
Chúng ta đã sẵn sàng để viết ví dụ đầu tiên bằng WinAVR. Từ pn, chọn menu
“File>New>Project” , bạn chọn nơi lưu Project của bạn (ví dụ C:\WinAVR\My Project) và hãy
đặt tên cho Project của bạn là AVR1-gcc vì chúng ta sẽ viết lại ví dụ cho bài AVR1 bằng C.
Hãy type đoạn code sau vào của s
ổ “new” (phần bên phải trong pn)
//code 1
//file: main.c
//Description: AVR1 by GCC, "Cung hoc AVR" Series
#include <avr/io.h>
#include <util/delay.h>
unsigned char val=1;
int main(void){
DDRB=0xFF; //set PORTB as output lines
while(1){

PORTB=val;
_delay_loop_2(65000);
val*=2;
if (!val) val=1;
}
return 0;
}

Chọn “File>save” để lưu đoạn code trên thành 1 file có tên là main.c
Trong vùng “Project” (phần bên trái), click phải vào Project “AVR1-gcc” rồi chọn “Add
Files”, hãy add file “main.c” vào Project của bạn.
Hãy thử biên dịch Project của bạn bằng cách chọn menu “Tools>[WinAVR] Make All”,
quan sát vùng thông báo biên dịch (nằm ở phía dưới) bạn sẽ thấy thông báo như sau:

Nghĩa là có lỗi trong quá trình biên dịch, quá trình biên dịch thất bại vì “No rule to make
target ‘all’.” Lỗi này do Project của chúng ta không có file Makefile. Hãy tạo 1 file Makefile bằng
tiện ích MFile.
Từ Desktop, chạy chương trình MFile, giao diện của MFile rất đơn giản như trong hình 7
(chú ý: chúng ta có thể thêm bớt các cài đặt cho MFile bằng cách chỉnh sửa file mfile.tcl trong thư
mục cài đặt của WinAVR).
AUTO.NLU

For more details and questions, contact me:


Hình 7: chương trình tiện ích MFile
Hãy chọn menu “Makefile>Main file name”, một hộp thoại nhỏ xuất hiện, điền tên file chính
trong Project của bạn (trong trường hợp của chúng ta ở đây là “main”), nhấn “OK”. Tiếp tục chọn
“Makefile > MCU type > ATmega > atmega8”. Đối với Project ví dụ này, chỉ cần set 2 tham số
tên file và loại chip như trên là đủ, bạn chọn “File>save as” và lưu Makefile vào chung thư mục

chứa Project (C:\WinAVR\My Project trong trường hợp của tôi). Bạn đã có thể tắt chương trình
MFile.
Trở lại pn, chọn menu “Tools>[WinAVR] Make All” lần nữa để biên dịch, lần này bạn sẽ
thấy thông báo cuối cùng là “>Process Exit code: 0”, biên dịch thành công. Vào thư mục chứa
Project bạn sẽ thấy 1 file “main.hex” được tạo ra. Để thử nghiệm chức năng tạo file COFF mà bạn
đã add vào pn, chọn “Tools > [WinAVR] Make COFF”, quay lại thư mục chứa Project bạn sẽ
thấy 1 file mới “main.cof” được tạo.
Hãy tìm và copy file “AVR1.DSN” (file của phần mềm Proteus) trong bài học AVR1 vào
thư mục Project củ
a bạn, chạy AVR1.DSN với Proteus, đổi file program cho chip atmega8 thành
main.hex (xem lại bài AVR1 nếu bạn quên cách thực hiện). Chạy mô phỏng để kiểm tra kết quả,
nếu kết quả tương tự như trong AVR1, bạn đã thành công (<congrat ^.^). Lưu Project của bạn.
Như thế tôi đã giới thiệu cách hoạt động cùng với pn và MFile lập trình C cho AVR, phần
tiếp theo chúng ta tìm hiểu một số điểm cơ bản về C cho AVR thông qua việc giải thích ví dụ trên.


AUTO.NLU

For more details and questions, contact me:

IV. C cho AVR.
Tôi không có ý định nói về ngôn ngữ C ở đây nhưng chỉ giới thiệu một cách cơ bản nhất về
cách viết chương trình cho AVR bằng C. Để có thể hiểu và viết những chương trình phức tạp hơn,
bạn cần tự trang bị kiến thức về C, tài liệu này sẽ không giúp bạn phần đó. Tuy nhiên, nếu bạn
chưa tùng lập trình bằng C thì bạn cũng yên tâm đọc tài liệu này, vì ích ra tôi s
ẽ giải thích những
gì tôi viết.
Một chương trình C cho AVR thường bao gồm các thành phần như: chú thích (comments),
biểu thức (expressions), câu lệnh (statements), khối (blocks), toán tử, cấu trúc điều khiển (Flow
controls), hàm (functions)…

Chú thích (comments): có 2 cách để tạo phần chú thích trong C là chú thích từng dòng
bằng 2 dấu “
//” như trong dòng đầu của đoạn ví dụ “//day la chu thich, khong duoc bien dich” hoặc
chú thích block bằng cách kẹp block cần chú thích vào giữa
/* ….*/ ví dụ:
/*
Ban co the type bat ky chu thich nao trong block nay
Ngay ca khi ban xuong dong
Phan chu thich thuong co mau chu la green
*/

Tiền xử lí (preprocessor): là một tiện ích của ngôn ngữ C, các preprocessor được trình biên
dịch xử lí trước tất cả các phần khác, các preprocessor có chức năng tương tự các Directive trong
ASM cho AVR.Các preprocessor được bắt đầu bằng dấu “#”, trong số các preprocessors trong
ngôn ngữ C có hai preprocessors được sử dụng phổ biến nhất là #include và #define. Preprocessor
#include chỉ định 1 file được đính kèm trong quá trình biên dịch (tương đương .INCLUDE trong
ASM) và #define để định nghĩa 1 chuổi thay thế hoặc 1 macro. Xem các ví dụ sau:
#include <avr/io.h> :đính kèm nội dung file io.h trong lúc biên dịch (file io.h nằm trong thư mục
con avr của thư mục include trong thư mục cài đặt của WinAVR).
#define max (a,b) ((a)>(b)? (a): (b)) : định nghĩa một macro tìm số lớn nhất trong 2 số a và b,
trong chương trình nếu bạn gọi x=max(2,3) thì kết quả thu được x=3.

Biểu thức (Expressions): là 1 phần của các câu lệnh, biểu thức có thể bao gồm biến, toán
tử, gọi hàm…, biểu thức trả về 1 giá trị đơn. Biểu thức không phải là 1 câu lệnh hoàn chỉnh. Ví
dụ: PORTB=val.
Câu lệnh (Statement): thường là 1 dòng lệnh hoàn chỉnh, có thể bao gồm các keywords,
biểu thức và các câu lệnh khác và được kết thúc bằng dấu “;”. Ví dụ:
unsigned char val=1; val*=2;
…là các câu lệnh.
Khối (Blocks): là sự kết hợp của nhiều câu lệnh để thực hiện chung 1 nhiệm vụ nào đó,

khối được bao bởi 2 dấu mở khối “{“ và
đóng khối “}”: ví dụ 1 khối:
while(1){
PORTB=val;
_delay_loop_2(65000);
val*=2;
if (!val) val=1;
}

AUTO.NLU

For more details and questions, contact me:

Toán tử (Operators): là những ký hiệu báo cho trình biên dịch các nhiệm vụ cần thực hiện,
các bảng bên dưới tóm tắt các toán tử C dùng cho lập trình AVR:
Bảng 1 các toán tử đại số: dùng thực hiện các phép toán đại số quen thuộc, trong đó đáng chú ý là
các toán tử “++” (tăng thêm 1) và “ “ (bớt đi 1), chú ý phân biệt y=x++ và y=++x, ví dụ ta có x=3 trong
khi y=x++ nghĩa là gán x cho y rồi sau đó tăng x thêm 1, điều này không ảnh hưởng đến y (cuối cùng y=3,
x=4) trong khi y=++x nghĩa là tăng x trước r
ồi mới gán cho y (cuối cùng y=x=4), tương tự cho các trường
hợp của toán tử “ “ .

Bảng 2 Toán tử truy cập và kích thức : toán tử [] thường được sử dụng khi bạn dùng mảng trong
lúc lập trình, phần tử thứ của mảng sẽ được truy xuất thông qua [i], chú ý mảng trong C bắt đầu từ 0.










AUTO.NLU

For more details and questions, contact me:

Bảng 3 Toán tử Logic và quan hệ: thực hiện các phép so sánh và logic, thường được dùng làm
điều kiện trong các cấu trúc điều khiển, chú ý toán tử so sánh bằng “==”, toán tử này khác với toán tử gán
“=”, trong khi y = x nghĩa là lấy giá trị của x gán cho y thì (y== x) nghĩa là “nếu y bằng x”.

Bảng 4 Toán tử thao tác Bit (Bitwise operator): là các toán tử thực hiện trên từng bit nhị phân của
các con số, các toán tử dịch trái “<<” và “>>” rất thường được sử dụng khi sử lí số.

Bảng 5 các toán tử khác :là 1 số toán tử đặc biệt rất hay sử dụng nhưng chúng ta thường không để
ý vì vai trò của chúng rất dễ nhận thấy. Đặc biệt chú ý toán tử “?:” là 1 toán tử rất đặc biệt của C so với
các ngôn ngữ lập trình khác, “?:” là toán tử 3 ngôi duy nhất có thể dùng thay thế cho cấu trúc “if” đơn giản.



AUTO.NLU

For more details and questions, contact me:

Cấu trúc điều khiển (Flow Controls): các cấu trúc điều khiển biến ý tưởng của bạn thành
hiện thực. Một số cấu trúc điều khiển cơ bản trong C như sau:
- “If (điều kiện) statement;” : nếu điều kiện là đúng thì thực hiện statement theo sau,
statement có thể được trình bày cùng dòng hoặc dòng sau điều khiển If. Điều kiện có thể là một
biểu thức bất kỳ, có thể là sự kết hợp của nhiều điều kiện bằng các toán tử quan hệ AND (&&),
OR (||)…Điều kiện được cho là đúng khi nó khác 0, ví dụ if (1) thì điều kiện hiển nhiên là đúng.

Xét một vài ví dụ dùng cấu trúc if như sau:
If (!val) val=1; nghĩa là nếu val bằng 0 thì chương trình sẽ gán cho val giá trị là 1, “!” là
toán tử NOT, NOT của một số khác 0 thì bằng 0, ngược lại, NOT của 0 thì thu được kết quả
là 1. Trong ví dụ này, nếu val bằng 0 thì !val sẽ bằng 1, như thế điều kiện sẽ trở thành đúng
và câu lệnh “val=1”
được thực thi.
If (x==1 && y==2) result=’A’; nghĩa là nếu x bằng 1 và y bằng 2 thì gán ký tự ‘A’ cho biến
result. Trong ví dụ này, toán tử logic “&&” được sử dụng để “nối” 2 điều kiện lại, bạn hoàn
toàn có thể sử dụng nhiều toán tử logic khác nếu cần thiết.
Trong trường hợp bạn muốn thực thi nhiều câu lệnh cùng lúc nếu một điều kiện nào
đó thỏa
thì bạn cần đặt tất cả các câu lệnh đó trong 1 khối như bên dưới:
If (điều kiện) {
Statement1;
Statement2;

}
- “If (điều kiện ) statement1; else statement2; ”: nếu điều kiện đúng thì thực hiện
statement1, ngược lại thực thi statement2. Việc đặt các statement và else trên cùng 1 dòng hay
trên những dòng khác nhau đều không ảnh hưởng đến kết quả.
Tương tự trường hợp trên, nếu có nhiều statements thì cần đặt chúng trong 1 khối.
If (điều kiện) {
Statement1;
Statement2;

}else {
Statement1;
Statement2;

}

Ngoài ra, bạn cũng có thể đặt nhiều cấu trúc if…else… lồng vào nhau.
AUTO.NLU

For more details and questions, contact me:

- Trong trường hợp có nhiều khả năng có thể xảy ra cho 1 biểu thức (hay 1 biến), ứng với
mỗi khả năng bạn cần chương trình thực hiện một việc nào đó, khi này bạn nên sử dụng cấu trúc
switch. Cấu trúc này được trình bày như bên dưới.
switch (biểu thức) {
case hằng_số_1:
các statement1;
break;
case hằng_số_2:
các statement2;
break;

Default:
các statement khác;
}
Hãy xét 1 ví dụ bạn kết nối 2 chip AVR với nhau, 1 chip làm Master sẽ ra các lệnh điều
khiển chip Slaver, chip Slaver nhận mã lệnh từ Master và thực hiện các công việc được thảo
hiệp trước. Giả sử mã lệnh được lưu trong biến Command, dưới đây là chương trình ví dụ
cách xử lí của chip Slaver ứng với từng mã lệnh.
switch (Command){
case 1:
PWM=255;
ON_Motor();
break;
case 2:
PWM=0;

OFF_Motor();
break;
default:
Get_Cmd();
break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Nếu Command=1, gán giá trị 255 cho biến PWM và gọi chương trình con ON_Motor().
Trong trường hợp này, break được sử dụng, break nghĩa là thoát khỏi cấu trúc điều khiển
hiện tại ngay lập tức, như vậy sau khi thực hiện 2 lệnh, switch kết thúc mà không cần xét đến
các trường hợp khác. Bây giờ, nếu Command=2, gán giá trị 0 cho biến PWM và gọi chương
trình con OFF_Motor(), trong tất cả các trường h
ợp còn lại (default), thực hiện chương trình
con Get_Cmd().
AUTO.NLU

For more details and questions, contact me:


- “while (điều kiện ) statement1;”: là một cấu trúc lặp (Loop), ý nghĩa của cấu trúc while là
khi điều kiện còn đúng thì sẽ thực hiện statement1 (hoặc các statements nếu chúng được đặt trong
1 khối {} như trong trường hợp của if được giới thiệu ở trên). Cẩn thận, bạn rất dễ rơi vào một
vòng lặp “không lối thoát” với while nếu điều kiện luôn luôn đ
úng.
- “for (biểu_thức_1; biểu_thức_2; biểu_thức_3) statement;”: là một cấu trúc lặp khác, trong
cấu trúc for, biểu_thức_1 thường được hiểu là khởi tạo, biểu_thức_2 là điều kiện và biểu_thức_3
là biểu thức được thực hiện sau. Cấu trúc for này tương đương với cấu trúc while sau:
biểu_thức_1;

while (
biểu_thức_2){
statement;
biểu_thức_3;

}
Các biểu thức trong cấu trúc for có thể vắng mặt trong cấu trúc nhung các dấu “;” thì không
được bỏ. Nếu bạn viết for( ; ; ) tương đương với vòng lặp vô tận while (1).
Cấu trúc for thường được dùng để thực hiện 1 hay những công việc nào đó trong số lần nào
đó, ví dụ bên dưới thực hiện xuất các giá trị từ 0 đến 200 ra PORTB, sau mỗi lần xuất sẽ gọi
lệnh delay trong 65000 chu kỳ máy.
for (uint8_t i=0; i<=200; i++){
PORTB=i;
_delay_loop_2(65000);
}
Chú ý, bạn có thể thực hiện việc khai báo 1 biến (xem phần khai báo biến bên dưới) ngay
trong cấu trúc for nếu biến lẩn đầu được sử dụng. ví dụ trên được hiểu như sau: khai báo 1
biến i kiểu byte không âm, cho giá trị khởi đầu cho i=0 (chỉ thực hiện 1 lần duy nhất), kiểm
tra điều kiện i<=200 (nhỏ hơn hoặc bằng), nếu điều kiện còn đúng, thực hiện 2 statements
trong block {}, sau đó quay về

để thực hiện i++ (tăng i thêm 1) rồi lại kiểm tra điều kiện
i<=200 và quá trình lặp lại. Như thế đoạn code trong {} được thực thi khoảng 201 lần trước
khi biến i bằng 201 và điều kiện i<=200 sai.
Hàm (Functions): ngôn ngữ C bao gồm tập hợp của rất nhiều hàm, mỗi hàm thực hiện một
chức năng cụ thể, các hàm trong C thường được thiết kết rất nhỏ gọn, để có các hàm phức tạp
người dung cần tự tạo ra. Hàm C cho AVR được định nghĩa trong thư viện avr-libc, ngoài các
hàm C thông thường, avr-libc còn chứa rất nhiều các hàm riêng dùng riêng cho chip AVR, các
hàm này được khai báo trong các file header riêng, để sử dụng hàm nào, bạn cần #include file
header tương ứng (tham kh
ảo tài liệu “avr-libc user manual” để biết them chi tiết, trong tài liệu
này, khi cần sử dụng một hàm nào tôi sẽ nói rõ file header cần thiết). Ví dụ:
_delay_loop_2(65000) là một hàm được định nghĩa trong file “delay.h” (trong thư mục
C:\WinAVR\avr\include\util), hàm này thực hiện việc delay khoảng 65000 chu kỳ máy.


AUTO.NLU

For more details and questions, contact me:

Có 4 hàm delay bạn có thể sử dụng sau khi include file <util/delay.h> đó là:
• _delay_loop_1(uint8_t __count) : delay theo một số lần chu kỳ máy nhất định (biến
__count), số lượng chu kỳ delay là số 8 bit (từ 0 đến 255).
• _delay_loop_2(uint16_t __count) : delay theo một số lần chu kỳ máy nhất định (biến
__count), số lượng chu kỳ delay là số 16 bit (từ 0 đến 65535).
Chú ý: thực chất 2 hàm delay trên được định nghĩa trong file header “delay_basic.h”.
• _delay_us(double __us): delay 1 microsecond.
• _delay_ms(double __ms): delay 1 milisecond.
Chú ý: để dùng 2 hàm _delay_us và _delay_ms cần định ngh
ĩa tần số xung clock trong
Makefile (biến F_CPU), sử dụng 2 hàm này trực tiếp thường cho kết quả không như mong

muốn, tôi sẽ trình bày cách sử dụng 2 hàm này trong ví dụ bên dưới.
Từ khóa (Keywords): từ khóa là những từ quy định của ngôn ngữ C như tên các kiểu dữ
liệu (char, int, float,…), các cấu trúc điều khiển (if, for, while,…)…Một chú ý quan trọng đối với
từ khóa là bạn không được khai báo một biến có tên trùng với từ khóa (trong đa số các trình
IDE, như programmer notepad, các từ khóa sẽ được tự động nhận dạng và thể hiện với màu
(thường là màu blue
) khác các từ thông thường khác.
Main: một chương trình C cho AVR phải bao gồm 1 chương trình chính main, tất cả các nội
dung chính sẽ được đặt bên trong chương trình chính. Cấu trúc chương trình chính có thể như sau:
int main(void){
//noi dung chinh
Return 0; // gia tri tra ve cho chuong trinh chinh
}
Trong đó, int là kiểu giá trị trả về của main, từ khóa void nói rằng chương trình chính của
chúng ta không cần bất ký tham số nào kèm theo (just follow that way, I suggest)
Còn rất nhiều các vấn đề liên quan đến C cho AVR, chúng ta sẽ tìm hiểu trong lúc viết các ví
dụ cụ thể
.
* Đã đến lúc chúng ta tìm hiểu ý nghĩa đoạn code trong ví dụ 1:
Hãy nhìn lại đoạn code một lần nữa:
#include <avr/io.h>
#include <util/delay.h>
unsigned char val=1;
int main(void){
DDRB=0xFF; //set PORTB as output lines
while(1){
PORTB=val;
1
2
3

4
5
6
7
AUTO.NLU

For more details and questions, contact me:

_delay_loop_2(65000);
val*=2;
if (!val) val=1;
}
return 0;
}
8
9
10
11
12
13
- Trước hết là preprocessor đính kèm các file khi biên dịch, #include <avr/io.h> là đính kèm
file header io.h, file này thực ra không phải là file chứa các thông tin về chip nhưng nó sẽ làm một
nhiệm vụ trung gian là đính kèm 1 file khác tương ứng với biến MCU trong Makefile, ví dụ trong
Makefile, MCU=atmega8 thì dòng
“#include <avr/iom8.h>” được thực thi, file iom8.h được tự
động đính kèm kèm vào và file iom8.h mới thực chất chứa các định nghĩa cho chip ATmega8 (các
định nghĩa về địa chỉ thanh ghi, kích thước bộ nhớ,…). Điều này giúp bạn không cần nhớ hết tất
cả các file header của từng chip AVR.Nếu “không an tâm”, bạn có thể thêm dòng
#include
<avr/iom8.h>

sau khi include io.h (điều này không thật sự cần thiết). Ngoài ra, mỗi lần include
file io.h sẽ có 4 file header khác được đính kèm là “avr/sfr_defs.h”, “avr/portpins.h”,
“avr/common.h”, và “avr/version.h”. Tóm lại bạn cần (hoặc phải) include file io.h và khai báo
loại chip AVR trong file Makefile (dùng MFile, như hướng dẫn ở trên) là có thể an tâm viết
chương trình C cho AVR.
- Dòng thứ 2 include file header delay.h để sử dụng lệnh delay như đã đề cập ở trên.
- Dòng 3 : khai báo 1 biến tên val trong bộ nhớ SRAM, kiểu của val là unsigned char là
kiểu dữ liệu 8 bit không dấu có khoảng giá trị từ 0 đến 255. Biến val được dùng làm biến tạm để
chứa giá trước khi xuất ra PORTB. Biến trong C được khai báo bằng cách đặt kiểu biến trước sau
đó tên biến. Một số kiêu dữ liệu cơ bản trong C được tóm tắt trong bảng 6.
Bảng 5 các kiểu dữ liệu trong C
Tên kiểu dữ liệu (Data type) Số byte Khoảng dữ liệu (Range)
char 1 –127 to 127 or 0 to 255
unsigned char 1 0 to 255
signed char 1 –127 to 127
int 2 –32,767 to 32,767
unsigned int 2 0 to 65,535
signed int 2 Như kiểu int
short int 2 Như kiểu int
unsigned short int 2 0 to 65,535
signed short int 2 Như kiểu short int
long int 4 –2,147,483,647 to 2,147,483,647
signed long int 4 Như kiểu long int
unsigned long int 4 0 to 4,294,967,295
AUTO.NLU

For more details and questions, contact me:

Tên kiểu dữ liệu (Data type) Số byte Khoảng dữ liệu (Range)
long long int 8 –(2

63
–1) to 2
63
–1 (C99 only)
signed long long int 8 same as long long int (C99 only)
unsigned long long int 8 0 to 2
64
–1 (C99 only)
float 4 6 digits of precision
double 8 10 digits of precision
long double 12 10 digits of precision
Một số kiểu dữ liệu thông dụng nhất là char (1 byte), int (2 byte) và float. Từ khóa
unsigned được thêm trước 1 kiểu dữ liệu nguyên để chỉ định các số nguyên dương, khi đó khoảng
giá trị nguyên sẽ được tăng lên gần 2 lần. Ví dụ char chỉ các số nguyên từ -127 đến 127 thường
được dùng để chỉ mã ASCII của các ký tự trong bảng mã ASCII, nhưng unsigned char sẽ bao
gồm các s
ố nguyên dương từ 0 đến 255 và thường được dùng khi làm việc với các thanh ghi 8 bit.
Ngoài ra, avr-libc còn định nghĩa một số kiểu dữ liệu thay thế, chúng ta có thể dùng các kiểu
dữ liệu này thay cho các kiểu thông thường, xem tóm tắt như bên dưới.

Một khai báo Uint8_t val tương đương usigned char val, sử dụng kiểu khai báo nào là do
thói quen của người sử dụng. Chú ý là theo mặc định, một biến mới được khai báo theo cách
thông thường như trên sẽ được đặt trong SRAM, như các bạn đã biết SRAM trong AVR tương đối
nhỏ vì thế nên khai báo và sử dụng hợp lí biến, đừng khai báo quá nhiều biến nếu bạn không sử
dụng hết, đừng khai báo kiểu biến quá l
ớn so với giá trị thật sử dụng, tuy nhiên cũng không được
khai báo kiểu dữ liệu có kích thước quá nhỏ so với giá trị mà biến đó có thể vươn tới.
Sử dụng bộ nhớ chương trình (flash program memory) để lưu trữ dữ liệu không đổi là một
kỹ thuật khác để tiếc kiệm bộ SRAM, tôi sẽ đề cập vấn đề này trong 1 bài khác.
Cuối cùng về việc khai báo biến, một bi

ến có thể được gán giá trị khởi tạo ngay lúc khai
báo như trong trường hợp của chúng ta, biến val=1 lúc được khai báo.s
- Dòng 4 “int main(void){” bắt đầu chương trình chính.
- Dòng 5: “DDRB=0xFF” gán giá trị hexadecimal 0xFF (11111111) cho thanh thi điều khiển
của Port B, DDRB, Port B khi đó sẽ trở thành Port xuất. như thế trong C.
AUTO.NLU

For more details and questions, contact me:

- Dòng 6 “while (1){”: bắt đầu 1 vòng lặp vô tận.
- Dòng 7 và dòng 8: xuất val ra PORTB và gọi lệnh delay.
- Bạn cần chú ý 9 và 10, 2 dòng này có chức năng “xoay” giá trị của biến val để xuất ra
PORTB tạo hiệu ứng xoay vòng.
val*=2 được hiểu là val=val*2, đây là 1 kiểu viết thu gọn của C, nếu toán hạng thứ nhất và
kết quả trả về là cùng 1 biến, chúng ta có thể bỏ bớt 1 tên biến và di chuyển toán tử về bên
phải toán tử gán “=”. Ví dụ: i = i + 6 được rút gọn thành i + = 6.
i
= i + 6 i
= i + 6
i
+ = 6

Như thế sau câu lệnh val*=2 giá trị của val được tăng lên 2 lần. Ý nghĩa thật sự của việc gấp
đôi biến val là gì? Hãy nhìn vào giá trị nhị phân của val, lúc khai báo val, chúng ta gán cho
val = 1 hay val = 00000001 (nhị phân), sau khi gấp đôi lần thừ nhất, val = 2=00000010, tiếp
tục gấp đôi lần thứ hai, val = 2=00000100…có thể bạn đã thấy chuyện gì xảy ra?
Đây là cuâ
trả lời: “trong thao tác với số nhị phân, gấp đôi một số nghĩa là di chuyển số đó sang trái
1 vị trí”…Quá trình gấp đôi sẽ tiếp diễn đến lúc val = 128=10000000, nếu tiếp tục gấp đôi,
bạn nghĩ val = 256 ? Tuy nhiên bạn nhớ rằng chúng ta đã khai báo biến val có kiểu unsigned

char (8 bits), trong khi đó 256=100000000 (9 bits), nếu gán val = 256, chỉ có 8 bits thấp
(00000000) của 256 s
ẽ được gán cho val, kết quả là val = 0. Nói một cách khác, sau khi
val=128, val = 0, câu lệnh: “ if (!val) val=1; ” sẽ giúp cho quá trình quét lặp quay lại từ đầu
nếu val = 0. Mọi thứ đã rõ.
- Cuối cùng vì chương trình chính của chúng ta có kiểu int (int main…) chúng ta cần “trả
về” một giá trị nào đó, “return 0;” thực hiện trả về 0 (bạn có thể trả về giá trị nào tùy ý).
Có thể bạn đã biết cách viết 1 chương trình C cho AVR bằng WinAVR. Chúng ta hãy
“RJMP” đến một khái niệm mới, INTERRUPTS.

V. INTERRUPTS
Interrupts, thường được dịch là Ngắt, là một “tín hiệu khẩn cấp” gởi
đến bộ xử lí, yêu cầu
bộ xử lí tạm ngừng tức khắc các hoạt động hiện tại để “nhảy” đến một nơi khác thực hiện một
nhiệm vụ “khẩn cấp” nào đó, nhiệm vụ này gọi là trình phục vụ ngắt – isr (interrupt service
routine ). Sau khi kết thúc nhiệm vụ trong isr, bộ đếm chương trình sẽ được trả về giá trị trước đó
để bộ x
ử lí quay về thực hiện tiếp các nhiệm vụ còn dang dở. Như vậy, ngắt có mức độ ưu tiên xử
lí cao nhất, ngắt thường được dùng để xử lí các sự kiện bất ngờ nhưng không tốn quá nhiều thời
gian. Các tín hiệu dẫn đến ngắt có thể xuất phát từ các thiết bị bên trong chip (ngắt báo bộ đếm
timer/counter tràn, ngắt báo quá trình gởi dữ liệu bằng RS232 kết thúc…) hay do các tác nhân bên
ngoài (ngắt báo có 1 button đượ
c nhấn, ngắt báo có 1 gói dữ liệu đã được nhận…).
Ngắt là một trong 2 kỹ thuật “bắt” sự kiện cơ bản bao gồm: hỏi vòng (Polling) và ngắt. Hãy
tưởng tượng bạn cần thiết kế một mạch điều khiển hoàn chỉnh thực hiện rất nhiều nhiệm vụ bao
AUTO.NLU

For more details and questions, contact me:

gồm nhận thông tin từ người dùng qua các button hay keypad (hoặc keyboard), nhận tín hiệu từ

cảm biến, xử lí thông tin, xuất tín hiệu điều khiển, hiển thị thông tin trạng thái lên các LCD…(bạn
hoàn toàn có thể làm được với AVR <great >), rõ ràng trong các nhiệm vụ, việc nhận thông tin
người dùng (start, stop, setup, change,…) rất hiếm xảy ra (so với các nhiệm vụ khác) nhưng lại rất
“khẩn cấp”, được yêu tiên hang đầu. Nếu dùng Polling nghĩa là bạn cần viết 1 đoạ
n chương trình
“chuyên” thăm dò trạng thái của các button (tôi tạm gọi là Input() ) và bạn phải chèn đoạn chương
trình này vào rất nhiều vị trí trong chương trình chính để tránh trường hợp bỏ sót lệnh từ người
dùng, điều này thật lãng phí thời gian thực thi. Giải pháp cho vấn đề này là sử dụng ngắt, bằng
cách kết nối các button với đường ngắt của chip và sử dụng chương trình Input() làm isr của ngắt
đó, bạn không cần ph
ải chèn Input() trong lúc đang thực thi và vì thế không tốn thời gian cho nó,
Input() chỉ được gọi khi người dùng nhấn các button. Đó là ý tưởng của ngắt.
Hình 8 minh họa cách tổ chức ngắt thông thường trong các chip AVR, số lượng ngắt trên
mỗi dòng chip là khác nhau, ứng với mỗi ngắt sẽ có vector ngắt, vector ngắt là các thanh ghi có
địa chỉ cố định, được định nghĩa trước nằm trong phần đầu của bộ nhớ chương trình. Ví dụ vector
ngắt ngoài 0 (external interrupt 0) c
ủa chip atmega8 có địa chỉ là 0x009 (theo datasheet từ Atmel).
Trong lúc chương trình chính đang thực thi, nếu có một sự thay đổi dẫn đến ngắt xảy ra ở chân
INT0 (chân 4), bộ đếm chương trình (Program Counter) nhảy đến địa chỉ 0x009, giả sử ngay tại
địa chỉ 0x009 chúng ta có đặt 1 lệnh RJMP đến một trình phục vụ ngắt (IRS1 chẳng hạn), một lần
nữa bộ đếm chương trình nhảy đến IRS1 để thực thi trình phục vụ ng
ắt, kết thúc ISR1, bộ đếm
chương trình lại quay về vị trí trước đó trong chương trình chính, quá trình ngắt kết thúc. Không
mang tính bắt buộc nhưng tôi khuyên bạn nên tổ chức chương trình ngắt theo các này để tránh
những lỗi liên quan đến địa chỉ chương trình.

Hình 8. Interrupts.
Bảng 7 tóm tắt các vector ngắt có trên chip atmega8, cho các chip khác bạn hãy tham khảo
datasheet để biết thêm.
AUTO.NLU


For more details and questions, contact me:

Bảng 8 các vector ngắt và Reset trên chip Atmega8.

External Interrupt – Ngắt ngoài: phần này tôi dành giới thiệu các bạn cách cài đặt và sử
dụng ngắt ngoài vì đây là loại ngắt duy nhất độc lập với các thiết bị ngoại vi của chip, các ngắt
khác thường gắn với hoạt động của 1 thiết bị nào đó như Timer/Counter, giao tiếp nối tiếp
USART, chuyển đổi ADC…chúng ta sẽ khảo sát cụ thể khi tìm hiểu về hoạt động của các thiết bị
này.
Ngắt ngoài là cách rất hiệu quả để thực hiện giao tiếp giữa người dùng và chip, trên chip
atmega8 có 2 ngắt ngoài có tên tương ứng là INT0 và INT1 tương ứng 2 chân số 4 (PD2) và số 5
(PD3). Như tôi đã đề cập trong bài AVR2, khi làm việc với các thiết bị ngoại vi của AVR, hầu
như chúng ta chỉ thao tác trên các thanh ghi chức năng đặc biệt - SFR (Special Function
Registers) trên vùng nhớ IO, mội thiết bị bao gồm một tập hợp các thanh ghi điều khiển, trạng thái,
ngắt…khác nhau,
điều này đồng nghĩa chúng ta phải nhớ tất cả các thanh ghi của AVR nếu muốn
trở thành AVR Master
~
_^ (sounds impossible). Lúc này datasheet phát huy tác dụng, bạn phải
nhanh chóng download file datasheet của chip mình đang sử dụng, có rất nhiều nơi để download
như tại www.atmel.com
(of course) hay trên các trang web chuyên cung cấp IC datasheet free
(www.alldatasheet.com
là 1 ví dụ). Quay về với ngắt ngoài, có 3 thanh ghi liên quan đến ngắt
ngoài đó là MCUCR, GICR và GIFR. Cụ thể các thanh ghi được trình bày bên dưới.
AUTO.NLU

For more details and questions, contact me:


- Thanh ghi điều khiển MCU – MCUCR (MCU Control Register) là thanh ghi xác lập chế
độ ngắt cho ngắt ngoài, giả sử chúng ta kết nối các chân ngắt như hình 9.

Hình 9: kết nối ngắt ngoài cho atmega8.
Giả sử chúng ta kết nối các ngắt ngoài trên AVR mega8 như phía trái hình 9, các button
dùng tạo ra các ngắt, có 4 khả năng (tạm gọi là các MODE) có thể xảy ra khi chúng ta
nhấn và thả các button. Nếu không nhấn, trạng thái các chân INT là HIGH do điện trở kéo
lên, khi vừa nhấn 1 button, sẽ có chuyển trạng thái từ HIGH sang LOW, chúng ta gọi là
cạnh xuống - Falling Edge, khi button được nhấn và giữ, trạng thái các chân INT được xác
định là LOW và cuối cùng khi thả các button, trạng thái chuy
ển từ LOW sang HIGH, gọi
là cạnh lên – Rising Edge. Trong những trường hợp cụ thể, 1 trong 4 MODEs trên đều
hữu ích, ví dụ trong các ứng dụng đếm xung (đếm encoder của servo motor chẳng hạn) thì
2 MODE “cạnh” phải được dùng. Thanh ghi MCUCR chứa các bits cho phép chúng ta
chọn 1 trong 4 MODE trên cho các ngắt ngoài. Dưới đây là cấu trúc thanh ghi MCUCR
được trích ra từ datasheet của chip atmega8.

MCUCR là một thanh ghi 8 bit nhưng đối với hoạt động ngắt ngoài, chúng ta chỉ quan tâm
đến 4 bit thấp của thanh ghi này (4 bit cao dùng cho Power manager và Sleep Mode), 4 bit
thấp là các bit Interrupt Sense Control (ISC), 2 bit ISC11, ISC10 dùng cho INT1 và ISC01,
ISC00 dùng cho INT0. Hãy nhìn vào bảng tóm tắt bên dưới để biết chức năng của các bit
trên, đây là bảng “chân trị” của 2 bit ISC11, ISC10. Bảng chân trị cho các bit ISC01,
ISC00 hoàn toàn tương tự.
Bảng 9: INT1 Sence Control

AUTO.NLU

For more details and questions, contact me:

Thật dễ dàng để hiểu chức năng của các bit Sense Control, ví dụ bạn muốn set cho INT1 là

ngắt cạnh xuống (Falling Edge) trong khi INT0 là ngắt cạnh lên (Rising Edge), hãy đặt
dòng lệnh MCUCR =0x0B (0x0B = 00001011 nhị phân) trong chương trình của bạn.
- Thanh ghi điều khiển ngắt thông thường – GICR (General Interrupt Control Register)
(trên các chip AVR cũ, như các chip AT90Sxxxx, thanh ghi này có tên là thanh ghi mặt nạ
ngắt thông thường GIMSK, bạn tham khảo thêm datasheet của các chip này nếu cần sử
dụng đến). GICR cũng là 1 thanh ghi 8 bit nhưng chỉ
có 2 bit cao (bit 6 và bit 7) là được sử
dụng cho điều khiển ngắt, cấu trúc thanh ghi như bên dưới (trích datasheet).

Bit 7 – INT1 gọi là bit cho phép ngắt 1(Interrupt Enable), set bit này bằng 1 nghĩa bạn cho
phép ngắt INT1 hoạt động, tương tự, bit INT0 điều khiển ngắt INT0.
- Thanh ghi cờ ngắt thông thường – GIFR (General Interrupt Flag Register) có 2 bit INTF1
và INTF0 là các bit trạng thái (hay bit cờ - Flag) của 2 ngắt INT1 và INT0, nếu có 1 sự
kiện ngắt phù hợp xảy ra trên chân INT1, bit INTF1 được tự động set bằng 1 (tương tự cho
trường hợp của INTF0), chúng ta có thể sử dụng các bit này để nhận ra các ngắt, tuy nhiên
điều này là không cần thi
ết nếu chúng ta cho phép ngắt tự động, vì vậy thanh ghi này
thường không được quan tâm khi lập trình ngắt ngoài. Cấu trúc thanh ghi GIFR được trình
bày trong hình ngay bên dưới.

Sau khi đã xác lập các bit sẵn sàng cho các ngắt ngoài, việc sau cùng chúng ta cần làm là
set bit I, tức bit cho phép ngắt toàn cục, trong thanh ghi trạng thái chung của chip (thanh ghi
SREG, xem lại bài AVR2). Một chú ý khác là vì các chân PD2, PD3 là các chân ngắt nên bạn
phải set các chân này là Input (set thanh ghi DDRD).
Quá trình thiết lập ngắt ngoài được trình bày trong hình 10.

Hình 10: thiết lập ngắt ngoài.
AUTO.NLU

For more details and questions, contact me:


Interrupt với ASM:
Dưới đây tôi trình bày cách viết chương trình sử dụng ngắt ngoài bằng ngôn ngữ ASM, đối
với các ngắt khác bạn chỉ cần thêm các DIRECTIVE để định vị các vector ngắt tương ứng và viết
chương trình phục vụ ngắt tương ứng.
.CSEG
.INCLUDE "M8DEF.INC"
.ORG 0x000 ; Định vị vị trí đầu tiên
RJMP BATDAU

.ORG 0x001; Định vị vector ngắt ngoài 0 - INT0 (xem bảng vector)
RJMP INT0_ISR ; Nhảy đến INT0_ISR nếu có ngắt INT0 xảy ra
.ORG 0x002; Định vị vector ngắt ngoài 1 – INT1 (xem bảng vector)
RJMP INT1_ISR ; Nhảy đến INT1_ISR nếu có ngắt INT1 xảy ra

;Tương tự, định vị các vector ngắt khác ở đây………………
;………………………………………………………………

.ORG 0x020 ; Định vị chương trình chính
BATDAU:
; khởi tạo Stack
LDI R16, HIGH(RAMEND)
LDI R17, LOW(RAMEND)
OUT SPH, R16
OUT SPL, R17

; set chân PD2 và PD3 như các chân input
LDI R16, 0Bxxxx00xx ; x là trạng thái do bạn tự chọn, 0 hoặc 1
OUT DDRD, R16 ; PD2 và PD3 là input
LDI R16, 0Bxxxx11xx ; x là trạng thái do bạn tự chọn, 0 hoặc 1

OUT PORTD, R16 ; mắc điện trở kéo lên cho PD2, PD3


; khởi động ngắt
LDI R16, $0B ; $0B=00001011, INT1: ngắt cạnh xuống, INT0: ngắt cạnh lên
OUT MCUCR, R16 ; xuất giá trị điều khiển ra thanh ghi MCUCR
LDI R16, $C0 ;$C0=11000000: Enable INT1 và INT0
OUT GICR, R16 ;xuất giá trị điều khiển ra thanh ghi GICR
SEI ;set bit cho phép ngắt toàn cục
; Chương trình chính
MAIN:
;các công việc mà chương trình chính cần thực hiện………………
AUTO.NLU

For more details and questions, contact me:

;…………………………………………………………………….
RJMP MAIN

;và đây là định nghĩa trình phục vụ ngắt INT0_ISR…………………
INT0_ISR:
; các công việc cần thực hiện khi có ngắt ……………………
;……………………………………………………………….
RETI ; phải dùng lệnh RETI để quay về chương trình chính

;và đây là định nghĩa trình phục vụ ngắt INT1_ISR…………………
INT1_ISR:
; các công việc cần thực hiện khi có ngắt ……………………
;……………………………………………………………….
RETI ; phải dùng lệnh RETI để quay về chương trình chính

Bạn thấy các các ngắt được định vị nằm giữa vị trí 0x0000, khi mới khởi động, tại ví trí
0x000 là lệnh “RJMP BATDAU”, như thế các lệnh RJMP tại các vector ngắt và các ISR đều
không được thực hiện, chúng chỉ được thực hiện một cách tự động khi có ngắt.
Interrupt với C:
Avr-libc hỗ trợ một thư viện hàm cho ngắt khá hoàn hảo, để sử dụng ngắt trong chương trình
viết bằng C (avr-gcc) bạn chỉ cần include file “interrupt.h” nằm trong thư mục con “avr” là xong.
file header interrupt.h chứa định nghĩa các hàm và phương thức phục vụ cho viết trình phục vụ
ngắt, các vector ngắt không
được định nghĩa trong file này mà trong file iom8.h (cho atmega8).
Nếu bạn vô tình tìm thấy 1 chương trình ngắt nào đó không include file interrupt.h mà include
file signal.h thì bạn đừng ngạc nhiên, đó là cách viết cũ trong avr-gcc, thật ra bạn hoàn toàn có thể
sử dụng cách viết cũ vì các phiên bản mới của avr-libc (đi cùng với các bản WinAVR mới) vẫn hỗ
trợ cách viết này nhưng không khuyên khích bạn dùng.
Trong C các trình phục vụ ngắt có tên là ISR (vector_name), trong các phiên bản cũ, trình
phục vụ ngắ
t có tên SIGNAL(vector_name) nhưng cũng như file header signal.h, trình này vẫn
được hỗ trợ trong phiên bản mới nhưng không được khuyến khích sử dụng.
Mẫu trình phục vụ ngắt được trình bày như sau:
#include <avr/interrupt.h>

ISR (vector_name)
{
//user code here
}

AUTO.NLU

For more details and questions, contact me:

Trong đó vector_name là tên của các vector ngắt định nghĩa sẵn avr-libc, ISR là tên bắt buộc,

bạn không được dùng các tên khác tùy ỳ (nhưng có thể dùng SIGNAL như đã trình bày ở trên).
Đặc biệt, bạn có thể đặt ISR ở trước hoặc sau chương trình chính đều không ảnh hưởng vì thật ra,
đã có khá nhiều “công đoạn” được thực hiện khi bạn gọi ISR (nhưng bạn không thấy và cũng
không cần quan tâm), ISR luôn đượ
c trình biên dịch đặt ở ngoài vùng vector ngắt như cách
chúng ta thực hiện trong ASM, như thế một chương trình sử dụng nhiều loại ngắt sẽ phải có số
lượng trình ISR tương ứng nhưng với vector_name khác nhau, mỗi khi có ngắt xảy ra, tùy thuộc
vào giá trị của vector_name mà 1 trong các trình ISR được thực thi. Đối với các vector_name, để
biết được vector_name cho mỗi loại ngắt, bạn cần tham khảo tài li
ệu “avr-libc manual”. Bảng 10
tóm tắt các vector_name của một số ngắt thông dụng trên atmega8, bạn chú ý rằng các
vector_name trong avr-libc được định nghĩa rất khác nhau cho từng loại chip, bạn nhất thiết
phải sử dụng tài liệu “avr-libc manual” để biết chính xác các vector_name cho loại chip mà
bạn đang dùng.
Bảng 10: vector_name cho atmega8
Vector name Old vector name Description
ADC_vect SIG_ADC ADC Conversion Complete
ANA_COMP_vect SIG_COMPARATOR Analog Comparator
EE_RDY_vect SIG_EEPROM_READY EEPROM Ready
INT0_vect SIG_INTERRUPT0 External Interrupt 0
INT1_vect SIG_INTERRUPT1 External Interrupt Request 1
SPI_STC_vect SIG_SPI Serial Transfer Complete
SPM_RDY_vect SIG_SPM_READY Store Program Memory Ready
TIMER0_OVF_vect SIG_OVERFLOW0 Timer/Counter0 Overflow
TIMER1_CAPT_vect SIG_INPUT_CAPTURE1 Timer/Counter Capture Event
TIMER1_COMPA_vect SIG_OUTPUT_COMPARE1A Timer/Counter1 Compare Match A
TIMER1_COMPB_vect SIG_OUTPUT_COMPARE1B Timer/Counter1 Compare MatchB
TIMER1_OVF_vect SIG_OVERFLOW1 Timer/Counter1 Overflow
TIMER2_COMP_vect SIG_OUTPUT_COMPARE2 Timer/Counter2 Compare Match
TIMER2_OVF_vect SIG_OVERFLOW2 Timer/Counter2 Overflow

TWI_vect SIG_2WIRE_SERIAL 2-wire Serial Interface
USART3_UDRE_vect SIG_USART3_DATA USART3 Data register Empty

AUTO.NLU

For more details and questions, contact me:

Để thực hiện ví dụ sử dụng ngắt ngoài bằng C, tôi sẽ viết lại chương trình ví dụ của bài
AVR nhưng bằng ngôn ngữ C và sử dụng ngắt. Trong chương trình ví dụ của bài AVR2, chúng ta
thực hiện việc đếm lên và đếm xuống dùng 2 button, chúng ta sẽ vẫn thực hiện trên ý tưởng này
nhưng có chút thay đổi trong kết nối, trước hết bạn vẽ 1 mạch điện mô phỏng trong Proteus như
hình 11.
A
7
QA
13
B
1
QB
12
C
2
QC
11
D
6
QD
10
BI/RBO
4

QE
9
RBI
5
QF
15
LT
3
QG
14
U2
7447
R1
10
+5V
PB0/ICP
14
PB1/OC1A
15
PB2/SS/OC1B
16
PB3/MOSI/OC2
17
PB4/MISO
18
PB5/SCK
19
PC0/ADC0
23
PC1/ADC1

24
PC2/ADC2
25
PC3/ADC3
26
PC4/ADC4/SDA
27
PC5/ADC5/SCL
28
PC6/RESET
1
PB6/XTAL1/TOSC1
9
PB7/XTAL2/TOSC2
10
PD0/RXD
2
PD1/TXD
3
PD2/INT0
4
PD3/INT1
5
PD4/XCK/T0
6
PD5/T1
11
PD6/AIN0
12
PD7/AIN1

13
AVCC
20
AREF
21
U3
ATMEGA8

Hình 11: mạch điện mô phỏng ngắt.
Kết nối button đếm lên với ngắt INT0, button đếm xuống với INT1, PORTB được chọn làm
PORT xuất. Hãy chạy Programmer Notepad, tạo 1 Project mới tên AVR2-INT, type đoạn code
bên dưới vào 1 file new và lưu với tên main.c, add file này vào Project của bạn, sau đó tạo một
Makefile cho Project (xem lại ví dụ 1 để biết cách thực hiện).
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/delay.h>

int8_t val=0; //khai báo 1 biến val 8 bit, có dấu và giá trị khởi tạo bằng 0.
int main(void){

DDRD=0x00; //khai báo PORTD là Input để sử dụng 2 chân ngắt.
PORTD=0xFF; //sử dụng điện trở nội kéo lên.
DDRB=0xFF; //PORTB là Output để xuất LED 7 đoạn

MCUCR|=(1<<ISC11)|(1<<ISC01); //cả 2 ngắt là ngắt cạnh xuống
GICR |=(1<<INT1)|(1<<INT0); //cho phép 2 ngắt hoạt động
sei(); //set bit I cho phép ngắt toàn cục

DDRC=0xFF; //PORTC là Output
1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

×