LED彩灯程序错误点分析
本文对比了原始代码与范例程序,总结了三处关键差异及错误所在,并给出润色后的修改建议,以帮助理解正确逻辑。
1. 数据显示逻辑
1.1 原始代码
if(Seg_Mode == 0 && Led_Flag == 1) // 处于运行状态界面且系统暂停
{
if(Key_Old == 4)
Seg_Mode = 2; // 长按4进入数据显示界面
}
if(Seg_Mode == 2) // 处于数据显示界面
{
if(Key_Up == 4)
Seg_Mode = 0; // 松开4返回运行界面
}
case 0: // 运行状态界面
Seg_Buf[0] = Led_Flag ? 11 : 12; // 运行状态
Seg_Buf[1] = Led_Mode + 1; // 运行模式
Seg_Buf[2] = Led_Time[Led_Mode] / 1000 % 10;
Seg_Buf[3] = Led_Time[Led_Mode] / 100 % 10;
Seg_Buf[4] = Led_Time[Led_Mode] / 10 % 10;
Seg_Buf[5] = Led_Time[Led_Mode] % 10;
if(Seg_Buf[2] == 0) Seg_Buf[2] = 10; // 高位管熄灭
break;
case 2: // 数据显示界面
Seg_Buf[0] = 0; // D
Seg_Buf[1] = 12; // A
Seg_Buf[2] = 13; // -
Seg_Buf[3] = Led_Mode + 1; // 运行模式
Seg_Buf[4] = Led_Data / 10 % 10;
Seg_Buf[5] = Led_Data % 10;
break;
1.2 范例程序
if(System_Flag == 0 && Key_Old == 4) // 在系统停止状态下长按S4跳转到数据显示界面
Data_Disp_Flag = 1; // 显示数据
else
Data_Disp_Flag = 0; // 不显示数据
case 0: // 运行状态界面
if(Data_Disp_Flag == 0)
{
// 运行状态
Seg_Buf[0] = System_Flag ? 11 : 12; // 11-S启动 12-R停止
// 运行模式
Seg_Buf[1] = Led_Mode + 1;
// 流转间隔
Seg_Buf[2] = Led_Time_Dat[Led_Mode] / 1000 % 10;
Seg_Buf[3] = Led_Time_Dat[Led_Mode] / 100 % 10;
Seg_Buf[4] = Led_Time_Dat[Led_Mode] / 10 % 10;
Seg_Buf[5] = Led_Time_Dat[Led_Mode] % 10;
if(Seg_Buf[2] == 0) Seg_Buf[2] = 10; // 高位管熄灭
}
else
{
// 模式标识符
Seg_Buf[0] = 0;
Seg_Buf[1] = 12;
// 运行模式编号
Seg_Buf[2] = 13; // -
Seg_Buf[3] = Led_Mode + 1;
// Led 灯数据
Seg_Buf[4] = Led_Disp_Data / 10 % 10;
Seg_Buf[5] = Led_Disp_Data % 10;
}
break;
1.3 差异与问题分析
| 对比项 | 原始代码 | 范例程序 |
|---|---|---|
| 模式切换 | 使用 Seg_Mode 状态机,分为 0(运行)、2(数据) |
使用独立标志位 Data_Disp_Flag,在运行界面内分支 |
| 切换条件 | 长按进入数据显示,松开返回运行 | 长按进入数据显示,否则一直显示运行界面 |
| 潜在问题 | 两个独立 if 可能导致逻辑混乱,尤其当 Seg_Mode 被意外修改时 |
结构清晰,一个 if-else 完成状态切换 |
主要问题:原始代码将数据显示界面作为独立 case,需要额外管理 Seg_Mode 的切换,容易引入状态不一致。范例则仅用一个标志位控制显示内容,所有显示逻辑都集中在 case 0 中,更简洁可靠。
2. LED 数据读取
2.1 原始代码
while((~ucLed & (0x01 << i)) != 0)
i++;
if(Led_Mode < 2) // 处于前两种模式
Led_Data = i + 1;
else
Led_Data = (i+1)*10 + (8 - (i+1));
2.2 范例程序
i = 0;
while((~ucLed & (0x01 << i)) == 0) // 判断0x01左移i位是否等于ucLed,==优先级大于&
i++; // 不等于则+1
if(Led_Mode < 2) // 处于前两种运行模式
Led_Data = i + 1;
else
Led_Data = (8 - i) + (i + 1)*10; // 高位为i+1,低位为8-i
2.3 差异与问题分析
循环条件完全相反:
- 原始:
(~ucLed & (0x01 << i)) != 0表示当位为 1(即ucLed对应位为 0,LED 亮)时继续循环,直到遇到 0(灭)停止,找到的是第一个灭的位。 - 范例:
(~ucLed & (0x01 << i)) == 0表示当位为 0(即ucLed对应位为 1,灭)时循环,直到遇到 1(亮)停止,正确找到第一个亮的位。
后两种模式计算错误:
- 原始:
(i+1)*10 + (8 - (i+1))= 9i + 17 - 范例:
(8 - i) + (i + 1)*10= 9i + 18
两者相差 1,原始计算了错误的数据。
建议:直接采用范例的逻辑,确保寻找第一个亮灯位置,并正确计算显示值。
3. LED 流转处理逻辑
3.1 原始代码
if(Timer == Led_Time[Led_Mode]) // 当计时到达当前模式的流转间隔
{
Timer = 0; // 复位
switch(Led_Mode)
{
case 0: // 模式1
if(ucLed == 0x7e) // 从模式4跳转到模式1
ucLed = 0xfe; // 复位
else
{
ucLed = _crol_(ucLed, 1);
P1 = ucLed;
if(ucLed == 0x7f) // 流转到最后一个灯
Led_Mode = 1; // 跳转到模式2
}
break;
case 1: // 模式2
ucLed = _cror_(ucLed, 1);
P1 = ucLed;
if(ucLed == 0xfe) // 流转到最后一个灯
Led_Mode = 2; // 跳转到模式3
break;
case 2: // 模式3
ucLed = Led_Disp[Led_Time_Index];
P1 = ucLed;
if(++Led_Time_Index == 4)
{
Led_Mode = 3; // 跳转到模式4
Led_Time_Index = 3;
}
break;
case 3: // 模式4
ucLed = Led_Disp[Led_Time_Index];
P1 = ucLed;
if(--Led_Time_Index == 255)
{
Led_Mode = 1; // 跳转到模式1(错误,应为0)
Led_Time_Index = 0;
}
break;
}
}
3.2 范例程序
P1 = ucLed; // 每次更新前先输出当前状态
if(Sys_Tick == Led_Time_Dat[Led_Mode]) // 当计时器走到当前模式的流转时间间隔时
{
Sys_Tick = 0; // 计时器复位
switch(Led_Mode)
{
case 0: // 模式1
if(ucLed == 0x7e) // 从模式4进入模式1
ucLed = 0xfe; // 复位至模式1初始情况
else
{
ucLed = _crol_(ucLed, 1); // 左移
if(ucLed == 0x7f)
Led_Mode = 1; // 当灯8亮时,进入模式2
}
break;
case 1: // 模式2
ucLed = _cror_(ucLed, 1); // 右移
if(ucLed == 0xfe)
Led_Mode = 2; // 当灯1亮时,进入模式3
break;
case 2: // 模式3
ucLed = ucLed_Dat[ucLed_Dat_Index]; // 从外往里
if(++ucLed_Dat_Index == 4)
{
Led_Mode = 3; // 进入模式4
ucLed_Dat_Index = 3; // 指针复位至模式4初始值
}
break;
case 3: // 模式4
ucLed = ucLed_Dat[ucLed_Dat_Index]; // 从里往外
if(--ucLed_Dat_Index == 255)
{
Led_Mode = 0; // 进入模式1
ucLed_Dat_Index = 0; // 指针复位至模式3初始值
}
break;
}
}
3.3 差异与问题分析
| 对比项 | 原始代码 | 范例程序 |
|---|---|---|
| 输出时机 | 在每个 case 内部单独赋值 P1 = ucLed,重复且易遗漏 |
在 switch 之前统一执行 P1 = ucLed,保证每次更新后立即输出 |
| 模式4跳转 | Led_Mode = 1(跳转到模式2,错误) |
Led_Mode = 0(跳转到模式1,正确) |
| 变量命名 | Led_Time, Led_Data, Led_Time_Index 等 |
Led_Time_Dat, Led_Disp_Data, ucLed_Dat_Index 等 |
| 可读性与维护 | 重复代码多,状态转移易错 | 结构清晰,状态转移明确 |
主要问题:
- 输出位置:原始代码每处修改
ucLed后都要手动写P1 = ucLed,容易忘记;范例统一在每次进入前输出上一次的ucLed,修改ucLed后在下一次循环输出,减少了重复和遗漏。 - 模式4跳转错误:原始代码
Led_Mode = 1会进入模式2(case 1),而正确应回到模式1(case 0),范例修正了这一点。 - 变量命名规范:范例使用更描述性的名称,如
Sys_Tick替代Timer,ucLed_Dat_Index替代Led_Time_Index,增强了可读性。
4. 总结
本文通过对比原始代码与范例程序,发现原始代码主要存在以下错误:
- 数据显示:状态管理混乱,建议使用独立标志位代替多状态机。
- LED 数据读取:循环条件写反,导致找到了错误的位置;后两种模式数据计算有误。
- LED 流转处理:模式4跳转目标错误,且输出逻辑分散,容易遗漏。
电压采集器学习笔记
1. 上电后数码管显示异常
现象:
上电后,后三个数码管仅短暂显示“-”,随后全部熄灭。
2. C 语言类型转换写法错误
2.1 原始代码
#define u8 unsigned char
#define u16 unsigned int
// 电压显示界面
case 1:
Seg_Buf[1] = Seg_Buf[2] = 10; // 固定熄灭
Seg_Buf[0] = 12; // U
Seg_Buf[3] = u8(Voltage); // 个位(错误写法)
Seg_Buf[4] = u16(Voltage*1000) / 100 % 10;
Seg_Buf[5] = u16(Voltage*1000) / 10 % 10;
break;
2.2 错误分析
C 语言中类型转换必须使用 (类型)表达式 的格式,即括号包裹类型名。写成 u8(Voltage) 不符合标准 C 语法,部分编译器可能扩展支持,但不建议使用。
2.3 修正代码
Seg_Buf[3] = (u8)(Voltage); // 个位
Seg_Buf[4] = (u16)(Voltage*1000) / 100 % 10;
Seg_Buf[5] = (u16)(Voltage*1000) / 10 % 10;
3. S11 按键逻辑冲突
3.1 原始代码
case 11: // 电压采集按键
if (Seg_Mode == 0 && Seg_Input_Index >= 4) // 处于采集界面且输入完成
{
Voltage = (Seg_Input[0]*1000 + Seg_Input[1]*100 + Seg_Input[2]*10 + Seg_Input[3] + 5) / 1000.0;
if (Voltage >= 0 && Voltage < 11) // 电压输入符合规范
Seg_Mode = 1; // 跳转电压显示界面
else
{
for (i = 0; i < 4; i++)
Seg_Input[i] = 11;
Seg_Input_Index = 0;
}
}
if (Seg_Mode > 0)
{
for (i = 0; i < 4; i++)
Seg_Input[i] = 11;
Seg_Input_Index = 0;
Seg_Mode = 0;
}
break;
3.2 问题现象c
输入完成并合规后,按下 S11 会立即重新进入输入状态,无法停留在显示界面。
3.3 逻辑分析
第一次按下 S11(假设初始 Seg_Mode = 0):
-
第一个
if条件Seg_Mode == 0 && Seg_Input_Index >= 4可能不成立(因为尚未输入完成),跳过。 -
第二个
if条件Seg_Mode > 0也不成立,跳过。 -
因此第一次按下按键时可能无任何效果。
输入完成后再次按下 S11:
-
此时
Seg_Mode == 0且Seg_Input_Index >= 4,进入第一个if。 -
计算
Voltage,若合法则将Seg_Mode设为1(显示界面)。 -
紧接着执行第二个
if,因为Seg_Mode > 0成立,执行清空输入、将Seg_Input_Index清零,并将Seg_Mode重新置为0。 -
最终效果:电压虽然保存,但显示模式被立即取消,回到采集界面,表现为“又进行重新输入”。
3.4 解决思路
将两个 if 改为 if-else 或使用状态标志,避免连续执行:
if (Seg_Mode == 0 && Seg_Input_Index >= 4)
{
// 处理输入并切换模式
}
else if (Seg_Mode > 0)
{
// 退出显示模式
}
也可以在第一个 if 内设置 Seg_Mode 后直接 break 或使用 return,但需注意 break 通常用于跳出 switch,逻辑需谨慎。
4. 高位管熄灭逻辑
4.1 原始代码
i = 0;
while (Seg_Buf[i] == 0 && i < 5)
{
Seg_Buf[i] = 10;
i++;
}
范例:
u8 j = 1;
while (Seg_Buf[j] == 0)
{
Seg_Buf[j] = 10;
if (++j == 5) break;
}
4.2 功能说明
用于熄灭高位无效的 0,例如将 0 0 3 5 显示为 3 5。
小结
本文记录了电压采集器调试中遇到的四个问题:上电显示异常、类型转换错误、按键逻辑冲突以及高位熄灭处理。重点在于 C 语言类型转换格式 和 状态机逻辑顺序,需在编程时特别注意。