蓝桥杯单片机第二周笔记

蓝桥杯单片机第二周笔记

本周核心学习成果:通过「彩灯控制系统」综合检验单片机按键、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,延时期间程序阻塞,无法刷新按键状态和数码管;
  • 说明:后续教程会讲解非阻塞式延时解决方案,优化后代码已实现该逻辑。

二、个人编程反思

  1. 逻辑简洁性:初期使用大量if判断实现模式切换,后续发现switch语句更简洁高效;
  2. 变量命名:原始变量命名不够规范(如isys),优化后按功能分类命名(如led_indexsys_flag),可读性大幅提升;
  3. 代码结构:初期未分离功能模块,主循环冗余;优化后将灯效控制分离为独立函数,逻辑更清晰。

三、原始代码(功能实现版)

/*头文件声明区域*/
#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位显示模式1

while(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();
}

}

五、总结

本周通过彩灯控制系统的开发与优化,重点掌握了:

  1. 单片机按键读取、消抖、状态检测的核心逻辑;
  2. 数码管动态扫描的实现(定时器中断应用);
  3. 代码结构重构与命名规范的重要性。