ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F407 标准库 USART 串口通信:中断收发 + printf 重定向

STM32F407 标准库 USART 串口通信:中断收发 + printf 重定向 一、USART 是什么USART通用同步/异步收发器说白了就是两根线 一个约定发送方 TX ────────────► RX 接收方 接收方 RX ◄──────────── TX 发送方 (全双工两边可以同时说)一帧数据的格式无校验、1 停止位┌──────┬──────────────────────┬────────┐ │起始位│ D0 D1 D2 ... D7 │ 停止位 │ │ 0 │ 8 位数据(低位在前) │ 1 │ └──────┴──────────────────────┴────────┘双方约定好波特率每秒多少个 bit发送方按这个节奏把数据一位一位往线上放接收方按同样的节奏采样就能还原出数据。没有时钟线全靠对表——这也是为什么收发双方波特率必须一致差一点就全是乱码。两个最重要的标志位本篇的主角标志全称含义我们的用法TXETransmit data register Empty发送数据寄存器空了可以写下一个数据发送前死等它置 1RXNEReceive data register Not Empty收到数据了赶紧来读开中断一来数据就进中断服务函数记住这对标志位串口收发的代码就都能看懂了。二、硬件连接我的开发板STM32F407上串口引脚连接用途USART1PA9TX, PA10RX板载 CH340C 芯片 → USB Type-B 口 → 电脑主调试串口115200USART3PB10TX, PB11RX引出排针接外部模块9600电脑端用串口助手XCOM / 串口调试助手打开 CH340 对应的 COM 口即可。注意不是所有板子都有板载 CH340。如果你的板子没有需要外接 USB 转 TTL 模块共地。三、代码实现3.1 整体流程① 开时钟GPIOA USART1USART1 挂在 APB284MHz ② GPIO 复用配置PA9/PA10 设为 AF 模式 复用映射到 USART1 ③ USART 参数波特率/字长/停止位/校验/流控/模式 ④ NVIC使能 USART1 中断 ⑤ USART_ITConfig 开 RXNE 中断收到数据触发 ⑥ USART_Cmd 启动串口3.2 USART1 初始化voidUART_Init_Config(void){GPIO_InitTypeDef GPIO_InitStructure;//1. 使能 GPIOA 时钟 USART1 外设时钟// 注意USART1 挂在 APB2 总线上RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);GPIO_InitStructure.GPIO_PinGPIO_Pin_9|GPIO_Pin_10;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF;//复用功能模式GPIO_InitStructure.GPIO_SpeedGPIO_High_Speed;GPIO_InitStructure.GPIO_OTypeGPIO_OType_PP;GPIO_Init(GPIOA,GPIO_InitStructure);//引脚复用把 PA9/PA10 交给 USART1 这个外设管GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//2. 配置串口参数USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate115200;//波特率USART_InitStructure.USART_WordLengthUSART_WordLength_8b;//8 位数据USART_InitStructure.USART_StopBitsUSART_StopBits_1;//1 位停止位USART_InitStructure.USART_ParityUSART_Parity_No;//无奇偶校验USART_InitStructure.USART_HardwareFlowControlUSART_HardwareFlowControl_None;//无硬件流控USART_InitStructure.USART_ModeUSART_Mode_Rx|USART_Mode_Tx;//收发USART_Init(USART1,USART_InitStructure);//3. NVIC 配置NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannelUSART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority0x01;NVIC_InitStructure.NVIC_IRQChannelSubPriority0x01;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);//开中断——接收数据寄存器非空RXNE触发USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启串口1USART_Cmd(USART1,ENABLE);}还是熟悉的套路开时钟 → 配 GPIO → 配外设 → 配 NVIC → 使能。标准库的外设初始化基本都是一个模板写多了自然手感就熟了。3.3 发送等 TXE//发送一个字符voidUart_SendByte(USART_TypeDef*USARTx,uint16_tch){USART_SendData(USARTx,ch);//等待发送数据寄存器为空TXE 置 1 才能写下一个while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)RESET);}//发送字符串voidUart_SendString(USART_TypeDef*USARTx,char*str){uint16_tk0;while(*(strk)!\0){Uart_SendByte(USARTx,*(strk));k;}}发送是阻塞式的把数据扔进DR寄存器后死等TXE置位再返回。简单可靠调试阶段够用了。3.4 printf 重定向最爽的一步在 uart.c 里重写 fputc让printf的输出全部走 USART1#includestdio.h//重定向C库函数使printf函数能够将数据输出串口1打印intfputc(intch,FILE*stream){USART_SendData(USART1,ch);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET);returnch;}从此你的代码里可以直接写printf(Freq %.2f Hz, Duty %.1f %%\r\n,Freq,Duty);串口助手里实时看到数据调试体验直接上一个台阶。⚠️ 两个前置条件缺一个 printf 就卡死Keil 里勾选Target → Use MicroLIBfputc 所在的 .c 文件#include stdio.h。另外记得加\r\n换行很多串口助手只认这个。3.5 中断接收RXNE 触发uint16_treceive;voidUSART1_IRQHandler(void){//RXNE 为 1表示有数据来了——赶紧去拿if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)SET){receiveUSART_ReceiveData(USART1);//收到什么就原样发回去回显USART_SendData(USART1,receive);}USART_ClearITPendingBit(USART1,USART_IT_RXNE);}流程串口助手发一个字节 → RXNE 置 1 → 触发USART1_IRQHandler→ 读DR把数据取走读取动作本身也会清 RXNE→ 想干嘛干嘛这里做的是回显。注意中断服务函数里不要做耗时操作比如 printf、延时。中断里只做取数据 存缓冲区真正的业务处理放到主循环里做否则会拖垮整个系统。四、USART3 第二路串口数据转发到串口 14.1 为什么单独讲 USART3USART1 挂在APB284MHz而 USART3 挂在APB142MHz。两路串口波特率故意设成不一样115200 vs 9600就是为了把不同总线上的串口这个坑讲清楚——初始化时开时钟的总线别搞错否则外设根本起不来。4.2 USART3 初始化9600bpsvoidUART3_Init_Config(void){GPIO_InitTypeDef GPIO_InitStructure;//1. 使能 GPIOB 时钟 USART3 外设时钟// 注意USART3 挂在 APB1 总线上不是 APB2RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);GPIO_InitStructure.GPIO_PinGPIO_Pin_10|GPIO_Pin_11;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF;//复用功能模式GPIO_InitStructure.GPIO_SpeedGPIO_High_Speed;GPIO_InitStructure.GPIO_OTypeGPIO_OType_PP;GPIO_Init(GPIOB,GPIO_InitStructure);//引脚复用把 PB10/PB11 交给 USART3 这个外设管GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3);GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3);//2. 配置串口参数——波特率 9600USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate9600;//波特率USART_InitStructure.USART_WordLengthUSART_WordLength_8b;//8 位数据USART_InitStructure.USART_StopBitsUSART_StopBits_1;//1 位停止位USART_InitStructure.USART_ParityUSART_Parity_No;//无奇偶校验USART_InitStructure.USART_HardwareFlowControlUSART_HardwareFlowControl_None;//无硬件流控USART_InitStructure.USART_ModeUSART_Mode_Rx|USART_Mode_Tx;//收发USART_Init(USART3,USART_InitStructure);//3. NVIC 配置NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannelUSART3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority0x00;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);//开中断——RXNE 触发USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);//开启串口3USART_Cmd(USART3,ENABLE);}对比 USART1 的初始化只有两处不同时钟总线RCC_APB1PeriphClockCmdUSART3 在 APB1引脚复用GPIO_AF_USART3 PB10/PB11。其余结构完全一样——这就是标准库的模板化魅力。4.3 USART3 中断收到数据转发到串口 1voidUSART3_IRQHandler(void){uint16_tdata;//USART3 收到数据if(USART_GetFlagStatus(USART3,USART_FLAG_RXNE)SET){dataUSART_ReceiveData(USART3);//转发到串口 1 打印走 printf 重定向printf(USART3 RX: 0x%02X (%c)\r\n,data,data);}USART_ClearITPendingBit(USART3,USART_IT_RXNE);}说明这里在中断里直接 printf 是演示用的写法方便一眼看到效果。实际项目中请遵循 3.5 节的原则——中断里只存数据、置标志把 printf 挪到主循环里执行。本文示例波特率低9600数据量小看不出问题一旦数据密集printf 的耗时会导致后面的中断排队甚至丢字节。这样外部模块GPS、蓝牙等发来的数据就能在电脑串口助手上实时看到了——串口 3 当耳朵串口 1 当嘴巴。五、主函数把两路串口跑起来#includestm32f4xx.h#includeuart.hintmain(void){//初始化两路串口UART_Init_Config();//USART1: 115200UART3_Init_Config();//USART3: 9600printf(STM32F407 USART Demo Start!\r\n);printf(USART1: 115200bps, USART3: 9600bps\r\n);while(1){//主循环里可以干别的活//中断里已经处理了回显和转发}}六、常见坑与排查清单现象可能原因排查方法全是乱码波特率不一致 / 时钟配置错核对收发双方波特率检查 USART1 是否误用 APB1 时钟printf 卡死没勾 MicroLIB / 没 include stdio.h / 没加 \r\n勾选 Use MicroLIB补 include补换行串口完全没反应引脚复用没配 / 时钟没开检查 GPIO_PinAFConfig 和 RCC 时钟使能中断不触发NVIC 没使能 / 没开 RXNE 中断检查 NVIC_Init 和 USART_ITConfig收到数据但回显乱码发送和接收波特率不一致确认两路串口各自波特率别混用USART3 起不来时钟总线开错了用了 APB2USART3 必须用RCC_APB1PeriphClockCmd七、总结串口通信的核心就三件事开时钟——注意 USART1 在 APB2、USART3 在 APB1总线别搞错配 GPIO 复用——PA9/PA10 给 USART1PB10/PB11 给 USART3收发靠标志位——发送等 TXE接收看 RXNE中断。标准库的套路高度统一开时钟 → 配 GPIO → 配外设 → 配 NVIC → 使能。把 USART1 跑通USART3 就是改三个地方的事。串口通了你的嵌入式调试之路才算真正开始。
返回列表