:从点亮第一盏LED开始)
【51单片机入门】从点亮第一盏LED开始四个实验开启我的嵌入式学习写在前面8 月 12 日白天还在对着屏幕练数据结构和算法到了晚上突然想换换脑子于是把之前买来、却一直没有真正学下去的 51 单片机开发板重新翻了出来。说起来我第一次接触单片机还是去年夏天。刚知道自己被软件工程专业录取时想着提前接触一些专业内容就找到了江协科技的 51 单片机入门教程。可那时候连 C 语言都没有系统学过看着代码里的循环、函数、寄存器和 IO 口几乎全程懵硬撑着看了两三集之后这件事也就暂时搁置了。转眼大一学年已经结束。从最基础的 C 语言语法到函数、数组、指针再到后来自己实现顺序表、单链表和双向链表至少现在再看到单片机代码时不会先被 C 语言本身挡住了。这样一来学习重点也终于可以从“这段代码是什么意思”慢慢转向“这段代码为什么能控制硬件”。而嵌入式本身也是我很感兴趣的一个方向。天天和控制台、算法题打交道久了也很想体验一下代码真正作用在物理器件上的感觉。所以 8 月 12 日晚我正式重新开始了 51 单片机学习并从最基础的 LED 入手一口气完成了四个小实验点亮一个 LEDLED 闪烁LED 流水灯封装毫秒延时实现渐变加速流水灯这也是我 CSDN 上第一篇正式的嵌入式方向学习记录算是给后面的硬件学习开一个头。本篇源码仓库Code_2026 / 嵌入式-单片机 / C51四个实验均按独立文件夹保存并附有编译生成的.hex文件方便后续复习和烧录测试。一、正式开始前先认识一下 51 单片机1.1 什么是 51 单片机单片机Microcontroller可以简单理解成把 CPU、存储器、IO 接口、定时器等功能集成在一块芯片里的微型计算机。而平时学习中常说的“51 单片机”并不是特指某一个芯片型号而是一类兼容经典 8051 架构的单片机。我目前使用的是比较常见的STC89C52RC对于刚开始学习嵌入式来说51 的优势很明显资料多、外设简单、成本低而且很多硬件现象都能直接看到非常适合用来建立“软件怎样控制硬件”的基本认识。相比直接上 STM32先通过 51 把 IO、电平、寄存器、定时器、中断这些概念过一遍对我来说也更容易接受。1.2 目前使用的开发环境现阶段主要使用Keil uVision5负责写代码 → 编译 → 生成.hex固件文件51 单片机代码依然使用 C 语言但会接触到很多和普通控制台程序完全不同的内容比如寄存器、IO 端口以及硬件相关头文件。STC-ISP主要负责把 Keil 编译出来的.hex文件烧录进 STC 单片机。另外它还提供了一些很方便的小工具比如本篇使用的软件延时函数就可以根据晶振频率直接生成。开发板本次实验使用 STC89C52RC 开发板板载 LED、按键、数码管等基础外设。目前四个实验主要使用P2 端口 板载 LED。二、第一次真正理解代码是怎样把 LED 点亮的在普通 C 语言里printf(Hello World);最终只是在屏幕上显示一句话但到了单片机里P2 0xFE;却可以真的让开发板上的 LED 亮起来这背后首先要理解几个最基础的概念。1. IO 口单片机需要通过 IO 引脚和外部器件交互。51 中常见的是P0、P1、P2、P3每组端口包含 8 位因此可以分别控制多个引脚。本篇实验主要使用P2。2. 高低电平目前可以先简单理解为数字电路中的两种逻辑状态高电平 → 逻辑1低电平 → 逻辑0而对于我当前使用的这块开发板LED 采用的是低电平点亮的接法。所以0→ LED 亮1→ LED 灭这也是为什么后面的代码中1111 1110最低位为0时第一个 LED 会被点亮。3. P0 口和上拉电阻经典 51 架构中P0 口的输出结构和 P1P3 不完全一样使用时通常还需要考虑外部上拉。这部分目前我还没有实际操作先记住它和其他几个端口存在区别等后面真正使用 P0 时再结合电路理解。4. 软件延时入门实验暂时还没有学习定时器所以 LED 的延时主要通过程序不断执行循环来消耗 CPU 时间。这种方法很好理解也方便观察现象但缺点同样明显CPU 在延时期间基本一直被占用。后面学习到定时器之后还会再回来解决这个问题。三、实验一点亮第一盏 LED3.1 实验目标先从单片机版的“Hello World”开始让第一个 LED 一直亮着。代码如下#include REGX52.H int main() { while(1) { P20xFE;//1111 1110 } }第一次看到P2 0xFE;其实有点像之前 C 语言里的普通赋值。但这里的P2对应的是单片机 IO 端口给它赋值之后实际上改变的是硬件引脚上的电平状态。0xFE转成二进制为1111 1110其中最低位是0。由于当前开发板上的 LED 为低电平点亮所以对应的第一盏 LED 就会亮起。REGX52.H则是 51 单片机开发中非常常见的头文件其中定义了P2等特殊功能寄存器包含之后才能直接在代码中使用这些名称。至于while(1)也是嵌入式程序中非常常见的结构。单片机通常需要上电以后持续运行任务而不是像普通控制台程序那样完成一次运算就退出因此主函数通常都会进入一个长期循环。当第一盏 LED 真正亮起来的时候虽然代码只有几行但感觉还是和普通 C 语言很不一样代码第一次真正影响到了屏幕以外的东西。四、实验二让 LED 闪起来单纯常亮之后下一步自然就是让 LED 一亮一灭。完整代码#include REGX52.H #include INTRINS.H void Delay500ms(void) //11.0592MHz { unsigned char data i, j, k; _nop_(); i 4; j 129; k 119; do { do { while (--k); } while (--j); } while (--i); } int main() { while(1) { P20xFE; Delay500ms(); P20xFF; Delay500ms(); } }这里相比第一个实验多出来的核心就是延时函数。本次使用的晶振频率为11.0592 MHz延时函数通过 STC-ISP 的软件延时工具生成并使用_nop_();进行空操作因此需要包含#include INTRINS.H主循环的逻辑实际上非常简单点亮 ↓ 等待 500 ms ↓ 熄灭 ↓ 等待 500 ms ↓ 重新开始P2 0xFE时第一个 LED 点亮而P2 0xFF;对应1111 1111八位全部为高电平因此 LED 熄灭。不断重复之后就形成了肉眼可以观察到的周期性闪烁。五、实验三八个 LED 开始“跑”起来会控制一个灯以后第三个实验开始一次控制整个 P2 端口的八位让 8 个 LED 依次点亮形成流水灯效果。代码#include REGX52.H #include INTRINS.H void Delay500ms(void) //11.0592MHz { unsigned char data i, j, k; _nop_(); i 4; j 129; k 119; do { do { while (--k); } while (--j); } while (--i); } int main() { while(1) { P20xFE;//1111 1110 Delay500ms(); P20xFD; Delay500ms(); P20xFB; Delay500ms(); P20xF7; Delay500ms(); P20xEF; Delay500ms(); P20xDF; Delay500ms(); P20xBF; Delay500ms(); P20x7F; Delay500ms(); } }观察这些十六进制数FE FD FB F7 EF DF BF 7F转换成二进制以后可以发现那个唯一的0在不断移动1111 1110 1111 1101 1111 1011 1111 0111 1110 1111 1101 1111 1011 1111 0111 1111因为当前开发板是低电平点亮 LED所以这个不断移动的0也就对应着不断移动的亮灯位置。这也是我第一次比较直观地感受到一个 8 位二进制数可以直接对应 8 个真实 IO 引脚的状态。以前学位运算的时候更多还是在纸面和算法题里处理0、1到了单片机这里这些位终于有了很直观的硬件含义。目前为了方便理解我还是采用逐个赋值的方式。等后面继续学移位操作和更灵活的写法之后再回来优化这一版流水灯。六、实验四把固定延时改成通用延时前三个实验里使用的都是固定Delay500ms();但如果每次都重新写一个Delay100ms()、Delay200ms()代码显然不够灵活。所以第四个实验开始尝试利用之前已经学过的函数参数把延时函数封装得更通用一些。代码#include REGX52.H #include INTRINS.H void Delay1ms(unsigned int xms) //11.0592MHz { unsigned char data i, j; while(xms) { _nop_(); i 2; j 199; do { while (--j); } while (--i); xms--; } } int main() { while(1) { P20xFE;//1111 1110 Delay1ms(900); P20xFD; Delay1ms(800); P20xFB; Delay1ms(700); P20xF7; Delay1ms(600); P20xEF; Delay1ms(500); P20xDF; Delay1ms(400); P20xBF; Delay1ms(300); P20x7F; Delay1ms(200); } }这里最大的变化并不在 LED而在void Delay1ms(unsigned int xms)延时长度不再固定而是通过参数传入。例如Delay1ms(900);就是大约延时 900 ms而Delay1ms(200);就是大约延时 200 ms。主函数中依次使用900 800 700 600 500 400 300 200所以每一盏灯停留的时间都会逐渐缩短最终表现出来的效果就是流水灯越来越快。这个实验其实没有使用什么新的 C 语言知识但恰好让我体会到了之前学函数封装时说的“代码复用”。以前只是在控制台程序里理解把重复代码封装成函数。现在它变成了写一个通用延时函数就可以直接控制真实硬件的运行节奏。同一个知识点放到硬件里体验完全不一样。七、四个实验放在一起看我到底学了什么如果只看最终效果这四个实验其实都很简单实验实现效果主要认识点亮 LED单灯常亮IO 与高低电平LED 闪烁周期亮灭软件延时流水灯8 灯依次点亮端口与二进制位渐变流水灯流速逐渐加快参数与函数封装但是对我来说更重要的是开始建立几个以前写普通 C 程序时没有的概念第一变量和赋值有可能直接对应硬件状态。P2 0xFE不再只是修改一个普通变量而是真的改变了一组 IO 引脚。第二二进制位开始和实际电路一一对应。以前看到1111 1110只是一个数字。现在看到它会开始想到哪一位是低电平哪一个 LED 会亮第三程序运行的时间也开始产生物理现象。500 ms 和 200 ms 不只是两个数字它们会直接决定 LED 在眼睛里到底闪多快、跑多快。这些应该才是第一天学习 51 单片机最值得记录的东西。八、写在最后去年看不懂的东西现在终于重新开始了8 月 12 日重新打开 51 单片机教程时我最大的感受其实不是“单片机变简单了”而是和去年相比我终于有了一些能够继续往下学的基础。去年看教程时循环、函数、变量、参数本身都还是问题所以硬件内容和 C 语言内容全部混在一起几乎每一行代码都需要停下来理解。现在再来看C 语言部分已经不再是主要障碍。至少看到while(1)知道为什么要循环看到Delay1ms(500);知道参数是怎么传进去的看到P2 0xFE;也可以把重点放在 P2 和实际硬件之间到底发生了什么。这让我更加确定了一点有些东西第一次学不下去不一定是内容本身学不会也可能只是前置知识还没有补齐。而且相比单纯在屏幕上输出结果单片机确实带来了完全不一样的反馈。自己写下的一行代码烧录进去之后面前的 LED 真的会亮、会灭、会按照设定的节奏跑起来。第一次真正产生了一种代码开始从屏幕走向现实世界。当然目前这四个实验还只是最基础的起点。软件延时本身就有明显局限流水灯的写法也还有很多可以继续优化的地方。但第一天的目标本来就不是一步做到多复杂而是先把开发环境跑通、理解最基础的 IO 控制并真正开始动手。接下来还会继续学习独立按键、数码管、定时器、中断以及更多基础外设。51 只是嵌入式学习的第一站后面也会继续往 STM32 等方向深入。至少这一次去年只看了几集就搁置的单片机学习终于正式重新开始了。本篇代码仓库四个实验的源码及编译后的.hex文件均已整理到 Code_2026 / 嵌入式-单片机 / C51后续 51 单片机相关实验也会继续整理在这个目录中。