十三届省赛第二次错误总结(附带满分代码)

蓝桥杯第十三届省赛第二批次 —— 代码错误总结与修复记录

题目:第十三届蓝桥杯单片机组省赛第二批次程序设计
平台:STC15F2K60S2 (CT107D开发板)
功能模块:电压测量(ADC) + 超声波测距 + DAC输出 + 参数设置 + LED指示
排查轮次:共2轮,第一轮代码审计查错6处,第二轮4T平台实测查错1处(引发14个测试失败)


## 错误总览

编号 严重度 文件:行号 错误类型 排查轮次 影响范围
BUG-1 致命 main.c:195-203 逻辑错误 第一轮 LED永不闪烁
BUG-2 致命 main.c:53 语法+逻辑双重错误 第一轮 上下限校验完全失效
BUG-3 致命 main.c:107-109 数值计算+显示逻辑 第一轮 测距界面无显示
BUG-4 警告 ultrasound.c:39 遗漏操作 第一轮 测距结果不稳定
BUG-5 警告 main.c:160 设计缺陷 第一轮 ADC显示与判断值可能不一致
BUG-6 提示 main.c:229 边界条件 第一轮 任务调度延迟1ms
BUG-7 致命 main.c:95,97 边界条件 第二轮 连锁引爆14个4T测试失败

第一轮:代码审计排查(6处)

BUG-1 [致命] Timer1中断LED闪烁逻辑 —— else分支毁掉整个计数器

位置main.c 原第195-203行(Timer1ISR中断服务函数)

错误代码

if (start_ultrasound){
    if (++time_100 == 100){
        time_100 = 0;
        led_light_flag ^= 1;
    }else{
        time_100 = 0;        // 致命!每个非100的tick都重置为0
        led_light_flag = 0;  // LED永远不会亮
    }
}

错误分析

else 分支在每一个 time_100 != 100 的中断周期都会执行,将 time_100 重置为0。
执行流程:

tick 1: time_100 = 1, 1 != 100 → else → time_100 = 0, flag = 0
tick 2: time_100 = 1, 1 != 100 → else → time_100 = 0, flag = 0
...(永远到不了100)

结果time_100 永远在 0→1→0→1 之间循环,LED永不闪烁。

修复代码

if (start_ultrasound){
    if (++time_100 >= 100){   // 改用 >= 更安全
        time_100 = 0;
        led_light_flag ^= 1;
    }
    // 删除 else 分支!计数中不应重置
}else{
    // else 应该在 start_ultrasound == 0 时执行
    time_100 = 0;
    led_light_flag = 0;
}

知识点

  • else 的层级归属要仔细核对,缩进可能骗人

  • 计时累加器中途不能随意重置,否则永远达不到目标值

  • >= 替代 == 更健壮,防止跳过精确值


BUG-2 [致命] 参数校验条件 —— 同变量比较 + 分号空语句,两个致命错误叠加

位置main.c 原第53行(S4按键退出参数设置界面时的校验)

错误代码

if (voltage_rb2_al_100x_max_10x_ctrl < voltage_rb2_al_100x_max_10x_ctrl);
//   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//   同一个变量!                          同一个变量!恒为false
//                                                                        ^
//                                                                    分号!if空语句
{
    voltage_rb2_al_100x_max_10x = voltage_rb2_al_100x_max_10x_ctrl;
    voltage_rb2_al_100x_min_10x = voltage_rb2_al_100x_min_10x_ctrl;
}

错误分析

这一行有两个致命错误叠加

错误 说明 后果
错误1:同变量比较 max_ctrl < max_ctrl 恒为 false 条件永远不成立
错误2:分号空语句 if(...); 分号使if体为空 大括号内代码无条件执行

两个错误"巧妙"抵消 —— 条件虽然恒false,但分号使if为空语句,大括号内代码无论如何都会执行
这意味着无论上下限是否合法(如min > max),参数都会被保存,完全绕过了校验机制。

修复代码

if (voltage_rb2_al_100x_min_10x_ctrl < voltage_rb2_al_100x_max_10x_ctrl)
//   ^^^min^^^                          ^^^max^^^ 正确:min < max 才保存
//   注意:没有分号!
{
    voltage_rb2_al_100x_max_10x = voltage_rb2_al_100x_max_10x_ctrl;
    voltage_rb2_al_100x_min_10x = voltage_rb2_al_100x_min_10x_ctrl;
}

知识点

  • 长变量名极易出现复制粘贴错误,对比时逐字检查

  • if(...); 是合法C语法(空语句),编译器不报错,是经典的隐蔽BUG

  • 校验条件的两个操作数必须是不同的变量(min vs max)


BUG-3 [致命] 测距数码管显示 —— 缺少%10 + 错误的零消隐逻辑

位置main.c 原第107-109行(case 1 测距界面显示)

错误代码

seg_buf[5] = (distance_val/100%10==0) ? 10 : distance_val/100%10;  // 百位:基本OK
seg_buf[6] = ((distance_val/10==0)&&(seg_buf[5]==10)) ? 10 : distance_val/10;
//            ^^^^^^^^^^^^^^^                                  ^^^^^^^^^^^^^^^
//            距离10时10/10=1≠0                                缺少 %10 !!!
seg_buf[7] = ((distance_val%10==0)&&(seg_buf[5]==10)) ? 10 : distance_val%10;
//            ^个位为0就消隐?大错特错!

错误分析

问题1:十位缺少 %10 取模

distance_val/10 对于距离 ≥ 100 时结果是两位数:

distance = 123 → distance_val/10 = 12 → 传给段码表索引12 → 显示'P'而不是'2'!

问题2:个位被错误消隐

当距离为整十(如30、50)或0时,个位被消隐为空白:

distance = 30:
  seg_buf[5] = (0==0)?10:0 = 10(空白)
  seg_buf[6] = ((3==0)&&true)?10:3 = 3 ✓
  seg_buf[7] = ((0==0)&&(10==10))?10:0 = 10(空白)← 应该显示0!
  显示结果: "L   3 "(而不是"L  30")

distance = 0:
  三位全部为10(空白) → 显示结果: "L     "(完全没有数字!)
  这就是用户反馈"测距界面没有距离显示"的直接原因!

修复代码

// 规范的前导零消隐:只消隐前导零,个位始终显示
seg_buf[5] = (distance_val >= 100) ? distance_val/100 : 10;     // 百位:<100时消隐
seg_buf[6] = (distance_val >= 10)  ? distance_val/10%10 : 10;   // 十位:<10时消隐,加%10
seg_buf[7] = distance_val % 10;                                   // 个位:始终显示

知识点

  • 多位数拆分公式:百位 = val/100十位 = val/10%10必须%10),个位 = val%10

  • 前导零消隐规则:从高位到低位,遇到第一个非零位后停止消隐,个位永远显示

  • 数码管显示空白(索引10)和显示数字0(索引0)是完全不同的!


BUG-4 [警告] 超声波PCA计数器未停止

位置ultrasound.c 原第39行

错误代码

CR = 1; // 开始计时
while ((US_RX == 1)&&(CF == 0));
// 这里缺少 CR = 0; !!!
if (CF == 0){
    time = CH<<8|CL;  // 此时PCA还在计数,CH/CL持续变化!

错误分析

while循环退出后PCA计数器仍在运行,CHCL寄存器持续递增。
读取CH<<8|CL时,如果在读取CH和CL之间PCA进位,会得到错误的组合值,导致测距跳变。

修复代码

while ((US_RX == 1)&&(CF == 0));
CR = 0;  // 立即停止PCA计数器,锁定CH/CL的值
if (CF == 0){
    time = CH<<8|CL;

知识点

  • PCA作为计时器使用时,测量完毕必须先停止(CR=0)再读值

  • 读16位寄存器时如果计数器仍在运行,高低字节可能不一致(竞态条件)


BUG-5 [警告] ADC重复读取导致数据不一致

位置main.c 原第160行(ad_da函数)

错误代码

void ad_da(){
    unsigned char temp_ad = Ad_Read(0x43);  // 第1次读:用于阈值判断
    // ...中间省略...
    voltage_rb2_al_100x = Ad_Read(0x43)*100/51;  // 第2次读:用于显示
}

错误分析

PCF8591的特性:每次读取返回的是上一次转换的结果。两次读取返回不同时刻的值:

  • temp_ad:上上次的转换结果

  • voltage_rb2_al_100x:上次的转换结果

虽然电压变化缓慢时差异不大,但这属于设计缺陷。

修复代码

void ad_da(){
    unsigned char temp_ad = Ad_Read(0x43);  // 只读一次
    voltage_rb2_al_100x = (unsigned int)temp_ad*100/51;  // 用同一个值计算显示
    start_ultrasound = (...);  // 用同一个值判断阈值
}

知识点

  • PCF8591返回前一次转换结果,当次转换在读操作时启动

  • 需要同一批判断的数据应使用同一个ADC采样值,避免数据不一致


BUG-6 [提示] 调度器时间比较用 > 而非 >=

位置main.c 原第229行

错误代码

if (nowtime > schedluer_task[i].rate_t + schedluer_task[i].last_t){

修复代码

if (nowtime >= schedluer_task[i].rate_t + schedluer_task[i].last_t){

影响:每个任务的首次执行会延迟1ms,影响不大但不规范。


第二轮:4T平台实测排查(1处,连锁引发14个测试失败)

BUG-7 [致命] S7减小按键回绕边界错误 —— 一个字符引爆14个测试

位置main.c 原第95、97行(S7减小按键处理)

错误代码

if (key_down == 7){
    if (setting_mode == 0){
        voltage_rb2_al_100x_max_10x_ctrl =
            (voltage_rb2_al_100x_max_10x_ctrl == 0) ? 50 : ...ctrl-5;
        //                                    ^^ 错!合法最小值是5(0.5V),不是0
    }else{
        voltage_rb2_al_100x_min_10x_ctrl =
            (voltage_rb2_al_100x_min_10x_ctrl == 0) ? 50 : ...ctrl-5;
        //                                    ^^ 同样的错误
    }
}

错误分析

参数的合法范围是 5~50(对应 0.5V~5.0V),步长为5。

S6加大的回绕是正确的:== 50 时回绕到 5

S6加大: 5 → 10 → 15 → ... → 45 → 50 → 5(回绕)  ✓

S7减小的回绕用了 == 0 而不是 <= 5

S7减小: 50 → 45 → ... → 10 → 5 → 0(非法值!)→ 50(下一次才回绕)
                                    ↑
                            这里产生了非法的 0.0V !

连锁故障推演

步骤1: 用户按S7减小,参数从0.5V(5)减到0.0V(0)
        ↓ 产生非法中间值0.0V
步骤2: 用户按S4退出参数界面,触发校验 min_ctrl < max_ctrl
        ↓ 若 max_ctrl=0,则 min(如25) < 0 为 false(unsigned比较)
步骤3: 校验失败,参数保存被阻止!
        ↓ 阈值停留在旧值/默认值 (max=4.5V, min=0.5V)
步骤4: start_ultrasound 基于错误的阈值做判断
        ├→ 本应关闭超声波的场景被误开启(电压在错误范围内)
        │   → 测试47: 期望L AAA, 实际L 13
        │   → 测试52: 期望DAC=0, 实际DAC=1.0
        │   → 测试48: L8不该亮却在闪烁
        │
        ├→ 本应开启超声波的场景被误关闭(电压在正确范围外)
        │   → 测试57/60/62: 期望L 65/L 15, 实际L AAA
        │   → 测试58/63: 期望DAC=4.0/1.0, 实际DAC=0
        │   → 测试61: L8该闪却不闪,周期error
        │
        └→ 参数界面显示错误的值(加载了旧的/默认的阈值)
            → 测试49/50: 期望P 4.5 2.5, 实际P 4.0 0.5
            → 测试64: 期望P 3.5 2.5, 实际P 3.0 0.5
            → 测试65: 期望P 3.5 1.5, 实际P 3.0 5.0

4T平台失败测试汇总(14个)

测试# 操作描述 期望结果 实际结果 故障类型
45 "减"上限参数 P 5.0 2.5 P 0.0 2.5 直接触发
47 切换到测距界面 L AAA L 13 阈值错误→超声误开
48 LED指示状态 L2亮,其余灭 L2灭/L8亮 超声误开→L8闪烁
49 切换参数界面,"减"上限 P 4.5 2.5 P 4.0 0.5 保存失败→加载旧值
50 切换参数界面,"减"上限 P 4.5 2.5 P 4.0 0.5 保存失败→加载旧值
52 超声未启动,检测DAC DAC≈0 DAC=1.0 超声误开→DAC输出1V
57 切换测距界面,距离65cm L 65 L AAA 阈值错误→超声误关
58 超声启动,检测DAC DAC≈4.0 DAC=0.0 超声误关→DAC输出0V
60 切换测距界面,距离65cm L 65 L AAA 阈值错误→超声误关
61 L8以0.1s闪烁 闪烁正确 周期error 超声误关→不闪烁
62 切换测距界面,距离15cm L 15 L AAA 阈值错误→超声误关
63 超声启动,检测DAC DAC≈1.0 DAC=0.0 超声误关→DAC输出0V
64 切换参数界面,"减"下限 P 3.5 2.5 P 3.0 0.5 保存失败→加载旧值
65 "减"下限参数 P 3.5 1.5 P 3.0 5.0 0→50回绕导致跳变

修复代码

if (key_down == 7){
    if (setting_mode == 0){
        voltage_rb2_al_100x_max_10x_ctrl =
            (voltage_rb2_al_100x_max_10x_ctrl <= 5) ? 50 : ...ctrl-5;
        //                                   ^^^^ 修复:<= 5 时回绕到50
    }else{
        voltage_rb2_al_100x_min_10x_ctrl =
            (voltage_rb2_al_100x_min_10x_ctrl <= 5) ? 50 : ...ctrl-5;
        //                                   ^^^^
    }
}

修复后值循环:

S7减小: 50 → 45 → 40 → 35 → 30 → 25 → 20 → 15 → 10 → 5 → 50(直接回绕,无非法值)

知识点

  • 循环边界值必须与加大/减小双向对称验证:S6从50→5,S7就必须从5→50

  • unsigned char 减法下溢极其危险:(unsigned char)0 - 5 = 251,不会报错

  • 一个边界错误可以通过连锁反应影响整个系统——这是嵌入式最可怕的地方

  • <= 5 而非 == 5,可以额外防御 ctrl 被意外设为 0~4 时的下溢问题


代码优化记录

除了BUG修复外,还做了以下优化:

# 维度 位置 改动说明
1 性能 ultrasound.c time*0.017 浮点运算 → (unsigned long)time*17/1000 整数运算,8051无FPU快数十倍
2 性能 main.c ad_da() float temp_daunsigned int,DAC计算公式改为纯整数运算
3 可读 main.c 变量区 变量分组归类,添加完整中文注释
4 可读 main.c 定义 #define SEG_DOT ',' 宏替代所有魔法字符
5 可读 seg.c 段码表拆分多行并标注索引含义(10=灭,11=U,12=P,13=L,14=A)
6 健壮 main.c uwtick 建议添加 volatile 修饰,防止编译器优化掉中断写入
7 健壮 main.c get_distance() 过滤超声波返回0的无效值,不更新显示

经验教训总结

1. 最危险的BUG往往最隐蔽

排名 BUG 隐蔽原因 影响
1 if(a < a); 编译器不报错,两个错误互相抵消 校验失效
2 == 0 vs <= 5 差两个字符,逻辑看似合理 14个测试全挂
3 else{time_100=0;} 缩进看起来属于外层,实际属于内层if LED永不闪

2. 嵌入式开发三大黄金法则

法则一:边界必须双向验证
  加大从50回绕到5 → 减小就必须从5回绕到50,不能从0回绕

法则二:if语句三检查
  ✓ 条件表达式中的变量是否正确(不是同一个变量)
  ✓ 条件后面有没有多余的分号
  ✓ else 归属是否正确(属于哪一层if)

法则三:连锁影响要推演
  改了一个变量 → 哪些函数读这个变量 → 那些函数的输出又影响了谁
  参数保存失败 → 阈值错误 → start_ultrasound错误 → 距离/DAC/LED全错

3. 多位数拆分显示模板(背下来!)

// 三位数显示模板(前导零消隐,个位始终显示)
seg_buf[pos]   = (val >= 100) ? val/100     : SEG_OFF;  // 百位
seg_buf[pos+1] = (val >= 10)  ? val/10 % 10 : SEG_OFF;  // 十位(必须%10!)
seg_buf[pos+2] = val % 10;                               // 个位(永远显示)

// 带小数点的电压显示模板(如3.30V,voltage_100x = 330)
seg_buf[pos]   = voltage_100x / 100 % 10 + SEG_DOT;  // 整数位+小数点
seg_buf[pos+1] = voltage_100x / 10 % 10;              // 十分位
seg_buf[pos+2] = voltage_100x % 10;                   // 百分位

4. 参数循环设置模板(背下来!)

// 合法范围: [MIN_VAL, MAX_VAL],步长 STEP
#define MIN_VAL 5    // 0.5V
#define MAX_VAL 50   // 5.0V
#define STEP    5    // 0.5V

// S6 加大
ctrl = (ctrl >= MAX_VAL) ? MIN_VAL : ctrl + STEP;

// S7 减小
ctrl = (ctrl <= MIN_VAL) ? MAX_VAL : ctrl - STEP;
//          ^^ 用 <= 而非 ==,防御异常值

满分代码(仅main)

/* 头文件包含 */

#include <STC15F2K60S2.H>//单片机寄存器专用头文件

// [优化:⚡性能] 移除未使用的 stdio.h/string.h,减少编译体积;intrins.h 此文件未使用也移除

// 如需 printf 调试可取消注释:

// #include "stdio.h"/* 头文件包含 */
#include <STC15F2K60S2.H>//单片机寄存器专用头文件
// [优化:⚡性能] 移除未使用的 stdio.h/string.h,减少编译体积;intrins.h 此文件未使用也移除
// 如需 printf 调试可取消注释:
// #include "stdio.h"
// #include "string.h"
#include "intrins.h"
#include "init.h"//初始化底层驱动专用头文件
#include "seg.h"//数码管底层驱动专用头文件
#include "led.h"//Led底层驱动专用头文件
#include "key.h"//按键底层驱动专用头文件

// [优化:⚡性能] ds1302/onewire/uart/filtering 本题未使用,保留头文件但注释提示
#include "ds1302.h"//ds1302底层驱动专用头文件(本题未使用)
#include "onewire.h"//ds18b20底层驱动专用头文件(本题未使用)
#include "ultrasound.h"//超声波底层驱动专用头文件
#include "iic.h"//iic底层驱动专用头文件
#include "uart.h"//串口底层驱动专用头文件
#include "filtering.h"//滤波器底层驱动专用头文件

// [优化:📖可读] 变量分组并添加清晰注释

// [优化:📖可读] 小数点编码偏移量,替代魔法字符','(ASCII 44)
// 用法: seg_buf[x] = digit + SEG_DOT 表示该位带小数点
#define SEG_DOT ','

/* ===== 系统变量 ===== */
// [优化:🛡️健壮] 中断与主程序共享的变量应使用volatile,防止编译器优化掉对其的读取
idata unsigned long int uwtick;                 // 系统节拍计数器(ms)

/* ===== 按键变量 ===== */
idata unsigned char key_val,key_old,key_up,key_down;

/* ===== 显示变量 ===== */
pdata unsigned char ucled[8] = {0,0,0,0,0,0,0,0};       // LED状态数组
pdata unsigned char seg_buf[8] = {10,10,10,10,10,10,10,10}; // 数码管缓冲(10=熄灭)
idata unsigned char seg_pos = 0;                         // 当前扫描位

/* ===== 界面与模式 ===== */
idata unsigned char seg_show_mode = 0;                   // 0:电压 1:测距 2:参数设置
idata bit setting_mode = 0;                              // 参数设置子模式 0:上限 1:下限

/* ===== 电压相关 ===== */
idata unsigned int voltage_rb2_al_100x;                  // RB2电压值×100 (如330=3.30V)
idata unsigned char voltage_rb2_al_100x_max_10x = 45;   // 电压上限×10 (45=4.5V) 生效值
data unsigned char voltage_rb2_al_100x_min_10x = 5;     // 电压下限×10 (5=0.5V)  生效值
idata unsigned char voltage_rb2_al_100x_max_10x_ctrl = 0; // 电压上限×10 编辑中临时值
idata unsigned char voltage_rb2_al_100x_min_10x_ctrl = 0; // 电压下限×10 编辑中临时值

/* ===== 超声波测距 ===== */
idata unsigned char distance_val = 0;                    // 测距结果(cm)
idata bit start_ultrasound = 0;                          // 超声波使能标志(电压在阈值范围内)

/* ===== LED闪烁控制 ===== */
idata bit led_light_flag = 0;                            // L8闪烁状态
idata unsigned char time_100 = 0;                        // 100ms闪烁计时器

//key
void key_proc()
{
    key_val = Key_Read();
    key_down = key_val&(key_val^key_old);
    key_up =~key_val&(key_val^key_old);
    key_old = key_val;

  if (key_down == 4){
    if (seg_show_mode == 1){
      voltage_rb2_al_100x_max_10x_ctrl = voltage_rb2_al_100x_max_10x;
      voltage_rb2_al_100x_min_10x_ctrl = voltage_rb2_al_100x_min_10x;
    }

    if (seg_show_mode == 2){
      if (voltage_rb2_al_100x_min_10x_ctrl < voltage_rb2_al_100x_max_10x_ctrl)
      {
        voltage_rb2_al_100x_max_10x = voltage_rb2_al_100x_max_10x_ctrl;
        voltage_rb2_al_100x_min_10x = voltage_rb2_al_100x_min_10x_ctrl;
      }
    }
      seg_show_mode = (++seg_show_mode)%3;
  }

   if (seg_show_mode == 2){
    if (key_down == 5){
      setting_mode ^=1;
    }
    if (key_down == 6){
      if (setting_mode == 0){
        voltage_rb2_al_100x_max_10x_ctrl = (voltage_rb2_al_100x_max_10x_ctrl == 50)? 5 :voltage_rb2_al_100x_max_10x_ctrl+5;
      }else{
        voltage_rb2_al_100x_min_10x_ctrl = (voltage_rb2_al_100x_min_10x_ctrl == 50)? 5 :voltage_rb2_al_100x_min_10x_ctrl+5;
      }
    }
    if (key_down == 7){
      if (setting_mode == 0){
        voltage_rb2_al_100x_max_10x_ctrl = (voltage_rb2_al_100x_max_10x_ctrl <= 5)? 50 :voltage_rb2_al_100x_max_10x_ctrl-5;
      }else{
        voltage_rb2_al_100x_min_10x_ctrl = (voltage_rb2_al_100x_min_10x_ctrl <= 5)? 50 :voltage_rb2_al_100x_min_10x_ctrl-5;
      }
    }
  }

}


void seg_proc()
{
  switch(seg_show_mode){
    case 0:
    seg_buf[0] = 11;
    seg_buf[1] = 10;
    seg_buf[2] = 10;
    seg_buf[3] = 10;
    seg_buf[4] = 10;
    seg_buf[5] = voltage_rb2_al_100x/100%10 + SEG_DOT;
    seg_buf[6] = voltage_rb2_al_100x/10%10;
    seg_buf[7] = voltage_rb2_al_100x%10;
    break;

    case 1:
    seg_buf[0] = 13;
    seg_buf[1] = 10;
    seg_buf[2] = 10;
    seg_buf[3] = 10;
    seg_buf[4] = 10;
    if (start_ultrasound == 1)
    {
    seg_buf[5] = (distance_val >= 100) ? distance_val/100 : 10;
    seg_buf[6] = (distance_val >= 10) ? distance_val/10%10 : 10;
    seg_buf[7] = distance_val%10;
    }else{
      seg_buf[5] =14;
      seg_buf[6] =14;
      seg_buf[7] =14;
    }
    break;


    case 2:
    seg_buf[0] = 12;
    seg_buf[1] = 10;
    seg_buf[2] = 10;
    seg_buf[3] = voltage_rb2_al_100x_max_10x_ctrl/10 + SEG_DOT;
    seg_buf[4] = voltage_rb2_al_100x_max_10x_ctrl%10;
    seg_buf[5] = 10;
    seg_buf[6] = voltage_rb2_al_100x_min_10x_ctrl/10 + SEG_DOT;
    seg_buf[7] = voltage_rb2_al_100x_min_10x_ctrl%10;
    break;
  }
}

void led_proc()
{
   
    ucled[0] = (seg_show_mode==0);
    ucled[1] = (seg_show_mode==1);
    ucled[2] = (seg_show_mode==2);
    ucled[7] = led_light_flag;

     Led_Disp(ucled);
}

void ad_da(){
  unsigned char temp_ad = Ad_Read(0x43);
  unsigned int temp_da; // [优化:⚡性能] unsigned int替代float,避免8051浮点模拟开销
  voltage_rb2_al_100x = (unsigned int)temp_ad*100/51;
  start_ultrasound = ((temp_ad*10 < voltage_rb2_al_100x_max_10x*51) && (temp_ad*10 > voltage_rb2_al_100x_min_10x*51));
  if (start_ultrasound){
    if (distance_val <=20){
      Da_Write(51);
    }else if(distance_val >=80){
      Da_Write(255);
    }else{
      // [优化:⚡性能] 整数运算替代浮点: DA = (4*(d-20)/60+1)*51 = (204*(d-20)/60+51)
      temp_da = (unsigned int)204*(distance_val - 20)/60 + 51; // [优化:🛡️健壮] unsigned int防止中间值溢出
      Da_Write((unsigned char)temp_da);
    }
  }
  else{
    Da_Write(0);
  }
}
  


void get_distance()
{
  if (start_ultrasound){
    unsigned char temp = Ut_Wave_Data();
    // [优化:🛡️健壮] 过滤无效测距值:0表示超时/失败,不更新显示值
    if (temp > 0){
      distance_val = temp;
    }
  }
}

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x18;				//设置定时初始值
	TH1 = 0xFC;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
  ET1 = 1;
  EA = 1;
}

void Timer1ISR() interrupt 3
{
  uwtick++;

  seg_pos = (++seg_pos)%8;
  if (seg_buf[seg_pos] > 20){
    Seg_Disp(seg_pos,seg_buf[seg_pos]-SEG_DOT,1);
  }else{
    Seg_Disp(seg_pos,seg_buf[seg_pos],0);
  }

  if (start_ultrasound){
    if (++time_100 >= 100){
      time_100 = 0 ;
      led_light_flag ^= 1;
    }
  }else{
    time_100 = 0 ;
    led_light_flag = 0;
  }
}

typedef struct {
    void (*task_func)(void);
    unsigned long int rate_t;
    unsigned long int last_t;
}task_t;

idata task_t schedluer_task[] = {
    {led_proc,1,0},
    {key_proc,10,0},
    {seg_proc,150,0},
    {ad_da,160,0},
    {get_distance,100,0},
};

idata unsigned char task_num;
void schedluer_init(){
  task_num = sizeof(schedluer_task)/sizeof(task_t);
}

void schedluer_run(){
  unsigned char i;
  for (i = 0 ;i < task_num;i++){
    unsigned long int nowtime = uwtick; 
    if (nowtime >= schedluer_task[i].rate_t + schedluer_task[i].last_t){
      schedluer_task[i].last_t = nowtime;
      schedluer_task[i].task_func();
    }
  }
}



void main (){
  System_Init();
  schedluer_init();
  Timer1_Init();
  while(1)
  {
    schedluer_run();
  }
}

// #include "string.h"

#include "intrins.h"

#include "init.h"//初始化底层驱动专用头文件

#include "seg.h"//数码管底层驱动专用头文件

#include "led.h"//Led底层驱动专用头文件

#include "key.h"//按键底层驱动专用头文件

  

// [优化:⚡性能] ds1302/onewire/uart/filtering 本题未使用,保留头文件但注释提示

#include "ds1302.h"//ds1302底层驱动专用头文件(本题未使用)

#include "onewire.h"//ds18b20底层驱动专用头文件(本题未使用)

#include "ultrasound.h"//超声波底层驱动专用头文件

#include "iic.h"//iic底层驱动专用头文件

#include "uart.h"//串口底层驱动专用头文件

#include "filtering.h"//滤波器底层驱动专用头文件

  

// [优化:📖可读] 变量分组并添加清晰注释

  

// [优化:📖可读] 小数点编码偏移量,替代魔法字符','(ASCII 44)

// 用法: seg_buf[x] = digit + SEG_DOT 表示该位带小数点

#define SEG_DOT ','

  

/* ===== 系统变量 ===== */

// [优化:🛡️健壮] 中断与主程序共享的变量应使用volatile,防止编译器优化掉对其的读取

idata unsigned long int uwtick;                 // 系统节拍计数器(ms)

  

/* ===== 按键变量 ===== */

idata unsigned char key_val,key_old,key_up,key_down;

  

/* ===== 显示变量 ===== */

pdata unsigned char ucled[8] = {0,0,0,0,0,0,0,0};       // LED状态数组

pdata unsigned char seg_buf[8] = {10,10,10,10,10,10,10,10}; // 数码管缓冲(10=熄灭)

idata unsigned char seg_pos = 0;                         // 当前扫描位

  

/* ===== 界面与模式 ===== */

idata unsigned char seg_show_mode = 0;                   // 0:电压 1:测距 2:参数设置

idata bit setting_mode = 0;                              // 参数设置子模式 0:上限 1:下限

  

/* ===== 电压相关 ===== */

idata unsigned int voltage_rb2_al_100x;                  // RB2电压值×100 (如330=3.30V)

idata unsigned char voltage_rb2_al_100x_max_10x = 45;   // 电压上限×10 (45=4.5V) 生效值

data unsigned char voltage_rb2_al_100x_min_10x = 5;     // 电压下限×10 (5=0.5V)  生效值

idata unsigned char voltage_rb2_al_100x_max_10x_ctrl = 0; // 电压上限×10 编辑中临时值

idata unsigned char voltage_rb2_al_100x_min_10x_ctrl = 0; // 电压下限×10 编辑中临时值

  

/* ===== 超声波测距 ===== */

idata unsigned char distance_val = 0;                    // 测距结果(cm)

idata bit start_ultrasound = 0;                          // 超声波使能标志(电压在阈值范围内)

  

/* ===== LED闪烁控制 ===== */

idata bit led_light_flag = 0;                            // L8闪烁状态

idata unsigned char time_100 = 0;                        // 100ms闪烁计时器

  

//key

void key_proc()

{

    key_val = Key_Read();

    key_down = key_val&(key_val^key_old);

    key_up =~key_val&(key_val^key_old);

    key_old = key_val;

  

  if (key_down == 4){

    if (seg_show_mode == 1){

      voltage_rb2_al_100x_max_10x_ctrl = voltage_rb2_al_100x_max_10x;

      voltage_rb2_al_100x_min_10x_ctrl = voltage_rb2_al_100x_min_10x;

    }

  

    if (seg_show_mode == 2){

      if (voltage_rb2_al_100x_min_10x_ctrl < voltage_rb2_al_100x_max_10x_ctrl)

      {

        voltage_rb2_al_100x_max_10x = voltage_rb2_al_100x_max_10x_ctrl;

        voltage_rb2_al_100x_min_10x = voltage_rb2_al_100x_min_10x_ctrl;

      }

    }

      seg_show_mode = (++seg_show_mode)%3;

  }

  

   if (seg_show_mode == 2){

    if (key_down == 5){

      setting_mode ^=1;

    }

    if (key_down == 6){

      if (setting_mode == 0){

        voltage_rb2_al_100x_max_10x_ctrl = (voltage_rb2_al_100x_max_10x_ctrl == 50)? 5 :voltage_rb2_al_100x_max_10x_ctrl+5;

      }else{

        voltage_rb2_al_100x_min_10x_ctrl = (voltage_rb2_al_100x_min_10x_ctrl == 50)? 5 :voltage_rb2_al_100x_min_10x_ctrl+5;

      }

    }

    if (key_down == 7){

      if (setting_mode == 0){

        voltage_rb2_al_100x_max_10x_ctrl = (voltage_rb2_al_100x_max_10x_ctrl <= 5)? 50 :voltage_rb2_al_100x_max_10x_ctrl-5;

      }else{

        voltage_rb2_al_100x_min_10x_ctrl = (voltage_rb2_al_100x_min_10x_ctrl <= 5)? 50 :voltage_rb2_al_100x_min_10x_ctrl-5;

      }

    }

  }

  

}

  
  

void seg_proc()

{

  switch(seg_show_mode){

    case 0:

    seg_buf[0] = 11;

    seg_buf[1] = 10;

    seg_buf[2] = 10;

    seg_buf[3] = 10;

    seg_buf[4] = 10;

    seg_buf[5] = voltage_rb2_al_100x/100%10 + SEG_DOT;

    seg_buf[6] = voltage_rb2_al_100x/10%10;

    seg_buf[7] = voltage_rb2_al_100x%10;

    break;

  

    case 1:

    seg_buf[0] = 13;

    seg_buf[1] = 10;

    seg_buf[2] = 10;

    seg_buf[3] = 10;

    seg_buf[4] = 10;

    if (start_ultrasound == 1)

    {

    seg_buf[5] = (distance_val >= 100) ? distance_val/100 : 10;

    seg_buf[6] = (distance_val >= 10) ? distance_val/10%10 : 10;

    seg_buf[7] = distance_val%10;

    }else{

      seg_buf[5] =14;

      seg_buf[6] =14;

      seg_buf[7] =14;

    }

    break;

  
  

    case 2:

    seg_buf[0] = 12;

    seg_buf[1] = 10;

    seg_buf[2] = 10;

    seg_buf[3] = voltage_rb2_al_100x_max_10x_ctrl/10 + SEG_DOT;

    seg_buf[4] = voltage_rb2_al_100x_max_10x_ctrl%10;

    seg_buf[5] = 10;

    seg_buf[6] = voltage_rb2_al_100x_min_10x_ctrl/10 + SEG_DOT;

    seg_buf[7] = voltage_rb2_al_100x_min_10x_ctrl%10;

    break;

  }

}

  

void led_proc()

{

    ucled[0] = (seg_show_mode==0);

    ucled[1] = (seg_show_mode==1);

    ucled[2] = (seg_show_mode==2);

    ucled[7] = led_light_flag;

  

     Led_Disp(ucled);

}

  

void ad_da(){

  unsigned char temp_ad = Ad_Read(0x43);

  unsigned int temp_da; // [优化:⚡性能] unsigned int替代float,避免8051浮点模拟开销

  voltage_rb2_al_100x = (unsigned int)temp_ad*100/51;

  start_ultrasound = ((temp_ad*10 < voltage_rb2_al_100x_max_10x*51) && (temp_ad*10 > voltage_rb2_al_100x_min_10x*51));

  if (start_ultrasound){

    if (distance_val <=20){

      Da_Write(51);

    }else if(distance_val >=80){

      Da_Write(255);

    }else{

      // [优化:⚡性能] 整数运算替代浮点: DA = (4*(d-20)/60+1)*51 = (204*(d-20)/60+51)

      temp_da = (unsigned int)204*(distance_val - 20)/60 + 51; // [优化:🛡️健壮] unsigned int防止中间值溢出

      Da_Write((unsigned char)temp_da);

    }

  }

  else{

    Da_Write(0);

  }

}

  
  

void get_distance()

{

  if (start_ultrasound){

    unsigned char temp = Ut_Wave_Data();

    // [优化:🛡️健壮] 过滤无效测距值:0表示超时/失败,不更新显示值

    if (temp > 0){

      distance_val = temp;

    }

  }

}

  

void Timer1_Init(void)    //1毫秒@12.000MHz

{

  AUXR &= 0xBF;     //定时器时钟12T模式

  TMOD &= 0x0F;     //设置定时器模式

  TL1 = 0x18;       //设置定时初始值

  TH1 = 0xFC;       //设置定时初始值

  TF1 = 0;        //清除TF1标志

  TR1 = 1;        //定时器1开始计时

  ET1 = 1;

  EA = 1;

}

  

void Timer1ISR() interrupt 3

{

  uwtick++;

  

  seg_pos = (++seg_pos)%8;

  if (seg_buf[seg_pos] > 20){

    Seg_Disp(seg_pos,seg_buf[seg_pos]-SEG_DOT,1);

  }else{

    Seg_Disp(seg_pos,seg_buf[seg_pos],0);

  }

  

  if (start_ultrasound){

    if (++time_100 >= 100){

      time_100 = 0 ;

      led_light_flag ^= 1;

    }

  }else{

    time_100 = 0 ;

    led_light_flag = 0;

  }

}

  

typedef struct {

    void (*task_func)(void);

    unsigned long int rate_t;

    unsigned long int last_t;

}task_t;

  

idata task_t schedluer_task[] = {

    {led_proc,1,0},

    {key_proc,10,0},

    {seg_proc,150,0},

    {ad_da,160,0},

    {get_distance,100,0},

};

  

idata unsigned char task_num;

void schedluer_init(){

  task_num = sizeof(schedluer_task)/sizeof(task_t);

}

  

void schedluer_run(){

  unsigned char i;

  for (i = 0 ;i < task_num;i++){

    unsigned long int nowtime = uwtick;

    if (nowtime >= schedluer_task[i].rate_t + schedluer_task[i].last_t){

      schedluer_task[i].last_t = nowtime;

      schedluer_task[i].task_func();

    }

  }

}

  
  
  

void main (){

  System_Init();

  schedluer_init();

  Timer1_Init();

  while(1)

  {

    schedluer_run();

  }

}