Hàm (function) và cách xây dựng hàm trong Python
1. Hàm (function) là gì? Hàm (function) là một đoạn chương trình được đặt tên, có...
Kết quả sẽ mở trong tab mới, giới hạn trong website này.
Truy cập nhanh
Tìm kiếm gần đây
Nối Arduino Uno với ESP8266 qua UART bằng 3 sợi dây, có mạch chia áp 5V xuống 3,3V. Code gửi dữ liệu cảm biến sang ESP8266 và nhận lệnh bật/tắt LED theo chiều ngược lại.
Kết nối Arduino Uno với ESP8266 giúp Arduino Uno giao tiếp với Internet thông qua Wi-Fi. Hai board trao đổi dữ liệu qua UART bằng 3 dây, cho phép gửi dữ liệu cảm biến và nhận lệnh điều khiển, chẳng hạn như bật/tắt LED.
Mục tiêu bài học: Sau bài này bạn sẽ:
Arduino Uno có nhiều chân, dễ lập trình, nhưng không có Wi-Fi. ESP8266 thì ngược lại: có Wi-Fi sẵn nhưng ít chân hơn và chạy mức logic 3,3V. Ghép hai board lại, mỗi bên làm đúng việc mình giỏi nhất.
| Thành phần | Nhiệm vụ chính |
|---|---|
| Arduino Uno | Đọc cảm biến, điều khiển LED, relay, servo, buzzer |
| ESP8266 | Kết nối Wi-Fi, gửi dữ liệu lên server, nhận lệnh từ Web/App |
| Web/App | Hiển thị dữ liệu, gửi lệnh điều khiển |
UART (Universal Asynchronous Receiver/Transmitter) là kiểu truyền nối tiếp dùng 2 dây dữ liệu. Chân TX (truyền) của board này nối vào chân RX (nhận) của board kia. Hai bên phải chạy cùng tốc độ baud, ở bài này là 9600.
Mình không dùng chân RX/TX cứng (GPIO3/GPIO1) của NodeMCU. Hai chân này đã nối với chip USB, nên vừa cắm Uno vừa nạp code rất dễ lỗi. Thay vào đó, cả hai board đều dùng SoftwareSerial để tạo thêm một cổng UART trên chân thường.
| Arduino Uno | Linh kiện | NodeMCU ESP8266 |
|---|---|---|
| D3 (TX) | R1 = 1kΩ, R2 = 2kΩ xuống GND | D5 (RX, GPIO14) |
| D2 (RX) | Nối thẳng | D6 (TX, GPIO12) |
| GND | Nối thẳng | GND |
Thêm 1 LED nối tiếp điện trở 220Ω vào chân D8 của Uno để thử lệnh ở mục 5. Chân A0 nối với một cảm biến analog bất kỳ, ví dụ cảm biến LDR hoặc biến trở.

Uno chạy mức logic 5V, nên chân TX xuất tín hiệu cao gần 5V. Theo datasheet ESP8266EX, chân I/O chỉ chịu tối đa 3,6V. Nối thẳng 5V vào chân RX của ESP8266 lâu ngày có thể làm hỏng chân đó.
Mạch chia áp hạ 5V xuống: 5V × 2kΩ / (1kΩ + 2kΩ) ≈ 3,3V. Chiều ngược lại thì không cần. Uno chỉ cần khoảng 3V là đã hiểu mức cao, nên tín hiệu 3,3V từ ESP8266 vẫn đọc được.
Lưu ý: Luôn nối chung GND giữa hai board. Thiếu dây GND thì hai bên không có mức điện áp tham chiếu chung, dữ liệu nhận được sẽ toàn ký tự rác.
UART chỉ truyền từng byte, nó không biết đâu là đầu, đâu là cuối một bản tin. Vì vậy hai bên cần thống nhất trước một quy ước (protocol). Cách đơn giản nhất là mỗi bản tin một dòng, dạng KEY:VALUE.
Uno -> ESP8266: SENSOR:523
ESP8266 -> Uno: LED1:ON
LED1:OFFKý tự xuống dòng \n đánh dấu hết bản tin. Bên nhận chỉ cần đọc tới \n là có trọn một lệnh. Cách này đọc bằng mắt trên Serial Monitor cũng dễ, debug cực kỳ nhanh.
Code cho Arduino Uno: mỗi giây đọc chân A0 rồi gửi sang ESP8266.
#include <SoftwareSerial.h>
// Uno: D2 = RX, D3 = TX
SoftwareSerial espSerial(2, 3);
void setup() {
Serial.begin(9600); // Serial Monitor của Uno
espSerial.begin(9600); // Đường UART nối sang ESP8266
}
void loop() {
int giaTri = analogRead(A0);
// Gửi theo dạng KEY:VALUE, println tự thêm ký tự xuống dòng
espSerial.print("SENSOR:");
espSerial.println(giaTri);
Serial.print("Da gui: ");
Serial.println(giaTri);
delay(1000);
}Code cho NodeMCU ESP8266: nhận từng dòng, tách lấy con số rồi in ra Serial Monitor.
#include <SoftwareSerial.h>
// NodeMCU: D5 (GPIO14) = RX, D6 (GPIO12) = TX
SoftwareSerial unoSerial(D5, D6);
void setup() {
Serial.begin(115200); // Serial Monitor qua cổng USB
unoSerial.begin(9600); // Phải cùng tốc độ với Uno
Serial.println();
Serial.println("ESP8266 san sang");
}
void loop() {
if (unoSerial.available()) {
// Đọc tới ký tự xuống dòng, rồi bỏ \r và khoảng trắng thừa
String duLieu = unoSerial.readStringUntil('\n');
duLieu.trim();
if (duLieu.startsWith("SENSOR:")) {
int giaTri = duLieu.substring(7).toInt(); // Lấy phần sau "SENSOR:"
Serial.print("Nhan tu Uno: ");
Serial.println(giaTri);
}
}
}Vài điểm đáng chú ý:
println() tự thêm \r\n ở cuối, nên bên nhận dùng readStringUntil('\n') để cắt từng dòng.trim() xóa ký tự \r còn sót lại. Thiếu bước này thì so sánh chuỗi sẽ luôn sai.substring(7) bỏ 7 ký tự đầu (“SENSOR:”), toInt() đổi phần còn lại thành số.Nạp code cho từng board, mở Serial Monitor của ESP8266 ở tốc độ 115200. Mỗi giây sẽ hiện một dòng như sau:
ESP8266 san sang
Nhan tu Uno: 523
Nhan tu Uno: 530
Nhan tu Uno: 528Giờ thêm chiều ngược lại: ESP8266 gửi lệnh, Uno bật/tắt LED ở chân D8. Để thử nhanh mà chưa cần Web/App, bạn gõ lệnh vào Serial Monitor của ESP8266, ESP8266 sẽ chuyển lệnh đó xuống Uno.
Code cho Arduino Uno:
#include <SoftwareSerial.h>
// Uno: D2 = RX, D3 = TX
SoftwareSerial espSerial(2, 3);
const int LED1 = 8;
unsigned long lanGuiTruoc = 0;
void setup() {
Serial.begin(9600);
espSerial.begin(9600);
pinMode(LED1, OUTPUT);
}
void loop() {
// 1. Mỗi 1 giây gửi giá trị cảm biến, không dùng delay()
if (millis() - lanGuiTruoc >= 1000) {
lanGuiTruoc = millis();
espSerial.print("SENSOR:");
espSerial.println(analogRead(A0));
}
// 2. Luôn sẵn sàng nhận lệnh từ ESP8266
if (espSerial.available()) {
String lenh = espSerial.readStringUntil('\n');
lenh.trim();
Serial.print("Lenh: ");
Serial.println(lenh);
if (lenh == "LED1:ON") {
digitalWrite(LED1, HIGH);
} else if (lenh == "LED1:OFF") {
digitalWrite(LED1, LOW);
}
}
}Điểm mới ở đây là millis() thay cho delay(1000). Nếu dùng delay(), Uno sẽ “ngủ” 1 giây và có thể bỏ lỡ lệnh gửi tới. Với millis(), vòng loop() chạy liên tục, vừa gửi đúng nhịp vừa luôn sẵn sàng nhận lệnh.
Code cho NodeMCU ESP8266:
#include <SoftwareSerial.h>
// NodeMCU: D5 (GPIO14) = RX, D6 (GPIO12) = TX
SoftwareSerial unoSerial(D5, D6);
void setup() {
Serial.begin(115200);
unoSerial.begin(9600);
Serial.println();
Serial.println("Go LED1:ON hoac LED1:OFF roi Enter");
}
void loop() {
// 1. Nhận dữ liệu cảm biến từ Uno
if (unoSerial.available()) {
String duLieu = unoSerial.readStringUntil('\n');
duLieu.trim();
Serial.print("Uno: ");
Serial.println(duLieu);
}
// 2. Lệnh gõ trên Serial Monitor -> chuyển xuống Uno
if (Serial.available()) {
String lenh = Serial.readStringUntil('\n');
lenh.trim();
if (lenh.length() > 0) {
unoSerial.println(lenh);
Serial.print("Da gui lenh: ");
Serial.println(lenh);
}
}
}Vì ESP8266 dùng cổng riêng (D5/D6) để nói chuyện với Uno, các dòng Serial.print() chỉ đi ra cổng USB. Chúng không bị gửi nhầm sang Uno. Đây chính là lý do mình không dùng chân RX/TX cứng của NodeMCU.
Trên Serial Monitor của ESP8266, chọn chế độ kết thúc dòng là Newline. Gõ LED1:ON rồi Enter, LED ở chân D8 sáng ngay. Gõ LED1:OFF thì LED tắt, quá dễ luôn!
Uno: SENSOR:523
Uno: SENSOR:527
Da gui lenh: LED1:ON
Uno: SENSOR:525| Hiện tượng | Nguyên nhân thường gặp | Cách sửa |
|---|---|---|
| Không nhận được gì | Nối TX vào TX, RX vào RX | Đảo lại: TX bên này vào RX bên kia |
| Nhận toàn ký tự lạ | Lệch tốc độ baud hoặc thiếu dây GND | Kiểm tra cả hai bên cùng 9600, nối chung GND |
| Gõ lệnh đúng mà LED không sáng | Chuỗi còn \r hoặc khoảng trắng | Gọi trim() trước khi so sánh |
| Thỉnh thoảng mất một lệnh | SoftwareSerial trên Uno không truyền và nhận cùng lúc được | Không gửi quá dày, gửi lại lệnh quan trọng |
Mẹo: Khi bị lỗi, hãy thử từng chiều một (code mục 4) trước khi chạy code 2 chiều. Chia nhỏ ra thì biết ngay lỗi nằm ở dây, ở baud hay ở code.
TEMP:28.5 và HUMI:70 sang ESP8266.LED2:ON, LED2:OFF, BUZZER:ON, BUZZER:OFF.DATA,LDR=523,TEMP=28.5,LED1=ON. Viết code ESP8266 tách chuỗi này thành từng giá trị.