现在我有完整的 main.c 代码了。让我仔细分析这个问题的根源。关键点:
unsigned int tm0count; // 没有 volatile!
以及定时器0中断:
void TIMER0_ISR(void) interrupt TMR0_VECTOR
{
if(tm0count > 0)
{
tm0count--;
}
}
主循环:
tm0count = 50000;
while(tm0count > 1);//printf_usb("tm0count:%d",tm0count);
...
if(s1==0)led1 = 0;
else led1 = 1;
if(s2==0)led2 = 0;
else led2 = 1;
我来分析这个问题的根因。
问题1:tm0count 没有声明为 volatile
这是最关键的问题。tm0count 在定时器0中断服务程序中递减,在主循环 while(tm0count > 1) 中读取。由于没有 volatile 修饰,Keil C251 在优化时,while(tm0count > 1) 循环会一次性读取 tm0count 到寄存器,然后一直判断寄存器值而不再重新从内存读取,导致中断里对 tm0count 的递减根本不影响循环判断 → 死循环。
问题2:while(tm0count > 1) 后面跟了分号——这会形成一个死循环,只有当 tm0count 递减到 ≤1 时才会退出。但因为没有 volatile,循环永远不会读到变化 → 程序卡死在 while 里,后面的 LED 控制代码永不执行。
但你又看到 LED 常亮。在主循环开始前有 led1 = 0; led2 = 0;(点亮),如果程序卡死在 while 里,LED 就保持点亮常亮状态。这完全吻合!
问题3:按键控制逻辑
if(s1==0)led1 = 0; // 按键按下(低电平)→ led 亮(0)
else led1 = 1; // 否则 led 灭(1)
这个逻辑本身没错,但问题是它被 while 死循环阻断了,根本没执行到。
让我进一步确认初始化时序问题。还有一个非常关键的点:主循环里先 tm0count = 50000; 然后 while(tm0count > 1);,但同时 SYS_Init() 里也定时器0中断已经开始计数递减。而且 USBLIB_Init() 是在 TIMER0_Init() 之后调用的,且 EnableGlobalInt() 在整个初始化最后才打开。
让我再确认定时器0的配置。注意 TIMER0 用的是模式2(8位自动重载),预分频 T0_PSCR=3。
让我也确认一下这个 while 死循环里其实还有一个更核心的逻辑问题——即使加了 volatile,定时器0模式2是 8 位定时器,定时间隔比较短(约20us),50000 次大概是 1 秒钟,这个延时是可以退出的。真正卡死的原因就是没有 volatile,循环读不到递减。
不过还有一个隐患我要确认——在 while(tm0count > 1); 这里如果中断被禁用了,或者定时器中断没触发,也会卡死。但定时器配置看起来较合理。
让我给出完整解答。这个问题的核心就是 tm0count 缺少 volatile。
问题根源分析
楼主你好,看到完整代码后,问题已经非常明确了。核心原因有两点,其中第一点是致命的:
一、致命问题:tm0count 缺少 volatile 声明(导致死循环卡死)
看你的代码:
unsigned int tm0count; // ← 缺少 volatile 关键字!
这个变量在中断里被递减、在主循环里被读取判断:
// 定时器0中断里
void TIMER0_ISR(void) interrupt TMR0_VECTOR
{
if(tm0count > 0) tm0count--;
}
// 主循环里
tm0count = 50000;
while(tm0count > 1); // 死循环卡死在这里
在 Keil C251 编译优化后,while(tm0count > 1) 这条语句只会从内存中读取一次 tm0count 到寄存器,然后持续判断那个寄存器的值,永远不会再回内存重新读取。因此:
- 中断里对
tm0count 的递减,根本不会影响主循环的判断;
- 于是
while(tm0count > 1) 变成真正的死循环,永远退不出来;
- 程序卡死在这里,后面的 LED 按键控制代码永远不会被执行;
- 而主循环之前你已经执行了
led1 = 0; led2 = 0;(点亮 LED),所以两个 LED 就一直保持常亮,看起来"该灭不灭"。
解决办法: 给这个跨中断访问的变量加上 volatile:
volatile unsigned int tm0count;
volatile 告诉编译器:该变量可能在程序以外被修改(这里就是中断),每次使用都必须从内存重新读取,禁止优化进寄存器。加法一行即可。
二、次要问题:while 后面直接加分号,是个阻塞式死循环
tm0count = 50000;
while(tm0count > 1); // ← 分号结尾,整个主循环卡在这等
即使加了 volatile,这种写法也是阻塞式延时——程序会一直占着 CPU 等到计数递减完,期间无法处理按键等其他事情。你注释掉的 /* led1=1; led2=0; ... */ 那段同样被阻塞。通常建议把这种"计数等待"改成非阻塞方式(每轮主循环只判断一次,不空转),这样按键扫描才能及时响应。
三、补充确认:定时器0配置本身是合理的
你的 TIMER0_Init() 配置为模式2(8位自动重载)+ 1T + 预分频3,20us 定时周期,中断向量 TMR0_VECTOR(对应 Keil 中断号1)。这部分配置基本正常,只要 volatile 补上,中断递减能正常工作,50000 次约 1 秒后循环即可退出。
推荐修改后的代码片段
// 全局变量:在中断与主循环间共享,必须加 volatile
volatile unsigned int tm0count;
void main(void)
{
SYS_Init();
led1 = 0;
led2 = 0;
while (1)
{
// 非阻塞的软件延时(每轮主循环判断一次即可,不空转)
tm0count = 50000;
while (tm0count > 1)
; // 等待定时器0中断把计数减到 1 以下
// 按键扫描
if (s1 == 0) led1 = 0; // 按下(低电平)→亮
else led1 = 1; // 松开→灭
if (s2 == 0) led2 = 0;
else led2 = 1;
}
}
说明:上面 while (tm0count > 1); 加了 volatile 后能正常退出,但仍是阻塞式。如果你希望主循环能同时处理其他任务(如 USB、按键消抖),建议把这段延时改为非阻塞的倒计时判断,避免长时间空转占用 CPU。
总结
| 问题 |
原因 |
解决 |
| LED 常亮、程序"卡死" |
tm0count 缺 volatile,while 死循环读不到中断递减 |
声明为 volatile unsigned int tm0count; |
| 按键控制代码不执行 |
被上面的 while 死循环阻断 |
加 volatile 后即可执行;进一步建议改非阻塞 |
如果你还希望我把这段带非阻塞倒计时延时的完整 main.c 重写给你,或者帮你确认 USB 库、看门狗等其他初始化的潜在问题,可以继续说,我来帮你完善。
🔍 搜索 "在 Keil C251编完了之后 tm0count一直都是坏的状态 因为发现 LED1、LED2一直常亮"
⏱️ 查找用时 1.5s | 总回答用时 14.8s