蓝桥杯 第四周 决赛试题

这是一个LED流水灯控制系统,具有以下功能:

核心功能

  • 4种LED流转模式:左移 → 右移 → 正序 → 倒序(循环)
  • 可调流转时间:每个模式的流转时间可独立设置(400ms~1200ms,步进100ms)
  • 启动/暂停控制:S7键控制系统运行/暂停
  • 数据显示功能:暂停时长按S4显示当前LED点亮数量
  • 参数设置界面:可设置各模式的流转时间

按键功能

┌──────┬─────────────────────────┬─────────────────────┐
│ 按键 │ 功能 │ 使用场景 │
├──────┼─────────────────────────┼─────────────────────┤
│ S4 │ 参数减少 / 长按显示数据 │ 设置界面 / 运行界面 │
├──────┼─────────────────────────┼─────────────────────┤
│ S5 │ 参数增加 │ 设置界面 │
├──────┼─────────────────────────┼─────────────────────┤
│ S6 │ 界面切换 / 设置确认 │ 全局 │
├──────┼─────────────────────────┼─────────────────────┤
│ S7 │ 启动/暂停 │ 全局 │
└──────┴─────────────────────────┴─────────────────────┘

二、遇到的问题与解决方法 ( ̄へ ̄)

问题1:注释乱码问题 :warning:

现象:
/* ͷ�ļ������� */ // 应该是"头文件声明区"
unsigned char Key_Slow_Down;//���̼���ר�ñ��� // 应该是"按键减速专用变量"

原因:

  • 文件使用了 GBK/GB2312 编码(中文Windows默认编码)
  • 但编辑器或显示工具使用了 UTF-8 编码读取
  • 导致中文字符显示为乱码

解决方法:
方法1:转换文件编码(推荐)

  1. 用记事本或VSCode打开文件
  2. 选择"另存为"
  3. 编码选择"UTF-8"
  4. 保存后重新打开

方法2:修改编辑器设置

  1. VSCode:右下角点击编码
  2. 选择"通过编码重新打开"
  3. 选择"GBK"或"GB2312"
  4. 文件正常显示后,再转换为UTF-8保存

方法3:使用iconv命令(Linux/Mac)
iconv -f GBK -t UTF-8 main.c > main_utf8.c

本小姐的建议:

  • 统一使用 UTF-8 编码,这是国际标准!( ̄▽ ̄*)
  • 避免使用中文路径和中文文件名
  • 在文件开头添加编码声明注释

问题2:数组越界风险 :warning::warning:

问题代码:
// 第132-137行
unsigned char i=2;
while(Seg_Buf[i] == 0)
{
Seg_Buf[i]=10;
i++; // :cross_mark: 没有边界检查!
}

风险分析:

  • Seg_Buf 数组长度为 6(索引 0~5)
  • 如果从 i=2 开始,最多可以访问到 i=5
  • 但如果 Seg_Buf[2]~Seg_Buf[5] 全是 0,i 会增加到 6,导致 数组越界!

可能后果:
轻则:读取到未知内存数据,显示异常
重则:程序崩溃,系统死机

解决方法:
// ✓ 添加边界检查
unsigned char i=2;
while(i < 6 && Seg_Buf[i] == 0) // 添加 i < 6 条件
{
Seg_Buf[i]=10;
i++;
}

更好的封装:
// 封装成函数,更安全
void Remove_Leading_Zero(unsigned char start_pos)
{
unsigned char i = start_pos;
while(i < 6 && Seg_Buf[i] == 0)
{
Seg_Buf[i] = 10;
i++;
}
}

// 使用时
Remove_Leading_Zero(2);


问题3:Led_Data变量未实现 :warning:

问题代码:
// 第28行:声明了变量
unsigned char Led_Data;

// 第145-146行:使用了变量
Seg_Buf[4] = Led_Data / 10;
Seg_Buf[5] = Led_Data % 10;

// 但是!整个代码中没有给Led_Data赋值!

后果:

  • Led_Data 的值是未定义的(可能是随机值)
  • 数据显示功能会显示错误的LED数量

解决方法:
// 在Led_Proc()函数中添加LED数量统计
void Led_Proc()
{
unsigned char i;
P1 = ucLed;

  // ✓ 添加:统计当前点亮的LED数量
  Led_Data = 0;
  for(i = 0; i < 8; i++)
  {
      if((ucLed & (1 << i)) == 0)  // 0表示LED亮
          Led_Data++;
  }

  // 原有的流转逻辑...
  if(Sys_Tick == Led_Time_Data[Led_Disp_Mode])
  {
      // ...
  }

}

优化版本(位运算):
// 使用查表法,更高效
const unsigned char BitCount[256] = {
8,7,7,6,7,6,6,5,7,6,6,5,6,5,5,4, // 0x00-0x0F
// … 完整的256个值
};

Led_Data = BitCount[ucLed]; // 直接查表


问题4:Set_Flag逻辑混乱 :warning:

问题代码:
// 第156-161行
if(Set_Flag==1) // 注释说是"编号设置界面"
{
Seg_Buf[0]=Seg_Star_Flag?13:10;
Seg_Buf[1]=Seg_Star_Flag?Led_Time_Set_Index+1:10;
}
else // 注释说是"时间修改界面"
{
Seg_Buf[2]=Seg_Star_Flag?Led_Time_Set[Led_Time_Set_Index]/1000%10:10;
// …
}

问题分析:

  • 第64-66行注释说:Set_Flag=0 是模式选择阶段,Set_Flag=1 是时间修改阶段
  • 但第156行的逻辑是:Set_Flag==1 时闪烁模式编号(应该是模式选择阶段)
  • 逻辑和注释矛盾!

正确理解:
实际逻辑(根据代码行为):

  • Set_Flag=0:模式选择阶段(闪烁时间参数)
  • Set_Flag=1:时间修改阶段(闪烁模式编号)

但这和注释说的相反!

解决方法:
// 方法1:修正注释(推荐)
if(Set_Flag==1) // 时间修改阶段(闪烁模式编号)
{
Seg_Buf[0]=Seg_Star_Flag?13:10;
Seg_Buf[1]=Seg_Star_Flag?Led_Time_Set_Index+1:10;
}
else // 模式选择阶段(闪烁时间参数)
{
Seg_Buf[2]=Seg_Star_Flag?Led_Time_Set[Led_Time_Set_Index]/1000%10:10;
// …
}

// 方法2:修正逻辑(如果注释是对的)
if(Set_Flag==0) // 模式选择阶段(闪烁模式编号)
{
Seg_Buf[0]=Seg_Star_Flag?13:10;
Seg_Buf[1]=Seg_Star_Flag?Led_Time_Set_Index+1:10;
}
else // 时间修改阶段(闪烁时间参数)
{
Seg_Buf[2]=Seg_Star_Flag?Led_Time_Set[Led_Time_Set_Index]/1000%10:10;
// …
}

本小姐的建议:

  • 使用 枚举类型 代替 bit 类型,更清晰!
    typedef enum {
    MODE_SELECT = 0, // 模式选择阶段
    TIME_EDIT = 1 // 时间修改阶段
    } SetStage_t;

SetStage_t Set_Stage = MODE_SELECT;

// 使用时
if(Set_Stage == TIME_EDIT)
{
// 时间修改逻辑
}


问题5:魔法数字过多 :warning:

问题代码:
if(Key_Down == 7) // 7是什么?
if(Led_Time_Set[Led_Time_Set_Index] >= 1300) // 1300是什么?
if(++Key_Slow_Down == 10) // 10是什么?

问题:

  • 代码可读性差
  • 修改时容易出错
  • 不利于维护

解决方法:
// ✓ 使用宏定义
#define KEY_START_STOP 7
#define KEY_SETTING 6
#define KEY_PARAM_UP 5
#define KEY_PARAM_DOWN 4

#define LED_TIME_MIN 400
#define LED_TIME_MAX 1200
#define LED_TIME_STEP 100

#define KEY_SCAN_PERIOD 10 // 按键扫描周期(ms)
#define SEG_UPDATE_PERIOD 500 // 数码管更新周期(ms)
#define BLINK_PERIOD 400 // 闪烁周期(ms)

// 使用时
if(Key_Down == KEY_START_STOP)
if(Led_Time_Set[Led_Time_Set_Index] >= LED_TIME_MAX)
if(++Key_Slow_Down == KEY_SCAN_PERIOD)


问题6:unsigned char下溢问题 :warning:

问题代码:
// 第100行
if(–Led_Time_Set_Index == 255)
Led_Time_Set_Index = 3;

// 第214行
if(–ucLed_Data_Index == 255)
{
Led_Disp_Mode = 0;
ucLed_Data_Index = 0;
}

问题分析:

  • unsigned char 类型范围是 0~255
  • 当值为 0 时再减 1,会下溢变为 255
  • 这里利用了下溢特性来检测边界

风险:

  • 代码不直观,容易误解
  • 如果变量类型改变(如改为 int),逻辑会失效

更好的写法:
// ✓ 方法1:先判断再操作
if(Led_Time_Set_Index == 0)
Led_Time_Set_Index = 3;
else
Led_Time_Set_Index–;

// ✓ 方法2:使用模运算
Led_Time_Set_Index = (Led_Time_Set_Index + 3) % 4; // 循环递减


问题7:第167行的计算错误 :warning::warning::warning:

严重BUG!
// 第167行
Seg_Buf[5]=Seg_Star_Flag?Led_Time_Set[Led_Time_Set_Index]/1%10:10;
// ↑
// 除以1???

问题:

  • 应该是 /10%10 或 %10
  • 写成 /1%10 是笔误,但不影响结果(除以1等于本身)
  • 但这是代码不规范的表现!

正确写法:
Seg_Buf[5]=Seg_Star_Flag?Led_Time_Set[Led_Time_Set_Index]%10:10;