STM32 串口通信的深度实践:从寄存器到 RTOS 的全栈解析
在嵌入式系统中,有线串行通信接口如 STM32 上的 UART 至关重要。它用于调试信息输出、传感器数据回传及工业总线协议承载。
一、物理层真相:你以为的 TX/RX,其实远不止两根线那么简单
UART 稳定通信需要关注四个关键信号:
- TX(Transmit):发送端输出
- RX(Receive):接收端输入
- RTS(Request to Send):硬件流控请求信号
- CTS(Clear to Send):硬件流控应答信号
详细讲解基于 C/C++语言在STM32H743与STM32F407两款微控制器上实现UART串口通信的核心技术。内容涵盖物理层信号分析、波特率精确计算、GPIO与寄存器配置流程、HAL库初始化、中断与DMA传输模式对比、错误检测与自动恢复机制、FreeRTOS任务架构集成以及常见协议栈(CLI、MODBUS、JSON)构建。通过对比底层操作与库函数使用,提供高可靠、低延迟的通信方案,解决乱码、丢包等常见问题,适用于物联网与工业控制场景。
在嵌入式系统中,有线串行通信接口如 STM32 上的 UART 至关重要。它用于调试信息输出、传感器数据回传及工业总线协议承载。
UART 稳定通信需要关注四个关键信号:
⚠️ 提示:高速传输中频繁丢包,可能因未启用 RTS/CTS 流控导致缓冲区溢出。
STM32 支持自动硬件流控,配置 CR3.RTSE 和 CR3.CTSE 位即可启用。当接收 FIFO 快满时,自动拉低 CTS 通知对方暂停发送。
引脚复用必须明确配置:
// 配置 PA2 为 USART2_TX 功能
GPIOA->AFR[0] |= (7U << GPIO_AFRL_AFRL2_Pos); // AF7 = USART2
AFR 是 Alternate Function Register,不设置则信号不会出现在 GPIO 上。
| 芯片型号 | 外设时钟 (MHz) | 目标波特率 | 实际波特率 | 误差 (%) |
|---|---|---|---|---|
| STM32F407 | 84 | 115200 | 115200 | 0.0% |
| STM32H743 | 100 | 115200 | 115205 | ~0.004% |
| STM32F407 | 84 | 921600 | 923077 | ~0.16% |
| STM32H743 | 100 | 921600 | 923077 | ~0.16% |
长距离 RS485 或噪声环境中,微小偏差足以导致帧错误(FE)频发。
STM32 使用以下公式生成波特率:
$$ \text{Baud Rate} = \frac{f_{\text{CK_PERIPH}}}{(8 \times (2 - OVER8)) \times \text{USARTDIV}} $$
其中:
OVER8:过采样模式(0=16 倍,1=8 倍)USARTDIV:除法因子,写入 BRR 寄存器例如 STM32H743 用 100MHz PCLK2 跑 115200 波特率:
USARTDIV = 100_000_000 / (16 * 115200) ≈ 54.2536
拆成整数部分 54(0x36),小数部分 0.2536×16≈4.0576→取整为 4(0x4),最终 BRR = 0x364
USART1->BRR = 0x364; // 手动写寄存器
建议改用 HAL 宏动态计算以保证跨平台移植。
UART 是异步通信,靠起始位 + 固定帧结构同步。
sequenceDiagram
participant T as 发送端
participant R as 接收端
T->>R: 空闲状态 (高电平)
T->>R: 起始位 (低电平,1 bit)
R-->>R: 检测下降沿触发同步
loop 5-9 bits
T->>R: 数据位 (LSB 先行)
end
opt 校验位使能?
T->>R: 奇偶校验位 (1 bit)
end
loop 1 or 2 bits
T->>R: 停止位 (高电平)
end
R-->>R: 完成一帧接收
关键参数说明:
| 参数 | 可选值 | 默认值 | 注意事项 |
|---|---|---|---|
| 数据位 | 7, 8, 9 | 8 | H7/F4均支持9位用于多机通信 |
| 停止位 | 0.5, 1, 1.5, 2 | 1 | 多数设备兼容性最好 |
| 校验位 | 无,奇校验,偶校验 | 无 | 工业现场仍建议开启 |
| 过采样模式 | 8 倍或 16 倍 | 16 倍 | 8 倍更抗干扰但精度要求更高 |
模拟偶校验生成逻辑:
uint8_t compute_even_parity(uint8_t data) {
uint8_t count = 0;
for (int i = 0; i < 8; ++i) {
if (data & (1 << i)) count++;
}
return (count % 2 == 0) ? 0 : 1;
}
STM32 硬件会自动完成,只需配置:
huart.Init.Parity = UART_PARITY_EVEN; // 开启偶校验
以 STM32F407 的 USART2 为例。
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // 开启 GPIOA 时钟
RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // 开启 USART2 时钟
忘记第二行会导致 HAL_UART_Init() 返回 HAL_ERROR。
// PA2 → USART2_TX
GPIOA->MODER &= ~GPIO_MODER_MODER2_Msk;
GPIOA->MODER |= GPIO_MODER_MODER2_1; // 复用模式
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_2; // 推挽输出
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR2; // 高速
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR2_Msk;
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR2_0; // 上拉防悬空
GPIOA->AFR[0] |= (7U << GPIO_AFRL_AFRL2_Pos); // AF7 = USART2
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK) {
Error_Handler(); // 必须检查返回值!
}
__HAL_RCC_GPIOA_CLK_ENABLE()?HAL_UART_Init() 里不动RCC->APB1ENR 第 17 位是否置 1| 维度 | 中断方式 | DMA 方式 |
|---|---|---|
| CPU 占用 | 中等 | 极低 |
| 吞吐能力 | ≤100KB/s | 可达 MB/s 级 |
| 编程复杂度 | 简单 | 较复杂 |
| 适用场景 | 命令交互、低频传感器上报 | 视频流、固件升级、音频传输 |
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
RingBuffer_Write(&rx_buf, rx_byte);
HAL_UART_Receive_IT(huart, &rx_byte, 1); // 重启监听
}
uint8_t dma_rx_buffer[256];
HAL_UART_Receive_DMA(&huart1, dma_rx_buffer, 256);
标准 DMA 模式存在覆盖风险,建议使用双缓冲机制。
工业现场需应对三大错误:
| 错误类型 | 触发条件 | 处理策略 |
|---|---|---|
| FE(帧错误) | 停止位未检测到(波特率不匹配) | 记录日志,尝试重连 |
| NE(噪声错误) | 采样窗口内检测到毛刺 | 忽略单次,连续多次则报警 |
| ORE(溢出错误) | 新数据来临时旧数据未读取 | 清除标志,重启 DMA |
捕获并清除错误标志的标准做法:
void USART1_IRQHandler(void) {
uint32_t isrflags = READ_REG(huart1.Instance->ISR);
if (isrflags & USART_ISR_ORE) {
__HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_OREF);
error_counter.overrun++;
RestartDMAReception(); // 自动恢复
}
if (isrflags & USART_ISR_FE) {
__HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_FEF);
error_counter.framing++;
}
HAL_UART_IRQHandler(&huart1); // 交给 HAL 库处理其余事件
}
结合软件看门狗实现超时重传可防止死机。
引入 FreeRTOS 后,串口通信变为任务驱动。
xTaskCreate(vUARTRxTask, "UART_Recv", 512, NULL, tskIDLE_PRIORITY + 3, NULL);
xTaskCreate(vUARTTxTask, "UART_Send", 512, NULL, tskIDLE_PRIORITY + 2, NULL);
QueueHandle_t xUARTQueue = xQueueCreate(10, sizeof(uint32_t));
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
uint32_t len = BUFFER_SIZE;
xQueueSendFromISR(xUARTQueue, &len, NULL);
}
void vUARTRxTask(void *pvParameters) {
uint32_t received_len;
while (1) {
if (xQueueReceive(xUARTQueue, &received_len, portMAX_DELAY)) {
ParseProtocolFrame(dma_buffer, received_len);
}
}
}
SemaphoreHandle_t xUARTMutex = xSemaphoreCreateMutex();
// 发送前加锁
if (xSemaphoreTake(xUARTMutex, 100) == pdTRUE) {
HAL_UART_Transmit(&huart1, data, len, 1000);
xSemaphoreGive(xUARTMutex);
}
CMD:SET_LED,1\r\n RESP:OK\r\n
用状态机解析,代码简洁,适合调试用途。
典型帧结构:
| 地址 | 功能码 | 数据 | CRC 低 | CRC 高 |
|---|---|---|---|---|
| 1B | 1B | NB | 1B | 1B |
CRC-16/MODBUS 算法实现:
uint16_t crc16_modbus(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < len; ++i) {
crc ^= data[i];
for (int j = 0; j < 8; ++j) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
虽然 JSON 开销大,但结构清晰,适合配置类通信:
{"t":25.5,"h":60,"s":1}
建议配合 cJSON 库使用。
在一条 RS485 总线上挂十几个从机,关键在于地址识别。
主机先广播地址,匹配的从机才解除静默模式接收后续数据。
对于必须回复的操作,采用随机延时策略防冲突:
if (frame[0] != BROADCAST_ADDR) {
uint32_t delay = rand() % 50; // 0~50ms 随机等待
vTaskDelay(pdMS_TO_TICKS(delay));
SendResponse(...);
}
从最基础的 TX/RX 讲到了 RTOS 任务调度,从波特率计算深入到双缓冲 DMA 设计。看似简单的 UART,背后藏着丰富的工程智慧。掌握这套完整的串口通信体系,有助于在物联网终端、工业控制器等开发中减少弯路。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online