Trong AVR, mỗi PORT liên quan đến 3 thanh ghi (8 bits) có tên tương ứng là DDRx, PINx, và PORTx với “x” là tên của PORT, mỗi bit trong thanh ghi tương ứng với mỗi chân của PORT. Trong trường hợp của Atmega128 “x” là B, C, D, E, G.
Ví dụ: Chúng ta quan tâm đến PORTB thì 3 thanh ghi tương ứng có tên là DDRB, PINB và PORTB, trong đó 2 thanh ghi PORTB và PINB được nối trực tiếp với các chân của PORTB, DDRB là thanh ghi điều khiển hướng ( Input hoặc Output). Viết giá trị 1 vào một bit trong thanh ghi DDRB thì chân tương ứng của PORTB sẽ là chân xuất (Output), ngược lại giá trị 0 xác lập chân tương ứng là ngõ nhập. Sau khi viết giá trị điều khiển vào DDRB, việc truy xuất PORTB được thực hiện thông qua 2 thanh ghi PINB và PORTB (Nguồn: hocavr.com)
Include các thư viện cần thiết
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <avr/io.h> #include <avr/interrupt.h> // sử dụng ngắt #include <util/delay.h> // sử dụng các hàm delay #include <avr/eeprom.h> // dùng cho đọc ghi vào eeprom |
Khai báo output cho các chân điều khiển ( điều khiển Relay, van điện tử, màn hình LCD..)
– Để khai báo cho 1 port là output ( ví dụ PORTA):
1 2 |
DDRA=0xFF; PORTA=0xFF; // dùng điện trở trong kéo lên, mặc định output mức cao. |
– Khai báo macro cho việc điều khiển các chân output: set (lên 1) hoặc clear (xóa về 0)
1 2 3 4 5 6 |
#ifndef cbi #define cbi(port, bit) (port) &= ~(1 << (bit)) // macro cho clear bit #endif #ifndef sbi #define sbi(port, bit) (port) |= (1 << (bit)) // macro cho set bit #endif |
– Giải thích macro:
~(1 << (bit) // ví dụ: ~(1<<3) = ~( 00000001<<3) = ~ (00001000) = 11110111
Trong file avr\include\avr\iom128.h ta có PORTG = _SFR_MEM8(0x65), chính là giá trị của thanh ghi tại địa chỉ 0x65 trong bộ nhớ.
Vì vậy cbi(PORTG, 3) = (gia_tri_thanh_ghi 0x65) &= 11110111 , lúc này bit số 3 của thanh ghi nối trực tiếp với PORTG được ghi vào 0 => Chân số 3 PORTG được đưa về 0.
Ví dụ:
1 2 3 4 5 6 7 8 9 10 |
int main(void) { DDRG=0xFF; // thiết lập PORT G là output PORTG=0xFF; while(1) { cbi(PORTG, 3) // đưa chân số 3 PORTG về 0 sbi(PORTG, 1) // đưa chân số 1 PORTG lên 1 } } |
Khai báo cho việc đọc các giá trị input ( công tắc, nút nhấn, cảm biến dò đường,…)
– Để khai báo cho 1 port là input ( ví dụ PORTA):
1 2 |
DDRA=0; PORTA=0xFF; // dùng điện trở trong kéo lên để nhận input chính xác. |
– Để đọc được giá trị input cần sử dụng một số macro sau:
#define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))
Cách hoạt động của macro này như sau:
Định nghĩa macro bit_is_set được khai báo trong hardware/tools/avr/avr/include/avr/sfr_defs.h
+ Trong đó _BV(bit) là một macro cũng được định nghĩa trong file nêu trên, với ý nghĩa là chuyển giá trị của một chân về dạng 8 bit.
#define _BV(bit) (1 << (bit))
// ví dụ _BV(3) thì thực hiện 00000001 << 3 => 00001000=0x08
+ _SFR_BYTE(sfr) là một macro trả về giá trị theo dạng byte của một địa chỉ.
#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr))
Trong đó _MMIO_BYTE lại được định nghĩa như sau:
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
Tóm lại, trình biên dịch sẽ thực hiện lệnh sau với _SFR_BYTE(sfr):
_SFR_BYTE(sfr) *(volatile uint8_t * uint8_t &(sfr))
Và kết quả nhận được đó là giá trị của thanh ghi có địa chỉ là sfr
(_SFR_BYTE(sfr) & _BV(bit)) sẽ thực hiện thao tác với 1 bít thứ tự là bit với thanh ghi sfr. Kết quả sẽ trả về 1 nếu bit đó bằng 1, trả về 0 nếu bit đó bằng 0.
Ví dụ:
Ta có câu lệnh kiểm tra giá trị input tại chân số 3 của PORTG như sau:
bit_is_set(PING, 3)
Tương đương với thực hiện lệnh
(_SFR_BYTE(PING) & _BV(3))
ó (giá trị thanh ghi PING) AND (0x08)
=> Nếu giá trị tại chân 3, PORTG bằng 1, bit_is_set(PING, 3) trả về giá trị khác 0
=> Nếu giá trị tại chân 3, PORTG bằng 0, bit_is_set(PING, 3) trả về giá trị bằng 0
Với trường hợp macro bit_is_clear tương tự.
#define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit)))
Ứng dụng:
Đọc nút nhấn, công tắc:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#define nut_nhan bit_is_clear(PING,0) // nút nhấn nối với chân số 0 PORTG #define cong_tac bit_is_clear(PING,1)// công tắc nối với chân số 1 PORTG int main(void) { DDRG=0; // thiết lập PORT G là input PORTG=0xFF; while(1) { if(nut_nhan) { thuc_hien_lenh;} if(cong_tac) { thuc_hien_lenh;} } } |
Khai báo cho điều xung PWM điều khiển mạch cầu H hoặc PID
Timer/Counter là các module độc lập với CPU. Chức năng chính của các bộ Timer/Counter, như tên gọi của chúng, là định thời (tạo ra một khoảng thời gian, đếm thời gian…) và đếm sự kiện. Trên các chip AVR, các bộ Timer/Counter còn có thêm chức năng tạo ra các xung điều rộng PWM (Pulse Width Modulation), ở một số dòng AVR, một số Timer/Counter còn được dùng như các bộ canh chỉnh thời gian (calibration) trong các ứng dụng thời gian thực. Các bộ Timer/Counter được chia theo độ rộng thanh ghi chứa giá trị định thời hay giá trị đếm của chúng, cụ thể trên chip Atmega128 có 2 bộ Timer 8 bit (Timer/Counter0 và Timer/Counter2) và 2 bộ 16 bit (Timer/Counter1 và Timer/Counter3). Chế độ hoạt động và phương pháp điều khiển của từng Timer/Counter cũng không hoàn toàn giống nhau. Ví dụ:
Timer/Counter1: là bộ định thời, đếm đa năng 16 bit. Bộ Timer/Counter này có 5 chế độ hoạt động chính. Ngoài các chức năng thông thường, Timer/Counter1 còn được dùng để tạo ra xung điều rộng PWM dùng cho các mục đích điều khiển. Có thể tạo 2 tín hiệu PWM độc lập trên các chân OC1A (chân 15) và OC1B (chân 16) bằng Timer/Counter1.
Timer/Counter2: tuy là một module 8 bit như Timer/Counter0 nhưng
Timer/Counter2 có đến 4 chế độ hoạt động như Timer/Counter1, ngoài ra nó nó còn được sử dụng như một module canh chỉnh thời gian cho các ứng dụng thời gian thực (chế độ asynchronous). (nguồn hocavr.com)
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 35 |
//Khai báo cho các cổng điều xung tương ứng với các thanh ghi điều khiển PWM #define Banh_trai OCR1AL //PWM 4 onboard #define Banh_phai OCR1BL //PWM 5 onboard #define Banh_1 OCR1CL //PWM 6 onboard #define tay_xoay_tr OCR3AL //PWM 1 onboard #define tay_xoay_ph OCR3BL //PWM 2 onboard //Khai bao timer 3 cho điều xung các cổng PWM1,2,3 trên board mạch //Atmega128 TCCR3A=0; // 0xfd//(WGM13=0, WGM12=0, WGM11=0, WGM10=1) giá tri TOP là 1 hang so, TOP = 255 TCCR3B=0; // 0x04// COMA1=COMA0=COMB1=COMB0 // GIA TRI XUNG RA HIGH(COMPARE) LOW(TOP) OCR3AL=0; //pwm1A value => PWM1 onboard OCR3BL=0; //pwm1B value => PWM2 onboard OCR3CL=0; //=> PWM3 onboard //Khai bao timer 1 cho PWM4,5,6 trên board mạch TCCR1A=0xFD; //(WGM13=0, WGM12=0, WGM11=0, WGM10=1) giá tri TOP là 1 hang so, TOP = 255 TCCR1B=0x04; // COMA1=COMA0=COMB1=COMB0 => GIA TRI XUNG RA HIGH (COMPARE) LOW(TOP) OCR1AL=0; //pwm1A value => PWM4 onboard OCR1BL=0; //pwm1B value => PWM5 onboard OCR1CL=0; //=> PWM6 onboard |
Khai báo cho ngắt ( đọc Encoder, đọc công tắc hành trình…)
Ngắt là gì? 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ở.
Ví dụ: Kkhai báo ngắt cho INT6,7 , bắt bất kì thay đổi nào trên chân vi điều khiển
1 2 3 4 5 |
EICRA=0x00; // INT0,1,2,3 không sử dụng EICRB=0x50; // INT6,7 sử dụng chế độ ngắt bắt bất kì thay đổi nào của cạnh EIMSK=0xC0; // khai báo cho INT0,1,2,3,4,5 off và INT6,7 on EIFR=0xC0; // cho phép cờ ngắt INT 6,7 sei(); // cho phep ngat toan cuc |
Các khai báo và ứng dụng này sẽ được sử dụng cụ thể trong bài lập trình dò đường, nên tôi không để code mẫu hoàn chỉnh nào ở đây, nội dung bên trên chỉ dùng tham khảo để các bạn hiểu code hơn cho các bài tới.