第十四届蓝桥杯单片机省赛刷题笔记

第十四届蓝桥杯单片机省赛(本科组)刷题笔记

题目:环境监测系统(温度 / 湿度 / 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%10wet%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 >= 2count >= 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,第十四届蓝桥杯省赛第一场(本科组)