一、序言:从搭建环境到动手实战

在上一篇中,我们成功搭建了Keil C51和Proteus的开发环境,指尖跃跃欲试!本篇我们将正式动手,完成首个单片机实战项目:使用51单片机控制LED灯闪烁,完成简单的流水灯。

二、项目目标

  • 硬件模拟:在Proteus中构建包含51单片机和LED的电路
  • 软件开发:在Keil中编写C51程序实现LED闪烁
  • 联合调试:将Keil生成的.hex文件加载到Proteus仿真

三、实战步骤详解

Step 1:构建Proteus电路(硬件模拟)

  • 启动Proteus:创建新工程
  • 选择新工程这里的名称和路径,最佳的选择是纯英文,将工程保存在自己方便查找并打开的位置,放置完毕后选择Next进入下一步
  • 这里按照图中所选即可,随后的窗口,我们均默认,直接点击Next
  • 图示为最后一个总结窗口,这边我们点击Finish,就完成了新工程的创建,进入如图所示的原理图绘制界面。
  1. 放置核心元件
    在原理图的左上角,我们选到元件模式

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

  • 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彩灯控制

给学习者的建议:

  1. 尝试修改延时参数观察不同动态效果
  2. 实验P2/P3口的控制差异
  3. 挑战用按键切换流水灯模式

"流水灯是单片机世界的'Hello World',
但它背后藏着嵌入式开发的所有秘密——
从硬件电路到软件算法,
从状态控制到时间管理。"

下篇预告
《中断实战:外部中断与定时器中断》将带您:

  • 掌握按键控制的写法与外部中断的触发
  • 告别阻塞延时,掌握中断驱动
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐