Arduino - Timer - Interrupt
Trong bài viết này chúng ta sẽ tìm hiểu:
Các khái niệm về Interrupt, Timer/Counter.
Tại sao và khi nào cần sử dụng Interrupt, Timer/Counter.
Cách sử dụng Interrupt, Timer/Counter.
Interrupt
Giới thiệu
Trong quá trình làm việc, bỗng nhiên bạn nhận được một yêu cầu gấp, cần
phải thực hiện ngay lập tức. Lúc này, bạn buộc phải dừng công việc hiện
tại để thực hiện yêu cầu đó và chỉ có thể tiếp tục công việc đang dở
dang khi hoàn thành song yêu câu. Tương tự vậy, trong lập trình, chúng
ta có một khái niệm, đó chính là ngắt (Interrupt).
Ngắt là khi một tín hiệu khẩn cấp được gửi tới bộ xử lý, yêu cầu bộ
xử lý tạm dừ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 được gọi là trình
phục vụ ngắt - ISR (Interrupt Service Routine). Sau khi kết thúc trình
phục vụ ngắt - ISR, bộ đếm chương trình sẽ trả về giá trị trước đó để bộ
xử lý quay về thực hiện chương trình đang dang dở.
Vì sao cần sử dụng ngắt
Quay trở lại ví dụ về button đã đề cập ở phần Hello world, chúng ta
phải liên tục đọc trạng thái của nút nhấn bằng hàm digitalRead()
trong
chương trình loop
.
void loop() {
// Đọc trạng thái của nút nhấn
buttonState = digitalRead(buttonPin);
// Kiểm tra nếu nút được nhấn, tức buttonState ở trạng thái LOW:
if (buttonState == LOW) {
// Bật LED
digitalWrite(ledPin, HIGH);
} else {
// Tắt LED
digitalWrite(ledPin, LOW);
}
}
Điều này sẽ cực kì khó khăn khi số lượng các câu lệnh trong chương trình
loop
tăng lên, lúc này tốc độ đáp ứng của chương trình khi chúng ta
nhấn nút sẽ không còn nhanh nữa bởi vì chương trình loop
phải thực
hiện song tất cả các lệnh rồi mới quay trở lại đọc trạng thái nút nhấn.
Ngắt sẽ giải quyết những vấn đề này cho bạn. Lúc này, chương trình
vẫn chạy liên tục những lệnh có trình chương trình loop
và chỉ khi có
ngắt xảy ra khi ta ấn nút nhấn thì chương trình mới thực hiện các lệnh
trong chương trình phục vụ ngắt và sau đó quay trở lại thực hiện tiếp
chương trình trong loop
.
Điều này sẽ giúp tốc độ đáp ứng của chương trình đối với các thao tác
của bạn được nhanh hơn và việc quản lý chương trình của bạn được dễ dàng
và hiệu quả hơn.
Vector ngắt
Trong thực tế trên mỗi bộ xử lý đều có rất nhiều ngắt khác nhau, vậy
điều gì sẽ xảy ra khi cùng một lúc có hai ngắt xuất hiện? Cũng như ví dụ
đầu bài, nhưng bây giờ bạn nhận đến 2 yêu cầu cần thực hiện gấp, vậy bạn
sẽ thực hiện yêu cầu nào? Tất nhiên là sẽ ưu tiên yêu cầu cấp thiết
nhất, sau khi hoàn thành thì mới thực hiện yêu cầu tiếp theo và cuối
cùng là trở lại công việc đang làm dở. Cũng như trong lập trình, mỗi
ngắt đều được quy định một mức ưu tiên khác nhau, được gọi là vector
ngắt (vector ngắt có giá trị càng nhỏ thì độ ưu tiên càng cao), Reset là
ngắt có mức ưu tiên cao nhất.
Ngắt trong Arduino
Trong khuôn khổ của Arduino và mục tiêu của sách là đơn giản hóa mọi vấn
đề để các bạn mới bắt đầu dễ dàng tiếp cần thì chúng ta sẽ không đi sâu
vào vấn đề này.
Trong Arduino, chúng ta được hỗ trợ 2 loại ngắt như sau:
Ngắt số 0 được nối với chân số 2.
Ngắt số 1 được nối với chân số 3.
Để sử dụng ngắt chúng ta cần phải kết nối nút nhấn hoặc cảm biến vào hai
chân này để tạo tín hiệu ngắt cho bộ xử lý.
Tùy thuộc vào vi điều khiển trên board mà mỗi dòng Arduino có số lượng
các ngắt khác nhau. Bạn có thể tham khảo bảng sau:
Board | Int.0 | Int.1 | Int.2 | Int.3 | Int.4 | Int.5 |
---|---|---|---|---|---|---|
Uno. Ehternet | 2 | 3 | . | . | . | . |
Mega2560 | 2 | 3 | 21 | 20 | 19 | 18 |
Leonardo | 2 | 3 | 0 | 1 | 7 | . |
Số lượng ngắt trên các Board Arduino
Arduino hỗ trợ 2 lệnh giúp ta khai báo và hủy một ngắt bất kì một cách
dễ dàng đó là attachInterrupt()
và detachInterrupt()
.
Lệnh attachInterrupt()
Mục đích: Giúp khai báo một ngắt.
Cú pháp lệnh:
attachInterrupt(interrupt, ISR, mode)
.-
Các đối số:
-
interrupt: Lựa chọn ngắt mà bạn muốn dùng, với Arduino Uno
có 2 lựa chọn là:- 0: Ứng với ngắt số 0 trong Arduino (chân số 2).
- 1: Ứng với ngắt số 1 trong Arduino (chân số 3).
ISR: Chương trình phục vụ ngắt. Chương trình này sẽ được
thực hiện khi có ngắt xảy ra.-
mode: Kiểu kích hoạt ngắt:
- LOW: Ngắt sẽ được kích hoạt khi trạng thái chân ở mức thấp.
- HIGH: Ngắt sẽ được kích hoạt khi trạng thái chân ở mức cao.
- CHANGE: Ngắt khi có sự thay đổi trạng thái trên chân ngắt (trạng thái thay đổi từ mức điện áp thấp lên mức điện áp cao hoặc ngược lại, từ mức điện áp cao xuống mức điện áp thấp.
- RISING: Ngắt sẽ được kích hoạt khi trạng thái của chân digital chuyển từ mức điện áp thấp sang mức điện áp cao.
- FALLING: Ngắt sẽ được kích hoạt khi trạng thái của chân digital chuyển từ mức điện áp cao sang mức điện áp thấp.
-
- Giá trị trả về: Không có giá trị trả về
Lệnh detachInterrupt()
Mục đích: Tắt ngắt hiện tại.
Cú pháp lệnh:
detachInterrupt(pin)
.-
Các đối số:
-
pin: Lựa chọn ngắt mà bạn muốn tắt, dùng board Arduino Uno
có 2 lựa chọn là:- 0: Tắt ngắt số 0 trong Arduino ( chân số 2).
- 1: Tắt ngắt số 1 trong Arduino ( chân số 3).
-
Giá trị trả về : Không có giá trị trả về.
Ví dụ
Yêu cầu
Thiết kế hệ thống tự động bật sáng đèn khi trời tối.
Linh kiện cần dùng
Board Arduino Uno
Cảm biến ánh sáng quang trở
Module Relay 5V 1 kênh
Cảm biến ánh sáng quang trở
Relay
Relay là công tắc chuyển mạch (có thể đóng cắt điện áp DC và AC) được
điều khiển bằng tín hiệu DC. Đây là linh kiện thụ động rất thường gặp
trong các mạch điện tử.
Relay có hai dạng phổ biến: Relay đóng mức thấp và đóng mức cao. Để hiểu
thêm nguyên lý hoạt động của Relay, Module relay và cách sử dụng.
Phân tích chương trình:
Chúng ta sẽ dùng chân DO (Digital Output) của quang trở kết nối với
ngắt số 0 (chân số 2). Trong điều kiện có ánh sáng, chân DO sẽ xuất
ra mức 0 và ngược lại khi không có ánh sáng sẽ xuất ra mức 1.Chúng ta sẽ khai báo ngắt số 0 (chân số 2) với kiểu kích hoạt là LOW
(ngắt khi trạng thái chân số 2 ở mức thấp) và chương trình phục vụ
ngắt làturnOffRelay
(để tắt Relay).Chương trình chính sẽ thực hiện việc bật Relay.
Khi môi trường có ánh sáng, chân DO của cảm biến sẽ xuất mức LOW,
lúc này ngắt xảy ra và Relay sẽ tắt (đèn tắt).
Kết nối
Arduino Uno | Relay |
---|---|
5V | 5V |
GND | GND |
A0 | IN |
Bảng đấu nối board Arduino Uno và Relay
Arduino Uno | Cảm biến ánh sáng |
---|---|
5V | 5V |
GND | GND |
2 | DO |
Bảng đấu nối board Arduino Uno và Cảm biến ánh sáng
Source code
int relay = A0; // khai báo chân relay là chân Ao
void turnOffRelay()
{
digitalWrite(relay, LOW); // Tắt relay khi xảy ra ngắt.
}
void setup()
{
pinMode(relay, OUTPUT);
pinMode(2, INPUT_PULLUP); // khai báo chân số 2 là ngõ vào sử dụng điện trở kéo lên.
attachInterrupt(0, turnOffRelay, LOW);
// khai báo ngắt số 0, xảy ra khi chân số 2 ở mức thấp
// chương trình turnOffRelay được gọi khi ngắt xảy ra.
}
void loop()
{
digitalWrite(relay, HIGH); // bật relay
}
Trong ví dụ sử dụng Relay đóng mức cao. Khi sử dụng Relay đóng mức thấp
thì cần phải đảo tín hiệu điều khiển.
Timer/Counter
Giới thiệu
Timer/Counter là một trong những ngoại vi hoạt động độc lập và không thể
thiếu trong bất kì vi điều khiển nào. Thực chất, Timer/Counter chỉ là
một bộ đếm xung clock (có thể là xung nhịp nội bên trong vi điểu khiển
hoặc xung clock bên ngoài). Nó tương tự như việc đếm giờ, thay vì ngồi
canh đồng hồ và đếm từng giây thì chúng ta có thể đặt báo thức và làm
những công việc khác, việc đếm thời gian thì giao cho đồng hồ xử lý.
Quá trình hoạt động của Timer/Counter được quản lý bởi thanh ghi, chúng
gồm thanh ghi chứa giá trị Timer/Counter đếm được và thanh ghi điều
khiển các hoạt động đếm của nó.
Trong khuôn khổ của sách này, chúng ta sẽ không tìm hiểu sâu về thanh
ghi, nếu bạn có nhu cầu tìm hiễu rõ hơn có thể truy cập vào đường dẫn:
arduino.vn/bai-viet/411-timercounter-tren-avrarduino.
Timer/Counter được sử dụng rất nhiều trong các ứng dụng định thời, đếm
sự kiện, tạo xung PWM,…
Timer/Counter trong Arduino
Một số định nghĩa mà các bạn cần lưu ý trong khi sử dụng Timer/Counter:
BOTTOM: Giá trị nhỏ nhất mà Timer/Counter có thể đạt được. Mặc định
giá trị này bằng 0.MAX: Giá trị lớn nhất mà thanh ghi giá trị của Timer/Counter có thể
chứa được. Tùy thuộc vào độ rộng của thanh ghi giá trị mà chúng ta
có được giá trị MAX khác nhau. Ví dụ như Timer/Counter 0 là bộ timer
8 bit tức là giá trị tối đa (MAX) của nó là 28-1=255 và
đối với timer 16 bit là 216-1=65535.TOP: Giá trị mà khi Timer/Counter đạt đến nó sẽ thay đổi trạng thái,
giá trị này phải bé hơn hoặc bằng 255 (đối với timer 8 bit) và 65535
(đối với timer 16 bit). Ví dụ: Trong Timer/Counter 0 chúng ta sẽ có
giá trị MAX là 255, nếu không không thiết lập giá trị TOP thì timer
sẽ đếm từ 0 đến 255 và sau đó quay trở lại 0, nếu chúng ta thiết lập
giá trị TOP thì timer sẽ đếm từ 0 đến giá trị TOP và quay về 0.Ngắt Timer: Bất cứ khi nào Timer đếm đến giá trị TOP hoặc Timer bị
tràn (đếm đến giá trị MAX) thì đều xảy ra ngắt.
Chip ATMega328p (vi điều khiển của board Arduino Uno) bao gồm các
Timer/Counter sau:
Timer/Counter 0: Là bộ timer 8 bit được sử dụng nhiều trong các hàm
delay(), millis(), micros(). Chúng ta nên hạn chế sử
dụng bộ timer này vì rất dễ gây ảnh hưởng đến những hàm trên.Timer/Counter 1: Là bộ timer 16 bit được sử dụng trong thư viện
servo.Timer/Counter 2: Là một bộ timer 8 bit tương tự như Timer/Counter 0
và được sử dụng trong hàm tone().
Thư viện TimerOne
Trên nền tảng Arduino và mục tiêu của eBook là đơn giản hóa cho những
bạn mới tiếp cận, vì vậy chúng ta sẽ không điều khiển trực tiếp Timer
bằng thanh ghi mà thay vào đó sẽ sử dụng thư viện TimerOne để tiếp
cận với Timer/Counter.
Đúng như tên gọi, thư viện sẽ sử dụng bộ Timer/Counter 1 (16 bit), ngoài
ra nếu bạn muốn tìm hiểu rõ hơn về cách sử dụng cũng như quá trình hoạt
động của tất cả Timer trong board Arduino Uno, bạn có thể tham khảo bài
viết sau: arduino.vn/bai-viet/411-timercounter-tren-avrarduino.
Các hàm cần chú ý trong thư viện:
-
initialize(microseconds): Khởi động ngắt Timer với một chu kì
xác định.-
Các đối số:
- microseconds: chu kỳ ngắt của Timer (tính bằng micro giây). Mặc định giá trị microseconds = 1 000 000 tương ứng với 1s.
-
start(): Khởi động lại Timer sau quá trình thay đổi, chỉnh sửa.
startBottom(): Cho Timer đếm lại từ 0 (giá trị BOTTOM).
read(): Đọc giá trị Timer đếm được ở thời điểm hiện tại.
stop(): Dừng Timer.
attachInterrupt(): Thêm chương trình phục vụ ngắt khi xảy ra sự
kiện ngắt Timer.detachInterrupt(): Hủy hàm ngắt Timer.
-
pwm(pin, duty): Xuất xung PWM ra một chân xác định.
-
Các đối số:
- pin: Lựa chọn chân xuất PWM.
- duty: Chu kì xung PWM.
-
-
disablePwm(char pin): Hủy băm xung PWM.
-
Các đối số:
- pin: Lựa chọn chân để hủy PWM.
-
Một số ví dụ
Điều khiển LED
Yêu cầu
Sử dụng Timer để điều khiển LED trên board nháy sau mỗi 0,15s và hiển
thị số lần LED sáng lên OLED.
Linh kiện cần dùng
Board Arduino Uno
OLED SSD1306
Phân tích:
Chúng ta sẽ khai báo một Timer để định thời gian là 0,15s. Cứ sau
0,15s Timer sẽ ngắt, thực hiện chương trìnhblinkLED
và đếm lại từ
đầu.Chương trình blinkLED sẽ thực hiện việc đảo trạng thái của đèn LED
từ sáng thành tắt và ngược lại, đống thời đếm số lần đèn LED sáng.Vòng lặp
loop()
sẽ thực hiện việc hiển thị số lần LED sáng lên màn
hình OLED SSD1306.
Thư viện
- Đầu tiên, chúng ta cần download thư viện TimerOne của PaulStoffregen cho Arduino. Link download.
Sau khi tải thư viện về, bạn mở phần mềm arduino, chọn Sketch → Import
Library… → Add Library…. Sau đó chọn file .zip mà bạn vừa tải về để có
thể sử dụng thư viện.
Source code
#include <TimerOne.h> //thư viện cho Timer1
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
const int led = LED_BUILTIN; // LED BUILD chính là LED mặc định trên BOARD
void setup(void)
{
pinMode(led, OUTPUT);
Timer1.initialize(150000); //Khởi động ngắt Timer1 tràn mỗi 0.15s
Timer1.attachInterrupt(blinkLED); // blinkLED to run every 0.15 seconds
setUpOLED(); // thiết lập OLED
}
void setUpOLED()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
}
int ledState = LOW;
volatile unsigned long blinkCount = 0; //biến đếm số lần Led sáng
void blinkLED(void)
{
if (ledState == LOW) {
ledState = HIGH;
blinkCount = blinkCount + 1; // Tăng biến đếm mỗi khi LED sáng
} else {
ledState = LOW;
}
digitalWrite(led, ledState);
}
void loop(void)
{
unsigned long blinkCopy; // biến linkCopy được dùng để copy số lần LED từ biến blinkCount
//tắt ngắt nhằm tránh trường hợp biến blinkCount thay đổi (do ngắt timer xảy ra) trong quá trình copy.
noInterrupts();
blinkCopy = blinkCount;
interrupts();
display.clearDisplay();
display.setCursor(0, 0);
display.println("So Lan Sang:" + String(blinkCopy));
display.display();
delay(1000);
}
Từ khóa volatile được dùng cho những biến sử dụng trong chương trình
phục vụ ngắt và các chương trình khác.
Điều khiển tốc độ quạt
Yêu cầu
Điều khiển tốc độ quạt sử dụng băm xung PWM.
Thực tế chúng ta vẫn có thể điều khiển tốc độ quạt bằng hàm
analogWrite ()
, nhưng vấn đề đặt ra là hàm analogWrite()
chỉ xuất ra
xung PWM với tần số mặc định khoảng 490Hz đến 3920Hz trong khi một số
quạt hiện nay yêu cầu xung PWM có tần số trong khoảng từ 21kHz đến
28kHz. Vì vậy chúng ta sẽ phải sử dụng Timer/Counter đề điều chế xung
PWM.
Linh kiện sử dụng
Board Arduino Uno
OLED SSD1306
Quạt mini (Fan)
FAN nói chung hoặc quạt tản nhiệt nói riêng thông thường sẽ có 3 chân,
chân (+) màu đỏ, chân GND màu đen và chân còn lại là chân điều khiển tốc
độ.
Kết nối
Arduino Uno | FAN |
---|---|
5V | 5V |
GND | GND |
4 | Xanh dương |
Bảng đấu nối board Arduino Uno và quạt mini
Source code
#include <TimerOne.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int fanPin = 4; //khai báo chân điều khiển tốc độ quạt
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
void setup(void)
{
Timer1.initialize(40); // thiết lập timer1 với chu kì 40us tương ứng 25kHz.
setUpOLED(); // thiết lập OLED
}
void setUpOLED()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
}
void loop(void)
{
for (float dutyCycle = 30.0; dutyCycle < 100.0; dutyCycle++)
{
Timer1.pwm(fanPin, (dutyCycle / 100) * 1023);
display.clearDisplay();
display.setCursor(32, 0);
display.println("PWM Fan");
display.setCursor(0, 15);
display.println("Duty Cycle = " + String(dutyCycle));
display.display();
delay(500);
}
}
Giải thích source code:
Đầu tiên khai báo Timer để định thời gian là 40 micro giây (tương
ứng với tần số PWM là 1/40 µs = 25kHz ) bằng lệnh
Timer1.initialize(40).Trong chương trình
loop()
, chúng ta dùng một vòng lặp for để tăng
tốc độ của quạt bằng cách tăng đối số duty của hàm pwm (duty
tăng dần theo vòng lặp for).
Điều khiển động cơ Servo bằng biến trở
Servo là gì?
Khác với những dạng động cơ thông thường chỉ quay liên tục khi cấp nguồn
điện, Servo là dạng động cơ điều khiển được góc quay bằng xung PWM. Trên
thị trường có rất nhiều loại động cơ Servo với kích thước, khối lượng,
cấu tạo khác nhau, từ những loại Servo kích thước nhỏ gọn (dùng cho máy
bay mô hình) đến những loại sỡ hữu moment xoắn cực lớn (vài chục N/m).
Để điều khiển được góc quay Servo (trong khoảng từ 0o -
180o ) chúng ta sẽ dùng xung PWM như sau:
Biến trở
Biến trở là linh kiện điện tử mà giá trị điện trở có thể thay đổi được.
Chúng có thể được sử dụng trong các mạch điện để điều chỉnh hoạt động
của mạch điện.
Yêu cầu
Chúng ta sẽ đọc giá trị analog từ biến trở (sử dụng biến trở để tăng
giảm mức điện áp từ 0V đến 5V tương ứng với góc quay từ
0o đến 180o).Sau đó, chúng ta xuất ra xung PWM tương ứng ra chân số 9.
Sơ đồ kết nối
Arduino Uno | Servo Motor |
---|---|
5V | Màu Đỏ |
GND | Màu nâu |
9 | Màu Vàng |
Bảng đấu nối board Arduino Uno với động cơ Servo
Arduino Uno | Biến trở |
---|---|
5V | 1 |
GND | 3 |
A7 | 2 |
Bảng đấu nối board Arduino Uno với Biến trở
Thư viện
Trong ứng dụng này, chúng ta sẽ sử dụng thư viện servo.h
Thư viện servo.h sử dụng bộ Timer/Counter 1, đóng vai trò quan
trọng nếu bạn muốn làm về dự án robot. Nó cung cấp cho bạn một phương
thức cực kì đơn giản để điều khiển động cơ Servo. Đồng thời, thư viện đã
được tích hợp sẵn trong arduino IDE nên bạn không cần phải tải thêm thư
viện khi sử dụng.
Một số hàm cần chú ý như:
-
myservo.attach(pin)
: Khai báo ngõ ra điều khiển Servo.- pin: lựa chọn chân ngõ ra điều khiển Servo.
-
myservo.write(deg)
: Điểu khiển góc quay Servo.- deg: giá trị góc mà bạn muốn quay (0o đến 180o).
Source code
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Servo.h> // Thư viện điều khiển servo
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
// Khai báo đối tượng myservo dùng để điều khiển servo
Servo myservo;
int bientro = A0; // Khai báo chân analog đọc biến trở điều khiển servo
int servoPin = 9; // Khai báo chân điều khiển servo
void setup ()
{
// Cài đặt chức năng điều khiển servo cho chân servoPin
myservo.attach(servoPin);
setUpOLED();
}
void setUpOLED()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
}
void loop ()
{
int value = analogRead(bientro);// Đọc giá trị biến trở
// Chuyển giá trị analog (0-1023) đọc được từ biến trở sang số đo độ (0-180độ)
// dùng để điều khiển góc quay cho servo
int servoPos = map(value, 0, 1023, 0, 180);
// Cho servo quay một góc là servoPos độ
myservo.write(servoPos);
display.clearDisplay();
display.setCursor(20, 0);
display.println("Control Servo");
display.setCursor(0, 15);
display.println("Goc quay = " + String(servoPos));
display.display();
delay(50);
}
Summary
Qua chương này, chúng ta đã tìm hiểu được những khái niệm cơ bản về
ngắt (Interrupt) và Timer/counter, lý do tại sao chúng ta cần sử
dụng nó trong các ứng dụng. Đồng thời chúng ta biết được thêm thư viện
TimerOne giúp ta dễ dàng tiếp cận và làm chủ bộ Timer/Couter trên
board Arduino Uno.
Đối với những ví dụ đơn giản, khó có thể không nhận thấy sự khác biệt
khi sử dụng ngắt (Interrupt) và Timer/Counter. Nhưng khi thực hiện những
ví dụ phức tạp, cần khai thác tối ta khả năng xử lý của vi điều khiển
thì đây sẽ là trợ thủ đắc lực cho bạn.
Top comments (0)