蓝桥杯单片机-决赛试题与过渡模拟一

蓝桥杯单片机第四周

第一讲:决赛试题与过渡模拟一

彩灯设计

从左往右,从右往左:

crol (位移变量,位移次数) a= crol (a,1)

不要对寄存器直接进行操作,使用中间变量过渡

P1 = ucLed ;

以 400ms 来流动流水灯

定义了一个系统计时变量,变量在中断里Sys_Tick++;

每当Sys_Tick == 400;Sys_Tick = 0;然后根据Led_Mode的不同,选择不同的彩灯运行模式

模式4跳到模式1时,避免模式4到模式1忽略L1

if(ucLed == 0x7e) //判断是否从模式4切换至模式1,若是,则复位ucLed数据

ucLed = oxfe;

else

{ucLed = crol(ucLed,1); //模式1 L1-L8

if(ucLed == 0x7f) Led_Mode = 1; //当L8点亮时,跳转到模式2

}

流转时间

利用数组

数码管显示

     switch(Seg_Disp_Mode)
     {
         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_Data[Led_Mode] / 1000 % 10;
                 Seg_Buf[3] = Led_Time_Data[Led_Mode] / 100 % 10;
                 Seg_Buf[4] = Led_Time_Data[Led_Mode] / 10 % 10;
                 Seg_Buf[5] = Led_Time_Data[Led_Mode] % 10;
                 while(Seg_Buf[i] == 0)//使用循环体结构令高位为0的数码管熄灭 直到不为0时退出
                 {
                     Seg_Buf[i] = 10;
                     i++;
                 }
             }
             else
             {
                 Seg_Buf[0] = 0;
                 Seg_Buf[1] = 12;
                 Seg_Buf[2] = 13;
                 Seg_Buf[3] = Led_Mode+1;
                 Seg_Buf[4] = Led_Data / 10;
                 Seg_Buf[5] = Led_Data % 10;
             }
         break;

这段代码是给 6 位数码管 “填显示内容” 的,而且只负责「运行状态界面」(Seg_Disp_Mode=0),它会根据 Data_Disp_Flag 这个开关,切换两种显示风格:

  1. 开关关(Data_Disp_Flag=0,默认):显示「系统是启动还是暂停」+「当前 LED 模式」+「这个模式的流转时间」。

  2. 开关开(Data_Disp_Flag=1,长按 S4 触发):显示「LED 当前的具体状态数据」(比如哪个 LED 亮了)。

/* 数据读取设计思路 */

     /* 数据读取设计思路 */
     i = 0;//每次循环判断前将i置0,便于读取Led状态
     while((~ucLed & (0x01 << i)) != (0x01 << i))
         i++;
     if(Led_Mode < 2)//若处于前两种模式,则数据直接等于i+1
         Led_Data = i+1;
     else
         Led_Data = (i+1)*10+(8-(i+1));//若处于后两种模式,则高位数据等于i+1,根据对称变换,低位加高位的数值之和等于8
 }
 ​

思路

  1. LED 模式对应点亮规则
  • 前 2 种模式(Led_Mode<2):单灯流水(每次只有 1 个 LED 点亮,比如 L1→L8→L1);

  • 后 2 种模式(Led_Mode>=2):对称双灯流水(每次两个 LED 对称点亮,比如 L1+L8、L2+L7、L3+L6、L4+L5)

  1. 最终目的:把「哪个 LED 亮了」这个信息,转换成数字(比如 L1 亮 = 1,L1+L8 亮 = 18),存到 Led_Data 中,后续数码管直接显示这个数字即可。

  2. ~ucLed & (0x01 << i):按位与运算,用来「判断第i个引脚对应的 LED 是否点亮」。

  • 规则:如果第i个 LED 点亮,结果等于 0x01 << i(非 0);如果未点亮,结果等于 0。

Led_Data = (i+1)*10+(8-(i+1));简化

else // 简化:利用数学运算化简,结果与原表达式一致

Led_Data = 9 * (i + 2);


过渡模拟一:电压采集器

数码管 电压采集和数据显示

四个模式,定义一个变量表示不同界面

![image-20260205125611953](file:///D:/softword/typora/%E7%AC%94%E8%AE%B0/%E8%93%9D%E6%A1%A5%E6%9D%AF%E5%8D%95%E7%89%87%E6%9C%BA%E7%AC%AC%E4%B8%89%E5%91%A8.assets/image-20260205125611953.png?lastModify=1770549193)

闪烁避免程序卡死

 if(Seg_Buf[5] == 11)             //只有当最后一位为杠时,数码管才开始闪烁
 {
    Seg_Buf[2+Seg_Input_Index] = Seg_Flag?Seg_Input[Seg_Input_Index]:10;
 }

Float型显示在数码管

比如2.78 = a

显示整数位**(int) a = 2;**

显示小数位**(int) (a*100)/10%10 = 7;**

               **(int) (a\*100)%10 = 8;**

显示数据

  • 输入9999进位后应该是10.0v

那么数码管的“.”怎么位移呢?

Seg_Point[3 + (int)Voltage/10] = 1;

如果Voltage = 10,即变成Seg_Point[4],实现了小数点后移一位

但是(int) a = 10,不符合我们显示整数位

Seg_Point[3] = (int) a /10? 1: (unsigned char) a %10;

可以对a进行判断,如果a/10正好等于1,也就是条件为真,直接就显示前者1;如果条件为假,我正常显示

数码管数据刷新

  1. 电压采集界面里面是没有小数点的,所以从数据显示界面回到电压采集界面的时候,要把小数点关上,也就是小数点刷新

    Seg_Point[3 + (int)Voltage/10] = 0;

  2. 数据显示界面的第三个数码管不显示数据了,要熄灭

参数设置界面

需要保存后再退出,需要一个显示值,一个设置值

计数统计界面

高位熄灭

从第一个数码管开始扫描

定义一个局部静态变量i = 1;

 while(Seg_Buf[i] = 0)
 {
 Seg_Buf[i] =10;
 i++;
 if(i == 5)
 }
 break;

计数值

思路:电压值大于参考值,a=1;小于参考值,只有当a=1时,a=0

 // 条件1:实际电压 > 参考电压(电压超限)
 if(Voltage > Parameter_Ctrol) 
 {
     Voltage_Flag = 1; // 标记“电压超限”(把标志位设为1)
 }
 // 条件2:电压已经恢复正常(实际电压 ≤ 参考电压),且之前处于“超限状态”
 else if(Voltage_Flag == 1) 
 {
     Voltage_Flag = 0; // 标志位复位(从“超限”切回“正常”)
     Count++; // 计数值+1(记录“这次超限事件结束了”)
 }

键盘输入

输入限制

 if(Key_Down >= 1 && Key_Down <= 10)    //键盘使能条件
 {
   if(Seg_Disp_Mode == 0 && Seg_Input_Index < 4)
   {
     Seg_Input[ Seg_Input_Index] = Key_Down - 1;
      Seg_Input_Index++;
      Key_Error_count = 0;
   }
   else
     Key_Error_count++;
 }

四舍五入保留两位数字

四位数实际上是四个数组

首先把单个的四个数变成四位数,比如A×1000+B×100+C×10+D

再让四位数+5,在整体/1000.0

Key_Error_Count

什么情况下需要这个变量?

当你需要「区分按键有效 / 无效」「处理连续无效按键」「防止误操作」时,就需要 Key_Error_Count 这个变量

  1. 有「输入规则限制」,需要判断按键是否合规

比如你的 4 位电压输入场景:

  • 规则 1:只能在「电压采集界面」按数字键;

  • 规则 2:4 位数据输满后,不能再按数字键;

  • 规则 3:只有 S1S10(09)是有效数字键,其他按键无效。

没有 Key_Error_Count:你只能知道 “按键无效”,但不知道 “无效了多少次”;

有了 Key_Error_Count

  • 有效按键 → 清零(Key_Error_Count=0),代表 “操作合规”;

  • 无效按键 → 累加(Key_Error_Count++),代表 “操作违规”。

  1. 需要「处理连续无效按键」,避免用户误操作

若用户在电压输入界面,连续按了 5 次无效按键(比如在非输入界面按数字键、输满 4 位还按),执行:

 if(Key_Error_Count >= 5)
 {
     Seg_Disp_Mode = 0; // 退出输入界面,回到正常界面
     Key_Error_Count = 0; // 清零,下次重新计数
 }

高阶Led

仅状态变化时才更新硬件

底层

 void Led_Control(unsigned char addr, bit enable)
 {
     // 静态变量:保存LED当前状态和上一次状态,函数调用间值不丢失
     static unsigned char temp = 0x00;
     static unsigned char temp_old = 0xff;
     
     // 根据enable标志,设置对应addr引脚的LED状态
     if(enable)
     {
         // 置位:对应addr位设为1(准备点亮LED)
         temp |= 0x01 << addr;
     }
     else
     {
         // 清零:对应addr位设为0(准备熄灭LED)
         temp &= ~(0x01 << addr);
     }
     
     // 仅当LED状态发生变化时,才更新硬件端口(优化开销)
     if(temp != temp_old)
     {
         // P1口输出(取反:适配低电平点亮LED的硬件电路)
         P1 = ~temp;
         // 更新上一次状态,为下一次判断做准备
         temp_old = temp;
     }
 }

核心功能是通过addr指定 LED 引脚、enable控制 LED 的亮灭,并实现 “仅状态变化时才更新硬件” 的优化逻辑