51单片机基础教程(二):单片机的“Hello World“-Keil+Proteus携手驱动LED
本文详细介绍了51单片机控制LED流水灯的完整开发流程。首先在Proteus中搭建包含AT89C51单片机和LED的电路,重点说明P0口需外接上拉电阻。接着在Keil中编写C51程序实现LED闪烁,包括延时函数设计和端口控制。通过生成HEX文件进行联合仿真调试。文章还提供了多种流水灯进阶实现方案:左右位移、循环流水以及左右点亮跑马灯等,展示了位移法和逻辑取反技巧。最后指出当前方案的不足和改进方向,
一、序言:从搭建环境到动手实战
在上一篇中,我们成功搭建了Keil C51和Proteus的开发环境,指尖跃跃欲试!本篇我们将正式动手,完成首个单片机实战项目:使用51单片机控制LED灯闪烁,完成简单的流水灯。
二、项目目标
- 硬件模拟:在Proteus中构建包含51单片机和LED的电路
- 软件开发:在Keil中编写C51程序实现LED闪烁
- 联合调试:将Keil生成的
.hex文件加载到Proteus仿真
三、实战步骤详解
Step 1:构建Proteus电路(硬件模拟)
- 启动Proteus:创建新工程


- 选择新工程这里的名称和路径,最佳的选择是纯英文,将工程保存在自己方便查找并打开的位置,放置完毕后选择Next进入下一步

- 这里按照图中所选即可,随后的窗口,我们均默认,直接点击Next

- 图示为最后一个总结窗口,这边我们点击Finish,就完成了新工程的创建,进入如图所示的原理图绘制界面。

-
放置核心元件:
在原理图的左上角,我们选到元件模式
-


-
在弹出的元器件界面中,我们依次搜索并选择以下器件
-

-
AT89C51或者AT89C52(单片机)(二选一)
LED-GREEN(LED灯)
RES(电阻) - 需要注意的是,LED灯实际上就是发光二极管,三角形朝向即电流的方向

- 由于单片机的驱动电流通常较小,我们常常采用让单片机连接到LED的阴极方式进行点亮,所以我们还需要为LED灯添加一个电源power


- 双击电阻可以修改它的属性,这里我们调整电阻阻值为100Ω即可
- 完整电路连接示意图:
在51单片机中,P0口是双向口,作为通用的I/O口使用时,这时需外加上拉电阻,成为准双向口。P1口、P2口、P3口均为准双向口,内部有上拉电阻。

Step 2:编写Keil程序(软件开发)
1.打开keil

点击后我们会跳转到保存工程文件的界面,根据上一篇我们说的最好是与Proteus工程放在同一父目录下,方便我们寻找调试,这里博主就新建了一个keil的文件夹,将工程放置在keil_prj文件夹下,文件名可以根据需要自己修改,建议与Proteus工程名一致。
接下来keil会要求我们选择对应的设备型号,博主电脑因为同时安装了MDK-ARM和C51,界面如下,如图选择
之后我们直接搜索AT89C51(根据Proteus选择的单片机型号选择),点击OK即可
之后会让我们选择是否添加引导文件到工程中,这一步我们选择是和否都可以,博主选择"是"
在keil工程界面,我们打开左侧工程栏的各级目录,最后在Source Group1中右键,选择添加新的文件进入该群组,注意这里的文件我们要选择C而不是C++,否则无法正常编译,文件名可以根据需要自取,这里博主命名为main


点击Add添加后,我们就进入了c文件的编写。

头文件根据所选单片机型号,常用的有reg51.h,reg52.h等,这里博主是AT89C51,选择了reg51.h,对于12MHZ晶振,我们采用了阻塞式的延迟,就是让单片机进行for循环,完成这个for循环的时间大概是1ms,这样我们就完成了阻塞式的1ms级的Delay函数,但是阻塞式的延时会造成单片机卡死在延时函数中,无法进行其他工作,后续我们将教学非阻塞式的延时。提供的代码如下,可以直接使用。
#include <reg51.h>
void Delay(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 114; j++); // 12MHz晶振的近似延时
}
void main(void) {
while(1) {
P1 = 0xFE; // 点亮P1.0 LED 11111110 即最低位为低电平,LED导通
Delay(500);
P1 = 0xFF; // 熄灭LED 11111111 P1端口均为高电平,LED不导通
Delay(500);
}
}
在编译之前,我们要生成Proteus所需的HEX文件,用于装载在Proteus上我们刚刚调出的单片机上。我们点击这个小魔法棒,在弹出的界面中如图选择即可。


之后我们点击编译,构建即可,下方状态栏显示"0 Error(s), 0 Warning(s)."即完成编译


生成的HEX文件在刚刚放置KEIL工程的Object文件夹中,等会我们会用到,如果没有hex文件,请检查上一步操作是否完成,编译是否正确。


Step 3:联合仿真
- 双击Proteus中的单片机芯片
- 在
Program File载入Keil生成的.hex文件 - 设置
Clock Frequency为12MHz 

- 点击仿真按钮(▶️)观察LED闪烁

四、排错锦囊
|
现象 |
可能原因 |
解决方案 |
|
LED不亮 |
极性接反/限流电阻过大 |
检查LED方向/改用100Ω电阻 |
|
常亮/常灭 |
程序逻辑错误 |
检查while循环内的电平切换 |
|
闪烁速度异常 |
延时参数不匹配 |
调整Delay()的循环次数 |
五、LED流水灯进阶实现
Step 1:硬件电路升级 在原电路基础上增加LED数量(建议P1口全接)

Step 2:编写Keil程序(软件开发)
1.左or右流水灯-位移法:
#include <reg51.h>
void Delay(unsigned int ms)
{
unsigned int i, j;
for(i=0; i<ms; i++)
for(j=0; j<114; j++);
}
void main()
{
unsigned char led = 0x01; // 初始模式:0000 0001 这里采用更实用的反向逻辑,1代表亮灯
while(1)
{
P1 = ~led; //采取反向逻辑后,注意要取反,或者根据实际电路连线方式调整
Delay(200);
led = led << 1; // 将1左移,即向左顺次点亮,右移则为led >> 1
if(led == 0x00) led = 0x01; // 持续左移后,LED将全灭,循环复位
}
}
然后依照之前相同的步骤,将程序在Proteus上运行观察效果。

2.左右循环流水灯-位移法
即:首先是往左的跑马灯,当最左边的LED点亮后,变成往右跑马灯,最右LED亮后又变为往左。
#include<reg51.h>
// 延时函数,粗略延时 1ms
void Delay(unsigned int ms)
{
unsigned int i, j;
for(i=0; i<ms; i++)
for(j=0; j<114; j++); // 空循环,用于产生延时
}
void main()
{
bit DIR = 0; // 方向标志,0 表示左移,1 表示右移
unsigned char LED = 0x01; // 初始LED显示状态,P1.0亮
while (1)
{
P1 = ~LED; // 将LED状态取反后输出到P1口
if(DIR == 0)
{
Delay(100); // 延时一段时间
LED = LED << 1 ; // 左移一位
if(LED == 0x80) // 如果移到最左位,切换方向
DIR = 1 ;
}
else
{
Delay(100); // 延时一段时间
LED = LED >> 1; // 右移一位
if(LED == 0x01) // 如果回到最右位,切换方向
DIR = 0;
}
}
}
依照相同的步骤,将程序在Proteus上运行观察效果。
3.左右点亮跑马灯-位移法
左右点亮跑马灯:也是左右跑马灯,但是每次跑到边缘都会点亮边上的LED(常亮),然后常亮的LED灯就被排除在跑马灯的范围。
#include <reg51.h>
// 延时函数,大约延时 1ms
void Delay(unsigned int ms)
{
unsigned int i, j;
for(i=0; i<ms; i++)
for(j=0; j<114; j++); // 空循环,用于产生延时
}
void main(void)
{
unsigned char LED = 0x01; // 初始点亮最低位的LED
unsigned char mask = 0; // 用于记录已经点亮过的LED位置
P1 = ~LED; // 将初始状态输出到P1端口,反向逻辑
Delay(500); // 延时一段时间
while(1)
{
if(mask == 0xff) // 如果所有LED都已经被点亮过
{
mask = 0; // 重置mask
}
// 左移过程
while(1)
{
P1 = ~(LED | mask); // 输出当前LED状态加上已点亮的LED,注意取反因为反向逻辑
Delay(500); // 延时
LED = LED<<1; // 当前LED左移一位
if(((LED<<1) | mask) == mask) // 检查是否达到最左端(避免溢出问题)
{
mask |= LED; // 更新mask,将当前LED位置加入已点亮列表
break; // 退出循环,进入右移过程
}
}
// 右移过程
while(1)
{
P1 = ~(LED | mask); // 输出当前LED状态加上已点亮的LED,注意取反因为反向逻辑
Delay(500); // 延时
LED = LED>>1; // 当前LED右移一位
if(((LED>>1) | mask) == mask) // 检查是否回到最右端(避免溢出问题)
{
mask |= LED; // 更新mask,将当前LED位置加入已点亮列表
break; // 退出循环,再次进入左移过程
}
}
}
}
六、结语:从闪烁到流光的蜕变
通过本项目的完整实践,我们完成了从单LED控制到复杂流水灯效果的进化,在这个过程中:
掌握了51单片机开发的核心流程
- Proteus电路设计 → Keil编程 → 联合调试的完整闭环
- 了解准双向口的特性(特别是P0口需要外接上拉电阻)
探索了编程实现方式
- 位移法的运用
- 正/反向逻辑的适配技巧(
P1 = ~led的精妙之处)
值得关注的提升点:
- 当前延时函数仍占用CPU资源(之后将用定时器中断优化)
- 端口驱动能力有限(实际硬件需增加驱动电路)
- 可扩展为矩阵LED或RGB彩灯控制
给学习者的建议:
- 尝试修改延时参数观察不同动态效果
- 实验P2/P3口的控制差异
- 挑战用按键切换流水灯模式
"流水灯是单片机世界的'Hello World',
但它背后藏着嵌入式开发的所有秘密——
从硬件电路到软件算法,
从状态控制到时间管理。"
下篇预告
《中断实战:外部中断与定时器中断》将带您:
- 掌握按键控制的写法与外部中断的触发
- 告别阻塞延时,掌握中断驱动
更多推荐



所有评论(0)