这段代码是基于51单片机(如STC89C52)的一个典型的倒计时定时器程序。它包含了按键扫描、数码管显示(动态扫描)、LED控制以及定时器中断功能。
代码整体逻辑是清晰的(采用了“大循环+时间片”轮询架构),但存在几个严重的编译错误和逻辑Bug,会导致程序无法编译或运行不正常。
以下是详细的问题分析和修改后的完整代码:
1. 主要问题分析
-
致命的编译错误(特殊字符):
- 在
Timer0_Init函数中,ET0 = 1;和EA = 1;前面和注释中包含了不可见的非ASCII字符(通常是从网页或文档复制时带入的全角空格)。这会导致编译器报错Syntax Error。
- 在
-
逻辑错误:
switch-case穿透:- 在
Key_Proc函数的case 2后面缺少break;。这意味着按下“按键2”后,会执行完代码,紧接着错误地执行case 3的代码。
- 在
-
命名笔误:
- 数码管处理函数写成了
Sey_Proc(应该是Seg_Proc),变量也写成了Sey_Slow_Down。虽然不影响编译(只要一致就行),但为了代码规范,建议修改。
- 数码管处理函数写成了
-
倒计时逻辑隐患:
- 在中断中,
Timer_Count--当计数为0时,unsigned char会溢出变成 255,然后你马上重置为0。虽然现象上看起来是停在0,但逻辑上建议用if(Timer_Count > 0)来判断是否需要减。
- 在中断中,
2. 修改后的完整代码
我已经修复了上述错误,清理了特殊字符,并添加了必要的注释。
C
/*头文件声明区域*/
#include <REGX52.H>
/* 注意:Key.h 和 Seg.h 需要你自己工程里有这两个文件,否则会报错 */
#include "Key.h"
#include "Seg.h"
/*变量声明区域*/
unsigned char Key_Slow_Down; // 按键减速专用变量 10ms
unsigned char Seg_Slow_Down; // 数码管减速专用变量 500ms (原 Sey_Slow_Down)
unsigned char Key_Val, Key_Down, Key_Old; // 按键扫描专用变量
unsigned char Seg_Pos; // 数码管扫描变量
unsigned char Seg_Buf[6] = {10, 10, 10, 10, 10, 10}; // 数码管显示数据存放数组
/* 状态变量 */
unsigned char Seg_Mode; // 0-显示模式 1-设置模式
unsigned int Timer_1000ms; // 1000ms计数
unsigned char Timer_Count = 30; // 倒计时当前值
bit System_Flag; // 0-暂停 1-开始
unsigned char Set_Dat[3] = {15, 30, 60}; // 设置可选参数
unsigned char Set_Dat_Index = 1; // 参数索引
unsigned int Timer_500ms; // 500ms闪烁标志
bit Seg_Flag; // 数码管闪烁标志位
/*按键处理函数*/
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down = 1; // 进入按键减速,需要在中断中清零
Key_Val = Key_Read(); // 读取按下的键码值
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 捕捉下降沿
Key_Old = Key_Val; // 辅助扫描
switch(Key_Down)
{
case 1: // 启动
if(Seg_Mode == 0)
System_Flag = 1;
break;
case 2: // 复位/重置
if(Seg_Mode == 0)
{
System_Flag = 0; // 建议复位时先暂停
Timer_Count = Set_Dat[Set_Dat_Index];
}
break; // 【修复】之前漏掉了break,会导致穿透
case 3: // 切换模式 (显示 <-> 设置)
if(Seg_Mode == 1) // 如果是从设置模式退回到显示模式
{
Timer_Count = Set_Dat[Set_Dat_Index]; // 更新倒计时时间
System_Flag = 0; // 切换回主界面时通常暂停等待开始
}
Seg_Mode ^= 1; // 切换模式
break;
case 4: // 修改设置参数
if(Seg_Mode == 1)
{
if(++Set_Dat_Index == 3)
Set_Dat_Index = 0;
}
break;
}
}
/*数码管信息处理函数*/
void Seg_Proc() // 【修复】名称规范化
{
if(Seg_Slow_Down) return;
Seg_Slow_Down = 1;
Seg_Buf[0] = Seg_Mode + 1; // 首位显示模式:1-运行界面,2-设置界面
if(Seg_Mode == 0) // 系统处于显示界面
{
Seg_Buf[4] = Timer_Count / 10 % 10;
Seg_Buf[5] = Timer_Count % 10;
}
else // 系统处于设置界面
{
if(Seg_Flag == 1) // 闪烁效果
{
Seg_Buf[4] = Set_Dat[Set_Dat_Index] / 10 % 10;
Seg_Buf[5] = Set_Dat[Set_Dat_Index] % 10;
}
else
{
Seg_Buf[4] = 10; // 10代表熄灭(取决于你的Seg.h定义)
Seg_Buf[5] = 10;
}
}
}
/* LED和蜂鸣器处理函数 */
void Led_Proc()
{
// 当倒计时归零且不在设置模式时
if(Timer_Count == 0 && Seg_Mode == 0)
{
P1 = 0x00; // 所有LED全亮
P2_3 = 0; // 蜂鸣器/继电器开启
}
else
{
P1 = 0xFF; // LED全灭
P2_3 = 1; // 蜂鸣器关闭
}
}
/*定时器0初始化函数*/
void Timer0_Init(void) // 1毫秒@12.000MHz
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x18;
TH0 = 0xFC;
TF0 = 0;
TR0 = 1;
ET0 = 1; // 【修复】删除了这里的非法隐藏字符
EA = 1; // 【修复】删除了这里的非法隐藏字符
}
/*中断服务函数*/
void Timer0Server() interrupt 1
{
TL0 = 0x18;
TH0 = 0xFC;
// 时间片处理
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0; // 修改为Seg
// 数码管刷新 (每1ms刷新一位,假设是6位共阴/共阳)
if(++Seg_Pos == 6) Seg_Pos = 0;
Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos]); // 需要 Seg.h 支持
// 倒计时逻辑
if(System_Flag == 1)
{
if(++Timer_1000ms == 1000)
{
Timer_1000ms = 0;
if(Timer_Count > 0) // 【优化】防止溢出
{
Timer_Count--;
}
else
{
// 倒计时结束,停止系统
System_Flag = 0;
}
}
}
// 闪烁标志翻转 (500ms)
if(++Timer_500ms == 500)
{
Timer_500ms = 0;
Seg_Flag ^= 1;
}
}
/*主函数程序*/
void main()
{
Timer0_Init();
while(1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
3. 给你的建议
-
关于头文件: 代码中使用了
<Key.h>和<Seg.h>。这两个不是标准库文件,而是你自己(或你的模板)写的。请确保你的工程文件夹里有Key.c,Key.h,Seg.c,Seg.h并且已经添加到工程目录(Project Source Group)中,否则编译会报错Source file not found。 -
关于中断刷新:
Seg_Disp放在中断里是非常好的做法(保证显示不闪烁)。 -
调试技巧: 如果你发现LED灯乱闪或者程序跑飞,大概率是因为
Timer0_Init里的那些看不见的字符导致配置没生效,直接复制我上面修改后的代码即可。