FEATURED · 精选文章

利用空闲中断实现串口不定长接收与printf重定向

发布时间 / 2026/9/1 7:11:09
来源 / 创域科博编辑部
栏目 / 资讯中心
利用空闲中断实现串口不定长接收与printf重定向 简介面向嵌入式开发者的USART串口通信实现方案基于普冉PY32搭配STM32等MCU均可参照重点解决不固定长度数据接收与printf发送重定向两个实用需求适用于远程调试、设备监控与数据传输等场景。资源包为RAR压缩格式共444个文件、约4.08MB其中包含305个C源码文件、70个头文件以及Keil工程文件、链接脚本、编译生成的axf/hex/map等既可直接打开工程运行也可提取核心代码移植到其他项目。目前已有625人学习。内容涵盖USART中断接收的完整流程通过起始与结束字符识别数据帧边界、在中断服务程序中填充缓冲区、校验长度后交给上层处理从而可靠接收不定长数据printf重定向部分则覆写stdio底层输出函数将打印信息发送至串口便于调试与日志记录。配套代码基于HAL库编写结构清晰注释充分适合需要快速实现串口通信的初中级嵌入式开发者。 我最近在调一块普冉PY32做的小板子调试串口时最烦的一件事就是上位机发过来的数据长度总是不固定有时候就发一个单字节命令有时候又带一串参数最长能有一两百字节。刚开始我也按“固定长度帧头帧尾”的思路去做结果又是判断、又是转义代码越写越乱还老在边界条件上出问题。后来我把USART接收机制彻底理了一遍用“空闲中断软件缓冲”的方式实现了变长数据接收同时把printf重定向到串口这样不管是调试日志还是数据回传都变得非常直观。整套思路不光适用于PY32放在STM32的M0/M3/M4内核上代码稍微改改就能直接复现。1. 不固定长度接收为什么最终选了空闲中断方案1.1 串口接收的三大痛点串口接收这个事表面看是一个字节一个字节地收实际上一到“组帧”就开始头疼。串口硬件本身不关心你什么时候算一帧的开始、什么时候算结束它只负责把字节按顺序搬到寄存器里。所以“不固定长度接收”真正的难点根本不是接收而是“定界”。具体来说会有三个痛点。第一你没法判断一帧什么时候结束如果只靠程序里数接收个数那收到多少个算完第二如果用固定缓冲区数据一旦超过预想长度要么被截断要么直接越界写内存这是很多人调试串口时程序跑飞的主要原因。第三就算缓冲区留够了上位机的发送节奏一变比如两帧连发、半包到达之前的接收逻辑就全乱了。这三个痛点不解决串口调试就永远在踩坑。1.2 三种常用接收方案对比我见过的嵌入式串口定界方案基本可以分成三类各有各的适用场合。第一种是协议帧方案也就是“帧头长度数据校验”。这种方式最正规通信双方约定好协议接收端先收固定长度的帧头从中解析出长度再按长度收完剩余数据。它在正式产品通信里最可靠但代码量最大还要处理粘包、转义、超时重传这些问题。如果一个调试小工具也这么搞那纯属自找麻烦。第二种是结束符判断。上位机每帧末尾加一个\r\n或者固定字节接收端逐字节收每收到一个就判断是不是结束符。这种方案代码量最小很多串口控制指令就是这么做的。但有个前提数据内容本身不能随便出现结束符否则帧就会提前断开。第三种就是本文重点讲的空闲中断IDLE。MCU串口硬件在接收线上检测到“空闲超过一个字节时间”时会置一个IDLE标志并触发中断。你完全不用知道这一帧有多长只要线路一空闲就默认这一帧收完了。我把这三种方案整理成了一个对比表方案实现复杂度实时性适用场景主要风险帧头长度协议高最准确正式通信协议粘包、转义处理复杂结束符判断低较高短指令、内容可控数据中夹杂结束符会截断空闲中断中高不定长报文、调试透传连续无间隙数据流无法定界字节超时定时器中较高无IDLE中断的芯片需要额外定时器阈值难调1.3 最终方案空闲中断加数组缓冲我最后选的是“IDLE中断数组缓冲”。考虑到这个项目的上位机是“发一帧停一下”的调试方式每帧之间天然有空闲用IDLE截帧再合适不过。在中断里每收一个字节就放进数组IDLE触发时记录长度并置标志主循环看到标志就取走数据。代码量小也不依赖复杂的协议约束。但选这个方案有两个前提要想清楚。一是缓冲区长度必须大于最大单帧长度否则会溢出二是数据流必须是间歇性的如果对端像串口摄像头一样连续高速发数据中间一点间隙都没有那IDLE永远不会触发这种场景就必须换协议帧方案。所以说方案本身没有绝对的好坏只有合不合适。2. 串口驱动关键细节初始化、printf重定向与临界区2.1 USART初始化先把这些参数配合适USART初始化的常规套路不外乎开时钟、配GPIO复用、设波特率、开中断。以PY32F0系列为例GPIO把TX/RX引脚复用成USART功能后波特率就能直接影响通信质量。很多人觉得波特率只是一个数字写115200就完了实际上它的计算基准是外设时钟也就是BRR PCLK / (16 * baud)。如果你系统时钟用的是内部RC温度变化后RC会有一定漂移115200这种常用速率一般没事但如果你强行拉到460800以上乱码概率就会明显上升。另外要特别注意中断优先级。M0内核的可配置中断优先级位数很少通常只有两三个bit。如果你把串口中断优先级设得太低而系统里又有高频的定时器或ADC中断抢占串口接收寄存器里的数据就很容易被新到的数据覆盖表现就是“偶发丢一个字节”。这种问题非常隐蔽波形和波特率都没问题就是不定时丢数据。2.2 printf重定向从半主机模式手里抢回输出再说printf重定向。很多人上来就问“为什么我printf之后串口没输出”其实是没想清楚printf和串口之间是什么关系。在C标准库里printf最终其实是把格式化后的字符逐个送到一个底层回调函数里桌面环境下这个回调负责往屏幕输出嵌入式环境下默认的fputc被链接到了半主机模式半主机模式需要调试器配合你的串口自然什么都收不到。所以要做的就是把底层回调改写为往USART发送寄存器写字符。最常用的环境是Keil MDK如果你在工程配置里勾选了“Use MicroLIB”只需要重写一个函数#include stdio.h int fputc(int ch, FILE *f) { while (__HAL_UART_GET_FLAG(huart1, UART_FLAG_TXE) RESET); huart1.Instance-TDR (uint8_t)ch; return ch; }如果你不勾选MicroLIB编译器默认会引入半主机相关的符号这时需要额外加一段代码告诉链接器“我不用半主机”#pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; void _sys_exit(int x) { x x; }如果你用的是GCC工具链重定向对象就变成了_write函数int _write(int fd, char *ptr, int len) { p a hrefhttps://download.csdn.net/download/weixin_65738402/89416622 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻