蓝桥杯单片机第二周笔记
本周核心学习成果:通过「彩灯控制系统」综合检验单片机按键、LED、数码管的应用能力。以下是自主复现代码时遇到的关键问题、解决方案,以及原始代码与优化后代码的对比参考(代码逻辑、功能完全不变,仅规范格式与命名)。
一、复现代码时的核心问题与解决方案
问题 1:按键仅第一次有效,后续无响应
- 现象:按下启动键可进入模式一,但停止键、模式切换键无效;
- 原因:
Key_Read()函数中中间变量temp未初始化,无按键时会返回随机值或残留上次按键值,导致程序卡住; - 解决方案:在
Key_Read()函数开头给temp赋初始值0,关键代码如下
unsigned char Key_Read()
{
unsigned char temp = 0; // 必须初始化为0,避免值残留
if (P3_4 == 0) temp = 1;
if (P3_5 == 0) temp = 2;
if (P3_6 == 0) temp = 3;
if (P3_7 == 0) temp = 4;
return temp;
}
问题 2:LED 灯卡在第二个,无法正常移位
- 现象:模式一中 LED 仅显示第二个灯,无法继续移位闪烁;
- 原因:将
ucLed初始值(如ucLed = 0xfe)写在模式循环内部,每次进入模式都会重复重置初始值; - 解决方案:将 LED 初始值初始化语句移至
main函数开头,模式二同类问题同理。
问题 3:按键响应慢、数码管显示延迟
- 现象:按键需长按才响应,数码管模式显示滞后,且
Delay值越大响应越慢; - 原因:程序中大量使用软件延时
Delay,延时期间程序阻塞,无法刷新按键状态和数码管; - 说明:后续教程会讲解非阻塞式延时解决方案,优化后代码已实现该逻辑。
二、个人编程反思
- 逻辑简洁性:初期使用大量
if判断实现模式切换,后续发现switch语句更简洁高效; - 变量命名:原始变量命名不够规范(如
i、sys),优化后按功能分类命名(如led_index、sys_flag),可读性大幅提升; - 代码结构:初期未分离功能模块,主循环冗余;优化后将灯效控制分离为独立函数,逻辑更清晰。
三、原始代码(功能实现版)
/*头文件声明区域*/
#include <REGX52.H>
#include <intrins.h>
/*参数定义区*/
unsigned char ucLed1 = 0xFE ;
unsigned char ucLed2 = 0x7F ;
unsigned char temp,Key_Val,Key_Down,Key_Up,Key_Old;
bit sys ;
unsigned Mode = 1;
unsigned int time = 500;
unsigned char Arr3[] = {0x7E,0xBD,0xDB,0xE7};
unsigned char Arr4[] = {0xE7,0xDB,0xBD,0x7E};
unsigned char i = 0;
unsigned char Seg_Dula[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x7f,0x6f,0x00}; //数码管断码储存数组
unsigned char Seg_Wela[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf}; //数码管位码储存数组
unsigned char Seg_Pos;
unsigned char Seg_Buf[] = {1,2,3,4,5,6};
/*函数定义区*/
void Delay(unsigned char t) //@12.000MHz
{
unsigned char data i, j;
while(t--)
{
i = 12;
j = 169;
do
{
while (--j);
}
while (--i);
}
}
unsigned char Key_Read()
{
unsigned char temp = 0; //不初始化为0,则按键返回的永远是上一个按键的值
if (P3_4 == 0)
{
Delay(1); //消抖处理,防止误触发
if(P3_4 == 0) temp = 1;
while(P3_4 == 0); //等待按键释放
}
if (P3_5 == 0)
{
Delay(1);
if(P3_5 == 0) temp = 2;
while(P3_5 == 0);
}
if (P3_6 == 0)
{
Delay(1);
if(P3_6 == 0) temp = 3;
while(P3_6 == 0);
}
if (P3_7 == 0)
{
Delay(1);
if(P3_7 == 0) temp = 4;
while(P3_7 == 0);
}
return temp;
}
void Seg_Disp (unsigned char Wela,unsigned char Dula) //一定要先传位选再传段选,否则会乱码,第一次写做错了
{
P0 = 0x00;
P2_6 = 1;
P2_6 = 0;
P0 = 0x00;
P2_7 = 1;
P2_7 = 0;
P0 = Seg_Wela[Wela];
P2_7 = 1;
P2_7 = 0;
P0 = Seg_Dula[Dula];
P2_6 = 1;
P2_6 = 0;
}
void Timer0_Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //开启定时器
EA = 1; //开启计时
}
/*定时中断函数*/
void Timer_Server() interrupt 1 //定义函数并设置优先级
{
/*着重注意初始化,第一次写漏掉了*/
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
if (++Seg_Pos == 6) Seg_Pos = 0;
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos]);
}
void main()
{
sys = 0; //初始化为0,防止出错
while(1)
{
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Val ^ Key_Old);
Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
Key_Old = Key_Val;
if (Key_Down==1)
{
sys = 1;
}
else if (Key_Down==2)
{
sys = 0;
}
else if (Key_Down==3)
{
Mode++;
if(Mode >= 5) Mode=1;
i = 0 ; //重置计数器,防止灯效错位
ucLed1 = 0xFE;
ucLed2 = 0x7F;
}
else if (Key_Down==4)
{
Mode--;
if(Mode <= 0) Mode = 4;
i = 0 ; //重置计数器,防止灯效错位
ucLed1 = 0xFE;
ucLed2 = 0x7F;
}
if(sys == 1)
{
if (Mode == 1)
{
//此处不可以放ucled等于0xfe,因为mode默认为1,每次进入这个模式都会把ucled重置为0xfe,只闪第二个灯,无法右移
ucLed1 = _crol_(ucLed1,1);
P1 = ucLed1;
Delay(time);
}
else if (Mode == 2)
{
//与mode1同理
ucLed2 = _cror_(ucLed2,1);
P1 = ucLed2;
Delay(time);
}
else if (Mode == 3)
{
P1 = Arr3[i];
Delay(time);
i++;
if(i >= 4) i = 0;
}
else if (Mode == 4)
{
P1 = Arr4[i];
Delay(time);
i++;
if(i >= 4) i = 0;
}
}
Seg_Disp(1,Mode);
}
}
四、ai优化过代码
/头文件声明区域/
#include <REGX52.H>
#include <intrins.h>/参数定义区 - 分类整理,增加注释/
// LED相关
unsigned char ucLed1 = 0xFE; // 模式1:左移初始值
unsigned char ucLed2 = 0x7F; // 模式2:右移初始值
unsigned char Arr3【】 = {0x7E,0xBD,0xDB,0xE7}; // 模式3灯效数组
unsigned char Arr4【】= {0xE7,0xDB,0xBD,0x7E}; // 模式4灯效数组
unsigned char led_index = 0; // LED数组索引(替换全局i,避免冲突)// 按键相关
unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
bit sys_flag = 0; // 系统使能标志(重命名更易读)
unsigned char Mode = 1; // 灯效模式(1-4)
unsigned int delay_time = 500; // 灯效延时时间(ms)// 数码管相关
unsigned char Seg_Dula = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x7f,0x6f,0x00}; // 0-8, 灭
unsigned char Seg_Wela = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf}; // 6位数码管位选
unsigned char Seg_Pos = 0; // 数码管当前刷新位置
unsigned char Seg_Buf = {0,0,0,0,0,0}; // 数码管显示缓冲区(第2位显示模式号)/函数定义区/
void Delay(unsig@12.000MH@12.000MHzed char t) //@12.000MHz
{
unsigned char data i, j;
while(t–)
{
i = 12;
j = 169;
do
{
while (–j);
}
while (–i);
}
}unsigned char Key_Read()
{
unsigned char temp = 0; //不初始化为0,则按键返回的永远是上一个按键的值
if (P3_4 == 0)
{
Delay(1); //消抖处理,防止误触发
if(P3_4 == 0) temp = 1;
while(P3_4 == 0); //等待按键释放
}
if (P3_5 == 0)
{
Delay(1);
if(P3_5 == 0) temp = 2;
while(P3_5 == 0);
}
if (P3_6 == 0)
{
Delay(1);
if(P3_6 == 0) temp = 3;
while(P3_6 == 0);
}
if (P3_7 == 0)
{
Delay(1);
if(P3_7 == 0) temp = 4;
while(P3_7 == 0);
}return temp;}
void Seg_Disp (unsigned char Wela,unsigned char Dula) //一定要先传位选再传段选,否则会乱码,第一次写做错了
{P0 = 0x00; P2_6 = 1; P2_6 = 0; P0 = 0x00; P2_7 = 1; P2_7 = 0; P0 = Seg_Wela[Wela]; P2_7 = 1; P2_7 = 0; P0 = Seg_Dula[Dula]; P2_6 = 1; P2_6 = 0; } void Timer0_Init(void) //1毫秒@12.000MHz{
TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0x18; //设置定时初始值 TH0 = 0xFC; //设置定时初始值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0 = 1; //开启定时器 EA = 1; //开启计时}
/定时中断函数/
void Timer_Server() interrupt 1 //定义函数并设置优先级
{
/着重注意初始化,第一次写漏掉了/
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值if (++Seg_Pos == 6) Seg_Pos = 0; Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos]);}
/LED模式控制函数:分离LED逻辑,主循环更清晰/
void Led_Mode_Control(void)
{if(sys_flag == 1) { switch(Mode) { case 1: // 模式1:LED左移 ucLed1 = _crol_(ucLed1,1); P1 = ucLed1; Delay(500); break; case 2: // 模式2:LED右移 ucLed2 = _cror_(ucLed2,1); P1 = ucLed2; Delay(500); break; case 3: // 模式3:自定义灯效1 P1 = Arr3[led_index]; Delay(500); led_index = (led_index + 1); if (led_index >= 5) led_index = 1; break; case 4: // 模式4:自定义灯效2 P1 = Arr4[led_index]; Delay(500); led_index = (led_index + 1); if (led_index >= 5) led_index = 1; break; } }}
void main(void)
{
Timer0_Init(); // 初始化定时器(数码管扫描)
Seg_Buf[1] = Mode; // 初始化数码管第2位显示模式1while(1) { // 1. 按键读取与处理 Key_Val = Key_Read(); Key_Down = Key_Val & (Key_Val ^ Key_Old); // 按键按下检测 Key_Up = ~Key_Val & (Key_Val ^ Key_Old); // 按键释放检测 Key_Old = Key_Val; // 2. 按键逻辑 if(Key_Down == 1) // 按键1:开启系统 sys_flag = 1; else if(Key_Down == 2) // 按键2:关闭系统 sys_flag = 0; else if(Key_Down == 3) // 按键3:模式+1 { Mode ++; if(Mode == 5) Mode = 1; // 1-4循环 led_index = 0; // 重置LED索引 ucLed1 = 0xFE; // 重置模式1初始值 ucLed2 = 0x7F; // 重置模式2初始值 Seg_Buf[1] = Mode; // 更新数码管显示模式号 } else if(Key_Down == 4) // 按键4:模式-1 { Mode--; if(Mode == 0) Mode = 4; // 1-4循环 led_index = 0; // 重置LED索引 ucLed1 = 0xFE; // 重置模式1初始值 ucLed2 = 0x7F; // 重置模式2初始值 Seg_Buf[1] = Mode; // 更新数码管显示模式号 } // 3. LED模式控制(非阻塞式) Led_Mode_Control(); }}
五、总结
本周通过彩灯控制系统的开发与优化,重点掌握了:
- 单片机按键读取、消抖、状态检测的核心逻辑;
- 数码管动态扫描的实现(定时器中断应用);
- 代码结构重构与命名规范的重要性。