22_串口HEX与文本数据包收发

串口 HEX 与文本数据包收发

课程录音:2026-09-09《串口数据包的发送接收》

对应源码:9-3 串口收发HEX数据包9-4 串口收发文本数据包

前置内容:21_USART发送与接收代码流程.md

整理日期:2026-09-09

1. 本节要解决的问题

USART 硬件一次只告诉程序"收到一个字节"。实际业务却经常需要一次传递多个相关数据,例如陀螺仪的 X、Y、Z 三轴值,或者一条 LED_ON 指令。

如果数据流只是不断重复 XYZXYZXYZ...,接收方从任意位置接入时,不知道当前字节属于 X、Y 还是 Z。数据包协议的作用就是给连续字节流增加边界,让接收方能够:

  1. 找到一包从哪里开始;
  2. 确定哪些字节属于有效载荷;
  3. 判断一包在哪里结束;
  4. 把完整的一包交给主程序处理。

#mermaid-svg-udNbWyfR2sj4AdsK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-udNbWyfR2sj4AdsK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-udNbWyfR2sj4AdsK .error-icon{fill:#552222;}#mermaid-svg-udNbWyfR2sj4AdsK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-udNbWyfR2sj4AdsK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-udNbWyfR2sj4AdsK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-udNbWyfR2sj4AdsK .marker.cross{stroke:#333333;}#mermaid-svg-udNbWyfR2sj4AdsK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-udNbWyfR2sj4AdsK p{margin:0;}#mermaid-svg-udNbWyfR2sj4AdsK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-udNbWyfR2sj4AdsK .cluster-label text{fill:#333;}#mermaid-svg-udNbWyfR2sj4AdsK .cluster-label span{color:#333;}#mermaid-svg-udNbWyfR2sj4AdsK .cluster-label span p{background-color:transparent;}#mermaid-svg-udNbWyfR2sj4AdsK .label text,#mermaid-svg-udNbWyfR2sj4AdsK span{fill:#333;color:#333;}#mermaid-svg-udNbWyfR2sj4AdsK .node rect,#mermaid-svg-udNbWyfR2sj4AdsK .node circle,#mermaid-svg-udNbWyfR2sj4AdsK .node ellipse,#mermaid-svg-udNbWyfR2sj4AdsK .node polygon,#mermaid-svg-udNbWyfR2sj4AdsK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-udNbWyfR2sj4AdsK .rough-node .label text,#mermaid-svg-udNbWyfR2sj4AdsK .node .label text,#mermaid-svg-udNbWyfR2sj4AdsK .image-shape .label,#mermaid-svg-udNbWyfR2sj4AdsK .icon-shape .label{text-anchor:middle;}#mermaid-svg-udNbWyfR2sj4AdsK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-udNbWyfR2sj4AdsK .rough-node .label,#mermaid-svg-udNbWyfR2sj4AdsK .node .label,#mermaid-svg-udNbWyfR2sj4AdsK .image-shape .label,#mermaid-svg-udNbWyfR2sj4AdsK .icon-shape .label{text-align:center;}#mermaid-svg-udNbWyfR2sj4AdsK .node.clickable{cursor:pointer;}#mermaid-svg-udNbWyfR2sj4AdsK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-udNbWyfR2sj4AdsK .arrowheadPath{fill:#333333;}#mermaid-svg-udNbWyfR2sj4AdsK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-udNbWyfR2sj4AdsK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-udNbWyfR2sj4AdsK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-udNbWyfR2sj4AdsK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-udNbWyfR2sj4AdsK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-udNbWyfR2sj4AdsK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-udNbWyfR2sj4AdsK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-udNbWyfR2sj4AdsK .cluster text{fill:#333;}#mermaid-svg-udNbWyfR2sj4AdsK .cluster span{color:#333;}#mermaid-svg-udNbWyfR2sj4AdsK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-udNbWyfR2sj4AdsK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-udNbWyfR2sj4AdsK rect.text{fill:none;stroke-width:0;}#mermaid-svg-udNbWyfR2sj4AdsK .icon-shape,#mermaid-svg-udNbWyfR2sj4AdsK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-udNbWyfR2sj4AdsK .icon-shape p,#mermaid-svg-udNbWyfR2sj4AdsK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-udNbWyfR2sj4AdsK .icon-shape .label rect,#mermaid-svg-udNbWyfR2sj4AdsK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-udNbWyfR2sj4AdsK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-udNbWyfR2sj4AdsK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-udNbWyfR2sj4AdsK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} USART逐字节接收
寻找包头
按协议收集载荷
验证长度和包尾
置完整包标志
主循环解析并执行业务

2. 老师的讲解路线

录音时间 课堂内容
00:08~02:27 演示 HEX 包收发、文本指令控制 LED
02:33~07:38 为什么需要数据包,固定/可变包长及包头包尾冲突
07:38~09:24 HEX 包与文本包的特点和应用场景
09:24~13:36 发送为何简单、接收为何需要状态机
13:36~24:28 编写 9-3 固定长度 HEX 数据包工程
24:38~30:39 编写 9-4 文本指令包工程并处理缓存竞争

3. 资料定位

问题 资料位置
HEX 包格式 教师 PPT 第 123 页
文本包格式 教师 PPT 第 124 页
HEX 包接收状态机 教师 PPT 第 125 页
文本包接收状态机 教师 PPT 第 126 页
USART 接收流程、RXNE、ORE 《STM32F10xxx参考手册(中文)》PDF 第 521~524 页
USART 中断 参考手册 PDF 第 538 页
USART_SR 状态位 参考手册 PDF 第 540~541 页
USART_DR 数据寄存器 参考手册 PDF 第 542 页
CR1.RXNEIE/RE/TE/UE 参考手册 PDF 第 542~543 页
USART1 的 PA9/PA10 引脚 《STM32F103x8B数据手册(中文)》PDF 第 17~20 页

包头、包尾、长度、校验等属于应用层协议,并不是 STM32 USART 寄存器自动提供的功能。手册解释的是"字节如何收发";源码中的状态机负责"如何把字节重新组成包"。

4. 两种数据包格式

4.1 HEX 数据包

本实验定义固定 6 字节的数据包:

text 复制代码
0xFF  Data0  Data1  Data2  Data3  0xFE
包头           4字节载荷          包尾

老师 PPT 第 123 页:固定包长和可变包长的 HEX 数据包。

课程工程选择"固定 4 字节载荷 + 包头 + 包尾"。由于接收载荷时只计数、不判断内容,所以载荷本身出现 0xFF0xFE 也不会误判。

4.2 文本数据包

本实验格式为:

text 复制代码
@载荷\r\n

例如串口工具实际发送:

text 复制代码
@LED_ON\r\n

状态机只把 LED_ON 保存到接收数组,不保存 @\r\n,最后由程序追加 \0,使它成为 C 字符串。

老师 PPT 第 124 页:以 @ 为包头、CRLF 为包尾的文本数据包。

4.3 如何选择

比较项 HEX 数据包 文本数据包
载荷 原始二进制字节 经过字符编码的文本
传输效率 高,例如数值 100 只需一个字节 较低,100 需要字符 '1' '0' '0' 三字节
人工可读性 较差 很好,可直接在串口助手输入
解析 固定字段时简单 还需把字符串转换成数值或命令
典型场景 传感器、实时数据、设备间通信 AT 指令、调试命令、人机交互

5. 包格式设计时必须考虑什么

5.1 固定包长与可变包长

  • 固定包长:接到包头后按已知数量收载荷,再检查包尾;字段定位稳定,适合二进制协议。
  • 可变包长:不断接收,直到发现包尾;灵活,但必须限制最大长度。

5.2 包头/包尾与载荷冲突

常用处理方法:

  1. 限制载荷取值,使其不会出现定界符;
  2. 使用固定长度,接收载荷阶段不解释定界符;
  3. 使用多字节包头、包尾,降低自然重复概率;
  4. 正式协议使用转义/字节填充,例如载荷遇到特殊字节时编码;
  5. 增加长度字段,让接收端按长度收取;
  6. 增加校验和或 CRC,判断一包是否可靠。

课程协议用于讲清状态机,只有包头、载荷、包尾,没有地址、命令码、长度和校验。实际项目应按可靠性要求扩展。

6. 为什么发送简单、接收复杂

发送方掌握完整数据,可以主动按顺序调用:

text 复制代码
发包头 → 发载荷 → 发包尾

接收方每次 RXNE 中断只拿到一个字节,而且每次中断彼此独立。要知道上一次已经收到哪里,就必须保存跨中断的状态,这就是状态机。
#mermaid-svg-MDPo10oMFZRtFhla{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MDPo10oMFZRtFhla .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MDPo10oMFZRtFhla .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MDPo10oMFZRtFhla .error-icon{fill:#552222;}#mermaid-svg-MDPo10oMFZRtFhla .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MDPo10oMFZRtFhla .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MDPo10oMFZRtFhla .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MDPo10oMFZRtFhla .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MDPo10oMFZRtFhla .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MDPo10oMFZRtFhla .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MDPo10oMFZRtFhla .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MDPo10oMFZRtFhla .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MDPo10oMFZRtFhla .marker.cross{stroke:#333333;}#mermaid-svg-MDPo10oMFZRtFhla svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MDPo10oMFZRtFhla p{margin:0;}#mermaid-svg-MDPo10oMFZRtFhla .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MDPo10oMFZRtFhla .cluster-label text{fill:#333;}#mermaid-svg-MDPo10oMFZRtFhla .cluster-label span{color:#333;}#mermaid-svg-MDPo10oMFZRtFhla .cluster-label span p{background-color:transparent;}#mermaid-svg-MDPo10oMFZRtFhla .label text,#mermaid-svg-MDPo10oMFZRtFhla span{fill:#333;color:#333;}#mermaid-svg-MDPo10oMFZRtFhla .node rect,#mermaid-svg-MDPo10oMFZRtFhla .node circle,#mermaid-svg-MDPo10oMFZRtFhla .node ellipse,#mermaid-svg-MDPo10oMFZRtFhla .node polygon,#mermaid-svg-MDPo10oMFZRtFhla .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MDPo10oMFZRtFhla .rough-node .label text,#mermaid-svg-MDPo10oMFZRtFhla .node .label text,#mermaid-svg-MDPo10oMFZRtFhla .image-shape .label,#mermaid-svg-MDPo10oMFZRtFhla .icon-shape .label{text-anchor:middle;}#mermaid-svg-MDPo10oMFZRtFhla .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MDPo10oMFZRtFhla .rough-node .label,#mermaid-svg-MDPo10oMFZRtFhla .node .label,#mermaid-svg-MDPo10oMFZRtFhla .image-shape .label,#mermaid-svg-MDPo10oMFZRtFhla .icon-shape .label{text-align:center;}#mermaid-svg-MDPo10oMFZRtFhla .node.clickable{cursor:pointer;}#mermaid-svg-MDPo10oMFZRtFhla .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MDPo10oMFZRtFhla .arrowheadPath{fill:#333333;}#mermaid-svg-MDPo10oMFZRtFhla .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MDPo10oMFZRtFhla .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MDPo10oMFZRtFhla .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MDPo10oMFZRtFhla .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MDPo10oMFZRtFhla .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MDPo10oMFZRtFhla .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MDPo10oMFZRtFhla .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MDPo10oMFZRtFhla .cluster text{fill:#333;}#mermaid-svg-MDPo10oMFZRtFhla .cluster span{color:#333;}#mermaid-svg-MDPo10oMFZRtFhla div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MDPo10oMFZRtFhla .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MDPo10oMFZRtFhla rect.text{fill:none;stroke-width:0;}#mermaid-svg-MDPo10oMFZRtFhla .icon-shape,#mermaid-svg-MDPo10oMFZRtFhla .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MDPo10oMFZRtFhla .icon-shape p,#mermaid-svg-MDPo10oMFZRtFhla .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MDPo10oMFZRtFhla .icon-shape .label rect,#mermaid-svg-MDPo10oMFZRtFhla .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MDPo10oMFZRtFhla .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MDPo10oMFZRtFhla .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MDPo10oMFZRtFhla :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 收到当前字节
读取上次保留的状态
根据当前状态解释该字节
保存载荷或丢弃
更新状态和数组下标
退出中断,等待下一字节

7. HEX 数据包发送

7.1 缓冲区

c 复制代码
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;

数组只存 4 字节载荷,不存固定的包头和包尾。主程序写 Serial_TxPacket,驱动函数负责自动封装:

c 复制代码
void Serial_SendPacket(void)
{
    Serial_SendByte(0xFF);
    Serial_SendArray(Serial_TxPacket, 4);
    Serial_SendByte(0xFE);
}

USART1 Serial_SendByte Serial_SendPacket 主程序 USART1 Serial_SendByte Serial_SendPacket 主程序 #mermaid-svg-Nz2Fp24Na8Be27HD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Nz2Fp24Na8Be27HD .error-icon{fill:#552222;}#mermaid-svg-Nz2Fp24Na8Be27HD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Nz2Fp24Na8Be27HD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Nz2Fp24Na8Be27HD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Nz2Fp24Na8Be27HD .marker.cross{stroke:#333333;}#mermaid-svg-Nz2Fp24Na8Be27HD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Nz2Fp24Na8Be27HD p{margin:0;}#mermaid-svg-Nz2Fp24Na8Be27HD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Nz2Fp24Na8Be27HD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Nz2Fp24Na8Be27HD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Nz2Fp24Na8Be27HD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Nz2Fp24Na8Be27HD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Nz2Fp24Na8Be27HD .sequenceNumber{fill:white;}#mermaid-svg-Nz2Fp24Na8Be27HD #sequencenumber{fill:#333;}#mermaid-svg-Nz2Fp24Na8Be27HD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Nz2Fp24Na8Be27HD .messageText{fill:#333;stroke:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Nz2Fp24Na8Be27HD .labelText,#mermaid-svg-Nz2Fp24Na8Be27HD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .loopText,#mermaid-svg-Nz2Fp24Na8Be27HD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Nz2Fp24Na8Be27HD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Nz2Fp24Na8Be27HD .noteText,#mermaid-svg-Nz2Fp24Na8Be27HD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Nz2Fp24Na8Be27HD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Nz2Fp24Na8Be27HD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Nz2Fp24Na8Be27HD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Nz2Fp24Na8Be27HD .actorPopupMenu{position:absolute;}#mermaid-svg-Nz2Fp24Na8Be27HD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Nz2Fp24Na8Be27HD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Nz2Fp24Na8Be27HD .actor-man circle,#mermaid-svg-Nz2Fp24Na8Be27HD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Nz2Fp24Na8Be27HD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 填写4字节 TxPacket 后调用 发送 0xFF 依次发送4字节载荷 发送 0xFE 写 DR,等待 TXE

8. HEX 数据包接收状态机

老师 PPT 第 125 页:S=0 等包头、S=1 收 4 字节、S=2 等包尾。

8.1 状态定义

RxState 含义 当前字节的处理
0 等待包头 只有收到 0xFF 才进入状态 1
1 接收载荷 无条件存入数组;收满 4 字节后进入状态 2
2 等待包尾 收到 0xFE 后完成一包并回到状态 0

状态和数组下标定义为 static

c 复制代码
static uint8_t RxState = 0;
static uint8_t pRxPacket = 0;

普通局部变量每次进入中断都重新创建,不能记住上次进度;静态局部变量在函数退出后仍保留值,同时作用域仍限制在中断函数内。

8.2 代码流程

#mermaid-svg-D1hWvx8myjUXb1Js{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D1hWvx8myjUXb1Js .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D1hWvx8myjUXb1Js .error-icon{fill:#552222;}#mermaid-svg-D1hWvx8myjUXb1Js .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D1hWvx8myjUXb1Js .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D1hWvx8myjUXb1Js .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D1hWvx8myjUXb1Js .marker.cross{stroke:#333333;}#mermaid-svg-D1hWvx8myjUXb1Js svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D1hWvx8myjUXb1Js p{margin:0;}#mermaid-svg-D1hWvx8myjUXb1Js defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-D1hWvx8myjUXb1Js g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-D1hWvx8myjUXb1Js g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-D1hWvx8myjUXb1Js g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-D1hWvx8myjUXb1Js g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-D1hWvx8myjUXb1Js g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-D1hWvx8myjUXb1Js .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-D1hWvx8myjUXb1Js .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-D1hWvx8myjUXb1Js .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-D1hWvx8myjUXb1Js .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-D1hWvx8myjUXb1Js .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-D1hWvx8myjUXb1Js .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-D1hWvx8myjUXb1Js .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-D1hWvx8myjUXb1Js .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D1hWvx8myjUXb1Js .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D1hWvx8myjUXb1Js .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D1hWvx8myjUXb1Js .edgeLabel .label text{fill:#333;}#mermaid-svg-D1hWvx8myjUXb1Js .label div .edgeLabel{color:#333;}#mermaid-svg-D1hWvx8myjUXb1Js .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-D1hWvx8myjUXb1Js .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-D1hWvx8myjUXb1Js .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-D1hWvx8myjUXb1Js .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-D1hWvx8myjUXb1Js .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-D1hWvx8myjUXb1Js .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D1hWvx8myjUXb1Js .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D1hWvx8myjUXb1Js #statediagram-barbEnd{fill:#333333;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D1hWvx8myjUXb1Js .cluster-label,#mermaid-svg-D1hWvx8myjUXb1Js .nodeLabel{color:#131300;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-D1hWvx8myjUXb1Js .note-edge{stroke-dasharray:5;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-note text{fill:black;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram-note .nodeLabel{color:black;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagram .edgeLabel{color:red;}#mermaid-svg-D1hWvx8myjUXb1Js #dependencyStart,#mermaid-svg-D1hWvx8myjUXb1Js #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-D1hWvx8myjUXb1Js .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D1hWvx8myjUXb1Js :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RxData == 0xFF / 下标清零
其他字节 / 丢弃
未收满4字节 / 保存并加1
已收满4字节
RxData == 0xFE / RxFlag置1
其他字节 / 继续等待
State 0 等待包头
State 1 接收4字节载荷
State 2 等待包尾

源码使用 if ... else if ... else if,是为了保证一次中断只执行一个状态。若写成三个独立 if,状态 0 中刚把 RxState 改为 1,下面状态 1 的 if 可能在同一次中断中立刻成立,导致一个字节被重复解释。

9. HEX 工程主循环

c 复制代码
if (KeyNum == 1)
{
    Serial_TxPacket[0]++;
    Serial_TxPacket[1]++;
    Serial_TxPacket[2]++;
    Serial_TxPacket[3]++;
    Serial_SendPacket();
}

if (Serial_GetRxFlag() == 1)
{
    // 在 OLED 显示 Serial_RxPacket[0..3]
}

Serial_GetRxFlag() 采用"读取即清零":完整包到达后 ISR 置 1;主循环取到 1 时清为 0。这表示主循环已经获知新包,但不保证处理期间数组不会被下一包改写。

10. 文本数据包接收状态机

老师 PPT 第 126 页:等待 @、接收不定长字符并等待 \r、再等待 \n

10.1 状态定义

状态 任务 转移条件
0 等待 @ 收到 @ 且上一包已处理完,进入 1
1 接收文本 普通字符写入数组;收到 \r 进入 2
2 等待 \n 收到 \n 后补 \0、置标志并回到 0

#mermaid-svg-F9YVpK0mYjhWaosa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-F9YVpK0mYjhWaosa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-F9YVpK0mYjhWaosa .error-icon{fill:#552222;}#mermaid-svg-F9YVpK0mYjhWaosa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-F9YVpK0mYjhWaosa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-F9YVpK0mYjhWaosa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-F9YVpK0mYjhWaosa .marker.cross{stroke:#333333;}#mermaid-svg-F9YVpK0mYjhWaosa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-F9YVpK0mYjhWaosa p{margin:0;}#mermaid-svg-F9YVpK0mYjhWaosa defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-F9YVpK0mYjhWaosa g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-F9YVpK0mYjhWaosa g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-F9YVpK0mYjhWaosa g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-F9YVpK0mYjhWaosa g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-F9YVpK0mYjhWaosa g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-F9YVpK0mYjhWaosa .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-F9YVpK0mYjhWaosa .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-F9YVpK0mYjhWaosa .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-F9YVpK0mYjhWaosa .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-F9YVpK0mYjhWaosa .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-F9YVpK0mYjhWaosa .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-F9YVpK0mYjhWaosa .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-F9YVpK0mYjhWaosa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F9YVpK0mYjhWaosa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-F9YVpK0mYjhWaosa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F9YVpK0mYjhWaosa .edgeLabel .label text{fill:#333;}#mermaid-svg-F9YVpK0mYjhWaosa .label div .edgeLabel{color:#333;}#mermaid-svg-F9YVpK0mYjhWaosa .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-F9YVpK0mYjhWaosa .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-F9YVpK0mYjhWaosa .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-F9YVpK0mYjhWaosa .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-F9YVpK0mYjhWaosa .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-F9YVpK0mYjhWaosa .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-F9YVpK0mYjhWaosa .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-F9YVpK0mYjhWaosa #statediagram-barbEnd{fill:#333333;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-F9YVpK0mYjhWaosa .cluster-label,#mermaid-svg-F9YVpK0mYjhWaosa .nodeLabel{color:#131300;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-F9YVpK0mYjhWaosa .note-edge{stroke-dasharray:5;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-note text{fill:black;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram-note .nodeLabel{color:black;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagram .edgeLabel{color:red;}#mermaid-svg-F9YVpK0mYjhWaosa #dependencyStart,#mermaid-svg-F9YVpK0mYjhWaosa #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-F9YVpK0mYjhWaosa .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-F9YVpK0mYjhWaosa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 收到 @ 且 RxFlag==0 / 下标清零
其他字节
普通字符 / 保存并加1
收到 CR
收到 LF / 补零并置 RxFlag
State 0 等待 @
State 1 接收载荷
State 2 等待 LF

10.2 为什么要补 \0

Serial_RxPacket 是字符数组。strcmpOLED_ShowString 等 C 字符串函数不知道有效内容有多长,只会一直读到 \0。所以收到 \r\n 后必须执行:

c 复制代码
Serial_RxPacket[pRxPacket] = '\0';

这不会通过串口发出去,它只是 RAM 中的字符串终止标记。

11. 文本命令怎样驱动 LED

主循环只有在 Serial_RxFlag == 1 时解析完整字符串:

c 复制代码
if (strcmp(Serial_RxPacket, "LED_ON") == 0)
{
    LED1_ON();
    Serial_SendString("LED_ON_OK\r\n");
}
else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
{
    LED1_OFF();
    Serial_SendString("LED_OFF_OK\r\n");
}
else
{
    Serial_SendString("ERROR_COMMAND\r\n");
}
Serial_RxFlag = 0;

strcmp(a, b) 返回 0 才表示两个字符串完全相等。整体链路为:
#mermaid-svg-G9ARJZ1O1EqhQ7vq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .error-icon{fill:#552222;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .marker.cross{stroke:#333333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq p{margin:0;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .cluster-label text{fill:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .cluster-label span{color:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .cluster-label span p{background-color:transparent;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .label text,#mermaid-svg-G9ARJZ1O1EqhQ7vq span{fill:#333;color:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .node rect,#mermaid-svg-G9ARJZ1O1EqhQ7vq .node circle,#mermaid-svg-G9ARJZ1O1EqhQ7vq .node ellipse,#mermaid-svg-G9ARJZ1O1EqhQ7vq .node polygon,#mermaid-svg-G9ARJZ1O1EqhQ7vq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .rough-node .label text,#mermaid-svg-G9ARJZ1O1EqhQ7vq .node .label text,#mermaid-svg-G9ARJZ1O1EqhQ7vq .image-shape .label,#mermaid-svg-G9ARJZ1O1EqhQ7vq .icon-shape .label{text-anchor:middle;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .rough-node .label,#mermaid-svg-G9ARJZ1O1EqhQ7vq .node .label,#mermaid-svg-G9ARJZ1O1EqhQ7vq .image-shape .label,#mermaid-svg-G9ARJZ1O1EqhQ7vq .icon-shape .label{text-align:center;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .node.clickable{cursor:pointer;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .arrowheadPath{fill:#333333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G9ARJZ1O1EqhQ7vq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G9ARJZ1O1EqhQ7vq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G9ARJZ1O1EqhQ7vq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .cluster text{fill:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .cluster span{color:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G9ARJZ1O1EqhQ7vq rect.text{fill:none;stroke-width:0;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .icon-shape,#mermaid-svg-G9ARJZ1O1EqhQ7vq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .icon-shape p,#mermaid-svg-G9ARJZ1O1EqhQ7vq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .icon-shape .label rect,#mermaid-svg-G9ARJZ1O1EqhQ7vq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G9ARJZ1O1EqhQ7vq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G9ARJZ1O1EqhQ7vq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G9ARJZ1O1EqhQ7vq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 电脑发送 @LED_ON CR LF
USART RXNE逐字节中断
状态机去掉包头包尾
数组得到 LED_ON 加字符串终止符
RxFlag置1
主循环 strcmp 匹配命令
执行 LED1_ON
回传 LED_ON_OK CR LF

RxFlag 在业务处理完成后才清零;状态 0 又要求 RxFlag==0 才接受新包头。这样 ISR 不会在主循环读取数组时写入下一包,但处理期间到来的数据包会被丢弃。这是一种"互斥但不排队"的简单策略。

12. 本节关键函数与硬件关系

函数/语句 所属层 作用及其前后关联
USART_GetITStatus(USART1, USART_IT_RXNE) 标准外设库 检查 SR.RXNE 和对应中断使能位,确认收到字节事件
USART_ReceiveData(USART1) 标准外设库 读取 DR 的接收数据;读 DR 会清 RXNE
USART_ClearITPendingBit(...RXNE) 标准外设库 课程代码再次清标志;读取 DR 后通常不需要
Serial_SendByte 课程驱动层 写 DR 并等待 TXE,是所有发送封装的最底层
Serial_SendArray 课程驱动层 循环发送定长二进制数组
Serial_SendPacket 协议层 为 4 字节载荷添加 0xFF/0xFE
Serial_GetRxFlag 模块接口 通知主程序完整 HEX 包已到,并采用读取后清零策略
strcmp C 标准库 比较已补 \0 的文本载荷与预设指令
Serial_SendString 课程驱动层 发送文本响应,字符串自身以 \0 结束,线路包尾为 \r\n

13. 手册视角:硬件到底做到了哪一步

#mermaid-svg-NvaoUldBxnC5cWBK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NvaoUldBxnC5cWBK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NvaoUldBxnC5cWBK .error-icon{fill:#552222;}#mermaid-svg-NvaoUldBxnC5cWBK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NvaoUldBxnC5cWBK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NvaoUldBxnC5cWBK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NvaoUldBxnC5cWBK .marker.cross{stroke:#333333;}#mermaid-svg-NvaoUldBxnC5cWBK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NvaoUldBxnC5cWBK p{margin:0;}#mermaid-svg-NvaoUldBxnC5cWBK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NvaoUldBxnC5cWBK .cluster-label text{fill:#333;}#mermaid-svg-NvaoUldBxnC5cWBK .cluster-label span{color:#333;}#mermaid-svg-NvaoUldBxnC5cWBK .cluster-label span p{background-color:transparent;}#mermaid-svg-NvaoUldBxnC5cWBK .label text,#mermaid-svg-NvaoUldBxnC5cWBK span{fill:#333;color:#333;}#mermaid-svg-NvaoUldBxnC5cWBK .node rect,#mermaid-svg-NvaoUldBxnC5cWBK .node circle,#mermaid-svg-NvaoUldBxnC5cWBK .node ellipse,#mermaid-svg-NvaoUldBxnC5cWBK .node polygon,#mermaid-svg-NvaoUldBxnC5cWBK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NvaoUldBxnC5cWBK .rough-node .label text,#mermaid-svg-NvaoUldBxnC5cWBK .node .label text,#mermaid-svg-NvaoUldBxnC5cWBK .image-shape .label,#mermaid-svg-NvaoUldBxnC5cWBK .icon-shape .label{text-anchor:middle;}#mermaid-svg-NvaoUldBxnC5cWBK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NvaoUldBxnC5cWBK .rough-node .label,#mermaid-svg-NvaoUldBxnC5cWBK .node .label,#mermaid-svg-NvaoUldBxnC5cWBK .image-shape .label,#mermaid-svg-NvaoUldBxnC5cWBK .icon-shape .label{text-align:center;}#mermaid-svg-NvaoUldBxnC5cWBK .node.clickable{cursor:pointer;}#mermaid-svg-NvaoUldBxnC5cWBK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NvaoUldBxnC5cWBK .arrowheadPath{fill:#333333;}#mermaid-svg-NvaoUldBxnC5cWBK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NvaoUldBxnC5cWBK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NvaoUldBxnC5cWBK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NvaoUldBxnC5cWBK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NvaoUldBxnC5cWBK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NvaoUldBxnC5cWBK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NvaoUldBxnC5cWBK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NvaoUldBxnC5cWBK .cluster text{fill:#333;}#mermaid-svg-NvaoUldBxnC5cWBK .cluster span{color:#333;}#mermaid-svg-NvaoUldBxnC5cWBK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NvaoUldBxnC5cWBK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NvaoUldBxnC5cWBK rect.text{fill:none;stroke-width:0;}#mermaid-svg-NvaoUldBxnC5cWBK .icon-shape,#mermaid-svg-NvaoUldBxnC5cWBK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NvaoUldBxnC5cWBK .icon-shape p,#mermaid-svg-NvaoUldBxnC5cWBK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NvaoUldBxnC5cWBK .icon-shape .label rect,#mermaid-svg-NvaoUldBxnC5cWBK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NvaoUldBxnC5cWBK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NvaoUldBxnC5cWBK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NvaoUldBxnC5cWBK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PA10检测串行电平
USART完成起始位检测和位采样
完整字节进入 USART_DR
USART_SR.RXNE置1
RXNEIE和NVIC触发中断
软件读取 DR
软件状态机解释包结构

参考手册能保证并说明 A~F;G 完全由我们的协议和 C 代码决定。若 CPU 未及时读 DR,而下一字节已经完成接收,SR.ORE 会置位,表示溢出错误,可能丢数据。数据包状态机不能修复已经丢失的字节,因此高数据率场景要使用更快的中断处理、环形缓冲区或 DMA。

14. 源码中必须认识的风险

14.1 文本数组没有边界检查

源码定义:

c 复制代码
char Serial_RxPacket[100];

但状态 1 每收到普通字符就让 pRxPacket++,没有检查是否达到数组末尾。超过容量会写坏其他内存。安全逻辑至少应确保只写入 sizeof(Serial_RxPacket)-1 个字符,为末尾 \0 留一个位置;超长包应丢弃并复位状态。

14.2 异常包尾会让状态机卡住

  • HEX 状态 2 若收到的不是 0xFE,源码一直等待 0xFE
  • 文本状态 2 若 \r 后不是 \n,源码一直等待 \n

教学代码容易理解,但正式项目通常会在错误时复位、重新把当前字节当作潜在包头,或增加超时机制,以便快速重新同步。

14.3 接收数组可能被同时读写

9-3 中 ISR 写 Serial_RxPacket,主循环读同一数组。若下一包来得太快,主循环可能读到两包混合内容。可选方案:

  • 处理完才允许接收下一包,忙时丢包;
  • ISR 写接收缓冲,完整后复制/交换到处理缓冲,即"双缓冲";
  • 使用环形缓冲区保存原始字节,在主循环解析;
  • 使用消息队列保存多包。

14.4 共享变量建议使用 volatile

Serial_RxFlag 在 ISR 和主循环之间共享,正式代码建议声明为 volatile uint8_t,防止编译器假定它不会被异步修改。volatile 只保证实际访问内存,不解决竞争、原子性或排队问题。

14.5 没有校验字段

包头包尾只能帮助划分边界,不能证明载荷正确。受干扰导致某个数据位翻转时,现有协议仍可能把错误载荷当成有效包。可靠通信应加入校验和或 CRC,并明确错误包的丢弃与重传策略。

15. 怎样仿制一个自己的数据包

先写协议表,再写代码,不要边写状态机边决定格式。

字段 示例 问题
包头 0xAA 0x55 如何快速重新同步?
命令 1 字节 这一包要执行什么?
长度 1 字节 载荷到底有多少字节?
载荷 N 字节 字段顺序、单位、字节序是什么?
校验 CRC16 校验覆盖哪些字段?

然后按以下流程实现:
#mermaid-svg-sEoU21lSWe9VuFth{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sEoU21lSWe9VuFth .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sEoU21lSWe9VuFth .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sEoU21lSWe9VuFth .error-icon{fill:#552222;}#mermaid-svg-sEoU21lSWe9VuFth .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sEoU21lSWe9VuFth .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sEoU21lSWe9VuFth .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sEoU21lSWe9VuFth .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sEoU21lSWe9VuFth .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sEoU21lSWe9VuFth .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sEoU21lSWe9VuFth .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sEoU21lSWe9VuFth .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sEoU21lSWe9VuFth .marker.cross{stroke:#333333;}#mermaid-svg-sEoU21lSWe9VuFth svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sEoU21lSWe9VuFth p{margin:0;}#mermaid-svg-sEoU21lSWe9VuFth .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sEoU21lSWe9VuFth .cluster-label text{fill:#333;}#mermaid-svg-sEoU21lSWe9VuFth .cluster-label span{color:#333;}#mermaid-svg-sEoU21lSWe9VuFth .cluster-label span p{background-color:transparent;}#mermaid-svg-sEoU21lSWe9VuFth .label text,#mermaid-svg-sEoU21lSWe9VuFth span{fill:#333;color:#333;}#mermaid-svg-sEoU21lSWe9VuFth .node rect,#mermaid-svg-sEoU21lSWe9VuFth .node circle,#mermaid-svg-sEoU21lSWe9VuFth .node ellipse,#mermaid-svg-sEoU21lSWe9VuFth .node polygon,#mermaid-svg-sEoU21lSWe9VuFth .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sEoU21lSWe9VuFth .rough-node .label text,#mermaid-svg-sEoU21lSWe9VuFth .node .label text,#mermaid-svg-sEoU21lSWe9VuFth .image-shape .label,#mermaid-svg-sEoU21lSWe9VuFth .icon-shape .label{text-anchor:middle;}#mermaid-svg-sEoU21lSWe9VuFth .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sEoU21lSWe9VuFth .rough-node .label,#mermaid-svg-sEoU21lSWe9VuFth .node .label,#mermaid-svg-sEoU21lSWe9VuFth .image-shape .label,#mermaid-svg-sEoU21lSWe9VuFth .icon-shape .label{text-align:center;}#mermaid-svg-sEoU21lSWe9VuFth .node.clickable{cursor:pointer;}#mermaid-svg-sEoU21lSWe9VuFth .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sEoU21lSWe9VuFth .arrowheadPath{fill:#333333;}#mermaid-svg-sEoU21lSWe9VuFth .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sEoU21lSWe9VuFth .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sEoU21lSWe9VuFth .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sEoU21lSWe9VuFth .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sEoU21lSWe9VuFth .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sEoU21lSWe9VuFth .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sEoU21lSWe9VuFth .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sEoU21lSWe9VuFth .cluster text{fill:#333;}#mermaid-svg-sEoU21lSWe9VuFth .cluster span{color:#333;}#mermaid-svg-sEoU21lSWe9VuFth div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sEoU21lSWe9VuFth .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sEoU21lSWe9VuFth rect.text{fill:none;stroke-width:0;}#mermaid-svg-sEoU21lSWe9VuFth .icon-shape,#mermaid-svg-sEoU21lSWe9VuFth .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sEoU21lSWe9VuFth .icon-shape p,#mermaid-svg-sEoU21lSWe9VuFth .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sEoU21lSWe9VuFth .icon-shape .label rect,#mermaid-svg-sEoU21lSWe9VuFth .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sEoU21lSWe9VuFth .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sEoU21lSWe9VuFth .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sEoU21lSWe9VuFth :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 写出协议字段表
为每个阶段定义状态
画状态转移条件
确定最大载荷和超时
编写发送封装
编写逐字节接收状态机
加入长度、边界和校验
测试正常包
测试错包头、错包尾、断包、粘包、超长包

16. 调试步骤

HEX 工程

  1. 串口助手发送和接收均切换到 HEX 模式;
  2. 发送 FF 11 22 33 44 FE
  3. OLED 应显示 11 22 33 44
  4. 再测试载荷含定界符:FF FE FF 88 FE FE
  5. 按键触发 STM32 发送,检查电脑是否收到严格 6 字节。

文本工程

  1. 串口助手切换到文本模式,并启用发送换行;
  2. 发送 @LED_ON 加 CRLF,LED 应点亮并返回 LED_ON_OK
  3. 发送 @LED_OFF 加 CRLF,LED 应熄灭;
  4. 发送未知命令,检查 ERROR_COMMAND
  5. 测试缺少 @、缺少 LF、空载荷、超长载荷和连续快速发送。

17. 复习自测

  1. 为什么仅仅连续发送 XYZ 会出现字段错位?
  2. 固定包长时,为什么载荷中的 0xFF/0xFE 不一定造成误判?
  3. static RxState 为什么不能换成普通局部变量?
  4. 为什么三个状态分支要用 else if
  5. \r\n\0 分别属于线路协议还是内存字符串规则?
  6. RXNE 代表一个字节到达,完整包标志由谁产生?
  7. RxFlag==0 才接收新包解决了什么,又牺牲了什么?
  8. 如何防止 100 字节文本缓冲区越界?
  9. 包头包尾能否检测载荷被干扰?为什么还需要 CRC?
  10. 高速连续数据为什么更适合环形缓冲区或 DMA?

18. 一句话总结

USART 硬件负责可靠地收发"一个字节",数据包格式负责规定"哪些字节属于一条消息",状态机负责在多次 RXNE 中断之间记住进度并恢复完整消息。

相关推荐
sunoo-2291 小时前
51单片机学习Day2:数码管动态扫描、定时器与中断系统深度总
单片机·嵌入式硬件·学习·51单片机
sweetone1 小时前
索尼MHC-V7700组合音响一修一改
经验分享·嵌入式硬件·音视频
小娄~~2 小时前
LVGL,STM32U575RIT6+指纹模块
stm32·单片机·嵌入式硬件
Brilliantwxx2 小时前
【STM32】 HAL库深度入门:从设计思想到 USART 全模式实战
stm32·单片机·嵌入式硬件
恒锐丰科技林技术员2 小时前
EG1192S:高耐压零功耗使能降压 DC‑DC 芯片,拓宽工业电源设计边界
经验分享·嵌入式硬件·硬件工程
深圳市恒锐丰科技杨生3 小时前
EG1192L 零功耗使能集成上管非同步降压 DC‑DC|屹晶 EGmicro
嵌入式硬件·硬件工程
JNX_SEMI3 小时前
HTD8239 5.5V~36V宽压H桥驱动:适配12V/24V主流电机供电系统
单片机·嵌入式硬件
2401_862880823 小时前
51单片机 --- GPIO、中断、定时器、PWM
单片机·嵌入式硬件·51单片机
零依赖极客3 小时前
《30 天手搓 ARM 架构零依赖纯 C 推理引擎》课程介绍与大纲
c语言·开发语言·arm开发·人工智能·嵌入式硬件·ai编程