蓝桥杯第十三届省赛第二批次 —— 代码错误总结与修复记录
题目:第十三届蓝桥杯单片机组省赛第二批次程序设计
平台: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计数器仍在运行,CH和CL寄存器持续递增。
读取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_da → unsigned 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();
}
}