一、Led 控制核心(含位移与模式切换)
1. 关键函数与变量
-
位移函数:
_crol_(变量, 位移次数)→ 循环左移(比如_crol_(ucLed,1)就是把ucLed的二进制位往左移 1 位) -
核心变量:
ucLed(8 位无符号字符,每 1 位对应 1 个 Led 的亮灭,比如0xfe是11111110,代表只有第 1 个 Led 亮) -
注意:不能直接对寄存器用位移函数,必须通过
ucLed这样的中间变量过渡
2. 模式 4→模式 1 切换(防漏亮 L1)
代码拆解
if(ucLed == 0x7e) // 0x7e是11111110?不!0x7e是01111110 → 只有第2个Led灭,其他都亮(模式4结束状态)
ucLed = 0xfe; // 0xfe是11111110 → 只有第1个Led亮(模式1初始状态,避免漏亮L1)
else
{
ucLed = _crol_(ucLed, 1); // 模式1核心:Led从L1到L8循环亮(左移1位=下一个Led亮)
if(ucLed == 0x7f) // 0x7f是01111111 → 只有第8个Led亮(模式1结束)
Led_Mode = 1; // 切换到模式2
}
通俗理解
-
模式 4 切换到模式 1 时,强制让第 1 个 Led 先亮,防止跳过;
-
模式 1 下,每次左移 1 位,相当于 “点亮的 Led 往右边挪一个”,直到第 8 个 Led 亮了,就切到模式 2。
3. 读取当前亮的是哪个 Led
代码拆解
int i = 0; // 用来记Led序号(0=第1个,1=第2个...7=第8个)
// ~ucLed:把ucLed的0和1反过来(亮变灭,灭变亮)
// 0x01 << i:把1往左移i位(比如i=2就是00000100,对应第3个Led)
while((~ucLed & (0x01 << i)) == 0)
i++; // 没找到亮的Led,就找下一个
通俗理解
- 逐个检查每个 Led 的状态,直到找到 “亮着的那个”,
i就是它的序号。
二、数码管显示核心(闪烁 + 多界面)
1. 闪烁控制(400ms 触发)
核心逻辑
- 用定时器记 400ms,到点后触发闪烁(要么显示内容,要么熄灭)
代码拆解
// 第一步:400ms计时触发闪烁标志
if(++Timer_400Ms == 400) // 每次循环Timer_400Ms加1,到400就是400ms
{
Timer_400Ms = 0; // 重置计时器,准备下一次
Seg_Star_Flag = 1; // 闪烁标志置1,触发一次闪烁
}
// 第二步:根据状态控制闪烁内容
Seg_Buf[5] = Led_Time_Set[Led_Time_Set_Index] % 10; // 基础显示:时间的个位
if(Set_Flag == 0) // 非设置状态:只闪第0、1位
{
// Seg_Star_Flag=1显示符号/索引,=0熄灭(10代表熄灭)
Seg_Buf[0] = Seg_Star_Flag ? 13 : 10;
Seg_Buf[1] = Seg_Star_Flag ? (Led_Time_Set_Index + 1) : 10;
}
else // 设置状态:闪时间的千、百、十、个位
{
Seg_Buf[2] = Seg_Star_Flag ? (Led_Time_Set[Led_Time_Set_Index]/1000%10) : 10; // 千位
Seg_Buf[3] = Seg_Star_Flag ? (Led_Time_Set[Led_Time_Set_Index]/100%10) : 10; // 百位
Seg_Buf[4] = Seg_Star_Flag ? (Led_Time_Set[Led_Time_Set_Index]/10%10) : 10; // 十位
Seg_Buf[5] = Seg_Star_Flag ? (Led_Time_Set[Led_Time_Set_Index]%10) : 10; // 个位
}
通俗理解
- 400ms 一到,闪烁标志变 1,显示要闪的内容;下一个 400ms 到,标志变 0,熄灭这些位,循环往复就是 “闪烁”。
2. 多界面切换(采集界面 + 数据界面)
代码拆解
switch(Seg_Disp_Mode)
{
case 0: // 采集界面:显示手动输入的电压原始数据
Seg_Point[3 + (int)Voltage/10] = 1; // 调整小数点位置(比如电压≥10就挪小数点)
for(int i=0; i<4; i++)
Seg_Buf[2+i] = Seg_Input[i]; // 显示输入的4位数字
break;
case 1: // 数据界面:显示处理后的电压值(带V单位)
Seg_Buf[0] = 12; // 12代表显示"V"(电压单位)
Seg_Buf[1] = Seg_Buf[2] = 10; // 这两位熄灭
Seg_Buf[3] = (int)Voltage/10 ? 1 : (unsigned char)Voltage%10; // 个位(≥10V显示1,否则显示个位)
Seg_Buf[4] = (unsigned int)(Voltage*100)/10%10; // 小数点后1位
Seg_Buf[5] = (unsigned int)(Voltage*100)%10; // 小数点后2位
break;
}
通俗理解
-
模式 0 是 “输入模式”,显示你手动按按键输入的数字;
-
模式 1 是 “结果模式”,显示计算后的电压值,还带单位 “V”,保留两位小数。
3. 高位熄灭(去掉前导零)
代码拆解
int j=0;
while(Seg_Buf[j] == 0) // 从最左边开始,找是0的位
{
Seg_Buf[j] = 10; // 把0改成10(熄灭),去掉前导零
if(++j == 5) break; // 最多检查到第5位,不往下了
}
通俗理解
- 比如输入 “0012”,会把前面两个 “0” 熄灭,只显示 “12”,看起来更整洁。
三、按键处理核心(防误触 + 长按 + 输入限制)
1. 模式切换按键(防多执行)
代码拆解
case 6: // 对应S6按键(模式切换键)
if(Seg_Disp_Mode == 0) // 当前是显示界面→切到设置界面
{
for(int i=0; i<4; i++)
Led_Time_Set[i] = Led_Time_Data[i]; // 复制当前设置,避免改乱
Seg_Disp_Mode = 1; // 切换模式
}
else if(Seg_Disp_Mode == 1) // 当前是设置界面→切设置状态
{
Set_Flag ^= 1; // 翻转状态(0→1,1→0)
if(Set_Flag == 0) // 退出设置→保存数据+回显示界面
{
for(int i=0; i<4; i++)
Led_Time_Data[i] = Led_Time_Set[i]; // 保存修改后的设置
Seg_Disp_Mode = 0; // 切回显示界面
}
}
通俗理解
-
按一次 S6:显示界面→设置界面;
-
再按一次 S6:设置界面→进入设置状态(可以改参数);
-
再按一次 S6:退出设置状态,保存参数→回显示界面;
-
用
else if避免一次按键触发多次切换。
2. 长按功能(S4 按键显示数据)
代码拆解
if(System_Flag == 0) // 系统暂停时才生效
{
// Key_Old=4:S4长按中;Seg_Disp_Mode=0:当前是显示界面
if(Key_Old == 4 && Seg_Disp_Mode == 0)
Data_Disp_Flag = 1; // 显示数据
else
Data_Disp_Flag = 0; // 隐藏数据
}
else
Data_Disp_Flag = 0; // 系统运行时,不显示
通俗理解
- 只有系统暂停、在显示界面,且长按 S4 按键时,才显示隐藏的数据;松开或切换状态就隐藏。
3. 输入限制(只允许 1-10 按键)
代码拆解
if(Key_Down >=1 && Key_Down <=10) // 只允许按1-10的按键(有效输入)
{
if(Seg_Disp_Mode == 0 && Seg_Input_Index <4) // 显示界面+还能输入(最多4位)
{
Seg_Input[Seg_Input_Index] = Key_Down - 1; // 按键1→0,按键10→9(存入数组)
Seg_Input_Index++; // 准备接收下一位数字
Key_Error_Count = 0; // 没出错,重置错误计数
}
}
else
Key_Error_Count++; // 按了无效键,错误计数+1
通俗理解
-
只能按 1-10 的按键,其他按键按了会累计错误次数;
-
最多输入 4 位数字,输入满了就不能再输了。
四、数据处理核心(四舍五入 + 计数逻辑)
1. 四舍五入(保留两位小数)
代码拆解
// Seg_Input[0]是千位,[1]百位,[2]十位,[3]个位(比如输入1234→1.234V)
// +5是为了四舍五入(比如1234+5=1239→1239/1000=1.239→保留两位是1.24)
Voltage = (Seg_Input[0]*1000 + Seg_Input[1]*100 + Seg_Input[2]*10 + Seg_Input[3] +5)/1000.0;
通俗理解
- 输入的 4 位数字是 “毫伏” 级(比如 1234=1234mV),加 5 后除以 1000,就是 “伏特” 级,还能自动四舍五入保留两位小数。
2. 过压恢复计数
代码拆解
if(Voltage > Voltage_Parameter_Ctrol) // 实际电压>参考电压(过压了)
Voltage_Flag = 1; // 记下来“发生过过压”
else if(Voltage_Flag == 1) // 电压恢复正常,且之前发生过过压
{
Voltage_Flag = 0; // 重置标志
Count++; // 计数+1(统计过压后恢复的次数)
}
通俗理解
- 只有 “先过压,再恢复正常” 才计数一次,避免反复过压时重复计数。
五、常用变量速查(记不住就看这)
| 变量名 | 作用 |
|---|---|
| ucLed | 控制单个 Led 亮灭(8 位对应 8 个 Led) |
| Seg_Buf | 数码管显示缓存(存要显示的数字 / 符号) |
| Seg_Star_Flag | 数码管闪烁标志(1 = 闪,0 = 不闪) |
| Set_Flag | 设置状态标志(1 = 在设置,0 = 不在) |
| Seg_Disp_Mode | 数码管模式(0 = 采集,1 = 数据) |
| Key_Error_Count | 按键错误次数(按错键就加 1) |
| Voltage | 处理后的电压值(保留两位小数) |
| Count | 过压恢复计数(过压后恢复一次加 1) |