1. 这段代码在做什么
这是江科大 51 单片机课程里「按键控制 LED」的经典例程:每按一次独立按键(接在 P3.1),P2 口上的 8 个 LED 就按二进制加一显示一次。
代码里给了两种写法:
- 按键延时法 :靠
Delay(20)消抖,简单直接; - 按键标志位法 :用变量
p31记录按键上一次的状态,靠状态变化判断「按下」这个动作。
小白第一次看容易懵的地方是:P3_1 == 0 为什么表示按下?P2 = ~LEDnum 里的 ~ 是干嘛的?下面逐段拆开讲。
2. 头文件与延时函数
c
#include <REGX52.H>
void Delay(unsigned int xms)
{
unsigned char i, j;
while (xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
#include <REGX52.H>:Keil 里 51 单片机的寄存器定义头文件,有了它才能直接写P2、P3_1这种名字。Delay(unsigned int xms):软件延时函数,参数是「大约多少毫秒」。它靠两层空循环消耗时间,i = 2; j = 199;这组初值是在特定晶振(课程用的是 12MHz)下凑出来的,所以只能算近似延时,换晶振就不准了。- 注意
while (--j);后面直接跟分号,表示「循环体是空的」,只是让j一直减到 0。这是嵌入式里很常见的写法,小白别误以为是漏写了大括号。
3. 按键延时法(主函数)
c
void main()
{
unsigned char LEDnum = 0;
while (1)
{
if (P3_1 == 0)
{
Delay(20); // 按下后先等 20ms,滤掉抖动
while (P3_1 == 0); // 松手前一直卡在这里
Delay(20); // 松手后再等 20ms,滤掉松开时的抖动
LEDnum++;
P2 = ~LEDnum;
}
}
}
逐行理解:
unsigned char LEDnum = 0;:计数器,记录按了几次,范围 0~255,正好对应 8 位 LED 的显示。while (1):单片机程序的标准写法,主循环永不退出。if (P3_1 == 0):按键一端接 P3.1、另一端接地,按下时引脚被拉低为 0,所以判断 0 就是判断按下。Delay(20):机械按键按下瞬间触点会抖动,电平在 0/1 之间跳几下。等 20ms 再判断,抖动基本结束,这就是软件消抖。while (P3_1 == 0);:只要手还按着就一直空转,防止按住不放时LEDnum疯狂自增。等松手(电平回到 1)才继续往下走。Delay(20):松开瞬间同样有抖动,再等一次。LEDnum++;然后P2 = ~LEDnum;:计数加一,取反后送到 P2 口。
为什么是 ~LEDnum
开发板上的 LED 通常是共阳极 接法:P2 口输出 0 时 LED 才亮。而 LEDnum 是「亮几个」的正逻辑,所以要按位取反 ~ 再输出。
举例(只看低 4 位):
| LEDnum | 二进制 | ~LEDnum |
P2 输出效果 |
|---|---|---|---|
| 0 | 0000 0000 | 1111 1111 | 全灭 |
| 1 | 0000 0001 | 1111 1110 | 最低位 LED 亮 |
| 2 | 0000 0010 | 1111 1101 | 次低位 LED 亮 |
| 3 | 0000 0011 | 1111 1100 | 低两位 LED 亮 |
所以每按一次,LED 就像二进制计数器一样往上加。
4. 按键标志位法(注释掉的那段)
c
void main()
{
unsigned char LEDnum = 0, p31 = 0;
while (1)
{
if (P3_1 == 0 && p31 == 0)
{
p31 = 1;
LEDnum++;
P2 = ~LEDnum;
}
if (P3_1 == 1 && p31 == 1)
p31 = 0;
}
}
思路是:用 p31 记住「上一次按键是不是已经处理过了」。
- 第一个
if:当前是按下(P3_1 == 0)并且 之前没处理过(p31 == 0),才认为是一次新的按下,执行加一并把p31置 1。 - 第二个
if:等松手(P3_1 == 1)后把p31清 0,为下一次按下做准备。
这样按住不放时,p31 已经是 1,第一个条件不成立,LEDnum 不会连续自增------效果和延时法里的 while (P3_1 == 0); 一样,但主循环不会被卡住,其他逻辑还能继续跑。
5. 两种写法怎么选
| 对比项 | 按键延时法 | 按键标志位法 |
|---|---|---|
| 代码量 | 少,直观 | 稍多,要维护状态变量 |
| 消抖 | 靠 Delay 阻塞 |
靠状态判断,不阻塞 |
| 主循环 | 按住时被 while 卡住 |
始终流畅运行 |
| 适用场景 | 单个按键、简单实验 | 多按键、需要同时处理其他任务 |
小白阶段先用延时法把「按下---消抖---松手---执行」这条链路跑通,理解了再换成标志位法,就能体会到「阻塞」和「非阻塞」的区别,这也是后面学定时器、中断的铺垫。
6. 小白容易踩的坑
- 按键没反应:先确认按键接的是不是 P3.1,以及开发板上按键是「按下为低」还是「按下为高」,接反了判断条件要跟着改。
- 按一次跳好几个数 :多半是消抖没做或延时太短,把
Delay(20)调大一点试试。 - LED 全亮/全灭不变化 :检查
P2 = ~LEDnum;有没有漏掉~,共阳极板子不取反就是反的。 while (--j);报错或死循环 :注意j是unsigned char,减到 0 再减会回绕成 255,所以这里用do...while配合--j的写法要照抄,别随手改成while (j--)。- 延时不准:软件延时和晶振、编译器优化都有关,只适合做「大概等一会儿」,需要精确计时请用定时器。