文章目录
- 一、前期准备
- 1. 软硬件环境
- 2. UART核心原理梳理
- 二、实战1:UART初始化(以USART1为例,9600波特率)
- 1. 配置思路
- 2. Keil工程代码实现
- 三、实战2:查询模式收发数据(基础版)
- 1. 核心函数编写
- 2. 串口调试助手验证
- 步骤1:硬件连接
- 步骤2:串口调试助手配置
- 步骤3:验证效果
- 四、实战3:中断模式收发数据(高效版)
- 1. 中断配置与代码实现
- 2. 串口调试助手验证
- 五、进阶配置:修改波特率(115200bps)
- 六、Keil调试与问题排查技巧
- 1. Keil调试验证中断流程
- 2. 常见问题与解决
- 问题1:串口无数据输出
- 问题2:接收数据乱码
- 问题3:中断不触发
- 问题4:缓冲区溢出
- 七、实战扩展:多字节/协议收发
串口通信(UART)是单片机与外设、上位机交互的核心方式,广泛应用于数据打印、指令接收、设备联动等场景。本文基于STM32F103C8T6单片机,结合Keil MDK-ARM开发环境,从底层寄存器配置到实战调试,手把手讲解UART初始化、查询模式收发、中断模式收发,并配套串口调试助手验证方法,零基础也能快速掌握串口开发核心技能。
一、前期准备
1. 软硬件环境
- 硬件:STM32F103C8T6最小系统板、USB-TTL模块(CH340/PL2303)、杜邦线、电脑(Windows);
- 软件:Keil MDK-ARM V5.36(需安装STM32F103器件库)、串口调试助手(SSCOM/串口助手V1.4);
- 基础知识:熟悉STM32 GPIO配置、USART寄存器结构、Keil工程创建流程、C语言基础。
2. UART核心原理梳理
STM32F103的USART1挂载在APB2总线,USART2/3挂载在APB1总线,核心参数:
- 波特率:数据传输速率(常用9600、115200bps),由波特率寄存器(USART_BRR)配置;
- 数据帧:1位起始位+8位数据位+1位停止位(无校验),可配置奇偶校验、停止位数量;
- 收发模式:查询模式(轮询寄存器标志位)、中断模式(数据收发触发中断);
- 硬件连接:USART_TX(发送)接USB-TTL的RX,USART_RX(接收)接USB-TTL的TX,共地(GND)。
二、实战1:UART初始化(以USART1为例,9600波特率)
初始化是串口通信的基础,需配置GPIO复用、时钟、波特率、帧格式等核心参数。
1. 配置思路
- 系统时钟72MHz,USART1时钟来自APB2(72MHz),波特率9600bps,计算USART_BRR值:
分频值 = 72000000 / 9600 = 7500 → BRR寄存器配置为0x1D4C(十进制7500的十六进制); - GPIO配置:PA9(USART1_TX)为复用推挽输出,PA10(USART1_RX)为浮空输入;
- 使能USART1时钟、GPIOA时钟,开启串口收发功能。
2. Keil工程代码实现
#include"stm32f10x.h"#include<stdio.h>// USART1初始化函数(9600波特率,8位数据位,1位停止位,无校验)voidUSART1_Init(void){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 1. 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);// 2. 配置GPIO(PA9=TX,PA10=RX)// PA9:USART1_TX 复用推挽输出GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);// PA10:USART1_RX 浮空输入GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA,&GPIO_InitStructure);// 3. 配置USART参数USART_InitStructure.USART_BaudRate=9600;// 波特率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_Tx|USART_Mode_Rx;// 收发使能USART_Init(USART1,&USART_InitStructure);// 4. 使能USART1USART_Cmd(USART1,ENABLE);}// 重定向printf到USART1(方便打印字符串)intfputc(intch,FILE*f){// 等待发送数据寄存器为空while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);// 发送字节USART_SendData(USART1,(u8)ch);returnch;}三、实战2:查询模式收发数据(基础版)
查询模式通过轮询USART状态标志位实现数据收发,逻辑简单,适合低频率数据交互。
1. 核心函数编写
// 查询模式:发送单个字节voidUSART1_SendByte(u8 data){// 等待TXE标志位(发送数据寄存器空)while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));USART_SendData(USART1,data);}// 查询模式:发送字符串voidUSART1_SendString(u8*str){while(*str!='\0'){USART1_SendByte(*str);str++;}// 等待发送完成while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);}// 查询模式:接收单个字节(阻塞式)u8USART1_ReceiveByte(void){// 等待RXNE标志位(接收数据寄存器非空)while(!USART_GetFlagStatus(USART1,USART_FLAG_RXNE));returnUSART_ReceiveData(USART1);}// 主函数测试intmain(void){u8 recv_data;// 初始化USART1USART1_Init();// 上电发送欢迎信息USART1_SendString((u8*)"STM32 USART1 Test (查询模式)\r\n");USART1_SendString((u8*)"请输入字符:\r\n");while(1){// 接收单个字节recv_data=USART1_ReceiveByte();// 回显接收到的字符USART1_SendByte(recv_data);// 接收到换行符,发送提示if(recv_data=='\r'){USART1_SendString((u8*)"\r\n已接收字符,等待下一次输入...\r\n");}}}2. 串口调试助手验证
步骤1:硬件连接
- STM32 PA9(USART1_TX)→ USB-TTL RX;
- STM32 PA10(USART1_RX)→ USB-TTL TX;
- STM32 GND → USB-TTL GND;
- USB-TTL插入电脑USB口,安装驱动(CH340/PL2303)。
步骤2:串口调试助手配置
- 打开串口调试助手(如SSCOM);
- 选择对应COM口(电脑设备管理器查看);
- 配置参数:波特率9600、8位数据位、1位停止位、无校验、无流控;
- 点击“打开串口”。
步骤3:验证效果
- 编译Keil工程,下载程序到STM32;
- 串口调试助手接收区显示“STM32 USART1 Test (查询模式) 请输入字符:”;
- 在发送区输入任意字符(如“123”),点击发送,接收区回显该字符;
- 输入回车(\r),接收区显示“已接收字符,等待下一次输入…”,验证查询模式收发正常。
四、实战3:中断模式收发数据(高效版)
查询模式会阻塞主程序,中断模式通过收发中断实现异步通信,适合高频率、实时性要求高的场景。
1. 中断配置与代码实现
#include"stm32f10x.h"#include<stdio.h>u8 recv_buf[100];// 接收缓冲区u8 recv_len=0;// 接收数据长度u8 recv_flag=0;// 接收完成标志(回车触发)// USART1初始化(含中断配置)voidUSART1_Init(void){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;// 1. 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);// 2. GPIO配置GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;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. USART参数配置USART_InitStructure.USART_BaudRate=9600;USART_InitStructure.USART_WordLength=USART_WordLength_8b;USART_InitStructure.USART_StopBits=USART_StopBits_1;USART_InitStructure.USART_Parity=USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;USART_Init(USART1,&USART_InitStructure);// 4. 中断配置// 开启接收中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);// NVIC配置NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;// 抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;// 子优先级0NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);// 5. 使能USART1USART_Cmd(USART1,ENABLE);}// 重定向printfintfputc(intch,FILE*f){while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);USART_SendData(USART1,(u8)ch);returnch;}// USART1中断服务函数voidUSART1_IRQHandler(void){u8 recv_data;// 接收中断触发if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET){recv_data=USART_ReceiveData(USART1);// 读取接收数据if(recv_data!='\r')// 未接收到回车,存入缓冲区{recv_buf[recv_len]=recv_data;recv_len++;// 防止缓冲区溢出if(recv_len>=99)recv_len=0;}else// 接收到回车,标记接收完成{recv_buf[recv_len]='\0';// 字符串结束符recv_flag=1;// 置位完成标志recv_len=0;// 重置长度}USART_ClearITPendingBit(USART1,USART_IT_RXNE);// 清除中断标志}}// 主函数测试intmain(void){USART1_Init();printf("STM32 USART1 Test (中断模式)\r\n");printf("请输入字符串(回车结束):\r\n");while(1){if(recv_flag==1)// 检测到接收完成{// 回显接收到的字符串printf("已接收:%s\r\n",recv_buf);// 重置标志recv_flag=0;printf("请继续输入:\r\n");}// 主循环可执行其他任务(如LED闪烁),不被串口阻塞}}2. 串口调试助手验证
- 硬件连接、串口助手配置同查询模式;
- 下载程序后,串口助手接收区显示“STM32 USART1 Test (中断模式) 请输入字符串(回车结束):”;
- 在发送区输入任意字符串(如“hello stm32”),点击发送;
- 串口助手接收区回显“已接收:hello stm32”,且主程序可同时执行其他任务(如添加LED闪烁代码,验证中断不阻塞主循环)。
五、进阶配置:修改波特率(115200bps)
实际开发中115200bps更常用,仅需修改USART_Init中的波特率参数,并重新计算BRR值:
// 波特率改为115200USART_InitStructure.USART_BaudRate=115200;STM32F103 APB2时钟72MHz时,115200波特率的BRR值由硬件自动计算,无需手动配置,仅需同步修改串口调试助手的波特率为115200即可。
六、Keil调试与问题排查技巧
1. Keil调试验证中断流程
- 编译工程后,点击“Debug”进入调试模式;
- 打开“Watch & Call Stack Window”,添加
recv_flag、recv_len、recv_buf; - 启动调试(Run),在串口助手发送字符串,暂停调试后查看变量:
recv_len随接收字符递增;- 发送回车后,
recv_flag变为1,recv_buf存储完整字符串;
- 断点调试:在USART1_IRQHandler函数内添加断点(F9),触发接收中断时自动暂停,分析中断执行流程。
2. 常见问题与解决
问题1:串口无数据输出
- 排查点1:GPIO配置错误(PA9是否为复用推挽,PA10是否为浮空输入);
- 排查点2:时钟未使能(RCC_APB2Periph_USART1/RCC_APB2Periph_GPIOA);
- 排查点3:USART未使能(USART_Cmd(USART1, ENABLE));
- 排查点4:硬件连接错误(TX/RX交叉连接,未共地)。
问题2:接收数据乱码
- 排查点1:波特率不匹配(Keil配置与串口助手波特率一致);
- 排查点2:系统时钟错误(确认STM32主频为72MHz,而非8MHz);
- 排查点3:硬件干扰(缩短杜邦线,远离电源模块)。
问题3:中断不触发
- 排查点1:未开启接收中断(USART_ITConfig(USART1, USART_IT_RXNE, ENABLE));
- 排查点2:NVIC优先级配置错误(抢占/子优先级未合理分配);
- 排查点3:中断标志未清除(USART_ClearITPendingBit)。
问题4:缓冲区溢出
- 解决方法:在中断服务函数中增加缓冲区长度判断,超出阈值时重置长度;
- 优化方案:采用环形缓冲区,提升数据存储效率。
七、实战扩展:多字节/协议收发
实际项目中常需收发结构化数据(如传感器数据、控制指令),可基于中断模式扩展:
// 示例:接收自定义协议(帧头0xAA + 数据 + 校验位 + 帧尾0xFF)voidUSART1_IRQHandler(void){staticu8 protocol_buf[20];staticu8 protocol_len=0;u8 recv_data;if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET){recv_data=USART_ReceiveData(USART1);// 帧头检测if(protocol_len==0&&recv_data!=0xAA)return;// 存入协议缓冲区protocol_buf[protocol_len]=recv_data;protocol_len++;// 帧尾检测(假设协议长度固定为8字节)if(protocol_len==8&&protocol_buf[7]==0xFF){// 校验位验证(示例:前6字节和 = 第7字节前的校验位)u8 check_sum=0;for(u8 i=1;i<6;i++)check_sum+=protocol_buf[i];if(check_sum==protocol_buf[6]){// 协议校验通过,处理数据printf("协议接收成功:");for(u8 i=0;i<8;i++)printf("%02X ",protocol_buf[i]);printf("\r\n");}protocol_len=0;// 重置}// 超时/溢出处理if(protocol_len>=19)protocol_len=0;USART_ClearITPendingBit(USART1,USART_IT_RXNE);}}