51 单片机按键控制 LED:江科大课程代码小白笔记

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;
        }
    }
}

逐行理解:

  1. unsigned char LEDnum = 0;:计数器,记录按了几次,范围 0~255,正好对应 8 位 LED 的显示。
  2. while (1):单片机程序的标准写法,主循环永不退出。
  3. if (P3_1 == 0):按键一端接 P3.1、另一端接地,按下时引脚被拉低为 0,所以判断 0 就是判断按下。
  4. Delay(20):机械按键按下瞬间触点会抖动,电平在 0/1 之间跳几下。等 20ms 再判断,抖动基本结束,这就是软件消抖。
  5. while (P3_1 == 0);:只要手还按着就一直空转,防止按住不放时 LEDnum 疯狂自增。等松手(电平回到 1)才继续往下走。
  6. Delay(20):松开瞬间同样有抖动,再等一次。
  7. 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--)。
  • 延时不准:软件延时和晶振、编译器优化都有关,只适合做「大概等一会儿」,需要精确计时请用定时器。
相关推荐
希和202712 分钟前
51 单片机 LED 流水灯:从直接法到循环法(江科大课程笔记)
笔记·单片机·mongodb
Zwawa12 小时前
用两个按键做一个OLED菜单:先把选择和执行分开
c语言·单片机·嵌入式
Dev_XH15 小时前
【成品设计】基于CH32H417的CH32H417AIOT家庭微电网
单片机·嵌入式硬件·物联网·ai
国科安芯16 小时前
人在回路中的总线:商业空间站环境控制与生命保障系统CANFD网络的可靠性与功能安全
网络·嵌入式硬件·安全·架构·php·抗辐射
一壶浊酒伴余生16 小时前
新概念英语第一册 · 句法笔记
笔记·英语学习·英语语法·新概念英语
国科安芯17 小时前
算力上天之后的电与静:星上边缘计算载荷带外管理网络的低功耗调度与辐射容错
网络·人工智能·嵌入式硬件·安全·边缘计算·低轨卫星·商业航天
蒸蒸yyyyzwd19 小时前
后端学习笔记 day111
笔记·学习
t139701819 小时前
MPPT 和 PWM 有什么区别?小功率太阳能供电到底该选哪个
嵌入式硬件·物联网
叼烟扛炮20 小时前
[LangChain] 学习笔记(08):工具调用底层闭环与第三方搜索(Tavily)实战
人工智能·笔记·学习·langchain