Mục Lục Bài Học
1. Ngắt (Interrupt) - Xử Lý Sự Kiện Nhanh
Interrupt cho phép Arduino tạm dừng chương trình chính để xử lý một sự kiện khẩn cấp ngay lập tức.
Các loại Interrupt:
- External Interrupt: Kích hoạt bởi tín hiệu từ chân digital (Arduino Uno có 2 chân: D2, D3)
- Timer Interrupt: Kích hoạt theo chu kỳ thời gian
Các chế độ External Interrupt:
| Chế độ | Mô tả |
|---|---|
| LOW | Kích hoạt khi chân ở mức LOW |
| CHANGE | Kích hoạt khi chân thay đổi trạng thái |
| RISING | Kích hoạt khi chân chuyển từ LOW → HIGH |
| FALLING | Kích hoạt khi chân chuyển từ HIGH → LOW |
Ví dụ: Đếm số lần nhấn nút bằng Interrupt
const int BUTTON_PIN = 2; // Phải là chân interrupt (D2 hoặc D3) const int LED_PIN = 13; volatile int counter = 0; // volatile vì được dùng trong ISR volatile bool ledState = false; void setup() { Serial.begin(9600); pinMode(BUTTON_PIN, INPUT_PULLUP); // Dùng pull-up nội pinMode(LED_PIN, OUTPUT); // Gắn interrupt vào chân 2, gọi hàm buttonPressed khi FALLING attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonPressed, FALLING); Serial.println("Nhấn nút để đếm!"); } void loop() { // Chương trình chính chạy bình thường Serial.print("Số lần nhấn: "); Serial.println(counter); delay(1000); } // ISR (Interrupt Service Routine) - hàm xử lý ngắt void buttonPressed() { counter++; ledState = !ledState; digitalWrite(LED_PIN, ledState); }
⚠️ Lưu ý về ISR:
- Hàm ISR phải ngắn gọn, thực thi nhanh
- Không dùng
delay()trong ISR - Biến được chia sẻ giữa ISR và loop() phải khai báo
volatile - Hạn chế dùng Serial trong ISR
Ứng dụng: Đo tốc độ vòng quay (RPM)
Đo RPM bằng Interrupt
const int SENSOR_PIN = 2; volatile unsigned long pulseCount = 0; unsigned long lastTime = 0; float rpm = 0; void setup() { Serial.begin(9600); pinMode(SENSOR_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), countPulse, FALLING); } void loop() { // Tính RPM mỗi giây if (millis() - lastTime >= 1000) { // Tắt interrupt tạm thời để đọc biến noInterrupts(); unsigned long pulses = pulseCount; pulseCount = 0; interrupts(); // Tính RPM (giả sử có 1 pulse/vòng) rpm = pulses * 60.0; Serial.print("Tốc độ: "); Serial.print(rpm); Serial.println(" RPM"); lastTime = millis(); } } void countPulse() { pulseCount++; }
2. Timer & Counter
Timer cho phép thực hiện tác vụ định kỳ mà không cần dùng delay(), giúp chương trình không bị blocking.
Kỹ thuật Blink Without Delay
LED nhấp nháy không dùng delay()
const int LED_PIN = 13; unsigned long previousMillis = 0; const long interval = 1000; // 1 giây bool ledState = LOW; void setup() { pinMode(LED_PIN, OUTPUT); } void loop() { unsigned long currentMillis = millis(); // Kiểm tra đã đủ thời gian chưa if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // Đảo trạng thái LED ledState = !ledState; digitalWrite(LED_PIN, ledState); } // Code khác có thể chạy ở đây mà không bị delay chặn }
Nhiều Timer đồng thời
Điều khiển 3 LED với tốc độ khác nhau
const int LED1 = 8, LED2 = 9, LED3 = 10; unsigned long timer1 = 0, timer2 = 0, timer3 = 0; const long interval1 = 500; // LED1 nháy nhanh (0.5s) const long interval2 = 1000; // LED2 trung bình (1s) const long interval3 = 2000; // LED3 chậm (2s) bool state1 = LOW, state2 = LOW, state3 = LOW; void setup() { pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT); } void loop() { unsigned long currentMillis = millis(); // Timer cho LED1 if (currentMillis - timer1 >= interval1) { timer1 = currentMillis; state1 = !state1; digitalWrite(LED1, state1); } // Timer cho LED2 if (currentMillis - timer2 >= interval2) { timer2 = currentMillis; state2 = !state2; digitalWrite(LED2, state2); } // Timer cho LED3 if (currentMillis - timer3 >= interval3) { timer3 = currentMillis; state3 = !state3; digitalWrite(LED3, state3); } }
3. Giao Tiếp I2C (Inter-Integrated Circuit)
I2C là giao thức truyền thông nối tiếp chỉ cần 2 dây: SDA (dữ liệu) và SCL (xung nhịp).
Arduino Uno: SDA = A4, SCL = A5
Quét địa chỉ I2C
I2C Scanner - Tìm địa chỉ thiết bị
#include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); Serial.println("Bắt đầu quét I2C..."); } void loop() { byte error, address; int deviceCount = 0; Serial.println("Đang quét..."); for(address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("Tìm thấy thiết bị I2C tại địa chỉ 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); deviceCount++; } } if (deviceCount == 0) Serial.println("Không tìm thấy thiết bị I2C\n"); else Serial.println("Hoàn thành!\n"); delay(5000); }
Ví dụ: OLED Display I2C (SSD1306)
Cài thư viện: Sketch → Include Library → Manage Libraries → Tìm "Adafruit SSD1306" và "Adafruit GFX"
Hiển thị text lên OLED 0.96"
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // Địa chỉ I2C (thường là 0x3C hoặc 0x3D) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); // Khởi tạo OLED if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println("Lỗi khởi tạo OLED!"); while(true); } // Xóa màn hình display.clearDisplay(); // Cài đặt màu chữ trắng display.setTextColor(SSD1306_WHITE); // Hiển thị text display.setTextSize(2); display.setCursor(0, 0); display.println("Arduino"); display.setTextSize(1); display.setCursor(0, 20); display.println("Hoc lap trinh"); display.println("Arduino & ESP32"); // Cập nhật lên màn hình display.display(); } void loop() { // Hiển thị thời gian chạy display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 40); display.print("Uptime: "); display.print(millis() / 1000); display.println("s"); display.display(); delay(1000); }
Đọc cảm biến nhiệt độ/độ ẩm DHT11/DHT22 hiển thị lên OLED
Trạm thời tiết mini với OLED
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <DHT.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define DHTPIN 7 #define DHTTYPE DHT11 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); DHT dht(DHTPIN, DHTTYPE); void setup() { Serial.begin(9600); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); dht.begin(); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.display(); } void loop() { float temp = dht.readTemperature(); float humid = dht.readHumidity(); if (isnan(temp) || isnan(humid)) { Serial.println("Lỗi đọc cảm biến!"); return; } display.clearDisplay(); // Tiêu đề display.setTextSize(2); display.setCursor(15, 0); display.println("TRAM"); display.setCursor(0, 16); display.println("THOI TIET"); // Nhiệt độ display.setTextSize(1); display.setCursor(0, 38); display.print("Nhiet do: "); display.print(temp, 1); display.println(" C"); // Độ ẩm display.setCursor(0, 50); display.print("Do am: "); display.print(humid, 1); display.println(" %"); display.display(); delay(2000); }
4. Giao Tiếp SPI (Serial Peripheral Interface)
SPI là giao thức truyền thông nối tiếp tốc độ cao, dùng 4 dây chính:
- MOSI (Master Out Slave In) - Chân 11 trên Uno
- MISO (Master In Slave Out) - Chân 12 trên Uno
- SCK (Serial Clock) - Chân 13 trên Uno
- SS (Slave Select) - Chân 10 trên Uno (có thể thay đổi)
Ví dụ: Thẻ nhớ SD Card qua SPI
Đọc/ghi file trên thẻ SD
#include <SPI.h> #include <SD.h> const int chipSelect = 10; // Chân CS của SD Card Module void setup() { Serial.begin(9600); Serial.print("Khởi tạo thẻ SD..."); if (!SD.begin(chipSelect)) { Serial.println("Lỗi! Không tìm thấy thẻ SD"); return; } Serial.println("Thẻ SD đã sẵn sàng."); // Ghi vào file File dataFile = SD.open("test.txt", FILE_WRITE); if (dataFile) { dataFile.println("Hello from Arduino!"); dataFile.println("Thời gian: " + String(millis())); dataFile.close(); Serial.println("Đã ghi dữ liệu vào test.txt"); } else { Serial.println("Lỗi mở file!"); } } void loop() { // Đọc file File dataFile = SD.open("test.txt"); if (dataFile) { Serial.println("Nội dung file test.txt:"); while (dataFile.available()) { Serial.write(dataFile.read()); } dataFile.close(); } delay(10000); // Đọc lại mỗi 10 giây }
7. Servo Motor - Điều Khiển Góc Quay
Servo motor SG90 có thể quay từ 0° đến 180°.
Kết nối: Dây đỏ → 5V, Dây nâu → GND, Dây cam → Chân PWM (ví dụ: chân 9)
Điều khiển Servo cơ bản
#include <Servo.h> Servo myServo; const int SERVO_PIN = 9; void setup() { myServo.attach(SERVO_PIN); myServo.write(90); // Vị trí giữa } void loop() { // Quét từ 0° đến 180° for (int pos = 0; pos <= 180; pos++) { myServo.write(pos); delay(15); } // Quét từ 180° về 0° for (int pos = 180; pos >= 0; pos--) { myServo.write(pos); delay(15); } }
Điều khiển Servo bằng biến trở
Servo điều khiển bởi Potentiometer
#include <Servo.h> Servo myServo; const int POT_PIN = A0; const int SERVO_PIN = 9; void setup() { myServo.attach(SERVO_PIN); } void loop() { // Đọc giá trị biến trở (0-1023) int potValue = analogRead(POT_PIN); // Chuyển đổi sang góc (0-180) int angle = map(potValue, 0, 1023, 0, 180); // Điều khiển servo myServo.write(angle); delay(15); }
8. Động Cơ DC - Điều Khiển Tốc Độ & Chiều
Sử dụng L298N Motor Driver để điều khiển động cơ DC.
Kết nối L298N:
- IN1, IN2 → Chân Digital (ví dụ: 7, 8) - Điều khiển chiều
- ENA → Chân PWM (ví dụ: 9) - Điều khiển tốc độ
- OUT1, OUT2 → Động cơ DC
- 12V → Nguồn 12V, GND → GND
Điều khiển động cơ DC với L298N
const int ENA = 9; // PWM - điều khiển tốc độ const int IN1 = 7; // Điều khiển chiều const int IN2 = 8; void setup() { pinMode(ENA, OUTPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); // Dừng động cơ ban đầu digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); } void loop() { // Quay thuận với tốc độ tăng dần motorForward(100); delay(2000); motorForward(200); delay(2000); // Dừng motorStop(); delay(1000); // Quay ngược motorBackward(150); delay(2000); motorStop(); delay(1000); } // Hàm quay thuận void motorForward(int speed) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); analogWrite(ENA, speed); } // Hàm quay ngược void motorBackward(int speed) { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); analogWrite(ENA, speed); } // Hàm dừng động cơ void motorStop() { digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); analogWrite(ENA, 0); }
Robot di chuyển 2 động cơ
Robot 2 bánh với L298N
// Motor trái const int ENA = 9; const int IN1 = 7; const int IN2 = 8; // Motor phải const int ENB = 10; const int IN3 = 5; const int IN4 = 6; const int SPEED = 200; void setup() { pinMode(ENA, OUTPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENB, OUTPUT); pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); } void loop() { moveForward(); delay(2000); stopRobot(); delay(500); turnRight(); delay(1000); stopRobot(); delay(500); } void moveForward() { // Motor trái tiến digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); analogWrite(ENA, SPEED); // Motor phải tiến digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENB, SPEED); } void moveBackward() { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); analogWrite(ENA, SPEED); digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); analogWrite(ENB, SPEED); } void turnRight() { // Motor trái tiến, motor phải lùi digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); analogWrite(ENA, SPEED); digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); analogWrite(ENB, SPEED); } void turnLeft() { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); analogWrite(ENA, SPEED); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENB, SPEED); } void stopRobot() { digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); analogWrite(ENA, 0); digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); analogWrite(ENB, 0); }
Bài Tập Thực Hành Nâng Cao
- Robot tránh vật cản: Sử dụng HC-SR04 + L298N để tạo robot tự động tránh vật cản.
- Hệ thống tưới cây tự động: Dùng cảm biến độ ẩm đất + relay điều khiển bơm nước.
- Đồng hồ hiển thị OLED: Tạo đồng hồ thời gian thực với DS3231 RTC + OLED.
- Data Logger: Ghi dữ liệu cảm biến vào thẻ SD theo thời gian.