第16届蓝桥杯单片机省赛第2部分
这题复盘笔记(优化美化版)
一、先抓题目的“核心逻辑骨架”
这题本质上是 4 条主线同时跑:
1. 采集链路
- 温度:DS18B20
- 光敏:AD 采集
- 距离:超声波 1 秒一次
2. 判定链路
- 温度高于参数
Para_Tep→Tep_Flag=1 - 距离低于参数
Para_Ds→Close_Flag=1 - 连续两次距离差判断运动状态:
<5:静止<10:徘徊>=10:跑动
且状态变化后锁定 3 秒。
3. 输出链路
- L1~L4:根据光照等级显示,但前提是“接近”成立
- L8:根据运动状态显示
- 继电器:
接近 && 高温时吸合。
4. 交互链路
- S4:四个主界面循环切换
- S5:参数界面两个子界面切换
- S8/S9:加减参数
- S8+S9:统计界面长按 2 秒清零。
二、我建议你以后复习时,优先盯这 8 个大坑
1)Distance_old 忘记更新
这是运动状态判断最经典的坑。
错误本质
运动状态要靠:
abs(Distance - Distance_old)
如果 Distance_old 不更新,那么后面每次比较的都是旧旧旧数据,状态判断会越来越偏。
正确思路
每次完成一次有效距离采样后,都要把当前值存成“上一帧”:
Distance_old = Distance;
你这里的重点
你已经发现这一点了,这个必须记死。你原始笔记里也特别提到了这个错误。
2)第一次进入距离判定时,必须用 F_Check 做首帧保护
为什么要这样做
第一次采样时,没有“上一帧距离”可比。
所以第一次不能直接做:
abs(Distance - Distance_old)
否则是拿垃圾旧值在比较。
正确做法
第一次只做初始化:
if(F_Check==1)
{
Distance_old=Distance;
sport_state=1;
sport_state_old=1;
F_Check=0;
}
你这里的易错点
你自己已经指出:F_Check=0 原本忘了。
这个一忘,程序会永远反复走“首次初始化分支”,后面根本进不了正常判定流程。
3)距离采样节拍不要写错,特别是比较符号
题目要求是设备间隔 1 秒采集一次距离数据。
你这里写的是:
if(Ds_Slow_Down<=999)return;
Ds_Slow_Down=0;
这个写法的理解
这是“没到 1000ms 就返回,到点了才执行一次”。
易错点
很多人会乱改成:
if(Ds_Slow_Down>=999)return;
这会导致逻辑完全反掉,测距函数可能一直不执行。
记忆法
你要养成一句话:
慢任务函数里,前面这个 if 是“没到时间就退出”。
你原始笔记里已经专门提醒“不要写成 >=”。这个提醒很对。
4)Ds_Slow_Down 的类型不能太小
这是隐藏很深但很致命的错误。
如果 Ds_Slow_Down 定义成 u8,它最大只能到 255,永远到不了 999,那么:
if(Ds_Slow_Down<=999)return;
就会一直 return,Get_Distance() 根本进不去。
你原始记录里已经抓到了这一点:Ds_Slow_Down 类型 u8,导致进不去中断/逻辑。
结论
凡是计到几百、几千毫秒的计数器,优先想:
unsigned int- 或更大的类型
不要顺手全写 u8。
5)AD 通道别读错:0x01 是光敏,0x03 是滑动
你这里:
AD_1_Data_100x=(float)AD_Read(0x01)/51.0f*100;
你笔记里强调得很对:
0x01:光敏0x03:滑动。
为什么这是高频坑
因为比赛里 AD 芯片常常一题多个通道,很多人逻辑写对了,结果读错通道,表现就全乱。
复习时这样记
不是背 0x01、0x03,而是背:
“先看原理图/题目接线,再看 AD 通道。”
6)参数界面里的按键逻辑,必须限制作用域
题目规定:
- S5 切换参数子界面
- S8/S9 调参数
这些都只在参数界面有效。
所以你这里这个判断非常关键:
if(Seg_Show_Mode==2)
{
...
}
易错点 1
如果不加这个限制,那么你在别的界面按 S5/S8/S9,也会偷偷改参数。
易错点 2
你还特别提醒了:
Para_Mode要在Seg_Show_Mode==2限制下修改,不然会被误修改。
这个非常对。
7)参数数值单位要想清楚:显示值和存储值别混
你自己这个提醒也很好:
Para_Tep在数码管显示的值到底是 ×10 还是 ×100,要特别小心。
这类题最怕什么
最怕“显示单位”和“内部比较单位”不统一。
比如你代码里:
Temperature_10x = rd_temperture() * 10;
Para_Tep = 300;
这说明:
- 温度内部比较单位是 0.1℃
300实际表示 30.0℃
但题目显示的是整数温度参数 20~80℃。
所以复习时要强制问自己 3 个问题
- 这个变量是“真实值”还是“放大后的值”?
- 比较的时候单位统一了吗?
- 显示的时候有没有做对应换算?
8)继电器逻辑不是“写了变量就算完”,必须真的调用输出函数
你这里这一段很关键:
Relay_Flag=(Close_Flag==1&&Tep_Flag==1);
...
Relay(Relay_Flag);
你在笔记里提到:
Relay()从未调用,以及这里的状态机,按键是事件触发不是状态触发。
这个坑的本质
很多人以为:
Relay_Flag = 1;
就等于继电器吸合了。
其实不是。
这只是“软件里的逻辑状态变了”,真正硬件动作还得靠:
Relay(Relay_Flag);
一句口诀
判定是判定,输出是输出。变量变化 ≠ 硬件动作。
三、你这题里一个特别容易忽略的硬件坑:P2 操作影响 I2C
这是你笔记里最有价值的一条之一。
你对比发现:
你的 Seg.c 某处写成了:
temp = P2 | 0x1f;
正确参考应是:
temp = P2 & 0x1f;
temp = temp | 0xc0;
P2 = temp;
为什么错得这么严重
因为 P2 | 0x1f 会把低 5 位全置 1。
而低位里又可能挂了 I2C 相关线,导致数码管扫描时不停干扰 I2C 通信,最终表现成:
- AD 读数乱跳
- PCF8591 数据异常
- 看起来像“采样不稳”,但根因其实是端口操作污染了总线。
这类问题怎么记
以后只要遇到:
- 数码管扫描异常
- I2C 读数乱跳
- 明明逻辑没问题但数据抖得离谱
就先检查:
- 端口复用
- 位操作是否写错了
|/& - 定时器中断里有没有反复改共享口线
这个经验非常值钱。
四、按键部分,你要牢牢记一句话
按键是“事件触发”,不是“状态触发”。
你自己已经意识到这一点了,这说明你思路在往对的方向走。
什么叫事件触发
例如:
Key_Down = Key_Val & (Key_Old ^ Key_Val);
这个代表“刚按下那一下”。
为什么不能直接用 Key_Val
因为 Key_Val 表示“当前正在按着”,如果拿它直接改界面、加参数,就会出现:
- 一按一下跳很多次
- 长按疯狂连发
- 状态错乱
所以考场上要这样分
Key_Down:适合切界面、加减一次参数Key_Up:适合松手触发的动作Key_Old==组合键值+ 计时:适合长按判定
五、你这题还要特别注意“参数界面锁定输出”
题目明确要求:
进入参数界面时,LED、继电器状态锁定(不可变化),退出参数界面后取消状态锁定。
你代码里是这样处理的:
if(Seg_Show_Mode==2) Para_Lock=1;
else Para_Lock=0;
然后在 Led_Proc()、AD_DA() 等地方去判断 Para_Lock。这条主思路是对的。
复习时别只看“能不能切界面”
还要看:
- 进入参数界面后,LED 是否保持原状
- 继电器是否保持原状
- 采集功能是否还在继续
- 退出参数界面后是否恢复实时控制
因为题目要求的是“锁定输出状态”,不是“停止整个系统”。
六、给你整理成一版“考场速查版”
1. 运动状态判断四连查
- 有没有每 1 秒采一次距离
Distance_old有没有更新- 首次采样有没有
F_Check - 状态变化后有没有锁定 3 秒。
2. 参数界面三连查
- S5/S8/S9 是否只在参数界面有效
- 进入参数界面默认是不是温度参数界面
- 参数范围有没有卡死在 20~80。
3. 继电器两连查
- 条件是不是
Close_Flag && Tep_Flag - 有没有真正调用
Relay()输出。
4. 数码管/I2C 一连查
P2位操作是不是写错成|0x1f,导致 I2C 被干扰。
5. 定时变量类型一连查
- 需要计到几百/几千 ms 的变量,别用
u8。
七、我帮你再压缩成“最终背诵版”
这题最容易死的点
- 距离旧值没更新
- 首次采样没做保护
- 1 秒采样节拍写反
Ds_Slow_Down类型太小- AD 通道读错
- 参数界面按键没加限制
- 温度参数单位混乱
- 继电器只改变量没真输出
P2写错影响 I2C- 把按键当状态触发,不是事件触发
八、给你一个更适合记在笔记本上的版本
本题一句话总纲
采集要准,判定要稳,输出要分离,按键要事件化,界面切换不能影响主功能。
本题调试顺序
先采集 → 再判定 → 再显示 → 再按键 → 最后查锁定与长按。
本题最值得反复提醒自己的三句话
1. 变量更新时机比公式本身更重要。
2. 输出函数没调用,前面全白写。
3. 端口操作错一位,外设全乱。