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ến | Ghi chú |
|---|---|---|
| Baud rate | 9600, 115200, 460800 | Tốc độ truyền (bit/giây) |
| Data bits | 8 (phổ biến), 7, 9 | Số bit dữ liệu mỗi frame |
| Stop bits | 1 (phổ biến), 2 | Bit kết thúc frame |
| Parity | None (phổ biến), Even, Odd | Bit kiểm tra lỗi |
Khác biệt giữa UART và các giao thức khác
| Đặc điểm | UART | I2C | SPI |
|---|---|---|---|
| Số dây | 2 (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 slave | Nhiều slave |
| Tốc độ | ~115200 bps (thường) | ~400 kHz | ~10 MHz+ |
| Phức tạp | Rất thấp | Trung bình | Cao 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
| UART | TX mặc định | RX mặc định | Ghi chú |
|---|---|---|---|
| UART0 | GPIO1 | GPIO3 | Dùng cho Serial Monitor (debug) |
| UART1 | GPIO10 | GPIO9 | Có thể dùng tự do |
| UART2 | GPIO17 | GPIO16 | Có 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 rate | Khoảng cách tối đa | Ứng dụng |
|---|---|---|
| 300 | ~1.5km | RS485, đường dây dài |
| 9600 | ~100m | GPS, thiết bị công nghiệp cũ |
| 19200 | ~50m | Cảm biến, module |
| 57600 | ~20m | HC-05 Bluetooth |
| 115200 | ~10m | Serial Monitor (debug) — phổ biến nhất |
| 230400 | ~5m | Debug nhanh, dữ liệu lớn |
| 921600 | ~1m | Nạ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
HardwareSerialobject? - 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:
Serial.begin(115200)— debug console qua USBHardwareSerial Serial2(2)— UART2 cho module ngoài- Kết nối chéo: TX→RX, RX→TX, GND chung
- 3 UART hardware (UART0 cho debug, UART1 và UART2 tự do)
- Có thể remap UART sang bất kỳ GPIO nào (GPIO Matrix)
- Tránh SoftwareSerial — dùng UART1/2 hardware thay thế
- 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:
HardwareSerialclass - TinyGPSPlus library documentation


