)
STM32多串口通信实战5路串口并发管理(2/10)作者宋一平分类嵌入式 / STM32 / 技术干货预计阅读12分钟一、为什么需要多串口在嵌入式项目中串口是最常用的通信方式之一。但一个复杂项目往往需要同时与多个设备通信设备接口典型应用调试口UART日志输出、固件升级WiFi/蓝牙模块UART无线通信GPRS/4G模块UART物联网通信GPS模块UART定位数据串口屏UART人机交互扫码枪UART条码识别传感器UART数据采集以我的玻璃水加注机项目为例需要同时管理5路串口┌─────────────────────────────────────────┐ │ STM32F103ZET6 │ │ │ │ USART1 ◄────► 调试口/固件升级 │ │ USART2 ◄────► GPRS模块云端通信 │ │ USART3 ◄────► GPS模块定位数据 │ │ UART4 ◄────► 串口屏人机交互 │ │ UART5 ◄────► 扫码枪扫码支付 │ │ │ └─────────────────────────────────────────┘多串口的核心挑战如何让多路串口同时工作互不干扰如何高效处理接收数据不阻塞主程序如何判断一帧数据接收完成二、硬件资源分析2.1 STM32F103串口资源STM32F103ZET6提供5路串口串口TX引脚RX引脚总线最高波特率USART1PA9PA10APB24.5MbpsUSART2PA2PA3APB12.25MbpsUSART3PB10PB11APB12.25MbpsUART4PC10PC11APB12.25MbpsUART5PC12PD2APB12.25Mbps注意USART1挂在APB2总线上72MHz其他串口挂在APB1总线上36MHz波特率计算时要注意区分。2.2 引脚冲突检查在设计硬件时需要检查引脚是否冲突USART1: PA9(TX), PA10(RX) → 无冲突 USART2: PA2(TX), PA3(RX) → 无冲突 USART3: PB10(TX), PB11(RX) → 无冲突 UART4: PC10(TX), PC11(RX) → 无冲突 UART5: PC12(TX), PD2(RX) → 无冲突 ✅2.3 电平转换如果串口设备是5V电平需要进行电平转换STM32(3.3V) ◄──► 电平转换芯片(如TXS0108) ◄──► 5V设备三、软件设计思路3.1 方案对比实现多串口通信有三种常见方案方案优点缺点适用场景轮询实现简单阻塞CPU效率低低波特率、简单场景中断不阻塞CPU响应快高波特率时中断频繁通用场景 ✅DMACPU占用最低实现复杂需额外资源高速、大数据量本项目选择中断缓冲区方式理由波特率适中9600~115200实现难度适中资源占用可控3.2 设计要点1. 每路串口独立缓冲区typedefstruct{u8 TxFinish;// 发送完成标志u8 RxFinish;// 接收完成标志u8 TxIndex;// 发送索引u8 RxIndex;// 接收索引u8 TxSize;// 发送数据长度u8 RxSize;// 接收数据长度u8 RxTimeOut;// 接收超时计数u8 TxBuffer[USART_TX_LEN];// 发送缓冲区u8 RxBuffer[USART_RX_LEN];// 接收缓冲区}Uartdata_str;2. 帧接收检测超时判断串口通信没有帧结束标志需要通过超时判断一帧是否接收完成┌─────────────────────────────────────────────────┐ │ 接收字节: D1 D2 D3 D4 ... Dn │ │ │←─ 10ms ─→│ │ │ │ │ │ 超时到 → 一帧接收完成 │ └─────────────────────────────────────────────────┘3. FreeRTOS信号量保护发送时获取信号量发送完成中断释放信号量防止多任务冲突。四、代码实现4.1 整体架构┌────────────────────────────────────────────────────┐ │ 用户接口层 │ │ UART_INIT() UARTSend() UARTRead() │ ├────────────────────────────────────────────────────┤ │ 数据管理层 │ │ 缓冲区管理 │ 超时检测 │ 信号量保护 │ ├────────────────────────────────────────────────────┤ │ 中断处理层 │ │ 接收中断 │ 发送完成中断 │ ├────────────────────────────────────────────────────┤ │ 硬件驱动层 │ │ GPIO配置 │ 波特率设置 │ NVIC配置 │ └────────────────────────────────────────────────────┘4.2 初始化函数初始化串口只需一行代码// UART1: 调试口115200波特率无校验UART_INIT(U_1,BOUND_115200,PRI_NONE);// UART2: GPRS模块9600波特率无校验UART_INIT(U_2,BOUND_9600,PRI_NONE);// UART3: GPS模块9600波特率无校验UART_INIT(U_3,BOUND_9600,PRI_NONE);// UART4: 串口屏115200波特率无校验UART_INIT(U_4,BOUND_115200,PRI_NONE);// UART5: 扫码枪9600波特率无校验UART_INIT(U_5,BOUND_9600,PRI_NONE);4.3 超时检测关键在1ms定时器中断中调用voidTIM4_IRQHandler(void){URecePro();// 必须检测帧接收完成}超时检测逻辑voidURecePro(void){// 检测UART1if(Uartsys.Uarten10){if(Uartsys.Uart1-RxTimeOut0){Uartsys.Uart1-RxTimeOut--;// 超时计数递减if(Uartsys.Uart1-RxTimeOut0Uartsys.Uart1-RxIndex0){// 超时到且有数据 → 一帧接收完成Uartsys.Uart1-RxSizeUartsys.Uart1-RxIndex;Uartsys.Uart1-RxFinish1;// 标记完成Uartsys.Uart1-RxIndex0;}}}// UART2~5 同理...}4.4 中断服务函数以USART1为例voidUSART1_IRQHandler(void){u8 res;// 接收中断if(USART1-SR(15)){USART1-SR~(15);// 清除标志if(Uartsys.Uart1-RxFinishIDLE){resUSART1-DR;// 读取数据Uartsys.Uart1-RxTimeOut10;// 重置超时10msUartsys.Uart1-RxBuffer[Uartsys.Uart1-RxIndex]res;Uartsys.Uart1-RxIndex;// 防止溢出if(Uartsys.Uart1-RxIndexUSART_RX_LEN){Uartsys.Uart1-RxIndexUSART_RX_LEN-1;}}}// 发送完成中断if(USART1-SR(16)){USART1-SR~(16);// 清除标志if(Uartsys.Uart1-TxSizeUartsys.Uart1-TxIndex){// 还有数据继续发送USART1-DRUartsys.Uart1-TxBuffer[Uartsys.Uart1-TxIndex];Uartsys.Uart1-TxIndex;}else{// 发送完成Uartsys.Uart1-TxIndex0;Uartsys.Uart1-TxSize0;xSemaphoreGiveFromISR(Uartsys.Sem1,Woken);// 释放信号量}}}4.5 发送数据s8UARTSend(u8 Uartnum,u8*buf,u8 len){if(lenUSART_TX_LEN)return-1;// 长度检查switch(Uartnum){case1:if(Uartsys.Uarten10)return-3;xSemaphoreTake(Uartsys.Sem1,1000);// 获取信号量memcpy(Uartsys.Uart1-TxBuffer,buf,len);USART1-DRUartsys.Uart1-TxBuffer[0];// 发送第一个字节Uartsys.Uart1-TxIndex1;Uartsys.Uart1-TxSizelen;break;// UART2~5 同理...}return1;}4.6 接收数据u8UARTRead(u8 uartnum,u8*rbuf,int*rcount){switch(uartnum){case1:if(Uartsys.Uart1-RxFinish0){Uartsys.Uart1-RxFinish0;*rcountUartsys.Uart1-RxSize;memcpy(rbuf,Uartsys.Uart1-RxBuffer,Uartsys.Uart1-RxSize);return1;// 有数据}return0;// 无数据// UART2~5 同理...}return0;}4.7 完整使用示例#includeusart.h// 接收缓冲区u8 rx_buf[200];intrx_len0;intmain(void){// 系统初始化SystemInit();// 初始化串口UART_INIT(U_1,BOUND_115200,PRI_NONE);// 调试口UART_INIT(U_2,BOUND_9600,PRI_NONE);// GPRSUART_INIT(U_3,BOUND_9600,PRI_NONE);// GPS// 启动FreeRTOSvTaskStartScheduler();while(1);}// 串口处理任务voidUART_Task(void*pvParameters){while(1){// 处理UART1数据if(UARTRead(U_1,rx_buf,rx_len)){// 收到调试命令处理...UARTSend(U_1,rx_buf,rx_len);// 回显}// 处理UART2数据GPRSif(UARTRead(U_2,rx_buf,rx_len)){// 收到服务器数据处理...}// 处理UART3数据GPSif(UARTRead(U_3,rx_buf,rx_len)){// 收到GPS数据解析...}vTaskDelay(10);// 10ms轮询}}五、常见问题Q1接收不到数据排查步骤检查是否在1ms定时器中断中调用了URecePro()检查波特率是否一致用示波器/逻辑分析仪检查TX/RX是否接反// 定时器初始化示例TIM41ms周期voidTIM4_Init(void){RCC-APB1ENR|12;// 使能TIM4时钟TIM4-PSC72-1;// 预分频 72MHz/72 1MHzTIM4-ARR1000-1;// 自动重装载 1msTIM4-DIER|10;// 使能更新中断TIM4-CR1|10;// 使能定时器MY_NVIC_Init(1,0,TIM4_IRQn,2);// 配置中断}voidTIM4_IRQHandler(void){if(TIM4-SR0x0001){TIM4-SR~(10);URecePro();// 必须调用}}Q2发送数据卡住原因信号量获取超时1000ms排查检查FreeRTOS是否正确启动检查信号量是否正确创建检查发送完成中断是否正常触发Q3数据乱码可能原因波特率不一致确保双方波特率相同时钟配置错误检查系统时钟是否为72MHz校验位不匹配确认无校验/奇校验/偶校验一致// 波特率计算USART172MHz时钟// BRR 72000000 / (16 * 115200) 39.0625// 整数部分 39 0x27// 小数部分 0.0625 * 16 1// BRR 0x271USART1-BRR0x271;Q4只能收发一次原因RxFinish标志没有正确清零解决// 正确的读取方式if(UARTRead(U_1,rx_buf,rx_len)){// RxFinish在UARTRead内部已自动清零// 处理数据...}Q5高波特率丢数据原因中断频率过高CPU来不及处理解决方案使用DMA方式接收增大缓冲区提高中断优先级六、性能测试在我的项目中5路串口同时工作的测试结果串口设备波特率数据量状态USART1调试口115200日志输出✅ 正常USART2GPRS9600心跳业务✅ 正常USART3GPS9600NMEA数据✅ 正常UART4串口屏115200界面刷新✅ 正常UART5扫码枪9600扫码数据✅ 正常资源占用RAM每路串口约320字节发送接收缓冲区CPU中断占用约3%5路全开七、进阶优化7.1 DMA方式发送对于高速通信可以将发送改为DMA方式// DMA发送配置以USART1为例voidUSART1_DMA_Send(u8*buf,u16 len){DMA1_Channel4-CMAR(u32)buf;DMA1_Channel4-CNDTRlen;DMA1_Channel4-CCR|10;// 使能DMA}// DMA发送完成中断voidDMA1_Channel4_IRQHandler(void){DMA1-IFCR112;// 清除标志// 发送完成处理...}7.2 环形缓冲区对于数据量大的场景可以使用环形缓冲区#defineRING_SIZE256typedefstruct{u8 buffer[RING_SIZE];u16 head;u16 tail;}RingBuffer;u16Ring_Read(RingBuffer*rb,u8*data,u16 len);u16Ring_Write(RingBuffer*rb,u8*data,u16 len);7.3 协议封装为每路串口封装独立协议typedefstruct{u8 header[2];// 帧头 0xAA 0x55u8 length;// 数据长度u8 cmd;// 命令字u8 data[128];// 数据域u8 checksum;// 校验和}ProtocolFrame;八、总结本文介绍了STM32多串口通信的完整实现方案要点说明架构中断缓冲区每路串口独立管理帧检测超时判断10ms无新数据帧结束多任务保护FreeRTOS信号量扩展性支持5路串口可根据需要增减适用场景物联网网关多传感器采集人机交互设备工业控制系统九、源码获取本文完整源码已收录至「STM32物联网项目资料包」资料包包含✅ 多串口驱动完整代码usart.c/usart.h✅ 使用示例代码✅ 玻璃水加注机完整工程✅ 外设驱动模板GPIO/ADC/TIM等✅ 调试技巧文档✅ 常见问题FAQ获取方式点赞 收藏本文私信我备注「资料」免费领取作者宋一平 | 嵌入式/物联网技术分享转载请注明出处