第十四届蓝桥杯单片机省赛(本科组)刷题笔记
题目:环境监测系统(温度 / 湿度 / RTC / 光敏触发)
平台:IAP15F2K61S2,12MHz,矩阵键盘(KBD)模式,J13扩展IO模式
一、题目核心功能速览
| 模块 | 关键点 |
|---|---|
| 显示 | 时间界面 / 回显界面(3子界面)/ 参数界面 / 温湿度界面 |
| 采集触发 | 光敏:亮→暗触发,3秒内防重触发,切换到温湿度界面3秒后返回 |
| 湿度 | NE555→P34频率计数,200~2000Hz线性映射10%~90%,范围外无效 |
| 按键 | S4界面切换 / S5回显子界面 / S8加 / S9减+长按清除 |
| LED | L1时间/L2回显/L3温湿度 / L4温度报警闪烁 / L5湿度无效 / L6均升高 |
二、全部错误点与改错批注
Bug 1:data_flag 逻辑运算符写反,且有效/无效永远判断错误
原代码:
// ❌ && 条件永远不成立(一个数不可能同时 < 200 且 > 2000)
// ❌ 结果:data_flag 永远 = 1,wet 永远被计算,L5 永远亮
if((Freq<200) && (Freq>2000)) data_flag = 0;
else
{
data_flag = 1;
wet = (2*(Freq-200) + 450)/45;
}
修正后:
// ✅ || 才是"不在范围内"的正确判断
// ✅ data_flag=0 表示无效,data_flag=1 表示有效
if((Freq<200) || (Freq>2000)) data_flag = 0;
else
{
data_flag = 1;
wet = (2*(Freq-200) + 450)/45;
}
记忆点:有效范围是"200 ≤ Freq ≤ 2000",取反是"< 200 或 > 2000",用
||。
Bug 2:温度最大值显示格式错误(×10存储后取位方法不对)
原代码:
// ❌ temperature_max 存储的是 Temperature_10x(温度×10)
// ❌ 以28°C为例:temperature_max = 280
// ❌ 280 / 10 % 10 = 28 % 10 = 8(个位)→ Seg[2] 显示 8
// ❌ 280 % 10 = 0 → Seg[3] 显示 0
// ❌ 结果:显示"80",应显示"28"
Seg_Buf[2] = temperature_max / 10 % 10;
Seg_Buf[3] = temperature_max % 10;
修正后:
// ✅ 先整除10得到整数部分(28),再取十位/个位
// ✅ 280 / 100 % 10 = 2 → 十位 ✓
// ✅ 280 / 10 % 10 = 8 → 个位 ✓
Seg_Buf[2] = temperature_max / 100 % 10;
Seg_Buf[3] = temperature_max / 10 % 10;
记忆点:凡是"×10存储"的变量(如
Temperature_10x),显示整数部分时要多除一个10。
湿度(wet)是整数%,不需要额外处理:wet/10%10和wet%10即可。
Bug 3:L3 指示灯条件写成了参数界面
原代码:
// ❌ Seg_Show_Mod==2 是"参数界面",不是"温湿度界面"
// ❌ 温湿度界面由 flag2 控制,与 Seg_Show_Mod 无关
if(Seg_Show_Mod == 2) ucLed[2] = 1;
else ucLed[2] = 0;
修正后:
// ✅ flag2=1 才是温湿度界面(触发后3秒)
if(flag2) ucLed[2] = 1;
else ucLed[2] = 0;
记忆点:
Seg_Show_Mod只管 0(时间)/ 1(回显)/ 2(参数)三个主界面;
温湿度界面是"插入式"的临时界面,专用flag2控制,与主界面无关。
Bug 4:温湿度界面只持续 1 秒而非 3 秒,且第二次触发立即消失
原代码:
if(flag2)
{
time3000++;
// ❌ 1000ms = 1秒,应为 3000
if(time3000 == 1000) flag2 = 0;
// ❌ 未清零 time3000,第二次触发时从旧值继续计数
}
修正后:
if(flag2)
{
time3000++;
// ✅ 3000ms = 3秒
// ✅ 清零,保证下次触发从0开始
if(time3000 == 3000)
{
flag2 = 0;
time3000 = 0;
}
}
Bug 5:S9 长按检测用了按下沿,导致长按功能永远失效
原代码:
// ❌ Key_Down 仅在按下瞬间(1个10ms周期)为 9,之后变为 0
// ❌ 下一个周期进入 else → flag3=0,计时器 time2000 被立即清零
// ❌ flag4 永远不会被置1,清除功能永远不会执行
if(Key_Down == 9)
{
flag3 = 1;
if(flag4) { /* 清除数据 */ }
}else
{
flag3 = 0;
}
修正后:
// ✅ Key_Val 是持续状态,按住时一直为 9
// ✅ 松开时进入 else,此时 flag4=1 才执行清除(满足"松开"语义)
if(Key_Val == 9)
{
flag3 = 1;
}else
{
flag3 = 0;
if(flag4)
{
flag4 = 0;
count = 0;
temperature_max = wet_max = 0;
temperature_average = wet_average = 0;
sum_temperature = sum_wet = 0; // ← 累加器也要清零!
}
}
记忆点:
Key_Down:按下沿,只触发一次,适合短按。Key_Val:当前状态,持续有效,适合长按检测。
Bug 6:sum_temperature / sum_wet 有声明但从未累加,平均值永远错误
原代码:
// ❌ sum_temperature 从未 +=,始终为 0
// ❌ (0 + Temperature_10x) / count = Temperature_10x / count → 只有最新值/次数
temperature_average = (sum_temperature + Temperature_10x) / count;
wet_average = (sum_wet + wet) / count;
修正后:
// ✅ 先累加,再整体除以次数
sum_temperature += Temperature_10x;
sum_wet += wet;
temperature_average = sum_temperature / count;
wet_average = sum_wet * 10 / count; // ← wet 是整数%,×10后才能保留1位小数
记忆点:
temperature_average存的是 ×10 的值(因为Temperature_10x已经 ×10),
直接除以 count 结果单位不变,显示时/100%10、/10%10+','、%10可得 XX.X 格式。
wet_average需要先*10再除,才能保留一位小数。
Bug 7:flag2 期间 AD_DA 未退出,3 秒内仍可重复触发
原代码:
// Key_Proc 有 if(flag2) return,但 AD_DA 没有
// ❌ 3秒内光敏再次亮→暗,仍会触发 count++、统计更新等
void AD_DA()
{
AD_1_Data_10x = Ad_Read(0x41) * 10 / 51;
// ...
}
修正后:
void AD_DA()
{
// ✅ 题目明确"3秒内不可再重复触发"
if(flag2) return;
AD_1_Data_10x = Ad_Read(0x41) * 10 / 51;
// ...
}
Bug 8:湿度无效时统计数据(count、最大值、平均值)仍被更新
原代码:
if(AD_1_Data_10x < 50)
{
flag1 = 0;
flag2 = 1;
count++; // ❌ 无效湿度也计入触发次数
if(wet > wet_max) wet_max = wet; // ❌ 无效湿度也更新最大值
sum_wet += wet; // ❌ 无效湿度也计入平均
// ...
}
修正后:
if(AD_1_Data_10x < 50)
{
// 快照保存和界面切换对所有触发均执行
saved_temp = Temperature_10x;
saved_wet = wet;
saved_dataflag = data_flag;
flag1 = 0;
flag2 = 1;
// ✅ 只有湿度有效时才更新统计
// 赛题:"若采集到的湿度数据无效,温度、湿度、触发次数、
// 触发时间等数据不在回显界面统计和计算。"
if(data_flag)
{
count++;
if(wet > wet_max) wet_max = wet;
// ...
}
}
Bug 9:L5 由实时频率控制,而非由"触发时"结果控制
原代码:
// ❌ 放在 Led_Proc(每1ms执行),基于实时 data_flag
// ❌ 频率一变回有效范围,L5 立即灭,不等下次触发
void Led_Proc()
{
if(data_flag == 0) ucLed[4] = 1;
else ucLed[4] = 0;
}
修正后:
// ✅ 在 AD_DA 触发时设置,持续保持到下次触发
// 赛题:"采集到无效的湿度数据时L5点亮,直到下一次采集到有效数据时熄灭。"
void AD_DA()
{
if(AD_1_Data_10x < 50)
{
if(!data_flag) ucLed[4] = 1; // 此次触发湿度无效 → L5亮
else ucLed[4] = 0; // 此次触发湿度有效 → L5灭
// ...
}
}
// Led_Proc 中删除 ucLed[4] 相关代码
Bug 10:L6 比较的是历史最大值,而非上一次触发值
原代码:
// ❌ wet_max 是历史最大值(且已被本次更新),不是"上一次触发的值"
// ❌ 实际上这行比较永远为 false(wet 不可能 > 刚更新的 wet_max)
if((wet > wet_max) && (Temperature_10x > temperature_max)) ucLed[5] = 1;
修正后:
// ✅ 触发时先保存上次的值,再与本次比较,最后更新"上次值"
// AD_DA 触发块内:
if(count >= 1) // count++ 前,count>=1 意味着这是第2次及以后
{
if((wet > last_wet) && (Temperature_10x > last_temperature))
ucLed[5] = 1;
else
ucLed[5] = 0;
}
last_wet = wet; // 保存本次值供下次比较
last_temperature = Temperature_10x;
count++;
记忆点:L6 比较顺序很重要——先比较,再保存,再 count++。
若先保存再比较,last_wet就是本次值,与本次比较永远相等,逻辑失效。
Bug 11:L6 触发时机差一次(count >= 2 → count >= 1)
原代码:
// ❌ count++ 之前,count>=2 意味着已触发过2次(将变为3次)
// ❌ L6 从第3次触发才开始比较,题目要求第2次触发起就比较
if(count >= 2)
{
// ...L6 判断
}
count++;
修正后:
// ✅ count++ 之前,count>=1 意味着已触发过1次(将变为2次)
// ✅ 即从第2次触发(N=2)起开始比较,符合"触发次数N≥2"的要求
if(count >= 1)
{
// ...L6 判断
}
count++;
Bug 12:温湿度界面显示实时值,而非触发时刻的快照值
原代码:
// ❌ Temperature_10x 每 300ms 由 Get_Temperature 更新
// ❌ wet 每 1ms 由 Led_Proc 更新
// ❌ data_flag 每 1ms 由 Led_Proc 更新
// ❌ 3秒显示窗口内,数值会变化,不符合"显示本次采集到的数据"
Seg_Buf[3] = Temperature_10x / 100 % 10;
Seg_Buf[4] = Temperature_10x / 10 % 10;
if(!data_flag)
{
Seg_Buf[6] = 16; Seg_Buf[7] = 16;
}else
{
Seg_Buf[6] = wet / 10 % 10;
Seg_Buf[7] = wet % 10;
}
修正后:
// ✅ 在触发时(AD_DA 里)保存快照变量
// saved_temp = Temperature_10x;
// saved_wet = wet;
// saved_dataflag = data_flag;
// ✅ Seg_Proc 中使用快照,3秒内显示固定不变
Seg_Buf[3] = saved_temp / 100 % 10;
Seg_Buf[4] = saved_temp / 10 % 10;
if(!saved_dataflag)
{
Seg_Buf[6] = 16; Seg_Buf[7] = 16;
}else
{
Seg_Buf[6] = saved_wet / 10 % 10;
Seg_Buf[7] = saved_wet % 10;
}
Bug 13:时间回显显示实时 RTC,而非最近一次触发时刻的时间
原代码:
// ❌ ucRtc[0]/ucRtc[1] 每 100ms 更新,显示的是当前时间
// ❌ 赛题要求显示"最近一次触发数据采集功能的时间"
Seg_Buf[3] = (ucRtc[0]/10%10) ? (ucRtc[0]/10%10) : 0;
Seg_Buf[4] = ucRtc[0] % 10;
Seg_Buf[5] = 17;
Seg_Buf[6] = (ucRtc[1]/10%10) ? (ucRtc[1]/10%10) : 0;
Seg_Buf[7] = ucRtc[1] % 10;
修正后:
// ✅ 有效触发时保存快照:saved_hour = ucRtc[0]; saved_min = ucRtc[1];
// ✅ 显示时使用快照,固定为最近一次有效触发的时刻
Seg_Buf[3] = (saved_hour/10%10) ? (saved_hour/10%10) : 0;
Seg_Buf[4] = saved_hour % 10;
Seg_Buf[5] = 17;
Seg_Buf[6] = (saved_min/10%10) ? (saved_min/10%10) : 0;
Seg_Buf[7] = saved_min % 10;
记忆点:凡是需要"记录某一时刻的值"的场景,都需要在事件发生时保存快照变量,
而不能直接用持续更新的全局变量。
Bug 14:温湿度界面期间 L1/L2 未随 flag2 互斥熄灭
原代码:
// ❌ 假设在回显界面触发(Seg_Show_Mod=1),flag2=1 期间:
// ❌ L2 因 Seg_Show_Mod==1 继续亮,L3 因 flag2 也亮
// ❌ L1 和 L2 同理,结果是两个界面指示灯同时亮
if(Seg_Show_Mod == 0) ucLed[0] = 1;
else ucLed[0] = 0;
if(Seg_Show_Mod == 1) ucLed[1] = 1;
else ucLed[1] = 0;
if(flag2) ucLed[2] = 1;
else ucLed[2] = 0;
修正后:
// ✅ flag2=1(温湿度界面)时,L1/L2 强制熄灭,只有 L3 亮
// 赛题:"时间界面下L1亮,否则L1熄灭"——温湿度界面不是时间界面,L1灭
if(Seg_Show_Mod == 0 && !flag2) ucLed[0] = 1;
else ucLed[0] = 0;
if(Seg_Show_Mod == 1 && !flag2) ucLed[1] = 1;
else ucLed[1] = 0;
if(flag2) ucLed[2] = 1;
else ucLed[2] = 0;
三、易错点总结
| 类别 | 易错点 | 正确做法 |
|---|---|---|
| 运算符 | 范围外判断用 && |
用 || |
| ×10存储 | 显示整数部分直接 /10%10 |
多除一个10:/100%10 和 /10%10 |
| 按键 | 长按用 Key_Down(沿) |
长按用 Key_Val(持续状态) |
| 平均值 | 直接 (avg + new) / count |
先累加 sum +=,再 sum / count |
| 精度 | 整数 sum/count 丢失小数 |
整数湿度平均值要 sum*10/count |
| 快照 | 用实时变量显示触发时刻数据 | 触发时保存 saved_* 快照变量 |
| 比较顺序 | L6先保存再比较(比自己) | 先比较,再保存,再 count++ |
| count时机 | count>=2 before count++(第3次起) |
count>=1 before count++(第2次起) |
| 界面互斥 | flag2期间L1/L2继续亮 | 加 && !flag2 条件 |
| 事件归属 | L5放在 Led_Proc 实时更新 | L5只在 AD_DA 触发时更新 |
| 防重触发 | 只禁了按键,未禁 AD_DA | AD_DA 开头加 if(flag2) return |
| 清除完整性 | 清除数据只清显示变量 | sum_temperature / sum_wet 也要一起清零 |
四、最终正确的 AD_DA 触发逻辑(核心流程梳理)
void AD_DA()
{
if(flag2) return; // 3秒内防重触发
AD_1_Data_10x = Ad_Read(0x41) * 10 / 51; // 读光敏 ADC
if(AD_1_Data_10x >= 50) flag1 = 1; // 检测到"亮"
if(flag1 && AD_1_Data_10x < 50) // 亮→暗,触发
{
// Step1:保存快照(所有触发都保存,供温湿度界面显示)
saved_temp = Temperature_10x;
saved_wet = wet;
saved_dataflag = data_flag;
flag1 = 0;
flag2 = 1; // 切换到温湿度界面
// Step2:L5 在触发时设置(不是实时更新)
if(!data_flag) ucLed[4] = 1;
else ucLed[4] = 0;
// Step3:仅湿度有效时更新统计(赛题:无效时不计入统计)
if(data_flag)
{
saved_hour = ucRtc[0]; // 保存触发时刻
saved_min = ucRtc[1];
// L6:先比较(用上次值),再保存(更新上次值),再 count++
if(count >= 1)
{
if(wet > last_wet && Temperature_10x > last_temperature)
ucLed[5] = 1;
else
ucLed[5] = 0;
}
last_wet = wet;
last_temperature = Temperature_10x;
count++;
if(wet > wet_max) wet_max = wet;
if(Temperature_10x > temperature_max) temperature_max = Temperature_10x;
sum_temperature += Temperature_10x;
sum_wet += wet;
temperature_average = sum_temperature / count;
wet_average = sum_wet * 10 / count;
}
}
}
笔记整理于 2026-03-31,第十四届蓝桥杯省赛第一场(本科组)