串口解析知识层
这个主题解决什么问题
串口解析解决的是“上位机发来一串字符后,单片机如何判断一帧已经收完,并从字符串里提取参数或识别查询命令”的问题。
在蓝桥杯单片机题里,串口通常不是单纯回显,而是用来完成:
-
设置参数,例如
H1:23.5、F:100 -
查询参数,例如
H1,? -
设置时间,例如
T:235950 -
回复执行结果,例如
OK或当前参数值
所以串口解析的核心不是 printf,而是把接收缓冲区里的字符转换成题目需要的变量更新和查询响应。
核心机制
串口解析可以拆成四个环节:
-
中断接收:每收到 1 个字节,就放入
uart_rec_string -
帧结束判断:超过一段时间没有新数据,就认为这一帧收完
-
内容解析:用关键字定位参数或查询命令
-
收尾复位:清空缓冲区,重置索引和状态
原始笔记中的帧结束策略是:
if (uart_index == 0) return;
if (uart_tick < 10) return;
这里的稳定知识是:
-
uart_index == 0表示当前没有收到任何数据,不需要解析 -
uart_tick < 10表示距离上一个接收字节还不够久,先继续等 -
uart_tick >= 10表示 10ms 内没有新字节,可以把当前缓冲区当作完整一帧
这种做法适合比赛里的简单命令帧。它不依赖固定帧头帧尾,写起来快,但前提是串口发送端不会故意制造非常复杂的粘包、拆包场景。
get_num 的知识点
get_num 的作用是:在接收缓冲区中寻找某个字段名,如果找到了,就读取字段名后面的数字。
原始笔记中的核心代码是:
float get_num(const char* string)
{
char* p1 = NULL;
float temp = 0;
p1 = strstr(uart_rec_string, string);
if (p1 == NULL) return -99;
sscanf(p1 + strlen(string), "%f", &temp);
return temp;
}
它背后的关系是:
-
strstr(uart_rec_string, string)用来找关键字,例如"H1:" -
找不到关键字时返回
-99,表示本帧没有这个字段 -
p1 + strlen(string)会跳过关键字,让sscanf从数字部分开始读 -
"%f"可以读取整数或小数形式,再交给上层决定是否放大、取整或转换
例如缓冲区是:
H1:23.5
调用:
temp = get_num("H1:");
strstr 会定位到 H1:,p1 + strlen("H1:") 指向 23.5,最终 temp 得到 23.5。
参数设置和查询命令的区别
串口命令大致分成两类。
第一类是设置参数,用冒号后跟数字:
H1:23.5
H2:40.0
F:100
S:20
T:235950
这类命令适合用 get_num("字段名:") 解析。解析成功后,更新变量并把 state 设为 0,最后回复 OK。
第二类是查询参数,用类似 H1,? 的字符串:
H1,?
H2,?
F,?
S,?
这类命令不需要取数字,只需要用 strstr 判断命令是否存在,然后把 state 设置成对应查询编号,最后在 switch 中输出对应值。
稳定分工是:
-
设置类命令:字段定位 + 数字读取 + 更新变量 + 回复
OK -
查询类命令:字符串匹配 + 选择输出项 + 回复当前值
state 的意义
原始笔记里的 state 不是系统状态机,而是“本帧串口处理结果”的临时分类。
常见含义是:
-
state = 0:本帧是有效设置命令,回复OK -
state = 1:查询H1 -
state = 2:查询H2 -
state = 3:查询F -
state = 4:查询S -
state = 88:默认无效状态,不回复有效内容
处理结束后要把它恢复为默认值:
state = 88;
否则下一帧如果解析失败,可能沿用上一帧的 state,导致误回复。
数量级和数据类型
串口读到的数字不等于最终变量的存储格式,设置参数时必须处理数量级。
原始笔记中:
H1 = temp * 10;
H2 = temp * 10;
F = temp;
S = temp;
说明 H1/H2 可能要保存为 10 倍值,而 F/S 直接保存为整数。稳定知识是:串口输入可以是小数,但内部变量要按题目显示、比较、存储的数量级统一。
设置时间时还有一个数据类型风险:
ucrtc[0] = (unsigned long int)temp / 10000 % 100;
ucrtc[1] = (unsigned long int)temp / 100 % 100;
ucrtc[2] = (unsigned long int)temp % 100;
235950 大于 unsigned int 常见的 65535 范围,所以拆 HHMMSS 这类六位数时不能直接依赖 16 位 unsigned int。应先转成 unsigned long int 再做除法和取模。
清缓冲和复位
一帧处理完后必须收尾:
memset(uart_rec_string, 0, uart_index);
uart_index = 0;
state = 88;
这三个动作职责不同:
-
memset清掉旧字符,避免下一帧strstr匹配到残留内容 -
uart_index = 0告诉接收逻辑从缓冲区开头重新存 -
state = 88清掉本帧结果,避免下一帧误用
如果只清 uart_index 不清字符串,在某些情况下旧内容仍可能被 strstr 搜到。比赛里最稳的做法是每帧处理完都清缓冲和复位状态。
依赖的前置知识
要稳定使用串口解析,需要先理解:
-
串口中断如何把
SBUF存入接收缓冲区 -
uart_index表示当前缓冲区已接收长度 -
uart_tick由 1ms 节拍累加,用来判断接收间隔 -
strstr用来找子串,strlen用来跳过字段名 -
sscanf可以从字符串中按格式读取数字 -
printf最终会通过串口发送回复
常见错误
-
忘记判断
uart_tick < 10,导致一帧还没收完就提前解析 -
缓冲区太小,命令稍长就越界或截断
-
get_num找不到字段时没有特殊返回值,导致误把默认数字写入参数 -
用
-99当错误标记时,又允许合法参数等于-99 -
只清
uart_index,不清接收缓冲区,导致旧命令残留影响下一帧 -
处理完不重置
state,下一帧无效命令沿用上一帧回复 -
H1/H2等带小数参数忘记放大存储,显示或比较时数量级混乱 -
拆
T:235950这类六位时间时使用 16 位整数,导致溢出 -
设置命令和查询命令的匹配条件写得过宽,导致一帧里多个条件同时命中
这份知识层的边界
这份笔记只说明蓝桥杯场景下的轻量串口命令解析:如何判断帧结束、如何从字符串取数、如何区分设置和查询、如何复位缓冲。
暂时不展开:
-
严格协议帧头帧尾和校验和
-
粘包、拆包、环形缓冲区的完整工程级处理
-
多命令同时存在时的优先级设计
-
完整可抄写的执行层代码骨架
-
比赛题中什么时候应该选择串口解析、什么时候只保留回显
这些内容后续应继续放到:
-
03-决策层 -
04-执行层 -
05-练习层
超短复习卡
-
串口解析 = 接收缓冲 + 10ms 空闲判帧 + 字符串匹配 + 清缓冲复位
-
get_num("H1:"):先strstr找字段,再sscanf读字段后的数字 -
设置命令用
字段名:数字,查询命令用字段名,? -
state只表示本帧处理结果,处理完必须恢复默认值 -
小数参数要统一内部数量级,六位时间拆分要防 16 位溢出
-
每帧结束后清
uart_rec_string、清uart_index、重置state