单片机第四周

一、Led 控制核心(含位移与模式切换)

1. 关键函数与变量

  • 位移函数_crol_(变量, 位移次数) → 循环左移(比如_crol_(ucLed,1)就是把ucLed的二进制位往左移 1 位)

  • 核心变量ucLed(8 位无符号字符,每 1 位对应 1 个 Led 的亮灭,比如0xfe11111110,代表只有第 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)