UART (Universal Asynchronous Receiver / Transmitter) là một trong những chuẩn giao tiếp nối tiếp phổ biến nhất trong lập trình nhúng. UART trong STM32 có thể được triển khai theo hai cách: UART Software (mô phỏng giao thức bằng GPIO) và UART Hardware (dùng ngoại vi USART tích hợp sẵn). Bài viết này hướng dẫn chi tiết cả hai phương pháp trên STM32F1 kèm code thực tế bằng thư viện SPL.

Về bản chất, UART là giao tiếp bất đồng bộ (asynchronous) – không có đường clock chung. Hai thiết bị phải thống nhất trước về tốc độ baudrate, số bit dữ liệu, bit parity và bit stop để hiểu được nhau.
I. UART Software – mô phỏng bằng GPIO
Hầu hết các dòng vi điều khiển đều tích hợp bộ truyền thông UART. Tuy nhiên, để hiểu rõ bản chất giao thức, ta có thể mô phỏng UART bằng GPIO (bit-banging). Cách này còn hữu ích khi số chân UART cứng không đủ dùng.
Cấu hình GPIO
UART chỉ sử dụng 2 chân để truyền: TX (Transmit) và RX (Receive). UART Software không yêu cầu chân cụ thể nên có thể dùng chân bất kỳ:
#define TX_Pin GPIO_Pin_9
#define RX_Pin GPIO_Pin_10
#define UART_GPIO GPIOA
TX là chân truyền nên được cấu hình OUTPUT (Push-Pull), RX là chân nhận nên cấu hình INPUT (Floating):
void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* TX -> Output Push-Pull */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = TX_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(UART_GPIO, &GPIO_InitStructure);
/* RX -> Input Floating */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = RX_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(UART_GPIO, &GPIO_InitStructure);
}
Khung truyền UART và baudrate
Tốc độ baudrate được xác định bởi thời gian truyền đi 1 bit. Bài này dùng tốc độ phổ thông 9600 bits/s, ứng với mỗi bit là ~105µs:
/* Baudrate = 9600 bits/s -> 0.10467ms cho 1 bit = 104.67us
=> time delay ~~ 105us */
#define BRateTime 105

Ở chế độ nghỉ (không truyền), đường TX được giữ ở mức cao. Hàm UART_Config() thiết lập trạng thái nghỉ cho đường truyền:
void UART_Config(void)
{
GPIO_SetBits(UART_GPIO, TX_Pin);
delay_us(1);
}
Hàm truyền (Transmit)
Hàm truyền gửi lần lượt 8 bit trong byte dữ liệu, sau khi tín hiệu start được gửi đi. Trình tự: (1) tạo start, delay 1 period; (2) truyền từng bit dữ liệu, mỗi bit 1 period; (3) dịch bit; (4) tạo stop, delay tương ứng số bit stop.
void UART_Transmit(const char DataValue)
{
/* Send Start Bit */
GPIO_WriteBit(UART_GPIO, TX_Pin, Bit_RESET);
delay_us(BRateTime);
for (unsigned char i = 0; i < 8; i++) {
if (((DataValue >> i) & 0x1) == 0x1)
GPIO_WriteBit(UART_GPIO, TX_Pin, Bit_SET);
else
GPIO_WriteBit(UART_GPIO, TX_Pin, Bit_RESET);
delay_us(BRateTime);
}
/* Send Stop Bit */
GPIO_WriteBit(UART_GPIO, TX_Pin, Bit_SET);
delay_us(BRateTime);
}
Tạo bit Parity
Dữ liệu truyền đi có thể được thêm bit parity tùy theo cấu hình chẵn/lẻ hay không dùng:
typedef enum {
Parity_Mode_NONE,
Parity_Mode_ODD,
Parity_Mode_EVEN
} Parity_Mode;
uint8_t Parity_Generate(uint8_t data, Parity_Mode Mode)
{
uint8_t count = 0;
for (int i = 0; i < 8; i++) {
if (data & 0x01) count++;
data >>= 1;
}
switch (Mode) {
case Parity_Mode_ODD:
return (count % 2) ? ((data << 1) | 1) : (data << 1);
case Parity_Mode_EVEN:
return (!(count % 2)) ? ((data << 1) | 1) : (data << 1);
case Parity_Mode_NONE:
default:
return data;
}
}
Hàm nhận (Receive)
Hàm nhận đọc lần lượt 8 bit. Trình tự: (1) chờ tín hiệu start từ thiết bị gửi; (2) delay 1,5 period (để lấy mẫu giữa bit); (3) đọc data trên RX rồi ghi vào biến; (4) dịch bit và delay 1 period; (5) delay 0,5 period rồi đợi stop bit.
unsigned char UART_Receive(void)
{
unsigned char DataValue = 0;
/* Chờ start bit (đường RX kéo xuống thấp) */
while (GPIO_ReadInputDataBit(UART_GPIO, RX_Pin) == 1);
delay_us(BRateTime);
delay_us(BRateTime / 2); /* căn giữa bit */
for (unsigned char i = 0; i < 8; i++) {
if (GPIO_ReadInputDataBit(UART_GPIO, RX_Pin) == 1)
DataValue += (1 << i);
delay_us(BRateTime);
}
/* Kiểm tra stop bit */
if (GPIO_ReadInputDataBit(UART_GPIO, RX_Pin) == 1) {
delay_us(BRateTime / 2);
return DataValue;
}
return DataValue;
}
Sau khi nhận được data, có thể tiến hành kiểm tra chẵn/lẻ bằng hàm Parity_Check():
uint8_t Parity_Check(uint8_t data, Parity_Mode Mode)
{
uint8_t count = 0;
for (int i = 0; i < 8; i++) {
if (data & 0x01) count++;
data >>= 1;
}
switch (Mode) {
case Parity_Mode_ODD: return (count % 2);
case Parity_Mode_EVEN: return (!(count % 2));
case Parity_Mode_NONE: return 1;
default: return 0;
}
}
II. UART Hardware – dùng ngoại vi USART
Với UART Hardware, STM32 dùng ngoại vi USART tích hợp sẵn để tự động xử lý việc tạo khung truyền, baudrate, parity… CPU chỉ cần ghi/đọc thanh ghi dữ liệu. Cách này chính xác, nhanh và tiết kiệm tài nguyên hơn nhiều so với bit-banging.
Cấu hình GPIO
Các bộ UART trong STM32F1 được xác định sẵn chân GPIO. Ví dụ với USART1:
| Chân | Chức năng UART | Chế độ GPIO |
|---|---|---|
| PA9 | USART1_TX | AF Push-Pull (AF_PP) |
| PA10 | USART1_RX | Input Floating (IN_FLOATING) |
Tương tự UART Software: TX là OUTPUT (chế độ Alternate Function Push-Pull) và RX là INPUT:
void GPIO_Config(void)
{
GPIO_InitTypeDef GPIOInitStruct;
/* RX (PA10) -> Input Floating */
GPIOInitStruct.GPIO_Pin = GPIO_Pin_10;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIOInitStruct);
/* TX (PA9) -> Alternate Function Push-Pull */
GPIOInitStruct.GPIO_Pin = GPIO_Pin_9;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIOInitStruct);
}
Cấu hình USART
Tương tự các ngoại vi khác, các tham số UART được cấu hình trong struct USART_InitTypeDef:
| Tham số | Mô tả |
|---|---|
USART_Mode | Chế độ hoạt động: USART_Mode_Tx, USART_Mode_Rx hoặc cả hai |
USART_BaudRate | Tốc độ baudrate (vd 9600, 115200) |
USART_HardwareFlowControl | Chế độ bắt tay (flow control) |
USART_WordLength | Số bit mỗi lần truyền (8b/9b) |
USART_StopBits | Số lượng stop bit |
USART_Parity | Bit kiểm tra chẵn/lẻ |
void UART_Config(void)
{
USART_InitTypeDef USARTInitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USARTInitStruct.USART_BaudRate = 9600;
USARTInitStruct.USART_WordLength = USART_WordLength_8b;
USARTInitStruct.USART_StopBits = USART_StopBits_1;
USARTInitStruct.USART_Parity = USART_Parity_No;
USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USARTInitStruct);
USART_Cmd(USART1, ENABLE);
}
Các hàm truyền / nhận và cờ trạng thái
USART_SendData(USARTx, Data): truyền data từ UARTx (đã tự thêm bit chẵn/lẻ theo cấu hình).USART_ReceiveData(USARTx): nhận data từ UARTx.USART_GetFlagStatus(USARTx, USART_FLAG): trả về trạng thái cờ tương ứng.
| Cờ (Flag) | Ý nghĩa |
|---|---|
USART_FLAG_TXE | Cờ truyền – set lên 1 khi quá trình truyền hoàn tất (thanh ghi rỗng) |
USART_FLAG_RXNE | Cờ nhận – set lên 1 khi quá trình nhận hoàn tất (có data mới) |
USART_FLAG_IDLE | Cờ báo đường truyền đang ở chế độ Idle |
USART_FLAG_PE | Cờ báo lỗi Parity |
Quá trình truyền/nhận có thể mô tả như sau: khi bắt đầu, UART xóa data trong thanh ghi DR để đảm bảo dữ liệu đúng. Khi truyền, gửi từng byte rồi đợi cờ TXE bật lên. Khi nhận, đọc data từ bộ UART sau khi cờ RXNE bật. Với mảng dữ liệu, lặp lại quá trình cho từng byte.
/* Truyền 1 byte */
void UART_SendByte(uint8_t data)
{
USART_SendData(USART1, data);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
/* Nhận 1 byte */
uint8_t UART_ReadByte(void)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (uint8_t)USART_ReceiveData(USART1);
}
/* Truyền cả chuỗi */
void UART_SendString(const char *str)
{
while (*str) UART_SendByte(*str++);
}
So sánh UART Software và UART Hardware
| Tiêu chí | UART Software (GPIO) | UART Hardware (USART) |
|---|---|---|
| Chân sử dụng | Bất kỳ chân GPIO nào | Chân cố định (PA9/PA10…) |
| Độ chính xác timing | Phụ thuộc delay, dễ sai lệch | Chính xác nhờ phần cứng |
| Tải CPU | Cao (CPU bận delay từng bit) | Thấp (phần cứng xử lý) |
| Tốc độ tối đa | Thấp (thường ≤ 9600) | Cao (115200 trở lên) |
| Ứng dụng | Học hiểu bản chất, thiếu chân UART | Sản phẩm thực tế |
Lưu ý khi sử dụng UART trong STM32
- Hai thiết bị phải cùng baudrate, số data bit, parity và stop bit – nếu lệch sẽ nhận sai (lỗi framing).
- Đấu nối TX ↔ RX chéo nhau và chung GND giữa hai thiết bị.
- Với UART Hardware, nhớ cấp clock cho USART (APB2 với USART1, APB1 với USART2/3) và cấu hình TX ở chế độ AF_PP.
- Nên dùng ngắt UART (RXNE) hoặc DMA để nhận dữ liệu thay vì polling, tránh chiếm CPU.
- UART Software phù hợp để học, nhưng sản phẩm thực tế nên ưu tiên UART Hardware vì ổn định và nhanh hơn.
Kết luận
UART trong STM32 là kỹ năng nền tảng cho hầu hết ứng dụng nhúng – từ debug qua cổng serial, giao tiếp với module (Bluetooth, GPS, GSM) đến truyền dữ liệu giữa các vi điều khiển. Qua bài viết, bạn đã nắm được cả hai cách triển khai: UART Software mô phỏng bằng GPIO giúp hiểu rõ khung truyền, start/stop bit và parity; còn UART Hardware dùng ngoại vi USART cho hiệu năng và độ ổn định tối ưu.
Nắm vững cấu trúc khung truyền và quy trình cấu hình (GPIO → USART → truyền/nhận theo cờ TXE/RXNE) sẽ giúp bạn tự tin ứng dụng UART trong các dự án automotive embedded như giao tiếp ECU, đọc dữ liệu cảm biến hay xây dựng giao thức truyền thông tùy biến.


