跳到主要内容STM32H743 与 STM32F407 串口通信配置与优化实践 | 极客日志C
STM32H743 与 STM32F407 串口通信配置与优化实践
详细讲解基于 C/C++语言在STM32H743与STM32F407两款微控制器上实现UART串口通信的核心技术。内容涵盖物理层信号分析、波特率精确计算、GPIO与寄存器配置流程、HAL库初始化、中断与DMA传输模式对比、错误检测与自动恢复机制、FreeRTOS任务架构集成以及常见协议栈(CLI、MODBUS、JSON)构建。通过对比底层操作与库函数使用,提供高可靠、低延迟的通信方案,解决乱码、丢包等常见问题,适用于物联网与工业控制场景。
菩提24 浏览 STM32 串口通信的深度实践:从寄存器到 RTOS 的全栈解析
在嵌入式系统中,有线串行通信接口如 STM32 上的 UART 至关重要。它用于调试信息输出、传感器数据回传及工业总线协议承载。
一、物理层真相:你以为的 TX/RX,其实远不止两根线那么简单
UART 稳定通信需要关注四个关键信号:
- TX(Transmit):发送端输出
- RX(Receive):接收端输入
- RTS(Request to Send):硬件流控请求信号
- CTS(Clear to Send):硬件流控应答信号
⚠️ 提示:高速传输中频繁丢包,可能因未启用 RTS/CTS 流控导致缓冲区溢出。
STM32 支持自动硬件流控,配置 CR3.RTSE 和 CR3.CTSE 位即可启用。当接收 FIFO 快满时,自动拉低 CTS 通知对方暂停发送。
引脚复用必须明确配置:
GPIOA->AFR[0] |= (7U << GPIO_AFRL_AFRL2_Pos);
AFR 是 Alternate Function Register,不设置则信号不会出现在 GPIO 上。
二、波特率不是随便设的:精度误差超 3% 就等着收乱码吧!
| 芯片型号 | 外设时钟 (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}} $$
其中:
- $ f_{\text{CK_PERIPH}} $:外设时钟频率(通常是 PCLK1 或 PCLK2)
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
三、帧格式解剖:你的数据是如何被打包传送的?
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;
}
huart.Init.Parity = UART_PARITY_EVEN;
四、手把手教你配置 USART:从寄存器到底层初始化全流程
步骤 1:使能时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
忘记第二行会导致 HAL_UART_Init() 返回 HAL_ERROR。
步骤 2:配置 GPIO 复用功能
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);
步骤 3:调用 HAL 初始化函数
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();
}
五、常见初始化失败原因及排查清单
问题 1:波特率偏差大 → 收到一堆乱码
- 检查 PCLK 是否准确
- 用逻辑分析仪测量实际波形周期
- 尝试切换到 8 倍采样模式(OVER8=1)
问题 2:TX 无输出 → 示波器上看不见任何波形
- 是否调用了
__HAL_RCC_GPIOA_CLK_ENABLE()?
- AFR 有没有正确设置 AF7?
- 是不是误用了普通推挽而不是复用功能?
问题 3:程序卡在 HAL_UART_Init() 里不动
- 使用调试器查看
RCC->APB1ENR 第 17 位是否置 1
- 添加 LED 指示灯辅助定位阻塞点
- 检查 NVIC 中断是否被错误禁用
六、中断 vs DMA:如何选择最适合你的数据接收方式?
| 维度 | 中断方式 | 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);
}
DMA 接收:真正的零拷贝后台传输
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);
}
八、RTOS 加持下的串口任务架构:FreeRTOS 实战范例
引入 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);
}
九、协议栈构建指南:从 ASCII 命令行到 MODBUS 再到 JSON
方案 1:轻量级 CLI 命令行接口
CMD:SET_LED,1\r\n RESP:OK\r\n
方案 2:工业标准 MODBUS RTU
| 地址 | 功能码 | 数据 | CRC 低 | CRC 高 |
|---|
| 1B | 1B | NB | 1B | 1B |
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;
}
方案 3:现代化 JSON 轻量化传输
虽然 JSON 开销大,但结构清晰,适合配置类通信:
十、多机通信实战:RS485 总线上的主从博弈
在一条 RS485 总线上挂十几个从机,关键在于地址识别。
方法一:9 位数据模式(地址/数据标记)
- 第 9 位为 0 → 地址帧
- 第 9 位为 1 → 数据帧
主机先广播地址,匹配的从机才解除静默模式接收后续数据。
方法二:静默监听 + 广播回避
if (frame[0] != BROADCAST_ADDR) {
uint32_t delay = rand() % 50;
vTaskDelay(pdMS_TO_TICKS(delay));
SendResponse(...);
}
结语:串口虽老,但永远不会过时
从最基础的 TX/RX 讲到了 RTOS 任务调度,从波特率计算深入到双缓冲 DMA 设计。看似简单的 UART,背后藏着丰富的工程智慧。掌握这套完整的串口通信体系,有助于在物联网终端、工业控制器等开发中减少弯路。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online