十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

UART串口协议帧头为何偏爱AA55?从同步原理到防粘包设计

UART串口协议帧头为何偏爱AA55?从同步原理到防粘包设计 把逻辑分析仪夹在 UART 的 RX 引脚上按下设备自检按键调试助手大概率会收到这么一串AA 55 01 03 00 01 02 5C。如果你写过自定义串口协议多半也用过 AA55、55AA、5AA5、A55A 这类帧头。问题来了帧头选择那么多凭什么 AA55 成了默认答案甚至不少 MCU 厂家的例程、串口屏协议、无线透传模块的出厂配置里都拿它当标准同步字。这可能是“嵌百科”里最常被问到、但很少有人真正讲透的问题之一。很多人只是“别人这么写我也这么写”一旦遇到串口粘包、伪帧、丢帧头这类问题就不知道该怎么排查了。这篇文章我尽量把 AA55 背后的同步原理、伪帧率计算、长度字段与校验的关系、RS485 换向丢字节、DMA 循环缓冲边界这些事一次性讲清楚。适合刚学会收发单个字节、想进阶到设计一套稳定串口协议的开发者也适合已经被粘包问题折磨了好几天的老哥参考。1. 先在示波器上看看AA 55 这两个字节到底在干什么1.1 0xAA 本质是一串自带“走时节奏”的方波0xAA展开成二进制是10101010放到串口线上就是 8 个连续的高-低-高-低电平翻转。UART 本身是异步通信收发双方没有共享时钟线接收端只能靠起始位的下降沿确定“一个字节从这儿开始”后面的 8 个数据位则按照约定的波特率在每一位的中心点采样。问题来了如果波特率有偏差或者数据线受到干扰导致起始沿定位偏了越靠后的数据位越容易采错。而AA这种 0101 交替的字节天然就是一段“训练序列”接收端拿到它之后可以迅速在内部校准采样点。我经常跟同事打比方这就像你在陌生的操场上跑步不知道每圈到底多长但如果地上每隔一段就有个标记你跑完第一圈就能把步频调准。AA就是串口线上的第一组标记。另外从物理层看连续翻转的方波在示波器上非常好认。你用 USB 转 TTL 模块比如常见的 CH340 方案循环发AA如果链路正常示波器上应该看到标准的 50% 占空比方波如果线接错、波特率配错、或者驱动有问题波形一眼就不对劲。这也是为什么很多老工程师在验证新板子串口好不好用时会先发一长串AA——它不只是在测数据更是在测物理链路的质量。1.2 0x55 补齐另一半极性确认与直流平衡0x55是0xAA的按位取反也就是01010101。两个字节拼起来就是 16 个 bit 的连续 1/0 交替。接收端收到这 16 个翻转沿后能做三件事第一确认极性没接反。TTL 电平下空闲是高点平起始位是低电平RS232 负逻辑下则反过来。如果线序极性错误收回来往往是一堆乱码。但如果先用 AA55 做同步你从示波器上能直接看出高低电平方向是否反了。第二做直流平衡检测。串口信号如果经过交流耦合比如某些隔离电路、无线透传模块内部长时间保持同一电平会让耦合电容累积电荷导致判决电平漂移。而AA55的直流分量接近零几乎不产生漂移对接收端来说非常友好。第三粗校准波特率。假设你单片机内部 RC 时钟有 1%~2% 的误差在 115200 波特率下单 bit 时间约 8.68 微秒8 个数据位累积下来误差可能会吃掉小半个 bit。而 AA55 这种高频翻转序列能最快暴露“采样点偏移”问题——如果你发 AA55 收回来变成A5 5A或D5 55这种变形先别急着怀疑别人拿逻辑分析仪测一下实际波形位宽多半是波特率时钟偏了。2. 防伪帧才是双字节帧头的真正意义撞包率、上电噪声与顺序之争2.1 单字节帧头的伪帧率是 1/256双字节是 1/65536很多人一开始图省事用一个字节做帧头比如AA或者F0。这在数据量小、总线环境干净的场景下确实没问题。但你要知道一个残酷事实任意一个随机字节出现在数据流里的概率是 1/256。假设你数据区长度是 32 字节那么平均每处理 8 帧左右就会有一次“数据内容里恰好冒出一个和帧头相同的字节”接收端可能就会把它误判成一帧的开始。如果把帧头拉长到两个字节比如AA55伪帧率一下降到 1/65536。这还没算上长度字段和校验码。你可以换一个更直观的算法在 115200 波特率下每秒大约有 11520 个字节如果是纯随机噪声平均每 5~6 秒就会出现一次伪AA55。如果这不处理你的协议解析层会频繁收到“假帧头”。虽然校验码能挡掉大部分错误帧但每次都等到收完一长串数据才发现校验不对CPU 和内存都被白白消耗了。2.2 上电瞬间的 0xFF 与 0x00AA55 天然避开两大伪帧源第二个容易被忽略的原因是设备上电瞬间的行为。很多单片机上电或复位时TX 引脚尚未被软件初始化IO 状态不确定常见的现象是拉高电平也就是总线一路都是空闲高这时候如果你拿示波器抓复位瞬间会看到一串FF。或者反过来某些电路上电瞬间电容充电会让 TX 短暂拉低收到一串00。如果你的帧头恰好是FF或者00那恭喜你每台上电的设备都会在复位瞬间吐出一个“伪帧头”接收端立刻开始解析然后等半天等不到后续数据最后超时。而AA55既不是全高也不是全低处于中间状态能很好地把上电毛刺和数据误判分隔开。在 RS232 负逻辑下还要注意TTL 的FF电平在 RS232 线上是持续负压看起来像空闲TTL 的00是持续正压更容易被误读成异常。AA55 这种交替电平同样避开了两个极端。2.3 用 AA55 还是 55AA没有本质差别但建议全系统统一我在论坛上看到过不少帖子争论 AA55 和 55AA 哪个更正统。结论是在普通 UART 字节流里先发 AA 还是先发 55协议上并没有本质差别但有两种工程习惯值得参考。一种是把首字节AA当成“唤醒/探测”次字节55当成“确认”接收端先看到一个高频率翻转的字节知道“链路是活的、波特率大概是对的”再看到55确认“帧同步开始”然后才进入长度和数据处理。另一种习惯是55 AA多见于一些串口屏和无线模块它们把55当前导的低电平触发AA做后续的数据起始。真正要命的问题不在于谁先谁后而在于整个系统里必须完全一致。我见过一个项目单片机固件里定义帧头是AA55上位机软件里写的是55AA两边都能各自发出数据但互相解析都对不上排查了两个小时才在代码里找到这个“镜像错误”。所以选好一种顺序后建议在协议文档和代码宏定义里统一写清楚连大小端、位序这类细节都不要放过。3. 帧头只是入口配合长度、校验与状态机才能形成完整防线3.1 只靠帧头切包会“粘包”长度字段才是边界主力不少新手协议是这样写的帧头AA55 若干数据 校验和接收端每收到AA55就当新的一帧开始。听上去没问题直到某一天总线上出现了一段内容恰好包含AA55的数据接收端当场“撕票”把一帧硬拆成两半后续所有数据全部乱套。解决方式就是给帧加“自描述信息”。我常用的最小帧格式是AA 55 | LEN | CMD | DATA[0..N-1] | CSUM其中LEN表示从 CMD 到 CSUM 之前的总字节数。接收端收到AA55后并不急着把后面所有字节都收完而是先取LEN按长度收满一帧再算校验。即使数据区里再次出现AA55接收端也已经在“按长度收数据”的状态里不会随便改判为新帧。这才是“帧头负责找起点长度字段负责框定边界校验负责保内容”的完整组合。3.2 一个能处理“AA AA 55”的接收状态机单纯在中断里判断“上一个字节是 AA当前字节是 55”是不够的。如果数据流是AA AA 55意味着第一个 AA 不是帧头第二个 AA 才是这时候还按上一字节判断就会漏掉。正确做法是维护一个状态机typedef enum { FRAME_IDLE, FRAME_GOT_AA, FRAME_GOT_55, FRAME_GOT_LEN, FRAME_GOT_DATA, FRAME_GOT_CSUM } frame_state_t; frame_state_t state FRAME_IDLE; uint8_t expected_len 0; uint8_t recv_cnt 0; uint8_t csum 0; void uart_byte_parse(uint8_t byte) { switch (state) { case FRAME_IDLE: if (byte 0xAA) state FRAME_GOT_AA; break; case FRAME_GOT_AA: if (byte 0x55) state FRAME_GOT_55; else if (byte 0xAA) state FRAME_GOT_AA; /* 连续AA时保留状态 */ else state FRAME_IDLE; break; case FRAME_GOT_55: if (byte 0xAA) state FRAME_GOT_AA; /* 可能又是新的帧头 */ else { expected_len byte; recv_cnt 0; csum byte; state (expected_len 0) ? FRAME_GOT_DATA : FRAME_GOT_CSUM; } break; case FRAME_GOT_DATA: csum ^ byte; recv_cnt; if (recv_cnt expected_len) state FRAME_GOT_CSUM; break; case FRAME_GOT_CSUM: if (byte csum) { /* 一帧完整且校验正确交给上层处理 */ frame_process(); } state FRAME_IDLE; break; } }这个状态机的巧妙之处在FRAME_GOT_AA分支收到一个 AA 后如果下一个字节不是 55不要立即回到 IDLE而是再看当前字节是不是又一个 AA。这样在AA AA 55这种连续干扰下第二个 AA 依然能作为帧头候选不会漏帧。很多第一次写解析代码的人都会在这里翻车。3.3 DMA 循环缓冲跨越边界时丢失帧头的坑用 STM32 的标准库或者 HAL 库做 DMA 接收时很多人会开一个大环形缓冲区空闲中断触发后把 DMA 收到的数据一次性交给解析层。这里有个隐蔽的坑如果AA55恰好跨在 DMA 缓冲区的结尾和下一次 DMA 的开头之间而你每次解析缓冲区时把状态机重置成FRAME_IDLE那帧头就永远不会被识别到。正确做法是解析状态机在整个接收过程中保持不重置跨缓冲区持续运行。也就是说解析函数处理完当前缓冲区后保留state、expected_len、recv_cnt、csum这些变量等下一次 DMA 数据到来时继续接着算。如果用的是双缓冲 DMA 或者半传输中断思路也一样——状态机是“流式”的不是“按包”的。3.4 RS485 方向切换导致“丢 AA”的排查链路RS485 是半双工总线发送前要把 DE 引脚拉高发完后拉低。很多收发器芯片从 DE 拉高到数据真正稳定出现在 A/B 线上需要一定的延时如果这段延时不够帧头的第一个字节可能被吃掉。我实际遇到过一次用 MAX3485 做 115200 的 RS485 通信主机发AA55开头的数据帧从机经常收不到第一个 AA但收第二个字节 55 又没问题。排查链路是先用示波器同时抓 DE 和 A/B 线发现 DE 拉高后 A/B 线还没稳定第一个字节的起始沿已经过去了。在发送函数里DE 拉高后延时至少 1 个字节时间115200 下约 87 微秒再开始写 UART 数据寄存器。问题消失。这个坑和帧头本身无关但它专门挑帧头下手因为帧头是每一帧的第一个字节。如果你在 RS485 项目里发现“丢第一个字节”优先检查方向切换时序而不是换帧头。4. 不同介质和场景下的帧头选择不是所有 AA55 都适用4.1 常见帧头候选横评帧头/同步方式典型场景优点缺点AA55通用 MCU 间 UART、RS232、RS485同步效果好、伪帧率低、可以测链路质量如果数据区恰好出现 AA55需要长度字段校验兜底55AA部分串口屏、无线模块和 AA55 几乎等价容易和 AA55 镜像混淆5AA5一些 Bootloader、无线透传同步字同样是对称方波和 AA55 没有本质区别看个人习惯7E7EPPP/HDLC 类协议和转义机制深度绑定天然防冲突数据区遇到 7E 需要转义处理实现更复杂静默时间地址Modbus RTU不需要显式帧头从地址即可识别帧间隙对时序要求高高波特率下容易误判引导码数据脉冲NEC 红外遥控无线/红外场景适配性好不能直接套用在 UART 字节流上从这个表能看出一个规律帧头不是凭空选的它必须和物理层特性匹配。比如红外遥控根本没有字节流的概念所以用脉冲宽度做引导码Modbus 靠总线空闲时间做帧边界在低波特率下很可靠但在高波特率下 3.5 字符时间间隔会变得极短对系统定时器精度要求很高很多人跑到 115200 就觉得 Modbus 不稳定本质上不是协议问题而是实现时间的精度问题。4.2 波特率偏差和内部 RC 时钟下AA55 怎样帮忙又怎样无能为力很多低成本单片机用内部 RC 振荡器跑串口误差可能到 ±1% 甚至 ±2%。以 115200、8N1 格式为例一个 bit 约 8.68 微秒一个字节 10 个 bit起始位8数据位停止位约 86.8 微秒。如果收发双方各偏 1%累积误差大约是 0.868 微秒对 8.68 微秒的 bit 来说约占 10%还在 UART 接收端的容错范围内。但如果波特率上到 460800bit 时间变成约 2.17 微秒同样 2% 的总误差就会吃掉约 18% 的 bit 窗口采样点严重偏移这时候发 AA55 能帮你“尽早发现”问题但解决不了问题。换句话说AA55 不是时钟校准器它只是把链路质量问题的暴露时间提前了。真正想跑高波特率还是得换外部晶振或者用锁相环修正时钟。如果你在 115200 下发 AA55 收回来是稳定的字节但一发长数据帧比如 256 字节就报错那就不是帧头的问题而是累计位偏移已经超过了容限需要检查通信双方的实际波特率差异。4.3 低功耗唤醒和无线透传真正需要的是“前导码”而不是帧头在无线透传模块、低功耗蓝牙串口这类场景里接收端通常不是时刻都在采样字节流而是处于休眠状态需要先检测到信号能量才会启动接收。此时你单发一个AA55往往不够因为接收端可能还没来得及同步就被数据淹没了。正确做法是在帧头之前再加一段时间的前导码比如连续发一串55或AA让接收端完成“能量检测-时钟同步-字节对齐”三步然后再发真正的同步字和数据。我调过一款无线串口模块它的同步字默认就是AA55但前面必须要有 8 个字节的55前导。如果你直接把单片机串口接到模块的 UART 口只发AA55大概率丢包加上前导后立刻稳定。所以看到“串口无线透传不稳定”的反馈时别急着怀疑天线先把前导长度换成标准值试一下。5. 关于帧头还有三块暗礁位序、电平极性和协议自检5.1 位序、电平极性和逻辑分析仪读数容易被忽略的三个基本点UART 在线上先发的是 LSB最低位。所以 0xAA 在逻辑分析仪上抓到的电平序列其实是0 1 0 1 0 1 0 1从起始位之后先看到低电平这是正常的别把它当成55而怀疑自己发错了。新手第一次用逻辑分析仪抓AA55时十有八九会对这个镜像现象困惑半天。TTL 和 RS232 的电平极性也是反的。USB 转 TTL 模块上看到的高电平对应逻辑 1但 RS232 线路上逻辑 1 是负压。你用 USB 转 RS232 线抓波形会发现 AA 的方波方向整体“反过来”了这也是正常现象。判断标准应该是“收发双方协议一致”而不是“示波器波形看起来像手册里的图”。5.2 当数据区不可避免地出现 AA 55转义与长度自描述方案即使有了长度字段和状态机数据区里直接出现AA55仍然有一个隐患如果长度字段因为干扰出错接收端可能按错误长度收数据这时候后续字节里的AA55会干扰重同步。解决思路有两种第一种是“长度自描述严格校验”前面代码已经给了思路。第二种是“转义”参考 HDLC 的做法数据区遇到AA就转义成7D 5E遇到55就转义成7D 5D接收端解析时再反转回来。转义让数据区不可能出现原生的AA55从而保证帧头唯一性。但这种做法实现复杂度高如果不是长帧大数据传输我不建议一开始就用转义先把长度字段和状态机做扎实解决 90% 的问题。5.3 协议健壮性自检清单我每次写完一套串口协议都会对着下面这个清单过一遍你可以直接抄走检查项常见问题自检标准帧头选择用了 0xFF/0x00 之类极端电平避开全高、全低、上电毛刺容易出现的值撞包率单字节帧头、数据易碰撞至少双字节帧头伪帧率 ≤ 1/65536长度字段缺失或不可靠必须有 LEN且和数据区长度强绑定校验不校验/校验太弱至少加 XOR 累加和重要场景用 CRC16重同步数据区出现帧头后无法恢复状态机连续处理长度错误时能跳回 IDLE超时机制收到半个帧头后无后续数据加帧间隔超时超过 N 个字节时间重置状态机边界处理DMA 跨缓冲区时丢状态解析状态机跨缓冲区持续运行5.4 我经手过的几个项目帧头选择和踩坑记录最后分享几个实际项目的选择供你参考。一个工业控制板主机和 8 个从机做 RS485 通信帧头用的AA55 单字节长度 累加和115200 波特率跑了三四年现场没出过粘包问题。当初选 AA55 不是因为它高级而是因为测试时用示波器看波形最直观排查链路问题时能省很多时间。一个串口屏项目屏端协议栈规定一帧必须以55 AA作为帧尾。刚开始我不理解后来发现屏的固件是边收边解析帧尾相当于“提交信号”收到55AA才把前面缓存的一帧数据交给显示任务。这提醒我帧头未必总在头有时候帧尾才是关键。一个 4G DTU 项目对端设备根本没有传统意义上的帧头它靠\r\n做帧边界用 AT 指令风格交互。这种情况你再怎么定义AA55都没用必须按对方协议来。设计自定义协议前先确认对接方的协议边界习惯能少走很多弯路。还有一次低功耗 LoRa 节点为了省电我一开始把前导码砍到很短结果接收端经常唤醒失败。后来老老实实把前导加长到协议默认值唤醒率才上来。这就是典型的“跳过前导只留帧头”的踩坑案例如果你也做低功耗无线串口务必记住无线场景下前导码比帧头更关键。我个人现在设计串口协议的习惯是默认AA55做帧头紧跟 1 字节长度、1 字节命令字、最多 255 字节数据、1 字节 CRC8接收端用流式状态机解析。不是因为它完美而是这套组合在所有 MCU 平台上实现成本低、排查链路上限高出了问题也容易跟同事描述。你完全可以根据自己的场景选55AA、5AA5甚至更长的同步字但一定要把撞包率、边界识别、校验、超时这几件事配套做齐才算一套能上线的协议。
返回列表