LED基本原理
031.2.1 LED基本原理
#include "reg52.h"
#include <intrins.h>
sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;
int i;
void Delay1000ms(void) //@11.0592MHz
{
unsigned char data i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
// P1 = 0xfe; //11111110
// Delay1000ms();
// P1 = 0xfd; //11111101
// Delay1000ms();
// P1 = 0xfb; //11111011
// Delay1000ms();
// P1 = 0xf7; //11110111
// Delay1000ms();
// P1 = 0xff;
// Delay1000ms();
// for(i=0; i<4; i++)
// {
// P1 = P1 << 1;
// Delay1000ms();
// }
// LED1 = 0;
// LED3 = 0;
// Delay1000ms();
// LED1 = 1;
// LED3 = 1;
// Delay1000ms();
LED2 = ~LED2;
LED4 = ~LED4;
Delay1000ms();
}
}
一、硬件知识
- 本节演示单片机的引脚输出:开发板上的四个蓝色 LED 按约 1 秒的间隔依次点亮,看起来像灯光在流动。要理解代码,必须先弄清 LED 的方向、限流电阻、P1 端口以及“低电平点亮”的含义。
- 配套
2LED/main.c的完整源码列在标题下方。课程先演示其中第一段流水灯写法;源码还保留了循环移位、位操作和 LED2/LED4 同步闪烁写法,其中被//注释的语句不会参与当前编译。
1.1 发光二极管
- LED 是 Light Emitting Diode(发光二极管)的缩写。它首先是二极管,具有明显方向性;在允许的正向电流流过时才发光。
- LED 的两个电极应称为阳极 A(Anode)和阴极 K(Cathode)。正常点亮时,电流从阳极流入、从阴极流出,即
A → K。原理图中的二极管竖线一端代表阴极。
阳极 A 阴极 K
────────────────|>|──────────────
↗ ↗
发出的光
正常点亮方向:较高电位 → A → LED → K → 较低电位
- LED 不能只看“两端有电压”就判断会亮,还要同时满足:方向接对、正向压差足够、存在闭合回路、电流受到限制。如果反接,通常不会亮;LED 的允许反向电压往往不高,不能随意长期反向加压。
- 视频建议通过百科资料扩展 LED 的理论与应用。实际选型和设计时,更可靠的依据是具体 LED 型号的数据手册,其中会给出正向压降、推荐电流、最大反向电压、亮度、波长、功耗和封装尺寸。
1.1.1 贴片LED
- 本开发板的四个用户 LED 是贴片 LED,原理图标为 LED1~LED4、
0805_B;0805表示贴片封装规格,B表示蓝色。贴片器件直接焊在 PCB 表面,体积小、适合机器装配。 - 视频还列举了 0402、0603、0805、1206 等常见贴片封装。数字越大,器件通常越大,但这些代号采用英制尺寸习惯,不能直接当作毫米数读取。例如 0805 约为 2.0mm × 1.25mm。
- 贴片 LED 仍然分阳极和阴极,但不同厂家可能使用缺角、绿点、色带、底部符号等不同极性标记。不能只背一种外观,应同时查看 PCB 丝印、原理图和器件数据手册,必要时用万用表二极管挡确认。
- “封装不同、原理相同”指二者都利用半导体结的电致发光效应,不表示所有参数都相同。不同封装的允许电流、散热、亮度、视角和焊接方式可能差别很大。
1.1.2 直插LED
- 直插 LED 通常有两根金属引脚,可插入面包板或通孔 PCB。新器件且引脚尚未剪短时,一般可用以下方法判断极性:
| 观察位置 | 阳极 A(正极) | 阴极 K(负极) |
|---|---|---|
| 引脚长度 | 较长 | 较短 |
| 内部金属电极 | 通常较小 | 通常较大,像“小碗” |
| 塑料外壳边缘 | 通常为圆边 | 常有一处平边 |
- 引脚一旦被剪齐,“长正短负”的方法就失效;透明或特殊封装的内部结构也可能不易观察。最稳妥的方法是查数据手册,或用万用表二极管挡低电流测试:表笔方向正确时 LED 可能微亮,同时显示正向压降。
- 对普通单向 LED,可用“阳极/阴极”判断方向。视频中的“所有二极管都分正负极”适合作为入门记忆,但工程上还存在双向 TVS、交流指示灯组件等特殊器件,仍应以具体符号和手册为准。
1.2 电阻
- LED 的伏安特性很陡:超过正向导通电压后,电流可能因很小的电压变化而迅速增大。因此不能把普通 LED 直接跨接在 5V 与 GND 之间,必须使用串联电阻或恒流驱动器限制电流。
- 本板四路用户 LED 的连接完全相同:LED1~LED4 分别串联 R8~R11(10kΩ),再连接 P1.0~P1.3。LED 阳极侧接 +5V,单片机引脚位于阴极一侧,所以它们是低电平点亮、低电平有效。
开发板 LED1 的实际回路
+5V ──► LED1(A → K)──► R8 10kΩ ──► P1.0 ──► MCU 内部下拉管 ──► GND
│
P1.0 = 0 时导通并吸收电流
- P1.0 输出低电平时,LED 左侧约为 +5V,右侧经 R8 和单片机输出级接近 GND,形成
+5V → LED → R8 → P1.0 → GND的闭合回路,LED 点亮。这里常说引脚“吸收”或“灌入”电流,更准确的英文概念是 sink current(灌电流/吸电流)。 - P1.0 输出高电平时,引脚也接近 +5V,LED 两端几乎没有足够的正向压差,电流接近零,LED 熄灭。因此不能只凭“输出 1 就是开、输出 0 就是关”判断外设状态,必须先读电路连接方式。
| P1.x 输出 | LED 阳极电位 | LED 阴极侧电位 | 是否形成明显正向压差 | LED 状态 |
|---|---|---|---|---|
| 0,约 0V | +5V | 接近 0V | 是 | 亮 |
| 1,接近 +5V | +5V | 接近 +5V | 否 | 灭 |
- 忽略引脚低电平残余电压时,LED 电流可粗略估算为
I_LED ≈ (5V - V_F) / 10kΩ。若蓝色 LED 的正向压降约为 3V,则电流约为 0.2mA。实际电流还受 LED 批次、温度和 P1.x 输出电压影响。 - 设计其他 LED 电路时,可按
R ≈ (V_CC - V_F - V_OL) / I_LED初步选择电阻,再校验 LED、I/O 引脚和电阻功耗是否满足手册限制。电阻越小,电流通常越大;不能为了更亮无限减小电阻。 - 高电平点亮采用相反的供电位置:单片机引脚从高电平向外提供电流,LED 阴极最终接 GND,这叫 source current(拉电流/源电流)。
低电平点亮(本开发板) 高电平点亮(视频中的对比电路)
+5V ─ LED ─ R ─ P1.0 P1.5 ─ R ─ LED ─ GND
│ │
P1.0=0 时亮 P1.5=1 时亮
前者由 MCU 引脚吸收电流;后者由 MCU 引脚提供电流。
- 电流不仅需要电压差,还需要闭合回路。水压差与水流的类比可以帮助入门,但电压是两点间的电势差,本身不会像水一样“流动”;真正沿闭合回路运动的是电荷形成的电流。
二、代码编写
- 本节代码的目标是通过 P1 寄存器改变 P1.0~P1.3 的高低电平,从而控制四个低电平有效 LED。整体关系为:
C 语句写 P1 寄存器
│
▼
P1 锁存器的 8 个数据位
│
├── bit0 ──► P1.0 ──► LED1
├── bit1 ──► P1.1 ──► LED2
├── bit2 ──► P1.2 ──► LED3
├── bit3 ──► P1.3 ──► LED4
└── bit4~bit7 ──► P1.4~P1.7
2.1 引脚控制
- 对本实验所用的 P1 口,可先按课程模型理解:向对应寄存器位写 1,引脚接近高电平;写 0,引脚内部下拉晶体管导通,把引脚拉向 GND。
P1.x 的简化输出模型
+5V
│
弱上拉/输出结构
│
P1 锁存位 ──► 控制逻辑 ─┼────────► P1.x 引脚
│
下拉晶体管开关
│
GND
写 1:下拉关闭,引脚被拉高;写 0:下拉导通,引脚被强力拉低。
- 真实 8051 端口并不是所有口、所有模式都等价于理想推挽输出。例如经典 P0 口通常为开漏结构,作为普通 I/O 输出高电平时需要外部上拉;本板的 RN1、RN2 正是 P0 口的上拉排阻。P1 口适合本节的直接输出实验。
- 软件开发者不必在第一节课就掌握 I/O 单元的所有晶体管,但必须理解三个工程概念:高低电平有允许范围、引脚能提供或吸收的电流有限、外部电路决定逻辑是高有效还是低有效。
2.2 寄存器
- P0、P1、P2、P3 都是 8 位特殊功能寄存器,每一位与相应端口引脚对应。例如 P1 的 bit0~bit7 对应 P1.0~P1.7。
- P0 与 P2 两个寄存器合计对应 16 个端口位;P1 与 P3 也合计对应 16 个端口位。P3.0、P3.1 在板上还承担 RXD、TXD 串口复用功能,所以实物位置可能与 P3.2~P3.7 分开。
- 四个 8 位端口寄存器共有 32 个数据位,但不能简单认为任何型号都一定提供 32 个完全独立、没有复用功能的普通引脚;还要看芯片封装、P4 扩展口以及各引脚的第二功能。
- 本节只需控制 P1 的低四位:
| P1 位 | 物理引脚网络 | 板载负载 | 写 0 | 写 1 |
|---|---|---|---|---|
| bit0 | P1.0 | LED1 | 亮 | 灭 |
| bit1 | P1.1 | LED2 | 亮 | 灭 |
| bit2 | P1.2 | LED3 | 亮 | 灭 |
| bit3 | P1.3 | LED4 | 亮 | 灭 |
- 给 P1 一次赋一个 8 位数,会同时更新 8 个端口位。位序从右向左是 bit0~bit7,因此二进制数最右边的一位控制 P1.0,而不是最左边的一位。
2.3 写代码
-
视频通过复制已有 Keil 工程建立
2LED工程,这样可保留芯片、启动文件、晶振和输出文件等配置。复制后必须确认工程路径、目标芯片与输出 HEX 文件确实属于新工程,避免修改 A 工程却烧录 B 工程。 -
8051 复位后先执行启动文件中的初始化代码,再进入
main,因此从应用程序角度可以把main理解为主要入口,但它并不是 CPU 复位后执行的第一条机器指令。 -
程序必须包含
reg52.h,其中声明了 P0、P1 等 51 特殊功能寄存器;否则编译器会报告P1: undefined identifier或类似错误。 -
配套延时函数调用
_nop_(),它由 Keil C51 的intrins.h声明。缺少该头文件时,编译器会报告:'_nop_': missing function-prototype/'_nop_': declaration/activation error解决方法就是加入源码第二行的
#include <intrins.h>。出现编译错误时不会生成新的有效 HEX 文件,必须先修复并重新编译,不能继续烧录旧文件冒充新结果。
2.3.1 示例代码
-
视频首先写出
P1 = 0xff;。0x表示十六进制,每个十六进制位对应 4 个二进制位;F = 15 = 8 + 4 + 2 + 1,所以:0xFF = 1111 1111B -
P1 的 8 位全为 1,P1.0~P1.3 都输出高电平。由于本板 LED 低电平点亮,四个用户 LED 此时全部熄灭。
-
把数据改为
0xFE后:0xFE = 1111 1110B只有 bit0 为 0,即 P1.0 输出低电平,所以只有 LED1 点亮。
-
配套源码顶部还用
sbit分别为 P1.0~P1.3 定义了 LED1~LED4。sbit LED1 = P1^0;中的^是 Keil C51 的位地址语法,不是普通 C 表达式中的异或运算。 -
当前配套源码最后实际参与编译的是
LED2 = ~LED2;与LED4 = ~LED4;,作用是每隔约 1 秒翻转 LED2、LED4 的状态,使它们同步闪烁。上方由//开头的几组代码均为注释,用于保留不同实验写法,不会同时执行。
2.3.2 烧写程序
- 在 Keil 中修改代码后,先执行编译/构建,并检查 Build Output。只有确认
0 Error(s)且 HEX 文件更新时间与本次编译相符,才进入下载软件。 - 下载软件中选择与实物一致的 STC89C52RC 系列型号和正确串口。串口号应与 Windows 设备管理器中 CH340 对应的 COM 口一致,不能照抄视频中的端口号。
- 点击“打开程序文件”选择
2LED工程最新生成的 HEX 文件,再点击“下载/编程”。若板载自动下载电路未触发,可按软件提示重新上电;成功后再观察 LED,而不是只看下载按钮是否点过。 P1 = 0xFF烧入后四灯全灭、P1 = 0xFE烧入后 LED1 点亮,正好反向验证了原理图中的低电平有效连接。若现象相反,应优先核对是不是另一种开发板或不同 LED 接法。- 常见的“代码改了但现象没变”原因包括:编译失败后仍烧录旧 HEX、选错工程输出文件、串口选择错误、下载未完成、板子未重新上电,以及代码修改处仍被
//注释。
2.3.3 应用案例
- 流水灯的本质不是“同时让灯亮”,而是让唯一的低电平位按 P1.0 → P1.1 → P1.2 → P1.3 的顺序移动,并在每个状态之间保留人眼可见的时间。
- 四个关键数据如下:
| P1 十六进制值 | 二进制值 | 为 0 的端口位 | 点亮的 LED |
|---|---|---|---|
0xFE | 1111 1110 | P1.0 | LED1 |
0xFD | 1111 1101 | P1.1 | LED2 |
0xFB | 1111 1011 | P1.2 | LED3 |
0xF7 | 1111 0111 | P1.3 | LED4 |
- 十六进制转换应以每 4 位二进制为一组:
1110 = E、1101 = D、1011 = B、0111 = 7,所以四个状态依次是 FE、FD、FB、F7。只观察低四位可以说明四盏 LED 的状态;书写完整 P1 值时,还必须保留高四位1111。
2.3.3.1 例题: 流水灯效果实现
- 把四次赋值放入
while (1),程序执行完 LED4 后会回到 LED1,形成无限循环。嵌入式主程序通常需要持续响应硬件,因此常见永久循环;但并非所有 MCU 程序都只能用这一种结构,事件、中断和操作系统任务也是常见实现方式。
┌──────────────────────────────────────┐
│ │
▼ │
P1=0xFE ─延时→ P1=0xFD ─延时→ P1=0xFB ─延时→ P1=0xF7 ─延时
LED1亮 LED2亮 LED3亮 LED4亮
- 如果没有延时,51 单片机会在极短时间内反复切换四个状态。由于视觉暂留,人眼可能觉得多个灯同时常亮,无法分辨顺序;这不是赋值没有生效,而是刷新速度远高于人眼可辨速度。
- 视频使用下载软件自带的延时计算器,在 11.0592MHz、STC89 指令集条件下生成
Delay1000ms()。这类函数通过 CPU 空循环消耗时间,属于忙等待:延时期间 CPU 不能做其他工作,精度还会受时钟、指令周期、编译器和优化设置影响,适合入门演示,不适合高精度或多任务场景。 - 调用函数时写
Delay1000ms();,不能写成void Delay1000ms();。前者是执行函数,后者在语法上是函数声明;每条调用语句末尾要有分号。 - 使用包含
_nop_()的延时函数时必须同时包含<intrins.h>,否则编译会失败。加入该头文件并重新编译,确认错误数为零后,才能生成可供烧录的 HEX 文件。 - 后续可用定时器中断代替软件忙等待:定时器周期到达时再切换 LED,主循环可以同时处理按键、串口等任务。其工作关系可先理解为:
硬件定时器计数 ──► 到达设定时间 ──► 产生中断 ──► 切换下一个 LED
▲ │
└──────────────── 重新计时 ◄──────────────────┘
主循环在等待期间仍可执行其他任务,不必卡在空循环中。