1. STM32 USART串行通讯基础解析
第一次接触STM32的USART模块时,我被这个看似简单却暗藏玄机的通信接口深深吸引。作为嵌入式开发中最基础也最常用的外设之一,USART(Universal Synchronous/Asynchronous Receiver/Transmitter)在STM32F103系列中扮演着数据交换的关键角色。
USART本质上是一个全双工的串行通信接口,支持同步和异步两种模式。在异步模式下(也就是我们常说的UART模式),它不需要时钟线,仅通过TX(发送)和RX(接收)两根数据线就能完成通信。这种简洁性使其成为MCU与传感器、模块、PC等设备通信的首选方案。
STM32F103的USART模块有几个显著特点:
- 支持多种数据格式(8位/9位数据长度)
- 可配置的停止位(1/0.5/1.5/2位)
- 灵活的波特率设置(最高4.5Mbps)
- 硬件流控制(RTS/CTS)支持
- 多处理器通信模式
注意:USART和UART在STM32中是不同的概念。所有USART都支持UART模式,但只有USART支持同步模式和智能卡模式等高级功能。在资源允许的情况下,建议优先使用USART而非UART外设。
2. USART硬件连接与初始化
2.1 硬件连接方案
以STM32F103C8T6为例,USART1的默认引脚为PA9(TX)和PA10(RX)。实际连接时需要注意:
- MCU的TX应连接对方设备的RX
- MCU的RX应连接对方设备的TX
- 如果通信距离超过1米,建议增加RS232或RS485电平转换芯片
- 在高速或长距离通信时,建议在信号线上串联33Ω电阻并添加适当滤波电容
典型连接电路如下:
code复制STM32F103 PC/模块
PA9(TX) ------> RX
PA10(RX) <------ TX
GND ------- GND
2.2 初始化配置步骤
使用HAL库初始化USART1的完整流程:
c复制// 1. 定义USART句柄
UART_HandleTypeDef huart1;
// 2. 初始化函数
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;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
// 3. 引脚和时钟配置(在HAL_UART_MspInit中实现)
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
实测经验:在115200波特率下,使用8MHz外部晶振时,实际波特率误差约为0.16%,完全在可接受范围内。但若使用内部HSI时钟,误差可能达到2.5%,可能导致通信失败。
3. USART数据收发实战
3.1 阻塞式收发
最简单的收发方式是使用HAL库提供的阻塞式函数:
c复制// 发送数据
uint8_t txData[] = "Hello World!\r\n";
HAL_UART_Transmit(&huart1, txData, sizeof(txData)-1, HAL_MAX_DELAY);
// 接收数据
uint8_t rxData[10];
HAL_UART_Receive(&huart1, rxData, 10, HAL_MAX_DELAY);
阻塞式收发虽然简单,但在实际项目中存在明显缺陷:
- 接收时会阻塞整个程序运行
- 无法及时响应其他事件
- 容易因超时而导致系统卡死
3.2 中断式收发
更实用的方式是使用中断接收:
c复制// 在main初始化后启动中断接收
HAL_UART_Receive_IT(&huart1, rxBuffer, BUFFER_SIZE);
// 实现接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
// 处理接收到的数据
processData(rxBuffer);
// 重新启动接收
HAL_UART_Receive_IT(&huart1, rxBuffer, BUFFER_SIZE);
}
}
中断接收的关键技巧:
- 接收缓冲区应定义为全局变量
- 每次接收完成后必须重新启动接收
- 在回调函数中避免耗时操作
- 建议使用环形缓冲区管理接收数据
3.3 DMA方式收发
对于高速或大数据量通信,DMA是最佳选择:
c复制// 初始化DMA
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_usart1_rx.Instance = DMA1_Channel5;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_usart1_rx);
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
// 启动DMA接收
HAL_UART_Receive_DMA(&huart1, rxBuffer, BUFFER_SIZE);
DMA模式下的注意事项:
- 环形缓冲区模式下,需要自行处理数据溢出
- 建议配合空闲中断实现帧检测
- DMA发送时需等待上次发送完成
- 在STM32F103中,USART1的RX和TX分别使用DMA1的不同通道
4. USART高级应用与问题排查
4.1 波特率精确计算
波特率计算公式:
code复制波特率 = fCK / (16 * USARTDIV)
其中USARTDIV是一个无符号定点数,整数部分保存在DIV_Mantissa[11:0],小数部分保存在DIV_Fraction[3:0](代表0/16到15/16)。
实际配置示例(8MHz时钟,115200波特率):
code复制USARTDIV = 8000000/(16*115200) ≈ 4.34
DIV_Mantissa = 4
DIV_Fraction = round(0.34*16) = 5
调试发现:当使用72MHz主频时,115200波特率的理想分频值应为39.0625,对应DIV_Mantissa=39,DIV_Fraction=1。实测通信稳定,误差为0%。
4.2 常见问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 能发送不能接收 | 1. 接线错误 2. 对方设备未发送 3. 接收未使能 |
1. 检查TX/RX交叉连接 2. 用逻辑分析仪确认信号 3. 检查USART_CR1的RE位 |
| 数据乱码 | 1. 波特率不匹配 2. 时钟配置错误 3. 电磁干扰 |
1. 核对双方波特率 2. 检查时钟树配置 3. 缩短连线并加滤波电容 |
| 偶尔丢数据 | 1. 缓冲区溢出 2. 中断优先级低 3. 未及时读取数据 |
1. 增大缓冲区 2. 调整中断优先级 3. 优化数据处理流程 |
| DMA不工作 | 1. DMA通道未使能 2. 内存地址未对齐 3. 传输长度超限 |
1. 检查DMA时钟和初始化 2. 确保地址符合对齐要求 3. 核对DMA_CNDTR寄存器 |
4.3 性能优化技巧
-
双缓冲技术:准备两个缓冲区,当一个正在处理时,另一个用于接收,通过指针交换实现无缝切换。
-
空闲中断+DMA:利用USART的空闲中断检测帧结束,配合DMA实现高效接收:
c复制// 启用空闲中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
// 在中断处理函数中
void USART1_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
// 处理接收到的数据
uint16_t len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
processData(rxBuffer, len);
}
HAL_UART_IRQHandler(&huart1);
}
- 自定义协议设计:在原始数据上增加帧头、校验和等字段提高可靠性。例如:
code复制[0xAA][0x55][长度][数据...][校验和]
- 流控制启用:当通信双方处理速度不匹配时,启用硬件流控(RTS/CTS)可以防止数据丢失。
5. 实际项目中的应用案例
5.1 与ESP8266通信实现WiFi连接
在物联网项目中,常用USART与ESP8266模块通信。关键配置要点:
- 波特率通常设置为115200或9600
- 需要实现AT指令的发送和响应解析
- 建议使用状态机管理连接流程
示例代码片段:
c复制void sendATCommand(const char* cmd)
{
HAL_UART_Transmit(&huart1, (uint8_t*)cmd, strlen(cmd), 100);
HAL_UART_Transmit(&huart1, (uint8_t*)"\r\n", 2, 100);
}
bool waitForResponse(const char* expected, uint32_t timeout)
{
uint32_t start = HAL_GetTick();
while(HAL_GetTick() - start < timeout)
{
if(strstr((char*)rxBuffer, expected) != NULL)
return true;
}
return false;
}
5.2 与PC端串口助手调试
使用USART与PC通信时的实用技巧:
- 在Windows设备管理器中确认COM端口号
- 推荐使用Tera Term或Putty等专业串口工具
- 调试时可启用十六进制显示模式
- 重要数据建议添加时间戳前缀
5.3 在多机通信中的应用
利用USART的多处理器通信模式,可以构建主从式网络:
- 配置主设备的USART_CR2的ADD[3:0]字段
- 从设备设置为静默模式(USART_CR1的MUTE位置1)
- 主设备发送地址帧时设置MSB位
- 匹配地址的从设备退出静默模式接收数据
这种模式在工业控制中十分常见,可以有效减少布线数量。
