Nick Chung gửi vào
- 47125 lượt xem
Xin chào các bạn, việc điều khiển nhập/ xuất trên arduino bằng digitalRead/ Write hẳn đã quá quen thuộc . Hài lòng với tốc độ hiện có, vậy bạn có muốn tăng tốc độ đọc/ ghi lên gấp 14 lần, điều khiển cả 8 pin cùng lúc chỉ với một dòng lệnh không. Hãy đọc ngay bài viết này.
Bật mí..
Mình không nói sai đâu nhé, nhanh gấp 14 lần và điều khiển 8 pin cùng lúc một cách đúng nghĩa. Hehe.
Tại sao lại như vậy, hãy cùng tìm lý do ra đời của các dòng lệnh sau:
- pinMode();
- digitalRead();
- digitalWrite();
- analogRead();
Arduino là 1 vi điều khiển, mà ở đó người ta hướng tới sự thuận tiện trong mọi việc, đặc biệt là phần lập trình Code. Arduino sử dụng chip AVR, với những ai đã từng làm trên chip này hoặc chip tương đương, bạn đều biết chúng ta không hề sử dụng các dòng lệnh trên. Mà sử dụng ngôn ngữ chuẩn C (true C) của chip đó.
Và như vậy,từ sự thuận tiện mà người dùng Arduino vô tình đã bị giới hạn khả năng vì không biết được năng lực thực sự của AVR/arduino. Bài viết này sẽ giúp bạn có cái nhìn đúng đắn hơn về điều khiển Pin, giúp bạn cải thiện hiệu năng nhập/xuất trên arduino của bạn. Ok, bắt đầu nào.
Đối tượng hướng đến: Bài viết này dành cho beginner đã có 1,2 tuần trải nghiệm arduino.
CẤU TRÚC PIN.
Các chân nhập xuất trên arduino sử dụng một thanh ghi 8 bit (cảng-register) (port - register).
Mỗi bit được gọi là các chân (Port). Mình dùng từ này để phân biệt với Pin trên mặt arduino.
Thứ tự các chân Port được đánh theo mức bit tăng dần (từ phải qua trái) của 1 byte (8 bit).
Trên arduino 168/328 có 3 thanh ghi (cảng) với tên hiệu là:
- Cảng C (analog 0->5);
- Cảng D (digital 0->7);
- Cảng B (digitsl 8->13);
Khi đó các chân Port ( các Port) được đánh tên cùng với tên các Cảng của nó, ví dụ:
PB0: Port 0 của cảng B.
PE3: Port 3 của cảng E. ( cảng này có trên arduino mega).
Cách điều khiển chuẩn C của AVR với arduino.
Chuẩn bị:
- 1 arduino bất kì, mình chọn arduino uno r3.
- 1 bóng led.
- 1 con trở 220 ôm.
- Các linh phụ kiện ráp mạch…
Chúng ta quay lại với ví dụ blink nhấp nháy led.
int myPin = 4; void setup() { pinMode(myPin,OUTPUT); } void loop() { digitalWrite(myPin,HIGH); delay(1000); digitalWrite(myPin,LOW); delay(1000); }
Các bạn cần nhớ, thủ tục bắt buộc theo thứ tự là :
- Cài đặt: Chọn chân pin, chọn kiểu Nhâp./Xuất.
- Thao tác: Chọn chân pin, (chọn kiểu high/low).
Vậy khi viết bằng ngôn ngữ chuẩn của AVR thì sẽ như sau.
Hàm pinMode();
Như ta đã biết, pinMode() là hàm cài đặt.
Trên AVR, hàm này có cấu trúc :
DDR_tên cảng = trạng thái chân port .
- Chân Port đặt ở 1 (high): port thuộc cảng này ở trạng thái OUTPUT.
- Chân Port đặt ở 0 (low): port thuộc cảng này ở trạng thái INTPUT hoặc INPUT_PULLUP
Ví dụ 1.0
Đặt chân pin digital 4 là OUTPUT. Tức là chân port 4 thuộc cảng D ở OUTPUT;
Kể từ đây bảng có 2 phần, mà ở đó ý nghĩa và khả năng hai khối code là bằng nhau, chúng có thể thay thế cho nhau.
int myPin = 4; void setup() { pinMode(myPin,OUTPUT); } |
void setup(){ DDRD=B00010000; } |
Khi đó các chân còn lại được đặt ở Input.
Ví dụ 1.1
Đặt chân pin digital 4 và 7 là OUTPUT. Tức là chân port 4 ,7 thuộc cảng D là OUTPUT;
Đặt chân pin digital 8 là OUTPUT. Tức là chân port 0 thuộc cảng B là OUTPUT;
void setup() { pinMode(4,OUTPUT); pinMode(7,OUTPUT); pinMode(8,OUTPUT); } |
void setup(){ DDRD=B10010000; DDRB=B00000001; } |
Khi đó các chân còn lại được đặt ở Input.
Ví dụ 1.2: INPUT_PULLUP
Bạn cần 2 bước:
- B1: đặt port là input
- B2: đặt port có nội trở kéo pull up.
Gỉa sử đặt chân digital 4 là input_pullup ( chân port PD4).
void setup() { pinMode(4, INPUT_PULLUP); } |
void setup(){ DDRD=B00000000;// 8 pin là input , trong đó có pin 4 PORTD=B00010000;// đặt port 4 có nội trở kéo. } |
HÀM digitalWrite();
digitalWrite() sẽ thiết lập mức logic đầu ra là High(5v) hoặc Low (0v).
Trên AVR, hàm này có cấu trúc :
PORT_tên cảng= mức logic của chân Port.
- High (1): bit 1.
- Low (0): bit 0.
Ví dụ 2.1
Đặt mức logic cao tại Pin digital 4. Tức đặt mức logic cao tại chân Port 4 cảng D.
void setup(){ pinMode(4,OUTPUT); } void loop(){ digitalWrite(4, HIGH); } |
void setup(){ DDRD=B00010000; } void loop(){ PORTD=B00010000; } |
Ví dụ 2.2
Đặt mức logic cao tại Pin digital 5, 6, 7, 11, 12 .
Tức đặt mức logic cao tại chân Port 5, 6, 7 cảng D và Port 3, 4 cảng B.
void loop(){ digitalWrite(5, HIGH); digitalWrite(6, HIGH); digitalWrite(7, HIGH); digitalWrite(11, HIGH); digitalWrite(12, HIGH); } |
void loop(){ PORTD=B11100000; PORTB=B00011000; } |
Khi đó các pin còn lại (không được đặt bit 1) sẽ được coi là Low (0v).
Ví dụ 2.3
Tạo một hiệu ứng nhấp nháy giống ví dụ đầu .
void setup() { DDRD=B00010000;// port4, output } void loop() { PORTD=B00010000; delay(1000); PORTD=B00000000; delay(1000); }
Ví dụ 2.4
Điều khiển đồng bộ. - Kĩ thuật Shiftout
Điều khiển cùng lúc 8 pin, đây chính là điểm mạnh của thanh ghi.
void setup() { DDRD=B11111111; } void loop() { PORTD=B11111111; delay(1000); PORTD=B00000000; delay(1000); }
Hàm digitalRead();
Hàm này trả về mức logic (0/1) khi đặt một mức điện áp logic vào chân.
Khi đó chân Port phải được khai báo trạng thái là INPUT.
(PIN_tên cảng)&(chọn chân PORT);
Ví dụ 1: Đọc giá trị 1 pin
PIN digital 8
|
PORT PB0
|
void setup(){ Serial.begin(9600); pinMode(8, INPUT); } void loop(){ byte read_b=digitalRead(8); // nối pin 8 lên 5V Serial.println(read_b); } |
void setup(){ Serial.begin(9600); DDRB=B00000000; } void loop(){ byte read_b=PINB&B00000001; //nối pin 8 lên 5v Serial.println(read_b); } |
Tất nhiên, bạn có thể sử dụng hàm này với cả chân Analog.
PIN analog A0
|
PORT PC0
|
void setup(){ Serial.begin(9600); pinMode(A0, INPUT); } void loop(){ byte read_b=digitalRead(A0); //nối pin A0 lên 5v Serial.println(read_b); } |
void setup(){ Serial.begin(9600); DDRC=B00000000; } void loop(){ byte read_b=PINC&B00000001; //nối pin A0 lên 5v Serial.println(read_b); } |
Kết quả cho cả 4 ví dụ khi nối chân pin lên 5v trả về 1.
Ví dụ 2: đọc cùng lúc nhiều pin - Kĩ thuật Shift in
void setup() { Serial.begin(9600); DDRD=255;// = B11111111 PORTD=255;//INTPUT PULLUP } void loop() { byte r=PIND; Serial.println(r,BIN); }
HÀM analogRead()
Hàm này có nhiệm vụ đọc giá trị điện áp (0-> Xv) rồi trả về giá trị tương ứng thuộc đoạn (0->1023);
Về cấu trúc điều khiển thanh ghi, hàm analogRead(); của arduino là đầy đủ và tối ưu nhất, nên ta không cần xây dựng hàm này.
Sự khác biệt nằm ở chỗ , chúng ta có thể thay đổi tần số mà bộ ADC đang sử dụng ( tần số này cần có để bộ ADC quét giá trị trong khoảng 0->1023).
ADCSRA là viết của “ADC Control and Status Register A”
ADCSRA=
ADEN
|
ADSC
|
ADATE
|
ADIF
|
ADIE
|
ADPS2
|
ADPS1
|
ADPS0
|
Trong đó xxx là 3 bit đầu của thanh ghi, ở đó ta có thể điều khiển tần số quét.
Tần số tính bằng công thức:
- F_ADC=XTAL/ DF.
- XTAL: tần số thạch anh trên arduino của bạn (tần số hệ thống).
- DF: division factor: thương số
ADPS2 |
ADPS1 |
ADPS0 |
DF |
F_ADC (XTAL=16mhz) |
0 |
0 |
0 |
1 |
16MHZ |
0 |
0 |
1 |
2 |
8MHZ |
0 |
1 |
0 |
4 |
4MHZ |
0 |
1 |
1 |
8 |
2MHZ |
1 |
0 |
0 |
16 |
1MHZ |
1 |
0 |
1 |
32 |
500KHZ |
1 |
1 |
0 |
64 |
250KHZ |
1 |
1 |
1 |
128 |
125KHZ |
Ví dụ; muốn cài tần số quét là 125khz, XTAL=16mhz, thì DF=128.
void setup(){ pinMode(A0,INPUT); Serial.begin(9600); } unsigned int ana; void loop(){ ana=analogRead(0); Serial.println(ana); } |
void setup(){ pinMode(A0,INPUT); Serial.begin(9600); ADCSRA = ((ADCSRA&(B11111000)) | B00000010);// Cài tần số quét 4mhz } unsigned int ana; void loop(){ ana=analogRead(0); Serial.println(ana); } |
Tấn số mặc định: 125khz |
Ép xung tần số: 4mhz |
- Các bạn nhớ đặt dòng set ở cuối cùng hàm Setup(), (sau pinMode và trước analogRead). ( để đảm bảo là bạn không còn tác vụ nào nữa ngoài dòng set ADCSRA này).
- ADCSRA&(B11111000):xóa 3 bit cuối để đặt lại .
- Việc cài tần số quá cao sẽ gây ra các sai lệch không mong muốn.
Bạn có thể tìm hiểu vấn đề tại đây:
Phần 2: SỬ DỤNG TOÁN BIT
Bạn có thể tham khảo trước bài viết về toán bit tại đây:
http://arduino.vn/reference/bit-math-cac-phep-toan-thao-tac-tren-bit
http://arduino.vn/reference-tags/bit-math-toan-bit
- Khi muốn đặt bit 1, dùng toán hợp (OR);
- Khi muốn đặt bit 0, dùng toán và (AND);
Ta có sốA= B1000.1000 là biểu diễn nhị phân của số 136 . (mình dùng dấu “.” Cho dễ nhìn).
Muốn A=B1000.1100.
thì A=B1000.1000 | B0000.0100;
<=> A=A|B0000.0100;
Muốn A=B1000.0000.
thì A=B1000.1000 & B1111.0111;
<=> A=A&B1111.0111;
Để tìm vế phải của phép OR hoặc AND, ta dùng toán tử Bit Value : <<.
Ví dụ:
- (1<<0)=B0000.0001
- (1<<3)=B0000.1000
- (1<<7)=B1000.0000
Để đảo bit, ta dùng toán NOT (~).
- ~(1<<0)=~(B0000.0001)=B1111.1110
- ~(1<<3)=~(B0000.1000)=B1111.0111
PIN=4
|
PORT=PD4
|
byte pin=4; void setup(){ pinMode(pin,OUTPUT); } void loop(){ digitalWrite(pin,HIGH); delay(1000); digitalWrite(pin,LOW); delay(1000); } |
byte port=4; void setup(){ DDRD=DDRD|(1<<port); } void loop(){ PORTD=PORTD|(1<<port); delay(1000); PORTD=PORTD&(~(1<<port)); delay(1000); } |
Xin chú ý lại, toán tử _BV : (1<<X) không phải là toán tử dịch bit : (<</>>).
Macro có sẵn
Nó được định nghĩa để phục vụ hỗ trợ tìm các chân Port một cách đơn giản hơn.
Mỗi thanh ghi chỉ có 8 port (0->7), người ta có quy ước đặt tên chân port với thứ tự sau:
P_Tên cảng_Thứ tự.
Khi đó giá trị của macro đó sẽ nằm trong khoảng từ (0->7).
Hoặc bạn cũng có thể tìm ngay trên sơ đồ Mapping arduino của bạn.
void setup(){ DDRD=DDRD|(1<<PD4); } void loop(){ PORTD=PORTD|(1<<PD4); delay(1000); PORTD=PORTD&(~(1<<PD4)); delay(1000); }
Tạm kết.
Còn một hàm không được nhắc tới: analogWrite();. Trên arduino, hàm này được định nghĩa để tạo xung PWM, để có được hàm này ta cần xây dựng một hàm ở đó có sử lí của thời gian. Cho thuận tiện , ta giữ nguyên cách dùng analogWrite() được viết sẵn. (Mình sẽ viết ở bài khác vì nó rất dài và bạn cần biết nhiều hơn về timer /counter và interrupt chút nữa, tất nhiên là thú vị hơn vì ta có thể tạo ra xung PWM có độ rộng và tần số khác với analogWrite )
Chúng ta sẽ kiểm chứng tốc độ ở bài 2, bạn sẽ thấy bất ngờ về kết quả thu được:
Bài 2: Kiểm chứng tốc độ khi điều khiển các pin bằng ngôn ngữ AVR so với các lệnh trên Arduino
Như đã đề cập , đối tượng hướng đến của bài viết này dành cho beginner. Cho dù các beginner chưa muốn viết chương trình theo cách này, nhưng cũng cần phải biết tốc độ thực sự của arduino, trước khi bị sự thuận tiện hóa của các dòng lệnh có sẵn che đi tầm nhìn về chip AVR.
Mình đã cố gắng tối giản để viết chung 1 bài cho các bạn tiện quan sát.
Hi vọng bài viết này hữu ích cho cộng đồng, mong là bạn thấy thích.
Tác giả: Thái Sơn.