UART 是 STM32 專案裡最常用、也最容易「看起來都設定對了卻沒有資料」的介面。遇到收不到資料、亂碼或偶發遺失時,先不要急著改程式;用固定順序排查,通常能很快縮小問題。
先確認接線與電氣條件
UART 的 TX 與 RX 必須交叉連接:開發板的 TX 接到對方 RX,開發板的 RX 接到對方 TX。同時,兩端一定要共地(GND)。
另一個常見問題是電壓位準。多數 STM32 GPIO 是 3.3V 邏輯,不能直接接 5V TTL 裝置的 TX;若對方輸出 5V,先確認該腳位是否 5V tolerant,或使用電平轉換器。
核對 UART 參數
兩端所有通訊參數都要一致。最常見組合是 115200 8N1:
- 鮑率:115200
- 資料位元:8
- 同位元檢查:None
- 停止位元:1
- 流量控制:None
只要停止位元或 parity 不一致,就可能出現可讀字元混雜亂碼的情況。先用 USB-to-UART 模組與序列埠工具驗證對方裝置是否真的以預期參數送資料。
檢查時脈是否真的符合預期
UART 的實際鮑率由 peripheral clock 推導。若 SystemClock_Config() 修改了 PLL、APB1 或 APB2 的分頻,UART 設定的 115200 可能已經不是實際的 115200。
建議在程式中確認 UART 使用的 peripheral clock,尤其是不同系列 STM32 對 APB 時脈與 timer/UART 的規則不完全相同。時脈錯誤時,終端機常會看到固定模式的亂碼。
先用最小化輪詢程式驗證
在導入 interrupt 或 DMA 前,先用阻塞式 API 驗證硬體路徑。這可以把問題切成「硬體與設定」或「非同步收發流程」。
uint8_t message[] = "UART ready\r\n";
HAL_UART_Transmit(&huart2, message, sizeof(message) - 1, 1000);
uint8_t rx;
if (HAL_UART_Receive(&huart2, &rx, 1, 1000) == HAL_OK) {
HAL_UART_Transmit(&huart2, &rx, 1, 1000); // echo back
}
若連這段都沒有輸出,優先回到接線、GPIO alternate function、時脈和鮑率,而不是先懷疑 DMA。
Interrupt 模式的常見陷阱
使用 HAL_UART_Receive_IT() 時,接收完成 callback 只會觸發一次;若要持續接收,必須在 callback 裡重新啟動下一次接收。
static uint8_t rx_byte;
void uart_start_receive(void) {
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
process_byte(rx_byte);
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
}
}
也要確認 USARTx_IRQHandler() 有呼叫 HAL_UART_IRQHandler(&huartx),且 NVIC 的 UART interrupt 已啟用。少了其中一項,callback 不會執行。
DMA 接收要處理資料邊界
DMA 很適合連續資料流,但它不知道一筆訊息在哪裡結束。實務上常搭配 UART idle line 偵測:當線路短暫閒置時,把目前收到的資料長度交給上層解析。
static uint8_t rx_buffer[256];
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer, sizeof(rx_buffer));
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t size) {
if (huart->Instance == USART2) {
parse_packet(rx_buffer, size);
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer, sizeof(rx_buffer));
}
}
若使用 circular DMA,解析端要記錄上次讀取位置,避免同一段資料重複處理。對於含有明確封包頭、長度欄位與 checksum 的協定,建議把「接收資料」與「封包解析」拆成不同層,除錯會容易很多。
一份實用的排查順序
遇到 UART 異常時,我通常依照以下順序:
- TX/RX 是否交叉、兩端是否共地、位準是否安全。
- 用序列埠工具核對鮑率與 8N1 參數。
- 確認 GPIO alternate function、UART peripheral clock 與 NVIC。
- 用阻塞式 transmit/receive 做最小測試。
- 再檢查 interrupt callback 是否重新啟動接收。
- 最後才處理 DMA buffer、idle line 與封包邊界。
這套順序的核心是:先證明底層鏈路能通,再逐層加入非同步機制。比起一次改很多設定,這樣更容易找到真正的故障點。