Giao thức UART (Universal Asynchronous Receiver-Transmitter) là một giao thức kinh điển khi làm lập trình nhúng mà các bản gặp phải.
📡Giới thiệu về giao tiếp nối tiếp (Serial Communication)
Serial vs Parallel
- Giao tiếp song song (Parallel): nhiều bit gửi đồng thời qua nhiều dây (ví dụ 8 bit gửi qua 8 dây)
- Giao tiếp nối tiếp (Serial): gửi từng bit lần lượt qua một (hoặc ít) dây dữ liệu
- Serial có ưu điểm: dùng ít dây, chi phí thấp hơn, dễ mở rộng đường truyền, đặc biệt khi khoảng cách truyền dài
📌Do đó trong vi điều khiển / nhúng, hầu hết giao tiếp giữa các chip sử dụng dạng nối tiếp như UART, SPI, I²C, USB, v.v.
Các chuẩn giao tiếp nối tiếp phổ biến
Một số chuẩn nối tiếp thường dùng:
Giao thức | Dây / tín hiệu chính | Đồng bộ / Không đồng bộ | Ứng dụng thường gặp |
---|---|---|---|
UART / USART | TX, RX (và đôi khi RTS/CTS) | Không đồng bộ | Giao tiếp máy tính – vi điều khiển, module Bluetooth, module GSM |
SPI | MOSI, MISO, SCK, CS | Đồng bộ (có xung giờ) | Flash, cảm biến cao tốc, màn hình, ADC ngoại vi |
I²C | SDA, SCL | Đồng bộ | Mạng nhiều thiết bị, cảm biến, EEPROM, RTC |
USB / CAN / LIN / RS-485 | – | – | Trong hệ thống lớn, mạng công nghiệp |
🛠️Cấu tạo & cách thức hoạt động của UART / Serial không đồng bộ
Các thành phần chính
Ở phía phần cứng / MCU, một khối UART thường gồm:
- Thanh ghi truyền (Transmit Register)
- Thanh ghi dịch (Shift Register)
- Thanh ghi nhận (Receive Register)
- Bộ sinh tần số (baud rate generator)
- Logic điều khiển khung dữ liệu (start bit, dữ liệu, parity, stop bit)
📌Khi bạn gửi một byte, dữ liệu từ thanh ghi truyền sẽ được dịch ra từng bit, nối tiếp gửi ra kênh TX; phía nhận thì đọc bit từ RX và lắp lại thành byte.
Khung dữ liệu (Frame) UART
Một khung UART cơ bản có cấu trúc:
- Start bit: 1 bit thấp (logic 0) để báo bắt đầu
- Data bits: thường 8 bit (LSB trước)
- Parity bit (tuỳ chọn): kiểm tra lỗi (even / odd)
- Stop bit(s): 1 hoặc 2 bit cao (logic 1) để báo kết thúc
Hai đầu bên giao tiếp phải thống nhất các tham số: baud rate, số bit dữ liệu, parity, stop bit để hiểu đúng dữ liệu gửi – nếu không sẽ nhận nhầm.
📌Ví dụ: nếu baud rate là 115200 bps, thì mỗi bit được gửi trong khoảng thời gian ~8.68 micro-giây.
Đồng bộ hoá và sai số
Do giao tiếp UART không gửi xung đồng hồ (clock line) chung, nên khi nhận bit, vi điều khiển dựa vào thời gian nội bộ (bằng cách chia tần số) để đo vị trí bắt bit, đọc bit giữa.
Điều này có nghĩa nếu baud rate giữa hai thiết bị không khớp, hoặc dao động clock nhiều, sẽ dẫn đến lỗi nhận bit. Thường người ta cho phép sai số ±2–3% để vẫn hoạt động ổn định.
⚙️Ứng dụng & ví dụ minh họa: giao tiếp giữa máy tính và MCU, hoặc giữa hai MCU
Mô hình giao tiếp cơ bản
Ví dụ chuẩn: PC ↔ Converter USB-TTL ↔ Vi điều khiển
- PC gửi dữ liệu qua USB, converter (ví dụ FT232, CH340) chuyển sang tín hiệu TTL 3.3 V hoặc 5 V
- MCU dùng chân TX / RX để nhận / gửi
📌Nếu hai MCU giao tiếp trực tiếp, ta nối TX của cái này vào RX của cái kia và ngược lại.
Ví dụ STM32 (ví dụ sử dụng HAL / CubeMX)
📌Dưới đây là minh hoạ đơn giản để giao tiếp UART giữa STM32 và máy tính:
Phần cứng
- STM32 sử dụng mức 3.3 V (không cần chuyển level nếu converter USB-TTL cũng 3.3 V)
- Nối: STM32 TX → RX của converter, STM32 RX ← TX converter
- GND chung
Cấu hình phần mềm (HAL + CubeMX)
- Trong CubeMX:
- Bật USART (UART): chọn 1 USART (ví dụ USART1)
- Chọn baud rate (ví dụ 115200), 8 bit dữ liệu, no parity, 1 stop bit
- Khai báo pin TX / RX tương ứng (ví dụ PA9 → TX, PA10 → RX)
- Enable “USART global interrupt” nếu muốn dùng ngắt
- Trong mã nguồn:
/* Khởi tạo UART */
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
- Gửi / nhận dữ liệu:
// Gửi chuỗi
char msg[] = "Hello from STM32!\r\n";
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
// Nhận 1 byte (blocking)
uint8_t rx;
HAL_UART_Receive(&huart1, &rx, 1, HAL_MAX_DELAY);
// Hoặc dùng ngắt / DMA để không block chính của chương trình
Khi bạn mở terminal (ví dụ PuTTY, Tera Term) ở baud 115200, bạn sẽ thấy chuỗi từ STM32 được gửi lên.
Ví dụ giao tiếp giữa hai STM32
- Cấu hình đúng UART trên cả hai MCU
- STM32-A gửi dữ liệu “Ping”, STM32-B nhận và trả lại “Pong”
- Sử dụng ngắt nhận hoặc polling để kiểm tra liệu dữ liệu có đến không
⚠️Những lưu ý khi làm việc với giao tiếp Serial / UART
- Khớp baud rate giữa hai đầu là bắt buộc
- Chọn dung lượng bộ đệm (buffer) phù hợp, vì nếu dữ liệu đến quá nhanh mà không đọc kịp có thể overflow
- Khi nối dài đường truyền, có thể cần bổ sung transceiver chuẩn RS-485 / RS-232 để cách ly và truyền xa
- Nếu sử dụng parity / stop bit / flow control (RTS/CTS), đảm bảo cả hai đầu hiểu cùng cách
- Nếu dùng DMA hoặc ngắt để tối ưu hiệu suất, phải chú ý đồng bộ hoá và quản lý trạng thái
✅Kết luận
UART là chuẩn giao tiếp nối tiếp đơn giản nhưng cực kỳ quan trọng trong các hệ thống nhúng. Với STM32, ta có thể:
- Bắt đầu từ polling (blocking) để test đơn giản.
- Tiến lên ngắt và DMA để tối ưu hiệu suất.
💡 Sau khi nắm UART, bạn sẽ dễ dàng tiếp cận các giao tiếp khác như SPI, I²C, CAN.