一、波特率你真的算对了吗?

USART 的波特率决定数据传输速率。STM32 通常使用分数波特率发生器,外设时钟 fck 经过分频后产生位时钟。标准公式为:

BRR = fck / (USARTDIV * 16)

其中 USARTDIV 写在 BRR 寄存器中,可含小数。例如 APB2 = 72MHz,要得 115200 波特率:

USARTDIV = 72e6 / (115200 * 16) = 39.0625
BRR = (39 << 4) | 0x0001   // 小数部分乘16取整

实际波形频率偏差应小于 2%~3%。CubeMX 会自动计算,但批量生产时需验证晶振精度,使用外部晶振时 HSE_VALUE 必须与实际一致。

二、中断收发:标志位是灵魂

USART 中断收发依赖两个核心标志:

  • RXNE:接收数据寄存器非空,读取 DR 后自动清除
  • TXE:发送数据寄存器空,写入 DR 后自动清除

HAL 库对此做了封装,但使用 HAL_UART_Receive_IT(&huart, buffer, size, callback) 时,每接收一个字节都会进入中断,直到收满 size 个字节回调 HAL_UART_RxCpltCallback。发送同理,HAL_UART_Transmit_IT 发送完 buffer 后回调。

注意:HAL 库的中断方式默认只触发一次(单次模式),若需连续接收,必须在回调中再次调用接收函数,否则串口将“停摆”。此外必须处理 ORE(过载错误),通常在中断中检查 __HAL_UART_GET_FLAG(&huart, UART_FLAG_ORE) 并调用 __HAL_UART_CLEAR_OREFLAG(&huart) 清除,否则可能死循环。

三、DMA:让 CPU 去干正事

DMA 收发无需 CPU 逐字节干预,适合高速或大块数据。关键配置点:

  • 方向:外设到内存(接收)或内存到外设(发送)
  • 模式:普通模式传完自动停止;循环模式配合环形缓冲可无缝接收流式数据
  • 数据宽度:通常字节对字节
  • DMA 中断:传输完成、半传输等,用于包处理

HAL 库用法:

HAL_UART_Receive_DMA(&huart, rx_buf, RX_LEN);  // 开始 DMA 接收
HAL_UART_Transmit_DMA(&huart, tx_buf, len);    // DMA 发送

完成回调:HAL_UART_RxCpltCallbackHAL_UART_TxCpltCallback。若开启循环模式,则配合 HAL_UARTEx_RxEventCallback 才能处理半满中断。

四、完整代码示例(HAL 库 + 中断接收 + DMA 发送)

/* 配置信号量/标志 */
volatile uint8_t rx_byte = 0;
volatile uint8_t rx_flag = 0;

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;
    HAL_UART_Init(&huart1);
}

/* 启动中断接收 */
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);

/* 回调:收到一个字节后触发 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1) {
        // 将收到的字节在 DMA 模式下回显
        HAL_UART_Transmit_DMA(&huart1, &rx_byte, 1);
        // 重新启用中断接收(单次模式必须重新调用)
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
    }
}

/* DMA 直接发送字符串示例 */
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)"Hello STM32\r\n", 15);

注意:DMA 发送时若 CPU 修改 buffer 可能导致数据错乱,应用层需等 HAL_UART_TxCpltCallback 后再修改;接收中断里操作 DMA 发送是安全的,但高频收发时考虑“忙碌”状态。

五、常见坑与注意事项

  • DMA 时钟__HAL_RCC_DMA_CLK_ENABLE() 必须开启,否则硬件不工作。
  • 缓存一致性问题:若使用多缓冲,请避免 CPU 与 DMA 同时访问同一段内存,必要时使用 __HAL_DMA_DISABLE 后再操作。
  • 波特率误差累积:多字节组帧时,误差会累积,建议接收端每帧重新同步。
  • 空闲中断(IDLE):处理不定长数据时,可开启 HAL_UARTEx_ReceiveToIdle_DMA 实现“收完一帧再处理”,避免逐字节回调开销。

掌握波特率原理、中断标志和 DMA 模式,能让你在资源紧张的系统中游刃有余。建议先理解寄存器行为,再借助 HAL 快速开发——知其然,更知其所以然。