1. STM32 USART串口通信基础解析
作为一名嵌入式开发工程师,我经常需要在STM32平台上实现设备间的数据通信。USART(通用同步异步收发器)作为最常用的串行通信接口之一,在项目开发中扮演着重要角色。今天我将分享基于STM32F103标准库的USART串口通信实现过程,重点讲解配置细节和实际应用中的经验技巧。
USART与UART的主要区别在于同步通信功能。USART支持同步和异步两种模式,而UART仅支持异步通信。同步模式下,USART会额外提供时钟信号(SCLK)来同步数据传输;异步模式下则不需要时钟线,仅通过TX(发送)和RX(接收)两根线即可完成通信。在实际项目中,我们大多使用异步模式,也就是常说的"串口通信"。
串口通信的核心参数包括:
- 波特率:常见的有9600、115200等,表示每秒传输的符号数
- 数据位:通常为8位或9位
- 停止位:1位、1.5位或2位
- 校验位:奇校验、偶校验或无校验
这些参数需要在通信双方保持一致,否则会导致数据传输错误。在STM32中,这些参数都通过USART_InitTypeDef结构体进行配置。
2. 硬件设计与平台搭建
2.1 硬件连接方案
在本次实验中,我们使用STM32F103的USART1接口,其引脚对应关系如下:
- PA9:USART1_TX(发送端)
- PA10:USART1_RX(接收端)
在嵌入式仿真平台上,我们需要将STM32的USART1_TX连接到虚拟串口工具的RX端,USART1_RX连接到虚拟串口工具的TX端。这种交叉连接方式是串口通信的标准做法。
注意:实际硬件连接时,务必确认电平匹配。STM32的IO口为3.3V电平,如果连接5V设备需要电平转换电路,否则可能损坏芯片。
2.2 仿真平台配置
我使用的是Puliedu嵌入式仿真实验教学平台,这个平台提供了完整的STM32外设模拟环境。平台优势在于:
- 无需实际硬件即可验证代码功能
- 提供可视化的信号监测工具
- 支持断点调试和寄存器查看
在平台中搭建电路的步骤如下:
- 选择STM32F103C8T6核心板
- 添加虚拟串口工具
- 连接PA9到串口工具的RX
- 连接PA10到串口工具的TX
- 配置串口工具参数与代码一致(115200,8N1)
3. USART初始化代码详解
3.1 时钟使能与GPIO配置
USART初始化首先需要使能相关时钟,这是STM32外设使用的前提条件:
c复制RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
这里同时使能了USART1和GPIOA的时钟,因为USART1的引脚位于GPIOA端口。
GPIO配置需要特别注意模式选择:
- TX引脚(PA9)配置为复用推挽输出(GPIO_Mode_AF_PP)
- RX引脚(PA10)配置为浮空输入(GPIO_Mode_IN_FLOATING)
c复制GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
3.2 USART参数配置
USART初始化结构体包含以下关键参数:
c复制USART_InitStructure.USART_BaudRate = bound; // 波特率,如115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
波特率计算是配置中的关键点。STM32的USART波特率计算公式为:
code复制波特率 = fCK / (16 * USARTDIV)
其中fCK是USART时钟频率,USARTDIV是一个无符号定点数,包含整数部分和小数部分。
例如,当APB2时钟为72MHz,要求波特率为115200时:
code复制USARTDIV = 72000000 / (115200 * 16) = 39.0625
整数部分为39,小数部分为0.0625(对应16*0.0625=1)
因此USART_BRR寄存器应设置为0x271(39<<4 | 1)
4. 数据收发实现与调试
4.1 发送单个字节
发送单个字节的函数实现如下:
c复制void Usart_SendByte(USART_TypeDef *pUSARTx, uint8_t ch)
{
USART_SendData(pUSARTx, ch);
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
这里有两个关键点:
- USART_SendData()将数据写入发送数据寄存器
- 通过检查USART_FLAG_TXE标志位等待发送完成
经验:在实际项目中,建议为等待标志位的循环添加超时判断,避免因硬件故障导致死循环。
4.2 发送字符串
基于单字节发送函数,我们可以实现字符串发送:
c复制void Usart_SendString(USART_TypeDef *pUSARTx, char *str)
{
unsigned int k=0;
do {
Usart_SendByte(pUSARTx, *(str + k));
k++;
} while(*(str + k) != '\0');
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}
这里最后等待USART_FLAG_TC(传输完成)标志位非常重要,它确保所有数据包括停止位都已发送完毕。
4.3 主函数实现
主函数中初始化USART后,我们可以测试发送功能:
c复制int main(void)
{
uart_init(115200); // 初始化USART1,波特率115200
Usart_SendByte(USART1, 'A'); // 发送单个字符'A'
Usart_SendString(USART1, "HELLO CSDN"); // 发送字符串
while(1)
{
// 主循环
}
}
5. 常见问题与调试技巧
5.1 通信失败排查步骤
当串口通信不成功时,可以按照以下步骤排查:
- 检查硬件连接:TX-RX是否交叉连接,地线是否共地
- 确认波特率设置:双方设备波特率必须一致
- 验证时钟配置:确保USART时钟已正确使能
- 检查GPIO模式:TX应为复用推挽输出,RX应为浮空输入
- 使用逻辑分析仪:捕获实际波形,验证数据是否符合预期
5.2 波特率误差问题
STM32的USART波特率存在一定的量化误差。当所需波特率不能精确匹配时,可以遵循以下原则:
- 误差应控制在2%以内(最好1%以内)
- 优先选择APB时钟的整数分频点
- 对于高波特率(如115200以上),误差影响更明显
计算波特率误差的公式:
code复制误差(%) = |(实际波特率 - 目标波特率)| / 目标波特率 * 100
5.3 抗干扰设计建议
在实际硬件环境中,串口通信易受干扰,可采取以下措施:
- 添加适当的滤波电容(0.1μF)在USART引脚附近
- 使用双绞线连接,减少电磁干扰
- 长距离通信时,考虑使用RS-232或RS-485电平转换
- 在软件中添加校验机制(如校验和或CRC)
6. 性能优化与进阶应用
6.1 DMA传输优化
对于大数据量传输,使用DMA可以大幅降低CPU开销。USART1的DMA通道配置如下:
- 发送:DMA1通道4
- 接收:DMA1通道5
DMA配置步骤:
- 使能DMA时钟
- 初始化DMA通道
- 配置USART的DMA使能位
- 启动DMA传输
6.2 中断接收模式
虽然本文示例使用轮询方式,但实际项目中更常用中断接收:
- 使能USART接收中断:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)
- 配置NVIC中断优先级
- 实现中断服务函数,读取USART_DR寄存器
中断接收的优势在于实时性高,CPU利用率低。
6.3 多串口管理策略
当项目需要使用多个USART接口时,建议:
- 为每个USART建立独立的数据结构管理状态
- 使用统一的接口封装发送/接收函数
- 采用环形缓冲区处理接收数据
- 为不同USART分配合理的优先级
在我的实际项目中,通常会抽象出如下接口:
- uart_send(uint8_t port, uint8_t *data, uint16_t len)
- uart_receive(uint8_t port, uint8_t *buf, uint16_t len)
- uart_set_baudrate(uint8_t port, uint32_t baud)
这种设计提高了代码的复用性和可维护性。
