蓝桥杯单片机第四周
第一讲:决赛试题与过渡模拟一
彩灯设计
从左往右,从右往左:
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 这个开关,切换两种显示风格:
-
开关关(
Data_Disp_Flag=0,默认):显示「系统是启动还是暂停」+「当前 LED 模式」+「这个模式的流转时间」。 -
开关开(
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
}
思路
- LED 模式对应点亮规则:
-
前 2 种模式(
Led_Mode<2):单灯流水(每次只有 1 个 LED 点亮,比如 L1→L8→L1); -
后 2 种模式(
Led_Mode>=2):对称双灯流水(每次两个 LED 对称点亮,比如 L1+L8、L2+L7、L3+L6、L4+L5)
-
最终目的:把「哪个 LED 亮了」这个信息,转换成数字(比如 L1 亮 = 1,L1+L8 亮 = 18),存到
Led_Data中,后续数码管直接显示这个数字即可。 -
~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);
过渡模拟一:电压采集器
数码管 电压采集和数据显示
四个模式,定义一个变量表示不同界面

闪烁避免程序卡死
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;如果条件为假,我正常显示
数码管数据刷新
-
电压采集界面里面是没有小数点的,所以从数据显示界面回到电压采集界面的时候,要把小数点关上,也就是小数点刷新
Seg_Point[3 + (int)Voltage/10] = 0;
-
数据显示界面的第三个数码管不显示数据了,要熄灭
参数设置界面
需要保存后再退出,需要一个显示值,一个设置值
计数统计界面
高位熄灭
从第一个数码管开始扫描
定义一个局部静态变量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 这个变量。
- 有「输入规则限制」,需要判断按键是否合规
比如你的 4 位电压输入场景:
-
规则 1:只能在「电压采集界面」按数字键;
-
规则 2:4 位数据输满后,不能再按数字键;
-
规则 3:只有 S1S10(09)是有效数字键,其他按键无效。
没有 Key_Error_Count:你只能知道 “按键无效”,但不知道 “无效了多少次”;
有了 Key_Error_Count:
-
有效按键 → 清零(
Key_Error_Count=0),代表 “操作合规”; -
无效按键 → 累加(
Key_Error_Count++),代表 “操作违规”。
- 需要「处理连续无效按键」,避免用户误操作
若用户在电压输入界面,连续按了 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 的亮灭,并实现 “仅状态变化时才更新硬件” 的优化逻辑。