Mục lục bài viết

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ẽ:

  • Nối đúng chân UART giữa Arduino Uno và NodeMCU ESP8266, có mạch chia áp.
  • Giải thích được vì sao chân TX của Uno phải đi qua mạch chia áp.
  • Viết được chương trình gửi dữ liệu cảm biến từ Uno sang ESP8266.
  • Viết được chương trình gửi lệnh từ ESP8266 xuống Uno để bật/tắt LED.

1. Vì sao cần ghép Arduino với ESP8266?

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.

Lập trình giao tiếp Arduino với ESP8266 qua UART
Kiến trúc hệ thống Arduino với ESP8266 giao tiếp qua UART
Thành phầnNhiệm vụ chính
Arduino UnoĐọc cảm biến, điều khiển LED, relay, servo, buzzer
ESP8266Kết nối Wi-Fi, gửi dữ liệu lên server, nhận lệnh từ Web/App
Web/AppHiể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.

2. Nối chân Arduino với ESP8266 qua UART

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 UnoLinh kiệnNodeMCU ESP8266
D3 (TX)R1 = 1kΩ, R2 = 2kΩ xuống GNDD5 (RX, GPIO14)
D2 (RX)Nối thẳngD6 (TX, GPIO12)
GNDNối thẳngGND

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ở.

Lập trình giao tiếp Arduino với ESP8266 qua UART
Sơ đồ nối Arduino với ESP8266 qua mạch chia áp

Vì sao phải chia áp chân TX?

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.

3. Quy ước giao thức truyền dữ liệu

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:OFF

Ký 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.

4. Arduino gửi dữ liệu sang ESP8266

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ố.
  • ESP8266 debug qua USB ở 115200, còn đường nối sang Uno chạy 9600. Hai tốc độ này độc lập nhau.

Kết quả

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: 528

5. Truyền 2 chiều giữa Arduino với ESP8266

Giờ 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.

Kết quả

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

6. Lỗi thường gặp khi nối Arduino với ESP8266

Hiện tượngNguyên nhân thường gặpCá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 GNDKiểm tra cả hai bên cùng 9600, nối chung GND
Gõ lệnh đúng mà LED không sángChuỗi còn \r hoặc khoảng trắngGọi trim() trước khi so sánh
Thỉnh thoảng mất một lệnhSoftwareSerial trên Uno không truyền và nhận cùng lúc đượcKhô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.

7. Bài tập

  • Gắn thêm cảm biến DHT, gửi thêm hai bản tin TEMP:28.5 và HUMI:70 sang ESP8266.
  • Thêm LED2 và buzzer, hỗ trợ các lệnh LED2:ON, LED2:OFF, BUZZER:ON, BUZZER:OFF.
  • Gửi tất cả dữ liệu trên một dòng, ví dụ DATA,LDR=523,TEMP=28.5,LED1=ON. Viết code ESP8266 tách chuỗi này thành từng giá trị.
Tác giả

HV Lee

500+ bài viết về lập trình, phần cứng, mạng máy tính và công nghệ. Chia sẻ kiến thức từ những gì đã học, đã dạy và đã trải nghiệm — để người học CNTT hiểu sâu hơn và làm được nhiều hơn.

Gợi ý tiếp theo

Cài đặt Python và môi trường lập trình với Visual Studio Code

Học tiếp