51 单片机入门笔记:江科大课程代码逐行讲解(按键控制 LED 流水灯)

1. 写在前面:这份笔记适合谁

这是跟着江科大(江协科技)51 单片机课程学习时整理的一份小白笔记。如果你刚接触单片机,看到满屏的 P3_0、0x01<<num、while(--j); 会一头雾水,那这篇笔记就是为你写的。

本文只讲一段代码------用两个按键控制 LED 流水灯左右移动。别看它短,里面包含了 51 单片机入门阶段几乎所有核心知识点:

  • 头文件与寄存器
  • 软件延时函数
  • 位操作与端口读写
  • 按键消抖
  • 循环移位与取反

下面逐行拆解。

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

void main()
{
    int num = 0;
    while(1)
    {
        if(P3_0 == 0)
        {
            Delay(20);
            while(P3_0 == 0);
            Delay(20);
            num++;
            if(num == 8){num = 0;}
            P2 = ~(0x01 << num);
        }
        if(P3_1 == 0)
        {
            Delay(20);
            while(P3_1 == 0);
            Delay(20);
            if(num == 0){num = 8;}
            num--;
            P2 = ~(0x01 << num);
        }
    }
}

3. 头文件:#include <REGX52.H>

c 复制代码
#include <REGX52.H>

这一行把 51 单片机的寄存器定义 引进来。有了它,你才能直接写 P2、P3_0 这样的名字。

  • P2、P3 是单片机的一组 8 个引脚,对应 8 个二进制位。
  • P3_0 表示 P3 端口的第 0 位(最低位)。

小白提示:REGX52.H 是 Keil 里 STC89C52 / AT89C52 常用的头文件。不同编译器可能叫 reg52.h,作用一样。

4. 延时函数:Delay

c 复制代码
void Delay(unsigned int xms)
{
    unsigned char i, j;
    while(xms--)
    {
        i = 2;
        j = 199;
        do
        {
            while(--j);
        }
        while(--i);
    }
}

这是江科大课程里最经典的软件延时写法,作用是「空转」一段时间,让程序停一停。

拆开看:

  1. while(xms--):传入多少毫秒,就循环多少次。xms-- 是先用后减,所以循环 xms 次。
  2. i = 2; j = 199;:内层循环的初值,这两个数决定了「1 毫秒」大概要空转多少圈。
  3. while(--j);:j 从 199 一直减到 0,减到 0 时条件为假,退出。注意这里分号前没有循环体,空语句就是「什么都不做,只消耗时间」。
  4. while(--i);:外层再套一层,i 从 2 减到 0。

小白提示:这种延时不精确,换晶振频率、换编译器优化等级,时间都会变。入门阶段够用,实际项目建议用定时器。

5. 主函数与变量

c 复制代码
void main()
{
    int num = 0;
    while(1)
    {
        ...
    }
}
  • void main():程序入口。
  • int num = 0;:记录当前点亮的是第几个 LED,取值 0~7,对应 8 个灯。
  • while(1):死循环。单片机程序不能「跑完就结束」,必须一直循环检测按键。

6. 按键检测与消抖

以 P3_0 这个按键为例:

c 复制代码
if(P3_0 == 0)
{
    Delay(20);
    while(P3_0 == 0);
    Delay(20);
    ...
}

6.1 为什么判断 == 0

51 单片机的按键一般接成按下接地,所以:

  • 没按下:引脚被上拉为高电平 1
  • 按下:引脚被拉到低电平 0

因此 P3_0 == 0 就表示「按键被按下了」。

6.2 为什么要 Delay(20)

机械按键在按下和松开的瞬间会产生抖动,电平会在 0 和 1 之间快速跳变几毫秒。如果直接判断,可能一次按下被识别成好几次。

Delay(20) 就是等 20 毫秒,等抖动过去再确认,这叫软件消抖。

6.3 while(P3_0 == 0); 是干什么的

这一句是等待松手 。只要按键还按着,程序就卡在这里不动。这样做的效果是:按住不放只算一次,必须松开再按才会触发下一次。

后面的 Delay(20) 是松手消抖,防止松开瞬间的抖动又被误判。

7. 核心:移位与取反点亮 LED

c 复制代码
num++;
if(num == 8){num = 0;}
P2 = ~(0x01 << num);

这三行是整段代码的精华,一步步看:

7.1 0x01 << num

0x01 的二进制是 0000 0001。左移 num 位:

num 0x01 << num 二进制
0 0000 0001
1 0000 0010
2 0000 0100
3 0000 1000
... ...
7 1000 0000

也就是「1 在第 num 位上」。

7.2 ~ 按位取反

51 开发板上的 LED 通常是低电平点亮(引脚输出 0 才亮)。所以要把「1 的位置」取反,变成「只有那一位是 0」:

复制代码
~(0000 0001) = 1111 1110

这样 P2 端口输出 1111 1110,只有最低位是 0,对应的那个 LED 亮,其余全灭。

7.3 num++ 与边界处理

c 复制代码
num++;
if(num == 8){num = 0;}

每按一次,num 加 1,灯就往左移一位。加到 8 时已经超出 0~7 的范围,所以归零,形成循环流水灯。

8. 反向移动:P3_1 按键

c 复制代码
if(P3_1 == 0)
{
    Delay(20);
    while(P3_1 == 0);
    Delay(20);
    if(num == 0){num = 8;}
    num--;
    P2 = ~(0x01 << num);
}

逻辑和上面完全对称,只是方向相反:

  • if(num == 0){num = 8;}:先判断再减。如果当前是 0,减 1 会变成 -1,越界了,所以先把它设成 8,减完正好是 7。
  • num--:灯往右移一位。

小白提示:注意这里 if 在 num-- 之前 ,顺序不能反。如果写成先减再判断,num 会先变成 -1,0x01 << -1 是未定义行为,程序会出怪问题。

9. 整体流程图

#mermaid-svg-yKXwlJteEe7s0lbM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yKXwlJteEe7s0lbM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yKXwlJteEe7s0lbM .error-icon{fill:#552222;}#mermaid-svg-yKXwlJteEe7s0lbM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yKXwlJteEe7s0lbM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yKXwlJteEe7s0lbM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yKXwlJteEe7s0lbM .marker.cross{stroke:#333333;}#mermaid-svg-yKXwlJteEe7s0lbM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yKXwlJteEe7s0lbM p{margin:0;}#mermaid-svg-yKXwlJteEe7s0lbM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yKXwlJteEe7s0lbM .cluster-label text{fill:#333;}#mermaid-svg-yKXwlJteEe7s0lbM .cluster-label span{color:#333;}#mermaid-svg-yKXwlJteEe7s0lbM .cluster-label span p{background-color:transparent;}#mermaid-svg-yKXwlJteEe7s0lbM .label text,#mermaid-svg-yKXwlJteEe7s0lbM span{fill:#333;color:#333;}#mermaid-svg-yKXwlJteEe7s0lbM .node rect,#mermaid-svg-yKXwlJteEe7s0lbM .node circle,#mermaid-svg-yKXwlJteEe7s0lbM .node ellipse,#mermaid-svg-yKXwlJteEe7s0lbM .node polygon,#mermaid-svg-yKXwlJteEe7s0lbM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yKXwlJteEe7s0lbM .rough-node .label text,#mermaid-svg-yKXwlJteEe7s0lbM .node .label text,#mermaid-svg-yKXwlJteEe7s0lbM .image-shape .label,#mermaid-svg-yKXwlJteEe7s0lbM .icon-shape .label{text-anchor:middle;}#mermaid-svg-yKXwlJteEe7s0lbM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yKXwlJteEe7s0lbM .rough-node .label,#mermaid-svg-yKXwlJteEe7s0lbM .node .label,#mermaid-svg-yKXwlJteEe7s0lbM .image-shape .label,#mermaid-svg-yKXwlJteEe7s0lbM .icon-shape .label{text-align:center;}#mermaid-svg-yKXwlJteEe7s0lbM .node.clickable{cursor:pointer;}#mermaid-svg-yKXwlJteEe7s0lbM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yKXwlJteEe7s0lbM .arrowheadPath{fill:#333333;}#mermaid-svg-yKXwlJteEe7s0lbM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yKXwlJteEe7s0lbM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yKXwlJteEe7s0lbM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yKXwlJteEe7s0lbM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yKXwlJteEe7s0lbM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yKXwlJteEe7s0lbM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yKXwlJteEe7s0lbM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yKXwlJteEe7s0lbM .cluster text{fill:#333;}#mermaid-svg-yKXwlJteEe7s0lbM .cluster span{color:#333;}#mermaid-svg-yKXwlJteEe7s0lbM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yKXwlJteEe7s0lbM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yKXwlJteEe7s0lbM rect.text{fill:none;stroke-width:0;}#mermaid-svg-yKXwlJteEe7s0lbM .icon-shape,#mermaid-svg-yKXwlJteEe7s0lbM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yKXwlJteEe7s0lbM .icon-shape p,#mermaid-svg-yKXwlJteEe7s0lbM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yKXwlJteEe7s0lbM .icon-shape .label rect,#mermaid-svg-yKXwlJteEe7s0lbM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yKXwlJteEe7s0lbM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yKXwlJteEe7s0lbM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yKXwlJteEe7s0lbM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
main 开始
num = 0
P3_0 按下?
消抖 + 等待松手
num++,到 8 归零
P2 = ~(0x01 << num)
P3_1 按下?
消抖 + 等待松手
num 为 0 则设为 8,再 num--
P2 = ~(0x01 << num)

10. 小白常见疑问

Q1:为什么 while(--j); 后面有个分号?

分号就是空循环体,表示「只做 --j 和判断,不做别的事」。这是延时函数的常见写法。

Q2:P2 = ~(0x01 << num); 能不能写成 P2 = 0xFE << num;?

可以,效果类似,但要注意左移溢出。用 ~ 的写法更直观,也更容易理解「哪一位是 0」。

Q3:为什么按键要接在 P3 而不是 P2?

因为 P2 已经接了 LED。实际开发板上按键和 LED 接在哪个口是硬件决定的,写代码前先看原理图。

Q4:int num 能不能改成 unsigned char?

可以,而且更省空间。这里用 int 只是习惯写法,num 最大只到 8,unsigned char 完全够用。

11. 小结

这段代码虽然只有几十行,但把 51 单片机入门的核心套路都串起来了:

知识点 对应代码
头文件与寄存器 #include <REGX52.H>、P2、P3_0
软件延时 Delay()
按键消抖 Delay(20) + while(P3_0 == 0);
位操作 0x01 << num、~
循环流水灯 num++ / num-- + 边界判断

建议你自己动手改一改:比如把延时改成 10ms 看看效果,或者让两个按键控制不同速度。改代码、看现象,是学单片机最快的方式。

相关推荐
前进的李工14 分钟前
Agent 学习笔记 12:Agent 的工具应该怎么设计
人工智能·笔记·学习·agent·工具调用·skill·工具设计
ever_you_love35 分钟前
2026.11.9录屏笔记
笔记
希和202739 分钟前
51 单片机按键控制 LED:江科大课程代码小白笔记
笔记·单片机·嵌入式硬件
希和202742 分钟前
51 单片机 LED 流水灯:从直接法到循环法(江科大课程笔记)
笔记·单片机·mongodb
Zwawa13 小时前
用两个按键做一个OLED菜单:先把选择和执行分开
c语言·单片机·嵌入式
Dev_XH15 小时前
【成品设计】基于CH32H417的CH32H417AIOT家庭微电网
单片机·嵌入式硬件·物联网·ai
一壶浊酒伴余生17 小时前
新概念英语第一册 · 句法笔记
笔记·英语学习·英语语法·新概念英语
我不会起名字32217 小时前
消费回调固定 return nil:错误传播与 Kafka 位点提交的区别
后端·mongodb·kafka·go·im·故障排查
蒸蒸yyyyzwd20 小时前
后端学习笔记 day111
笔记·学习