BÀI 13: ĐÈN GIAO THÔNG THÔNG MINH
Trong bài học này, chúng ta sẽ xây dựng một hệ thống đèn giao thông thông minh với hai chế độ hoạt động: chế độ tự động (đèn tự chuyển đổi theo thời gian định sẵn) và chế độ thủ công (người dùng chuyển đèn bằng nút nhấn). Đặc biệt, hệ thống sẽ tích hợp module LED 7 đoạn để hiển thị thời gian đếm ngược trong chế độ tự động, và hiển thị "00" trong chế độ thủ công.
I. Mục tiêu
- Hiểu và ứng dụng các chân điều khiển của Arduino cho đèn LED và nút nhấn.
- Làm quen với IC ghi dịch 74HC595 để điều khiển nhiều module LED 7 đoạn chỉ với 3 chân Arduino.
- Xây dựng hệ thống đèn giao thông hai chế độ: tự động và thủ công.
- Hiển thị thông tin đếm ngược lên module LED 7 đoạn.
- Thực hành kỹ thuật chống nhiễu (debounce) cho nút nhấn.
II. Chuẩn bị
- Arduino Uno (hoặc bất kỳ board Arduino tương thích nào)
- Module LED 7 đoạn (loại cathode chung, 2 chữ số hoặc 2 module 1 chữ số)
- IC 74HC595 (1 cái)
- Đèn LED giao thông (hoặc 3 đèn LED riêng lẻ: Đỏ, Vàng, Xanh lá)
- Nút nhấn (2 cái)
- Dây nối (đực - đực, đực - cái)
- Breadboard
III. Sơ đồ kết nối
Dưới đây là sơ đồ kết nối tổng quát.
ARDUINO UNO
+----------------+
| |
LED ĐỎ -----| D2 D10 | ---> SDI (Module LED 7 đoạn)
LED VÀNG ---| D3 D9 | ---> CLK (Module LED 7 đoạn)
LED XANH ---| D4 D8 | ---> LOAD (Module LED 7 đoạn)
| |
NÚT CHẾ ĐỘ ----| D5 (INPUT_PULLUP)
NÚT HÀNH ĐỘNG--| D6 (INPUT_PULLUP)
| |
| |
| GND | ---> GND (Module LED 7 đoạn)
| 5V | ---> VCC (Module LED 7 đoạn)
+----------------+
Bạn có tương tác bằng cách nhấn nút trên sơ đồ
Mode: Auto
Code
const int ledRed = 2;
const int ledYellow = 3;
const int ledGreen = 4;
const int modeButton = 5;
const int actionButton = 6;
const int latchPin = 8;
const int clockPin = 9;
const int dataPin = 10;
bool autoMode = true;
int autoState = 0;
int manualState = 0;
unsigned long lastChange = 0;
unsigned long lastDebounce = 0;
const unsigned long debounceDelay = 300;
byte digitCode[10] = {
B11000000, // 0
B11111001, // 1
B10100100, // 2
B10110000, // 3
B10011001, // 4
B10010010, // 5
B10000010, // 6
B11111000, // 7
B10000000, // 8
B10010000 // 9
};
void setup() {
// Khai báo LED
pinMode(ledRed, OUTPUT);
pinMode(ledYellow, OUTPUT);
pinMode(ledGreen, OUTPUT);
// Khai báo nút
pinMode(modeButton, INPUT_PULLUP);
pinMode(actionButton, INPUT_PULLUP);
// Khai báo IC 74HC595
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
Serial.begin(9600);
Serial.println("Chế độ: TỰ ĐỘNG");
}
void loop() {
handleModeSwitch(); // Đổi chế độ
if (autoMode) {
handleAutoTraffic(); // Tự động
} else {
handleManualTraffic(); // Thủ công
}
}
// 1. XỬ LÝ ĐỔI CHẾ ĐỘ
void handleModeSwitch() {
static bool lastButtonState = HIGH;
bool reading = digitalRead(modeButton);
if (reading != lastButtonState && millis() - lastDebounce > debounceDelay) {
if (reading == LOW) {
autoMode = !autoMode;
Serial.print("Chuyển chế độ: ");
Serial.println(autoMode ? "TỰ ĐỘNG" : "THỦ CÔNG");
allOff();
autoState = 0;
manualState = 0;
lastChange = millis();
}
lastDebounce = millis();
}
lastButtonState = reading;
}
// 2. CHẾ ĐỘ TỰ ĐỘNG
void handleAutoTraffic() {
unsigned long currentTime = millis();
int remaining = 0;
switch (autoState) {
case 0: // Xanh 15s
digitalWrite(ledGreen, HIGH);
digitalWrite(ledYellow, LOW);
digitalWrite(ledRed, LOW);
remaining = 15 - (currentTime - lastChange) / 1000;
if (currentTime - lastChange >= 15000) {
autoState = 1;
lastChange = currentTime;
}
break;
case 1: // Vàng 3s
digitalWrite(ledGreen, LOW);
digitalWrite(ledYellow, HIGH);
digitalWrite(ledRed, LOW);
remaining = 3 - (currentTime - lastChange) / 1000;
if (currentTime - lastChange >= 3000) {
autoState = 2;
lastChange = currentTime;
}
break;
case 2: // Đỏ 10s
digitalWrite(ledGreen, LOW);
digitalWrite(ledYellow, LOW);
digitalWrite(ledRed, HIGH);
remaining = 10 - (currentTime - lastChange) / 1000;
if (currentTime - lastChange >= 10000) {
autoState = 0;
lastChange = currentTime;
}
break;
}
displayNumber(remaining, 2, false);
}
// 3. CHẾ ĐỘ THỦ CÔNG
void handleManualTraffic() {
static bool lastActionButtonState = HIGH;
bool reading = digitalRead(actionButton);
if (reading != lastActionButtonState &&
millis() - lastDebounce > debounceDelay) {
if (reading == LOW) {
manualState++;
if (manualState > 2) manualState = 0;
Serial.print("Thủ công chuyển đèn: ");
if (manualState == 0) Serial.println("Xanh");
if (manualState == 1) Serial.println("Vàng");
if (manualState == 2) Serial.println("Đỏ");
allOff();
if (manualState == 0) digitalWrite(ledGreen, HIGH);
if (manualState == 1) digitalWrite(ledYellow, HIGH);
if (manualState == 2) digitalWrite(ledRed, HIGH);
}
lastDebounce = millis();
}
lastActionButtonState = reading;
displayNumber(0); // Không đếm trong thủ công, hiển thị 00
}
// 4. TẮT TOÀN BỘ LED
void allOff() {
digitalWrite(ledRed, LOW);
digitalWrite(ledYellow, LOW);
digitalWrite(ledGreen, LOW);
}
// 5. HIỂN THỊ GIÂY
void displayNumber(int number) {
int tens = number / 10;
int ones = number % 10;
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, digitCode[ones]); // IC thứ nhất
shiftOut(dataPin, clockPin, MSBFIRST, digitCode[tens]); // IC thứ hai
digitalWrite(latchPin, HIGH);
}
void displayNumber(int number, int numDigits, bool isAnode) {
// Giới hạn để tránh lỗi
if (numDigits < 1) numDigits = 1;
if (numDigits > 5) numDigits = 5;
byte digits[numDigits];
// Cắt từng chữ số từ phải sang trái
for (int i = 0; i < numDigits; i++) {
int digit = number % 10;
number /= 10;
byte code = digitCode[digit];
// Nếu là LED anode chung, đảo bit
if (isAnode) {
code = ~code;
}
digits[i] = code;
}
digitalWrite(latchPin, LOW);
// Gửi từ hàng cao (trái) đến hàng thấp (phải)
for (int i = 0; i < numDigits; i++) {
shiftOut(dataPin, clockPin, MSBFIRST, digits[i]);
}
digitalWrite(latchPin, HIGH);
}
Mô phỏng
Mode: Auto
IV. Giải thích Code
Đoạn code bạn cung cấp được tổ chức rất rõ ràng, tôi sẽ đi qua từng phần:
1. Khai báo Chân và Biến (const int
, bool
, int
, unsigned long
)
// ====== Chân LED giao thông ======
const int ledRed = 2; // Chân LED đỏ
const int ledYellow = 3; // Chân LED vàng
const int ledGreen = 4; // Chân LED xanh
// ====== Chân nút ======
const int modeButton = 5; // Nút chuyển chế độ (tự động/thủ công)
const int actionButton = 6; // Nút hành động (chuyển đèn trong chế độ thủ công)
// ====== Chân IC 74HC595 ======
const int latchPin = 8;
const int clockPin = 9;
const int dataPin = 10;
// ====== Biến điều khiển trạng thái ======
bool autoMode = true; // Biến trạng thái: true = tự động, false = thủ công
int autoState = 0; // Trạng thái hiện tại trong chế độ tự động (0: Xanh, 1: Vàng, 2: Đỏ)
int manualState = 0; // Trạng thái hiện tại trong chế độ thủ công (0: Xanh, 1: Vàng, 2: Đỏ)
unsigned long lastChange = 0; // Thời điểm cuối cùng đèn đổi trạng thái (dùng cho chế độ tự động)
unsigned long lastDebounce = 0; // Thời điểm cuối cùng nút được nhấn (dùng chống nhiễu)
const unsigned long debounceDelay = 300; // Thời gian chờ chống nhiễu (miligiây)
// ====== Bảng mã LED 7 đoạn (Anode chung) ======
byte digitCode[10] = {
B11000000, // 0
B11111001, // 1
B10100100, // 2
B10110000, // 3
B10011001, // 4
B10010010, // 5
B10000010, // 6
B11111000, // 7
B10000000, // 8
B10010000 // 9
};
const int
: Định nghĩa các chân cố định cho LED, nút nhấn và IC 74HC595. Việc này giúp code dễ đọc và dễ thay đổi nếu bạn chuyển chân.- Biến điều khiển trạng thái: Các biến
autoMode
,autoState
,manualState
giúp theo dõi chế độ và trạng thái hiện tại của đèn. lastChange
vàlastDebounce
: Quan trọng để quản lý thời gian và chống nhiễu cho nút nhấn.millis()
được dùng để đo thời gian không chặn (non-blocking).digitCode
: Mảng này chứa các mã nhị phân để bật tắt các đoạn của LED 7 đoạn, tạo thành các chữ số từ 0 đến 9. Với module LED 7 đoạn cathode chung, một bit '1' tương ứng với đoạn sáng.
2. Hàm setup()
void setup() {
// Khai báo chân LED là OUTPUT
pinMode(ledRed, OUTPUT);
pinMode(ledYellow, OUTPUT);
pinMode(ledGreen, OUTPUT);
// Khai báo chân nút là INPUT_PULLUP
// Kích hoạt điện trở kéo lên bên trong Arduino, không cần điện trở ngoài
pinMode(modeButton, INPUT_PULLUP);
pinMode(actionButton, INPUT_PULLUP);
// Khai báo chân IC 74HC595 là OUTPUT
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
Serial.begin(9600); // Khởi tạo giao tiếp Serial để debug
Serial.println("Chế độ: TỰ ĐỘNG"); // In ra trạng thái khởi đầu
}
pinMode
: Cấu hình các chân Arduino làOUTPUT
(cho LED và 74HC595) hoặcINPUT_PULLUP
(cho nút nhấn).INPUT_PULLUP
giúp đơn giản hóa mạch nút nhấn bằng cách sử dụng điện trở kéo lên bên trong chip, nút sẽ nối chân với GND khi nhấn.Serial.begin(9600)
: Khởi tạo giao tiếp Serial ở tốc độ 9600 baud, cho phép bạn xem thông báo trạng thái trên Serial Monitor của Arduino IDE.
3. Hàm loop()
void loop() {
handleModeSwitch(); // Luôn kiểm tra nút chuyển chế độ
if (autoMode) {
handleAutoTraffic(); // Nếu ở chế độ tự động, xử lý đèn tự động
} else {
handleManualTraffic(); // Nếu ở chế độ thủ công, xử lý đèn thủ công
}
}
- Hàm
loop()
là nơi chương trình chạy lặp đi lặp lại. - Nó gọi
handleModeSwitch()
liên tục để kiểm tra xem người dùng có muốn chuyển chế độ không. - Sau đó, dựa vào giá trị của
autoMode
, nó sẽ gọihandleAutoTraffic()
(chế độ tự động) hoặchandleManualTraffic()
(chế độ thủ công).
4. Hàm handleModeSwitch()
- Xử lý chuyển chế độ
void handleModeSwitch() {
static bool lastButtonState = HIGH; // Lưu trạng thái nút trước đó (HIGH vì dùng INPUT_PULLUP)
bool reading = digitalRead(modeButton); // Đọc trạng thái hiện tại của nút
// Kiểm tra nếu nút đã thay đổi trạng thái VÀ đã đủ thời gian debounce
if (reading != lastButtonState && millis() - lastDebounce > debounceDelay) {
if (reading == LOW) { // Nếu nút được nhấn (từ HIGH xuống LOW)
autoMode = !autoMode; // Đảo ngược chế độ
Serial.print("Chuyển chế độ: ");
Serial.println(autoMode ? "TỰ ĐỘNG" : "THỦ CÔNG");
allOff(); // Tắt tất cả đèn khi chuyển chế độ
autoState = 0; // Đặt lại trạng thái ban đầu cho cả hai chế độ
manualState = 0;
lastChange = millis(); // Đặt lại thời điểm thay đổi để đếm ngược chính xác
}
lastDebounce = millis(); // Cập nhật thời điểm debounce cuối cùng
}
lastButtonState = reading; // Lưu trạng thái hiện tại của nút
}
- Hàm này sử dụng kỹ thuật chống nhiễu (debounce). Khi một nút nhấn được nhấn, nó thường tạo ra các tín hiệu "nảy" (bounce) trong một thời gian ngắn.
millis() - lastDebounce > debounceDelay
đảm bảo rằng chỉ một lần nhấn hợp lệ được ghi nhận sau khi nút đã ổn định. - Khi nút
modeButton
được nhấn (từ HIGH xuống LOW doINPUT_PULLUP
), biếnautoMode
sẽ được đảo ngược, chuyển đổi giữa hai chế độ. - Các đèn được tắt hết và trạng thái của cả hai chế độ được reset để đảm bảo một khởi đầu sạch sẽ.
5. Hàm handleAutoTraffic()
- Chế độ tự động
void handleAutoTraffic() {
unsigned long currentTime = millis();
int remaining = 0;
switch (autoState) {
case 0: // Xanh 15s
digitalWrite(ledGreen, HIGH);
digitalWrite(ledYellow, LOW);
digitalWrite(ledRed, LOW);
remaining = 15 - (currentTime - lastChange) / 1000;
if (currentTime - lastChange >= 15000) {
autoState = 1;
lastChange = currentTime;
}
break;
case 1: // Vàng 3s
digitalWrite(ledGreen, LOW);
digitalWrite(ledYellow, HIGH);
digitalWrite(ledRed, LOW);
remaining = 3 - (currentTime - lastChange) / 1000;
if (currentTime - lastChange >= 3000) {
autoState = 2;
lastChange = currentTime;
}
break;
case 2: // Đỏ 10s
digitalWrite(ledGreen, LOW);
digitalWrite(ledYellow, LOW);
digitalWrite(ledRed, HIGH);
remaining = 10 - (currentTime - lastChange) / 1000;
if (currentTime - lastChange >= 10000) {
autoState = 0;
lastChange = currentTime;
}
break;
}
displayNumber(remaining, 2, false); // number, số LED, anode chung
}
- Sử dụng
switch-case
để quản lý các trạng thái của đèn giao thông (Xanh, Vàng, Đỏ). - Trong mỗi trạng thái, hàm sẽ:
- Bật đèn LED tương ứng và tắt các đèn còn lại.
- Tính toán thời gian còn lại (
remaining
) bằng cách lấy tổng thời gian của trạng thái trừ đi thời gian đã trôi qua. - Kiểm tra xem thời gian đã trôi qua có đủ để chuyển sang trạng thái tiếp theo không.
- Gọi
displayNumber(remaining)
để hiển thị thời gian đếm ngược.
6. Hàm handleManualTraffic()
- Chế độ thủ công
void handleManualTraffic() {
static bool lastActionButtonState = HIGH;
bool reading = digitalRead(actionButton);
// Kiểm tra nút hành động với debounce
if (reading != lastActionButtonState && millis() - lastDebounce > debounceDelay) {
if (reading == LOW) { // Nếu nút được nhấn
manualState++; // Tăng trạng thái lên 1
if (manualState > 2) manualState = 0; // Nếu vượt quá 2 (Đỏ), quay lại 0 (Xanh)
Serial.print("Thủ công chuyển đèn: ");
if (manualState == 0) Serial.println("Xanh");
if (manualState == 1) Serial.println("Vàng");
if (manualState == 2) Serial.println("Đỏ");
allOff(); // Tắt tất cả đèn
if (manualState == 0) digitalWrite(ledGreen, HIGH); // Bật đèn tương ứng với trạng thái
if (manualState == 1) digitalWrite(ledYellow, HIGH);
if (manualState == 2) digitalWrite(ledRed, HIGH);
}
lastDebounce = millis();
}
lastActionButtonState = reading;
displayNumber(0); // Luôn hiển thị 00 trong chế độ thủ công
}
- Cũng sử dụng debounce cho
actionButton
. - Mỗi lần
actionButton
được nhấn,manualState
sẽ tăng lên và quay lại 0 sau khi đạt 2. - Đèn LED tương ứng với
manualState
sẽ được bật. displayNumber(0)
được gọi để luôn hiển thị "00" trên LED 7 đoạn trong chế độ này.
7. Hàm allOff()
- Tắt tất cả LED giao thông
void allOff() {
digitalWrite(ledRed, LOW);
digitalWrite(ledYellow, LOW);
digitalWrite(ledGreen, LOW);
}
- Hàm đơn giản này tắt tất cả ba đèn LED giao thông. Được dùng khi chuyển chế độ hoặc khởi tạo.
8. Hàm displayNumber()
- Hiển thị số lên LED 7 đoạn
void displayNumber(int number, int numDigits, bool isAnode) {
// Giới hạn để tránh lỗi
if (numDigits < 1) numDigits = 1;
if (numDigits > 5) numDigits = 5;
byte digits[numDigits];
// Cắt từng chữ số từ phải sang trái
for (int i = 0; i < numDigits; i++) {
int digit = number % 10;
number /= 10;
byte code = digitCode[digit];
// Nếu là LED anode chung, đảo bit
if (isAnode) {
code = ~code;
}
digits[i] = code;
}
digitalWrite(latchPin, LOW);
// Gửi từ hàng cao (trái) đến hàng thấp (phải)
for (int i = 0; i < numDigits; i++) {
shiftOut(dataPin, clockPin, MSBFIRST, digits[i]);
}
digitalWrite(latchPin, HIGH);
}
- Đây là hàm quan trọng để điều khiển module LED 7 đoạn thông qua IC 74HC595.
shiftOut(dataPin, clockPin, MSBFIRST, data)
: Hàm này là thư viện tích hợp của Arduino dùng để gửi dữ liệu nối tiếp.dataPin
: Chân dữ liệu (DS của 74HC595).clockPin
: Chân xung đồng hồ (SHCP của 74HC595).MSBFIRST
: Gửi bit có giá trị cao nhất trước (Most Significant Bit First).data
: Byte dữ liệu cần gửi.
- Quy trình gửi dữ liệu cho 74HC595:
- Kéo
latchPin
(STCP) xuốngLOW
: Điều này khóa thanh ghi lưu trữ, chuẩn bị cho việc nhận dữ liệu mới. - Gọi
shiftOut()
hai lần:- Lần thứ nhất gửi mã của chữ số hàng chục. Dữ liệu này sẽ được dịch vào thanh ghi dịch của 74HC595 và "đẩy" dữ liệu cũ ra (nếu có).
- Lần thứ hai gửi mã của chữ số hàng đơn vị. Dữ liệu này sẽ đẩy mã hàng chục sang thanh ghi thứ hai (hoặc vào vị trí thứ hai của một thanh ghi lớn hơn nếu module 2 chữ số được nối tiếp bên trong) và dữ liệu mới (hàng đơn vị) sẽ vào vị trí đầu tiên.
- Kéo
latchPin
(STCP) lênHIGH
: Điều này sẽ chuyển dữ liệu từ thanh ghi dịch sang thanh ghi lưu trữ, và dữ liệu sẽ xuất hiện đồng thời trên các chân Q0-Q7, làm sáng các đoạn LED tương ứng.
- Kéo
V. Các bước thực hiện
- Lắp mạch: Dựa vào sơ đồ kết nối ở trên, tiến hành lắp mạch trên breadboard. Hãy đảm bảo các kết nối đúng và chắc chắn.
- Mở Arduino IDE: Khởi động phần mềm Arduino IDE trên máy tính của bạn.
- Dán Code: Sao chép toàn bộ đoạn code đã cung cấp và dán vào cửa sổ Arduino IDE.
- Chọn Board và Port:
- Vào
Tools
->Board
-> ChọnArduino Uno
(hoặc loại board bạn đang dùng). - Vào
Tools
->Port
-> Chọn cổng COM mà Arduino của bạn đang kết nối.
- Vào
- Biên dịch và Tải lên: Nhấn nút
Upload
(mũi tên sang phải) để biên dịch và tải chương trình lên Arduino của bạn. - Kiểm tra hoạt động:
- Mở Serial Monitor (biểu tượng kính lúp ở góc trên bên phải của Arduino IDE) để theo dõi thông báo trạng thái.
- Quan sát đèn giao thông và module LED 7 đoạn.
- Nhấn nút
modeButton
để chuyển đổi giữa chế độ tự động và thủ công. - Trong chế độ tự động, đèn sẽ tự đổi theo thời gian và LED 7 đoạn sẽ đếm ngược.
- Trong chế độ thủ công, nhấn nút
actionButton
để chuyển trạng thái đèn và LED 7 đoạn sẽ hiển thị "00".
VI. Nâng cao (Tùy chọn)
- Điều chỉnh thời gian: Thay đổi các giá trị thời gian (15000, 3000, 10000) trong
handleAutoTraffic()
để điều chỉnh chu kỳ đèn. - Thêm chế độ ban đêm: Bạn có thể thêm một chế độ ban đêm, nơi đèn vàng nhấp nháy liên tục.
- Thêm cảm biến: Tích hợp cảm biến (ví dụ: cảm biến quang trở) để tự động chuyển sang chế độ ban đêm khi trời tối.
- Tăng số lượng đèn/ngã tư: Nếu bạn muốn điều khiển nhiều ngã tư, bạn sẽ cần thêm các cặp LED và có thể thêm IC 74HC595 hoặc các IC điều khiển khác.
- Cải thiện hiển thị: Thay vì chỉ hiển thị thời gian, bạn có thể cân nhắc hiển thị số xe đang chờ hoặc các thông tin khác nếu có các cảm biến phù hợp.
- Điều chỉnh mã LED 7 đoạn: Nếu module LED 7 đoạn của bạn có thứ tự chân khác hoặc là loại anode chung, bạn sẽ cần điều chỉnh mảng
digitCode
cho phù hợp.
Tags
Arduino basic
Nhận xét