- 硬件连接:如何连接LED灯到单片机。
- 完整C语言代码:带有详细注释的源代码。
- 代码详解:逐行解释代码的工作原理。
- 如何烧录与运行:将程序下载到单片机并看到效果。
- 进阶与扩展:如何修改代码实现更复杂的效果。
硬件连接
跑马灯的原理非常简单,就是通过控制单片机的I/O口(输入/输出口)输出高电平或低电平,来点亮或熄灭连接在I/O口上的LED灯。

硬件清单:
- STC89C52单片机最小系统板(包含晶振和复位电路)
- 8个LED灯(任意颜色)
- 8个220Ω ~ 1kΩ 的电阻(用于限流,保护LED和单片机I/O口)
- 杜邦线若干
连接方法: 我们将8个LED连接到单片机的 P0口 (P0.0 ~ P0.7)。
- 单片机P0.0 -> 电阻R1 -> LED1正极 -> LED1负极 -> GND
- 单片机P0.1 -> 电阻R2 -> LED2正极 -> LED2负极 -> GND
- ... 以此类推,直到 P0.7。
为什么需要电阻? LED是电流敏感型器件,如果直接接到I/O口,电流过大可能会烧毁LED或单片机的I/O引脚,220Ω的电阻可以起到很好的限流作用,确保电流在安全范围内。
注意: STC89C52的P0口是开漏输出,在没有外部上拉电阻的情况下,输出高电平的能力很弱,如果你的开发板没有在P0口集成上拉电阻,强烈建议在P0口和VCC(+5V)之间接一个排阻(例如10kΩ的排阻),以确保能正常点亮LED。

完整C语言代码
#include <reg52.h> // 包含STC89C52或AT89S52的头文件
// 定义一个无符号整型变量,用于控制P0口的8个LED
// 0xFE (二进制 1111 1110) 表示第一个LED亮,其余灭
// 0xFD (二进制 1111 1101) 表示第二个LED亮,其余灭
// ...
// 0x7F (二进制 0111 1111) 表示第八个LED亮,其余灭
unsigned char code led_pattern[] = {
0xFE, 0xFD, 0xFB, 0xF7,
0xEF, 0xDF, 0xBF, 0x7F
};
// 延时函数,用于控制LED切换的速度
void delay_ms(unsigned int ms) {
unsigned int i, j;
for (i = ms; i > 0; i--)
for (j = 110; j > 0; j--); // 这个循环次数是经验值,具体取决于晶振频率
}
void main() {
// 死循环,让程序一直运行
while (1) {
// 使用for循环遍历led_pattern数组中的每一个模式
unsigned char i;
for (i = 0; i < 8; i++) {
P0 = led_pattern[i]; // 将当前模式值赋给P0口,控制LED亮灭
delay_ms(200); // 延时200毫秒
}
}
}
代码详解
#include <reg52.h>
这是一个预处理指令,它会引入 reg52.h 这个头文件,这个文件定义了STC89C52/AT89S52单片机内部所有的特殊功能寄存器(SFR)的名称和地址,P0, P1, TMOD 等,没有它,我们就无法使用 P0 = ... 这样的语句。
unsigned char code led_pattern[]
unsigned char: 定义一个无符号字符型变量,范围是0到255,正好可以用来表示8个LED的状态。code: 这是一个非常重要的关键字,它告诉编译器,将这个数组存储在程序存储区(ROM/Flash),而不是数据存储区(RAM),因为ROM掉电后数据不会丢失,而RAM会,这个数组是固定的模式数据,不需要修改,放在ROM中既节省了宝贵的RAM空间,又保证了数据安全。led_pattern[]: 我们定义了一个名为led_pattern的数组。{0xFE, 0xFD, ...}: 这是数组的具体内容,我们来看一下0xFE:- 二进制形式是
1111 1110。 - 当这个值赋给
P0口时,P0.0引脚输出低电平(0),电流从VCC流过LED和电阻到P0.0,第一个LED被点亮。 P0.1到P0.7引脚输出高电平(1),没有足够的电压差点亮LED,所以它们是熄灭的。- 数组中的下一个值
0xFD(1111 1101) 会让第二个LED亮,以此类推,就形成了“从左到右”的流水灯效果。
- 二进制形式是
void delay_ms(unsigned int ms)
这是一个软件延时函数。
- 它通过执行一个空循环来消耗时间,从而达到延时的目的。
for (i = ms; i > 0; i--)控制延时的毫秒数。for (j = 110; j > 0; j--)是内层循环,循环次数决定了每个毫秒的长度。- 注意:这个延时函数的精确度取决于单片机的晶振频率,这个例子是基于常见的 0592MHz 晶振计算的,如果你的晶振频率不同,需要调整内层循环的次数(j的值)来获得准确的延时。
void main()
这是C程序的入口函数,程序从这里开始执行。
while (1)
这是一个无限循环,单片机程序通常都是放在一个无限循环中的,因为程序执行到最后会继续从头开始,或者停止。while(1) 确保程序永远不会退出,持续运行跑马灯效果。

for (i = 0; i < 8; i++)
这个循环用于遍历 led_pattern 数组。i 从0开始,每次加1,直到7(共8次)。
P0 = led_pattern[i];
这是核心控制语句,它从数组中取出第 i 个元素(模式),然后将其赋值给P0端口,P0端口的8个引脚的电平状态会立即改变,从而控制8个LED的亮灭。
delay_ms(200);
在每次改变LED状态后,调用延时函数,让这个状态保持200毫秒,这样人眼才能清晰地看到灯的移动效果,而不是一闪而过。
如何烧录与运行
- 编译代码:将上面的代码复制到Keil C51等集成开发环境中,编译生成一个
.hex文件。 - 连接硬件:按照第1部分的说明,连接好单片机、LED、电阻和电源。
- 烧录程序:
- 使用USB-TTL下载器将单片机和电脑连接起来。
- 打开STC-ISP等烧录软件。
- 选择正确的单片机型号(STC89C52)。
- 加载刚刚生成的
.hex文件。 - 点击“下载”按钮,程序就会被烧录到单片机的Flash中。
- 上电运行:烧录完成后,给单片机系统上电,你就可以看到8个LED灯从左到右循环亮起了!
进阶与扩展
掌握了基础跑马灯后,你可以尝试修改代码,实现更多酷炫的效果:
效果1:双向跑马灯(来回流动)
// ... (前面的include和delay函数不变)
unsigned char code led_pattern[] = {
0xFE, 0xFD, 0xFB, 0xF7,
0xEF, 0xDF, 0xBF, 0x7F
};
void main() {
unsigned char i;
while (1) {
// 从左到右
for (i = 0; i < 8; i++) {
P0 = led_pattern[i];
delay_ms(200);
}
// 从右到左
for (i = 7; i > 0; i--) { // 注意这里 i > 0
P0 = led_pattern[i];
delay_ms(200);
}
}
}
效果2:全部闪烁
void main() {
while (1) {
P0 = 0x00; // P0口全部输出低电平,所有LED亮
delay_ms(500);
P0 = 0xFF; // P0口全部输出高电平,所有LED灭
delay_ms(500);
}
}
效果3:呼吸灯效果(单个LED由暗到亮再到暗)
这个效果需要用到 PWM(脉冲宽度调制),稍微复杂一些,基本原理是快速开关LED,通过改变开关的时间比例(占空比)来控制LED的平均亮度,从而在人眼中形成渐变的效果。
#include <reg52.h>
// 呼吸灯函数
void breath_light(unsigned char port) {
unsigned char i, j, k;
for (i = 0; i < 100; i++) { // 亮度从0%到100%
for (j = 0; j < 20; j++) { // 每个亮度级别保持一段时间
port = 0x00; // LED亮
for (k = i; k > 0; k--); // 延时,时间与亮度成正比
port = 0xFF; // LED灭
for (k = 100 - i; k > 0; k--); // 延时,时间与亮度成反比
}
}
for (i = 100; i > 0; i--) { // 亮度从100%到0%
// ... 类似上面的循环 ...
}
}
void main() {
while (1) {
breath_light(P0); // 对P0口做呼吸灯效果
}
}
注意:呼吸灯的延时函数需要非常精细的调整,上面的代码是一个简化示例,实际效果可能需要根据晶振频率进行调试。
希望这个详细的教程能帮助你成功实现单片机跑马灯,并激发你探索更多单片机乐趣!
