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

Chia sẻ: Le Van Tien | Ngày: | Loại File: PDF | Số trang:27

1
699
lượt xem
312
download

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

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Đâ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).

Chủ đề:
Lưu

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

  1. AUTO.NLU 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 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. Đâ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 (I2C)…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. For more details and questions, contact me: thanhtam.h@gmail.com
  2. AUTO.NLU - 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: http://winavr.sourceforge.net/index.html. 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 http://www.pnotepad.org/ ) (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. For more details and questions, contact me: thanhtam.h@gmail.com
  3. AUTO.NLU File Editor Vùng Project Kết quả biên dịch 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. For more details and questions, contact me: thanhtam.h@gmail.com
  4. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  5. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  6. AUTO.NLU 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). For more details and questions, contact me: thanhtam.h@gmail.com
  7. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  8. AUTO.NLU 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; } For more details and questions, contact me: thanhtam.h@gmail.com
  9. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  10. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  11. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  12. AUTO.NLU - 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){ 1 case 1: 2 PWM=255; 3 ON_Motor(); 4 break; 5 case 2: 6 PWM=0; 7 OFF_Motor(); 8 break; 9 default: 10 Get_Cmd(); 11 break; 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(). For more details and questions, contact me: thanhtam.h@gmail.com
  13. AUTO.NLU - “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. For more details and questions, contact me: thanhtam.h@gmail.com
  14. AUTO.NLU 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> 1 #include <util/delay.h> 2 unsigned char val=1; 3 int main(void){ 4 DDRB=0xFF; //set PORTB as output lines 5 while(1){ 6 PORTB=val; 7 For more details and questions, contact me: thanhtam.h@gmail.com
  15. AUTO.NLU _delay_loop_2(65000); 8 val*=2; 9 if (!val) val=1; 10 } 11 return 0; 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 For more details and questions, contact me: thanhtam.h@gmail.com
  16. AUTO.NLU Tên kiểu dữ liệu (Data type) Số byte Khoảng dữ liệu (Range) –(263–1) to 263–1 (C99 only) long long int 8 signed long long int 8 same as long long int (C99 only) 0 to 264–1 (C99 only) unsigned long long int 8 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. For more details and questions, contact me: thanhtam.h@gmail.com
  17. AUTO.NLU - 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 For more details and questions, contact me: thanhtam.h@gmail.com
  18. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  19. AUTO.NLU 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. For more details and questions, contact me: thanhtam.h@gmail.com
  20. AUTO.NLU - 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 For more details and questions, contact me: thanhtam.h@gmail.com
Đồng bộ tài khoản