Encoder là gì?
Đo tốc độ động cơ dùng encoder, tín hiệu từ encoder tạo ra các dạng xung vuông có tần số thay đôi phụ thuộc vào tốc độ động cơ. Do đó các xung vuông này được đưa vào bộ vi xử lý để đếm số xung trong khoảng thời gian cho phép từ đó ta có thể tính được giá trị vận tốc của động cơ.


Nguyên tắc hoạt động của Encoder
Nguyên lý cơ bản của encoder, đó là một đĩa tròn xoay, quay quanh trục. Trên đĩa có các lỗ (rãnh). Người ta dùng một đèn led để chiếu lên mặt đĩa. Khi đĩa quay, chỗ không có lỗ (rãnh), đèn led không chiếu xuyên qua được, chỗ có lỗ (rãnh), đèn led sẽ chiếu xuyên qua. Khi đó, phía mặt bên kia của đĩa, người ta đặt một con mắt thu. Với các tín hiệu có, hoặc không có ánh sáng chiếu qua, người ta ghi nhận được đèn led có chiếu qua lỗ hay không. Số xung đếm được và tăng lên nó tính bằng số lần ánh sáng bị cắt.
Như vậy là encoder sẽ tạo ra các tín hiệu xung vuông và các tín hiệu xung vuông này được cắt từ ánh sáng xuyên qua lỗ. Nên tần số của xung đầu ra sẽ phụ thuộc vào tốc độ quay của tấm tròn đó.

Các loại cơ bản của Encoder là gì?
Gồm 2 loại chính: Encoder tuyệt đối và Encoder tương đối.
Encoder tuyệt đối (adsolute encoder): Đã gọi là tuyệt đối thì tức là tín hiệu ta nhận được từ Encoder cho biết chính xác vị trí của Encoder mà người sử dụng không phải xử lý thêm gì cả.

Encoder kiểu tuyệt đối có kết cấu gồm những phần sau: Bộ phát ánh sáng (LED), đĩa mã hóa (có chứa dải băng mang tín hiệu), một bộ thu ánh sáng nhạy với ánh sáng phát ra (photosensor). Đĩa mã hóa ở encoder tuyệt đối được chế tạo từ vật liệu trong suốt, người ta chia mặt đĩa thành các góc đều nhau và các đường tròn đồng tâm.
Các đường tròn đồng tâm và bán kính giới hạn các góc hình thành các phân tố diện tích. Tập hợp các phân tố diện tích cùng giới hạn bởi 2 vòng tròn đồng tâm gọi là dải băng. Số dải băng tùy thuộc vào công nghệ sản xuất (chủng loại sản phẩm), ứng với một dải băng ta có một đèn LED và một bộ thu.
+ Ưu điểm: giữ được giá trị tuyệt đối khi Encoder mất nguồn.
+ Nhược điểm: giá thành cao vì chế tạo phức tạp, đọc tín hiệu khó.
Encoder tương đối
Về cơ bản thì Encoder kiểu tương đối đều giống nhau, chỉ khác ở đĩa mã hóa. Ở encoder tương đối thì đĩa mã hóa gồm 1 dải băng tạo xung. Ở dải băng này được chia làm nhiều lỗ bằng nhau và cách đều nhau (có thể chất liệu trong suốt để ánh sáng chiếu qua).

– Encoder tương đối (incremental encoder): phát ra tín hiệu tăng dần hoặc theo chu kỳ
+ Đĩa mã hóa bao gồm một dãi băng tạo xung, thường được chia thành nhiều lỗ bằng nhau và được cách đều nhau.
+ Chất liệu có thể là trong suốt để giúp ánh sáng chiếu qua.
+ Là Encoder chỉ có 1,2 hoặc tối đa 3 vòng lỗ, và thường có thêm một lỗ định vị.
+ Ưu điểm: giá thành rẻ, chế tạo đơn giản, xử lý tín hiệu trả vềdễ dàng.
+ Nhược điểm: dễ bị sai lệch về xung khi trả về. Sẽ tích lũy sai số khi hoạt động lâu dài.
Cách thức xác định chiều quay của encoder
Encoder thường có 3 kênh (3 ngõ ra) bao gồm kênh A, kênh B và kênh I (Index). Trong hình 2 bạn thấy hãy chú ý một lỗ nhỏ bên phía trong của đĩa quay và một cặp phat-thu dành riêng cho lỗ nhỏ này. Đó là kênh I của encoder. Cứ mỗi lần motor quay được một vòng, lỗ nhỏ xuất hiện tại vị trí của cặp phát-thu, hồng ngoại từ nguồn phát sẽ xuyên qua lỗ nhỏ đến cảm biến quang, một tín hiệu xuất hiện trên cảm biến. Như thế kênh I xuất hiện một “xung” mỗi vòng quay của motor.
Bên ngoài đĩa quay được chia thành các rãnh nhỏ và một cặp thu-phát khác dành cho các rãnh này. Đây là kênh A của encoder, hoạt động của kênh A cũng tương tự kênh I, điểm khác nhau là trong 1 vòng quay của motor, có N “xung” xuất hiện trên kênh A. N là số rãnh trên đĩa và được gọi là độ phân giải (resolution) của encoder. Mỗi loại encoder có độ phân giải khác nhau, có khi trên mỗi đĩa chĩ có vài rãnh nhưng cũng có trường hợp đến hàng nghìn rãnh được chia. Để điều khiển động cơ, bạn phải biết độ phân giải của encoder đang dùng. Độ phân giải ảnh hưởng đến độ chính xác điều khiển và cả phương pháp điều khiển.
Trên các encoder còn có một cặp thu phát khác được đặt trên cùng đường tròn với kênh A nhưng lệch một chút, đây là kênh B của encoder. Với 2 tín hiệu xung A và B giúp chúng ta xác định chiều quay của động cơ. Tín hiệu xung từ kênh B có cùng tần số với kênh A nhưng lệch pha 90 độ.

Khi cảm biến A bắt đầu bị che thì cảm biến B hoàn toàn nhận được hồng ngoại xuyên qua, và ngược lại. Hình trên là dạng xung ngõ ra trên 2 kênh. Xét trường hợp motor quay cùng chiều kim đồng hồ, tín hiệu “đi” từ trái sang phải. Bạn hãy quan sát lúc tín hiệu A chuyển từ mức cao xuống thấp (cạnh xuống) thì kênh B đang ở mức thấp. Ngược lại, nếu động cơ quay ngược chiều kim đồng hồ, tín hiệu “đi” từ phải qua trái. Lúc này, tại cạnh xuống của kênh A thì kênh B đang ở mức cao. Như vậy, bằng cách phối hợp 2 kênh A và B chúng ta không những xác định được góc quay (thông qua số xung) mà còn biết được chiều quay của động cơ (thông qua mức của kênh B ở cạnh xuống của kênh A).




Đo tốc độ động cơ với Encoder 1 kênh
Sử dụng Encoder 20 xung (trên 1 vòng quay của động cơ)

Arduino Uno | LCD16x02 | L298N Module | Encoder |
---|---|---|---|
8 | RS | ||
3 | EN | ||
GND | RW | ||
7 | D4 | ||
6 | D5 | ||
5 | D6 | ||
4 | D7 | ||
12 | IN1 | ||
13 | IN2 | ||
9 | ENA | ||
2 | D0 | ||
5V | 5V | VCC | |
GND | GND | GND |
Code:
#include <LiquidCrystal.h>
int motorIn1 = 12;
int motorIn2 = 13;
int motorEnA = 9;
int encoder = 2;
volatile unsigned int counter;
int rpm;
const int rs = 8, en = 3, d4 = 7, d5 = 6, d6 = 5, d7 = 4;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
void setup() {
pinMode(motorIn1, OUTPUT);
pinMode(motorIn2, OUTPUT);
pinMode(motorEnA, OUTPUT);
pinMode(encoder, INPUT);
digitalWrite(encoder, HIGH);
digitalWrite(motorIn1, HIGH);
digitalWrite(motorIn2, LOW);
analogWrite(motorEnA, 100);
attachInterrupt(0,countpulse,RISING);
lcd.begin(16, 2);
}
void countpulse(){
counter++;
}
void loop() {
static uint32_t previousMillis;
if (millis() - previousMillis >= 1000) {
rpm = (counter/20)*60;
counter = 0;
previousMillis += 1000;
}
lcd.setCursor(0,0);
lcd.print("Speed: ");
lcd.setCursor(7,0);
lcd.print(rpm);
lcd.print(" rps");
delay(1);
}

Code khác dùng cho Encode 1 kênh:
int encoder_pin = 2; // The pin the encoder is connected
unsigned int rpm; // rpm reading
volatile byte pulses; // number of pulses
unsigned long timeold;
// The number of pulses per revolution
// depends on your index disc!!
unsigned int pulsesperturn = 20;
void counter()
{
//Update count
pulses++;
}
void setup()
{
Serial.begin(9600);
//Use statusPin to flash along with interrupts
pinMode(encoder_pin, INPUT);
//Interrupt 0 is digital pin 2, so that is where the IR detector is connected
//Triggers on FALLING (change from HIGH to LOW)
attachInterrupt(0, counter, FALLING);
// Initialize
pulses = 0;
rpm = 0;
timeold = 0;
}
void loop()
{
if (millis() - timeold >= 1000){ /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
//Don't process interrupts during calculations
detachInterrupt(0);
//Note that this would be 60*1000/(millis() - timeold)*pulses if the interrupt
//happened once per revolution
rpm = (60 * 1000 / pulsesperturn )/ (millis() - timeold)* pulses;
timeold = millis();
Serial.print(pulses);
pulses = 0;
//Write it out to serial port
Serial.print(" RPM = ");
Serial.println(rpm,DEC);
//Restart the interrupt processing
attachInterrupt(0, counter, FALLING);
}
}
Lập trình hiển thị tốc độ động cơ với Rotary Encoder 2 kênh

Thông số kỹ thuật:
• Điện áp cung cấp: 3.3V
• Độ phân giải 20 xung/vòng.
• Các chân tín hiệu:
+ : chân cấp nguồn dương
GND: chân cấp nguồn âm
CLK: phase A
DT: phase B
SW: button.
Cách đọc encoder bằng Arduino
Sử dụng ngắt ngoài: đây là phương pháp dễ nhưng chính xác để đọc encoder và cũng là phương pháp được dùng trong bài học này. Ý tưởng của phương pháp rất đơn giản, chúng ta nối kênh A của encoder với 1 ngắt ngoài (INT2 chẳng hạn) và kênh B với một chân nào đó bất kỳ (không phải chân ngắt). Cứ mỗi lần ngắt ngoài xảy ra, tức có 1 xung xuất hiện trên ở kênh A thì trình phục vụ ngắt ngoài tự động được gọi. Trong trình phục vụ ngắt này chúng ta kiểm tra mức của kênh B, tùy theo mức của kênh B chúng ta sẽ tăng biến đếm xung lên 1 hoặc giảm đi 1. Tuy nhiên, bạn cần phải tính toán rất cẩn thận khi sử dụng phương pháp này. Trong bài này, chúng ta chọn độ phân giải của encoder là 20 (20 xung trên mỗi vòng quay, loại encoder đơn giản nhất).

Code trên Arduino
Thư viện giao tiếp I2C cho LCD16x02, các bạn có thể tải tại đây
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F,16,2);
// Cách nối các chân trên Encoder quay
#define encoderPinA 2 // Tương ứng chân DT trên Encoder quay
#define encoderPinB 3 // Tương ứng chân CLK trên Encoder quay
// Chân + nối nguồn 3.3V và chân GND nối cực âm
volatile int encoderPos = 0; // Cho vị trí đầu bằng 0
int lastReportedPos = 1; // Vị trí cuối bằng 1
static boolean rotating=false; // Quản lý debounce (giống như là chống nhiễu)
// các biến cho trình phục vụ ngắt interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
//Đo tốc độ
int newposition;
int oldposition=0;
long newtime;
long oldtime=0;
int vantoc = 0;
int ganvantoc = 0;
int ganxung = 0;
int sovong = 0;
void setup() {
lcd.begin(20,4);
lcd.init(); // initialize the lcd
lcd.backlight();
pinMode(encoderPinA, INPUT_PULLUP); // INPUT-PULLUP tương đương Mode INPUT và tự động nhận trạng thái HIGH hoặc LOW
pinMode(encoderPinB, INPUT_PULLUP);
// Chân encoder trên ngắt 0 (chân 2)
attachInterrupt(0, doEncoderA, CHANGE);
// Chân encoder trên ngắt 1 (chân 3)
attachInterrupt(1, doEncoderB, CHANGE);
Serial.begin(9600); // chuyển dữ liệu lên cổng Serial Port
lcd.setCursor(0,1);
lcd.print("SO VONG = ");
}
// Vòng lặp chính, công việc được thực hiện bởi trình phục vụ ngắt
void loop() {
delay(1000);
rotating = true; // Khởi động bộ debounce (có thể hiểu như bộ chống nhiễu)
newtime=millis();
newposition=encoderPos;
detachInterrupt(0);
detachInterrupt(1);
vantoc = (newposition-oldposition)*60/20;
Serial.print("vantoc=");
Serial.println(vantoc,DEC);
oldposition=newposition;
oldtime=newtime;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("TOC DO = ");
lcd.setCursor(11,0);
lcd.print(vantoc);
lcd.print(" RPM");
lcd.setCursor(0,1);
lcd.print("SO VONG = ");
lcd.setCursor(11,1);
lcd.print(sovong,DEC);
attachInterrupt(0, doEncoderA, CHANGE);
attachInterrupt(1, doEncoderB, CHANGE);
}
// Ngắt khi chuyển trạng thái của A
void doEncoderA(){
// debounce
if ( rotating ) delay (1); // Chờ 1 chút
// Kiểm tra việc chuyển đổi trạng thái, xem có thật sự thay đổi trạng thái chưa
if( digitalRead(encoderPinA) != A_set ) { // debounce một lần nữa
A_set = !A_set;
// Cộng 1 nếu có tín hiệu A rồi có tín hiệu B
if ( A_set && !B_set )
encoderPos += 1;
ganxung += 1;
sovong=encoderPos/20;
if (ganxung == 42){ganxung=0;}
rotating = false; // Không cần debounce nữa cho đến khi được nhấn lần nữa
}
}
// Ngắt khi thay đổi trạng thái ở B, tương tự như ở A
void doEncoderB(){
if ( rotating ) delay (1);
if( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
// Trừ 1 nếu B rồi đến A
if( B_set && !A_set )
encoderPos -= 1;
rotating = false;
}
}
Tham khảo: TDHShop, AVR, https://www.teachmemicro.com/