蓝桥杯单片机第四周
决赛试题
思维逻辑问题:
(1)决赛试题
1. 彩灯模式四后的 "小混乱"
**现象**:模式四结束后,两个相邻的灯一起 "走路"(同时流转)
**原因**:就像接力赛跑完最后一棒,没告诉下一棒选手 "从起点开始跑"—— 完成模式四后,控制灯状态的`ucLed`变量没复位到模式一的初始值,导致它带着 "上一模式的记忆" 跑偏了。
2. 设置界面的 "哑巴数据"
**现象**:在设置界面调了流转时间,屏幕上却看不到变化
**原因**:好比你在草稿纸上改了答案,却忘了抄到答卷上 —— 设置时用的是临时 "草稿数组",但显示时没调用这个草稿数组,自然看不到修改后的结果,数组使用错误。
3. 数码管的 "眨眼睛游戏"
**功能**:按 S6 进入设置界面后,数码管分两块(模式编号区和流转时间区),其中一块会闪烁,按 S6 能切换闪烁的区域。
**原理**:`Set_Flag`就像 "手电筒",照到哪个区域(模式编号 / 时间),哪个区域就 "眨眼睛";`Seg_Star_Flag`是 "眨眼节奏器",控制闪烁的快慢。
Set_Flag^=1;//模式编号 时间流转间隔 用于切换模式编号和之间流转间隔 以及返回运行状态的判断
4. S6 按键的 “三重身份”
功能:按 S6 第一次→进入设置界面(改模式编号);按第二次→切换到改流转时间;按第三次→退出 设置界面,保存修改。
原理:就像 “文件编辑” 流程 ——
- 进入设置时:把当前运行的数据 “复制” 到临时设置区(怕改坏原文件);
- 退出设置时:把临时设置区的内容 “粘贴” 回运行区(让修改生效)。
case 6://系统设置界面
if(System_Flag == 0)//系统暂停有效
{
if(Seg_Mode == 0)//初始按下S6时 处于运行状态界面
{
//将运行状态时间流转间隔数组中的值传递给设置界面中存放时间流转间隔的数组中进行修改
for(i=0;i<4;i++)
{
Led_Time_Set[i] = Led_Data_Time[i];//防止改变实际数组中的值
}
Seg_Mode =1;//进入设置界面
}
else if(Seg_Mode == 1)//else if防止第一次S6按下后直接进入时间流转间隔
{
Set_Flag^=1;//模式编号 时间流转间隔
if(Set_Flag == 0)
{
for(i=0;i++;i<4)
{
Led_Data_Time[i] = Led_Time_Set[i];//将设置值传递给实际数组中进行显示
}
Seg_Mode = 0;
Led_Mode = Led_Time_Set_Index;
}
}
}
5. 找亮灯的 “点名册”
功能:用数码管低位显示当前亮着的灯的序号(比如 L3 和 L5 亮,就显示 3 和 5)。原理:Led_State1和Led_State2就像两个 “点名本”,逐个检查 8 个灯(L1 到 L8):
- 第一个亮的灯,记在
Led_State1; - 第二个亮的灯,记在
Led_State2。
//用于记录led的运行状态变量
Led_State1 = 0;
Led_State2 = 0;
for(i=0; i<8; i++) // 扫描P1.0~P1.7(对应L1~L8)
{
// P1口低电平亮,所以~ucLed的对应位为1时表示灯亮
if( ((~ucLed) & (0x01 << i)) != 0 ) //扫描led变量 不等于零时 则扫到了亮着的led 若为0则未扫到亮着的led
{
if(Led_State1 == 0)
{
Led_State1 = i + 1; // 第一个亮灯的编号(1-8)
}
else
{
Led_State2 = i + 1; // 第二个亮灯的编号(1-8)
}
}
}
- 系统的 “心跳计时器”
功能:用一个变量记录时间,判断灯什么时候该 “换姿势”(流转)。
原理:Sys_Tick就像 “秒表”,定时器每 1ms"跳一下"(中断里累加),当秒表的数等于设定的流转时间,灯就开始切换状态,同时秒表清零重新计时。
if(Sys_Tick ==Led_Data_Time[Led_Mode])//系统时间延时间隔
{
Sys_Tick =0;//清空系统计时变量
if(System_Flag ==1)//判断系统是否启动
{
Sys_Tick++;//加一次为1ms
}
- 彩灯的 “四种舞步”
彩灯有四种流转模式,就像四支不同的舞蹈:
- 模式一:从低位到高位(L1→L2→…→L8)—— 像排队报数,从第一个人依次轮到最后一个;
- 模式二:从高位到低位(L8→L7→…→L1)—— 像倒着报数,从最后一个人倒回第一个;
- 模式三:从两边到中间(L1 和 L8→L2 和 L7→…)—— 像两边的人往中间走,慢慢靠近;
- 模式四:从中间到两边(…→L2 和 L7→L1 和 L8)—— 像中间的人往两边散开。
unsigned char i =0;//用于扫描Led状态
P1 =ucLed;
if(Sys_Tick ==Led_Data_Time[Led_Mode])//判断是否达到系统时间延时间隔
{
Sys_Tick =0;//清空系统计时变量
switch(Led_Mode)//判断当前彩灯控制系统的模式
{
case 0://模式一
if(ucLed ==0x7e)//模式四到模式一时需要重新对ucLed重新赋值 以达到从低位开始显示的过程
{
ucLed = 0xfe;
}
else
{
ucLed=_crol_(ucLed,1);
if(ucLed == 0x7f)//模式一最终状态
Led_Mode =1;
}
// ucLed=_crol_(ucLed,1);
// if(ucLed==0xfe)
// {
// Led_Mode = 1;//进入模式二
// ucLed =0x7f;//模式二初始状态
// }
break;
case 1://模式二
ucLed= _cror_(ucLed,1);
if(ucLed==0xfe)//模式二最终状态
{
Led_Mode = 2;
}
break;
case 2://模式三 采用一个数组来将模式三和模式四的值存放利用
ucLed = Led_Data[Led_Data_Index];
if(++Led_Data_Index ==4)
{
Led_Mode =3;
Led_Data_Index =3;
}
break;
case 3://模式四
ucLed = Led_Data[Led_Data_Index];
if(--Led_Data_Index ==255)
{
Led_Mode =0;//返回模式一
Led_Data_Index =0;//重新指向模式三的初始索引值
}
break;
}
电压采集界面
1、逻辑功能实现
(1)四舍五入:给数据 “凑整”
就像算钱时把零钱凑成整角:比如 3.456V,加上 0.005V 后再除以 1,得到 3.46V(四舍五入到两位小数)。
Voltage = (Voltage_Input[0]*1000 + Voltage_Input[1]*100 + Voltage_Input[2]*10 +Voltage_Input[3]+5) /1000.0;//保存当前输入电压并四舍五入
(2)电压显示:给数码管 “写价签”
把浮点型电压(比如 3.45V)转换成数码管能显示的数字,就像给商品写价签:
- 十位、个位、小数点后第一位、第二位,分别对应数码管的不同位置;
- 高位如果是 0 就不显示(比如 1.23V,十位不显示 0)。
case 1://数据显示界面
Seg_Point[3+(int)Voltage/10] =1;//转化为int类型 数组下标索引需要采用整形
Seg_Buf[0] =11;
Seg_Buf[1] =10;
Seg_Buf[2] =10;
Seg_Buf[3] =(int)Voltage/10?1:(unsigned char)Voltage % 10;//获取个位数,避免负数和溢出
Seg_Buf[4] =(unsigned int)(Voltage*100)/10%10;//防止数据超出unsigned char 范围限制
Seg_Buf[5] =(unsigned int)(Voltage*100)%10;
break;
(3)高阶 LED 控制:像 “智能开关”
addr是 “灯的编号”(选哪个灯),enable是 “开关状态”(开 / 关)。就像用遥控器选灯并控制亮灭,temp是 “当前开关总表”,只有总表变了,实际灯才会变。
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//led扫描数组
unsigned char Led_Pos;//led扫描变量
void Led_Disp(unsigned char addr,enable)
{
static unsigned char temp = 0x00;
static unsigned char temp_old = 0xff;
if(enable)
temp |= 0x01 << addr;//点亮
else
temp &=~(0x01 << addr);//熄灭
if(temp != temp_old)
{
P1 =~temp;
temp_old = temp;
}
}
(4)计数值自增:像 “超线计数器”
比如设定参考电压 3V,当实际电压超过 3V(像水位超警戒线),再降回 3V 以下时,计数器加 1(记一次 “超线事件”)。
if(Voltage > Voltage_Parament_Ctrol)//实际电压大于参考电压
Voltage_Flag = 1;//拉高标志
else if(Voltage_Flag == 1)//Voltage_Flag = 1 标志着上一次实际电压大于参考电压 若进入判断elseif 则(Voltage < Voltage_Parament_Ctrol
{
Voltage_Flag = 0;//拉高标志清零
Count++;//计数值自增
}
(5) 高位熄灭
while(Seg_Buf[j] == 0)//判断第一位开始是否为0 为0则熄灭 高位熄灭需要先写入数据
{
Seg_Buf[j] =10;//若为零则熄灭该数码管
if(++j ==5) break;//最低位初始值为0 仅能判断到第五位数码管就结束 防止循环卡死
}
程序报错集合:
1、User\main.c(41): error C193: ‘’: bad operand type
报错原因:
(1)Voltage是 浮点类型(float 或 double),而 % 取模运算符 只能用于整数类型。
解决方法:
1、强制类型转换

2、强转后传递给中间变量
!(C:\Users\HP\Pictures\Screenshots\屏幕截图 2025-12-19 220452.png)
2、User\main.c(82): warning C206: ‘crol’: missing function-prototype
解决方法: 添加头文件#include “intrins.h”