Điều khiển pin bằng ngôn ngữ chính thống

Mô tả dự án: 

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:

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à :

  1. Cài đặt: Chọn chân pin, chọn kiểu Nhâp./Xuất.
  2. 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: 

http://maxembedded.com/2011/06/the-adc-of-the-avr/

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.

winkyeslaughlaugh

Tác giả:  Thái Sơn.

lên
30 thành viên đã đánh giá bài viết này hữu ích.
Các dự án được truyền cảm hứng

Select any filter and click on Apply to see results

Các bài viết cùng tác giả

Hướng dẫn sử dụng ic eeprom 24Cxx của Atmel và thư viện

 Bấy lâu nay bạn đã sử dụng sai eeprom của arduino? ..Một ngày bạn sử dụng eeprom bao nhiêu lần? Có bao giờ bạn lo lắng tới tuổi thọ của EEPROM ?smiley

EEPROM là còn được hiểu như một thẻ nhớ có thể lưu trữ dữ liệu ngay cả khi mất điện.

Bộ nhớ EEPROM có sẵn của arduino có số lần ghi/xóa 100000 lần,  nếu dùng hết số lần ghi cho phép, eeprom sẽ hỏng. Để khắc phục điều này, chúng ta cần sử dụng một ic eeprom ngoại vi thay vì sử dụng eeprom của arduino.

lên
25 thành viên đã đánh giá bài viết này hữu ích.

Giới thiệu cách sử dụng module GPS NEO 6 và NEO 7 của hãng Ublox

Hôm nay mình sẽ giới thiệu module GPS NEO 6 và NEO 7, rất cần thiết cho các dự án định vị vị trí và chuyển động, tốc độ cập nhật rất nhanh, trả về tọa độ rất chính xác, kết nối và sử dụng rất đơn giản là những ưu điểm của loại module này.

lên
18 thành viên đã đánh giá bài viết này hữu ích.