IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

UART Là Gì? Debug Và Giao Tiếp Serial Với ESP32

1. UART Là Gì?

UART (Universal Asynchronous Receiver/Transmitter) là một giao thức giao tiếp nối tiếp, không đồng bộ, cho phép hai thiết bị trao đổi dữ liệu qua chỉ 2 dây: TX (truyền) và RX (nhận).

Nguyên lý hoạt động

  • TX (Transmit) — chân truyền dữ liệu
  • RX (Receive) — chân nhận dữ liệu
  • GND chung — cả hai thiết bị phải cùng tham chiếu GND

Lưu ý kết nối chéo:

ESP32 TX ───── RX (thiết bị kia)
ESP32 RX ───── TX (thiết bị kia)
ESP32 GND ──── GND (thiết bị kia)

Thông số UART

Thông sốGiá trị phổ biếnGhi chú
Baud rate9600, 115200, 460800Tốc độ truyền (bit/giây)
Data bits8 (phổ biến), 7, 9Số bit dữ liệu mỗi frame
Stop bits1 (phổ biến), 2Bit kết thúc frame
ParityNone (phổ biến), Even, OddBit kiểm tra lỗi

Khác biệt giữa UART và các giao thức khác

Đặc điểmUARTI2CSPI
Số dây2 (TX, RX)2 (SDA, SCL)4+ (MOSI, MISO, SCK, CS)
Đồng bộKhông (async)Có (clock)Có (clock)
Khoảng cách~10m (RS232), ~1.2km (RS485)~1m~1m
Nhiều thiết bịĐiểm-điểm (1:1)Nhiều slaveNhiều slave
Tốc độ~115200 bps (thường)~400 kHz~10 MHz+
Phức tạpRất thấpTrung bìnhCao hơn

2. UART Trên ESP32

ESP32 có 3 bộ UART phần cứng (UART0, UART1, UART2). Khác với GPIO thông thường, UART có thể được gán cho hầu hết GPIO nhờ GPIO Matrix.

Các chân UART mặc định

UARTTX mặc địnhRX mặc địnhGhi chú
UART0GPIO1GPIO3Dùng cho Serial Monitor (debug)
UART1GPIO10GPIO9Có thể dùng tự do
UART2GPIO17GPIO16Có thể dùng tự do

2.1. UART0 — Serial cho Debug

UART0 được kết nối với USB-to-UART bridge (CP2102 hoặc CH340) trên DevKit. Đây là cổng bạn dùng để:

  • In log debug qua Serial.print()
  • Nạp code (cùng với GPIO0)
  • Giao tiếp với Serial Monitor
void setup() {
  Serial.begin(115200); // Baud rate 115200
  Serial.println("ESP32 ready!"); // In ra Serial Monitor
}

2.2. Tạo UART tùy chỉnh (remap GPIO)

Bạn có thể gán UART1 hoặc UART2 cho bất kỳ GPIO nào:

#include <HardwareSerial.h>

// Tạo object Serial cho UART2
HardwareSerial Serial2(2); // (uart_number)

void setup() {
  Serial.begin(115200); // Debug
  
  // Cấu hình UART2 với chân tùy chỉnh
  // Serial2.begin(baud, mode, rx_pin, tx_pin)
  Serial2.begin(9600, SERIAL_8N1, GPIO_NUM_16, GPIO_NUM_17);
  // RX = GPIO16, TX = GPIO17
  
  Serial.println("UART2 ready on GPIO16(RX) / GPIO17(TX)");
  Serial2.println("Hello from ESP32 UART2!");
}

3. Gửi Và Nhận Dữ Liệu UART

3.1. Gửi dữ liệu

// Gửi text
Serial.println("Hello world");
Serial2.print("Temperature: ");
Serial2.println(25.5);

// Gửi số
Serial.write(65); // Gửi byte 65 (ASCII 'A')

// Gửi buffer
uint8_t data[] = {0x01, 0x02, 0x03, 0xFF};
Serial.write(data, sizeof(data));

3.2. Nhận dữ liệu

void loop() {
  // Kiểm tra có dữ liệu trong buffer không
  if (Serial2.available() > 0) {
    // Đọc 1 byte
    uint8_t byteReceived = Serial2.read();
    
    // Hoặc đọc thành string
    String message = Serial2.readStringUntil('\n');
    
    // Hoặc đọc buffer
    uint8_t buffer[64];
    int len = Serial2.readBytes(buffer, 64);
    
    Serial.print("Received: ");
    Serial.println(message);
  }
}

3.3. Ví dụ hoàn chỉnh: Giao tiếp 2 ESP32 qua UART

ESP32 #1 (Sender):

#include <Arduino.h>
#include <HardwareSerial.h>

HardwareSerial MySerial(2); // UART2

void setup() {
  Serial.begin(115200);
  MySerial.begin(115200, SERIAL_8N1, 16, 17); // RX=16, TX=17
  
  Serial.println("UART Sender ready");
}

void loop() {
  MySerial.println("Hello from ESP32 #1!");
  MySerial.printf("Milliseconds: %lu\n", millis());
  
  delay(1000);
}

ESP32 #2 (Receiver):

#include <Arduino.h>
#include <HardwareSerial.h>

HardwareSerial MySerial(2); // UART2

void setup() {
  Serial.begin(115200);
  MySerial.begin(115200, SERIAL_8N1, 16, 17);
  
  Serial.println("UART Receiver ready");
}

void loop() {
  if (MySerial.available()) {
    String line = MySerial.readStringUntil('\n');
    line.trim();
    
    if (line.length() > 0) {
      Serial.print("Received: ");
      Serial.println(line);
    }
  }
}

4. Kết Nối ESP32 Với Các Module UART Phổ Biến

4.1. GPS Module (Neo-6M)

GPS Neo-6M        ESP32
TX ──────────── GPIO16 (RX2)
RX ──────────── GPIO17 (TX2)
VCC ─────────── 3.3V
GND ─────────── GND
#include <HardwareSerial.h>
#include <TinyGPSPlus.h>

HardwareSerial GPS_Serial(2); // UART2
TinyGPSPlus gps;

void setup() {
  Serial.begin(115200);
  GPS_Serial.begin(9600, SERIAL_8N1, 16, 17);
  
  Serial.println("GPS Reader Ready");
}

void loop() {
  while (GPS_Serial.available() > 0) {
    char c = GPS_Serial.read();
    gps.encode(c);
  }
  
  if (gps.location.isUpdated()) {
    Serial.print("Lat: ");
    Serial.print(gps.location.lat(), 6);
    Serial.print(" Lng: ");
    Serial.println(gps.location.lng(), 6);
  }
}

4.2. HC-05 Bluetooth Module

HC-05             ESP32
TX ──────────── GPIO16 (RX2)
RX ──────────── GPIO17 (TX2)  ⚠️ Cần voltage divider (5V→3.3V)
VCC ─────────── 5V (HC-05 cần 5V)
GND ─────────── GND

Lưu ý: HC-05 dùng 3.3V cho tín hiệu, nhưng VCC cần 5V. Riêng chân RX của HC-05 có thể chịu 3.3V từ ESP32 — an toàn trực tiếp.

4.3. RFID Reader (UART)

RC522 UART        ESP32
TX ──────────── GPIO16 (RX2)
RX ──────────── GPIO17 (TX2)
VCC ─────────── 3.3V
GND ─────────── GND

5. SoftwareSerial Trên ESP32

Ngoài 3 UART phần cứng, bạn có thể dùng SoftwareSerial để tạo thêm cổng UART ảo, nhưng không khuyến nghị trên ESP32.

Vấn đề với SoftwareSerial trên ESP32:

  • ESP32 không có SoftwareSerial ổn định như Arduino
  • Thư viện SoftwareSerial (EspSoftwareSerial) hoạt động, nhưng: – Tốn tài nguyên CPU – Không ổn định ở baud rate cao (>57600) – Gây xung đột với WiFi/BT timer

Giải pháp tốt hơn:

// Thay vì SoftwareSerial, dùng UART1 hoặc UART2 hardware
HardwareSerial MySerial(1); // Dùng UART1
MySerial.begin(9600, SERIAL_8N1, 4, 5); // RX=GPIO4, TX=GPIO5

6. Xử Lý Buffer Và Timeout

Khi nhận dữ liệu UART, bạn cần quản lý buffer để không mất dữ liệu:

#include <HardwareSerial.h>

HardwareSerial Serial2(2);

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, 16, 17);
  
  // Tăng buffer UART2 lên 1024 byte (mặc định 256 byte)
  Serial2.setRxBufferSize(1024);
}

void loop() {
  // Đọc tất cả dữ liệu có trong buffer
  size_t available = Serial2.available();
  
  if (available > 0) {
    uint8_t* buffer = (uint8_t*)malloc(available);
    
    if (buffer) {
      int bytesRead = Serial2.readBytes(buffer, available);
      
      // Xử lý dữ liệu
      Serial.printf("Received %d bytes\n", bytesRead);
      
      // Ghi log bytes nhận được
      for (int i = 0; i < bytesRead; i++) {
        Serial.printf("0x%02X ", buffer[i]);
      }
      Serial.println();
      
      free(buffer);
    }
  }
}

Timeout khi đọc

// Timeout mặc định: 1 giây
Serial2.setTimeout(2000); // 2 giây

// Đọc với timeout
char buffer[100];
int len = Serial2.readBytesUntil('\n', buffer, 100);

if (len > 0) {
  buffer[len] = '\0';
  Serial.print("Line received: ");
  Serial.println(buffer);
} else {
  Serial.println("Timeout — no data received");
}

7. Baud Rate: Chọn Thế Nào Cho Phù Hợp?

Baud rateKhoảng cách tối đaỨng dụng
300~1.5kmRS485, đường dây dài
9600~100mGPS, thiết bị công nghiệp cũ
19200~50mCảm biến, module
57600~20mHC-05 Bluetooth
115200~10mSerial Monitor (debug) — phổ biến nhất
230400~5mDebug nhanh, dữ liệu lớn
921600~1mNạp firmware, truyền dữ liệu nhanh

Luôn dùng 115200 cho Serial debug. Đây là tốc độ cân bằng giữa tốc độ và độ tin cậy.

8. Lỗi Thường Gặp Và Debug

8.1. Không thấy dữ liệu trong Serial Monitor

  • Baud rate không khớp? (Serial.begin(115200) phải giống Serial Monitor)
  • Chọn đúng cổng COM?
  • Cáp USB có hỗ trợ dữ liệu?

8.2. Dữ liệu bị lỗi (ký tự lạ)

  • Baud rate không khớp giữa 2 thiết bị
  • Dây quá dài (>10m ở 115200)
  • Nhiễu điện từ (chạy dây UART song song dây nguồn)
  • GND không chung

8.3. Mất dữ liệu (data loss)

  • Buffer quá nhỏ (dùng setRxBufferSize())
  • Đọc không kịp (loop quá chậm so với tốc độ gửi)
  • Interrupt chiếm CPU lâu

8.4. UART không hoạt động sau khi remap GPIO

  • Có dùng đúng HardwareSerial object?
  • RX pin có cấu hình đúng?
  • TX pin có nối với RX của thiết bị kia?

Debug Checklist

  • [ ] Baud rate giống nhau trên cả hai thiết bị?
  • [ ] Kết nối chéo: TX→RX, RX→TX?
  • [ ] GND chung?
  • [ ] Đo tín hiệu TX bằng oscilloscope/multimeter?
  • [ ] Thử baud rate thấp hơn (9600) nếu nghi ngờ nhiễu?

9. Ví Dụ: ESP32 + MQTT + UART GPS

/**
 * ESP32 đọc GPS qua UART, gửi lên MQTT/IoTLabs Cloud
 */

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <HardwareSerial.h>
#include <TinyGPSPlus.h>

// Cấu hình
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASS";
const char* mqtt_server = "mqtt.iotlabs.vn";

WiFiClient espClient;
PubSubClient client(espClient);

HardwareSerial GPS_Serial(2); // UART2: RX=16, TX=17
TinyGPSPlus gps;

void setup() {
  Serial.begin(115200);
  GPS_Serial.begin(9600, SERIAL_8N1, 16, 17);
  
  // Kết nối WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  
  client.setServer(mqtt_server, 1883);
  client.connect("ESP32-GPS");
}

void loop() {
  // Đọc dữ liệu GPS
  while (GPS_Serial.available() > 0) {
    gps.encode(GPS_Serial.read());
  }
  
  // Gửi lên MQTT mỗi 10 giây nếu có GPS fix
  static unsigned long lastSend = 0;
  if (millis() - lastSend > 10000 && gps.location.isValid()) {
    char payload[100];
    snprintf(payload, sizeof(payload),
      "{\"lat\":%.6f,\"lng\":%.6f,\"alt\":%.1f,\"satellites\":%d}",
      gps.location.lat(), gps.location.lng(),
      gps.altitude.meters(), gps.satellites.value());
    
    client.publish("iotlabs/gps/data", payload);
    Serial.println(payload);
    
    lastSend = millis();
  }
  
  client.loop();
}

10. Kết Luận

UART là giao thức đơn giản nhất nhưng cực kỳ hữu ích — từ debug cơ bản đến giao tiếp với GPS, Bluetooth, RFID. ESP32 có 3 cổng UART phần cứng, đủ cho hầu hết dự án.

Tóm tắt:

  1. Serial.begin(115200) — debug console qua USB
  2. HardwareSerial Serial2(2) — UART2 cho module ngoài
  3. Kết nối chéo: TX→RX, RX→TX, GND chung
  4. 3 UART hardware (UART0 cho debug, UART1 và UART2 tự do)
  5. Có thể remap UART sang bất kỳ GPIO nào (GPIO Matrix)
  6. Tránh SoftwareSerial — dùng UART1/2 hardware thay thế
  7. Baud rate 115200 là chuẩn cho hầu hết ứng dụng

Bài tiếp theo: I2C là gì? Kết nối nhiều cảm biến chỉ với 2 dây.

Tài liệu tham khảo

  • Espressif ESP32 Technical Reference Manual — Chapter “UART”
  • Espressif ESP-IDF Programming Guide: UART Driver
  • Arduino-ESP32: HardwareSerial class
  • TinyGPSPlus library documentation