
本实验基于STC89C52单片机(兼容AT89C52),实现两个核心功能:按键控制LED亮灭 与定时器驱动数码管动态显示。通过本实验,读者可以掌握51单片机GPIO输入输出、独立按键检测、定时器中断以及数码管动态扫描的基本原理与编程方法。
2. 硬件原理
2.1 按键电路
本实验使用4个独立按键,分别连接P1.4~P1.7引脚。按键未按下时,引脚通过上拉电阻保持高电平;按下后引脚被拉低为低电平。因此程序通过读取引脚电平即可判断按键状态。
独立按键的典型接法如下:
- 按键一端接单片机I/O引脚,另一端接地。
- 引脚外部接10kΩ上拉电阻到VCC,保证未按下时为高电平。
- 按下按键时引脚被拉低,程序检测到低电平即认为按键被按下。
2.2 LED指示电路
4个LED分别连接P2.0~P2.3引脚,采用低电平点亮方式。当引脚输出低电平时,LED导通发光;输出高电平时,LED熄灭。
2.3 数码管显示电路
数码管采用共阴极接法,段选信号由P0口输出,位选信号由P1.0~P1.3控制。通过动态扫描方式,利用人眼视觉暂留效应实现多位数字同时显示。
3. 晶体管(三极管)驱动原理
在数码管和LED驱动电路中,晶体管(三极管)起到电流放大 和开关控制的作用。51单片机I/O口驱动能力有限(拉电流约0.5mA,灌电流约20mA),当需要驱动较大电流的负载时,必须借助晶体管进行功率放大。
3.1 NPN三极管开关电路
以NPN型三极管(如S8050)为例,其工作在开关状态时:
- 截止状态:基极电压低于0.7V,集电极与发射极之间不导通,负载断电。
- 饱和导通状态:基极电流足够大,集电极与发射极之间近似短路,负载通电。
典型驱动电路:单片机引脚通过限流电阻(1kΩ~10kΩ)连接三极管基极,集电极接负载(如LED或数码管公共端),发射极接地。当引脚输出高电平时,三极管导通,负载工作。
3.2 定时器控制晶体管闪烁
利用定时器中断产生周期性电平翻转,即可控制三极管周期性导通与截止,从而实现LED的闪烁效果。定时器每溢出一次,中断服务程序中将控制引脚电平取反,LED即按固定频率闪烁。
c
#include <reg52.h>
#include <intrins.h>
sbit LedEn = P1^0; // 三极管基极控制引脚
sbit Led = P2^0; // LED指示灯
// 定时器0中断服务函数:每5ms翻转一次LED状态
void Timer0Handler(void) interrupt 1
{
static unsigned int count = 0;
TH0 = 0xEE; // 定时器赋初值,约5ms
TL0 = 0x00;
count++;
if (100 == count) // 累计500ms翻转一次
{
count = 0;
Led = ~Led; // 翻转LED电平
LedEn = ~LedEn; // 翻转三极管基极电平
}
}
void main(void)
{
// 设置定时器0为16位定时模式
TMOD &= ~0x3;
TMOD |= 0x1;
// 定时器赋初值
TH0 = 0xEE;
TL0 = 0x00;
// 开启定时器0中断和总中断
ET0 = 1;
EA = 1;
// 启动定时器0
TR0 = 1;
while (1)
{
// 主循环可执行其他任务
}
}
4. 按键控制LED
按键控制LED的核心思路:主循环中不断读取按键引脚电平,根据电平状态控制对应LED的亮灭。按键按下时引脚为低电平,此时点亮LED;松开后引脚恢复高电平,熄灭LED。
c
#include <reg52.h>
sbit Key1 = P1^4;
sbit Key2 = P1^5;
sbit Key3 = P1^6;
sbit Key4 = P1^7;
sbit Led1 = P2^0;
sbit Led2 = P2^1;
sbit Led3 = P2^2;
sbit Led4 = P2^3;
void main(void)
{
while (1)
{
if (Key1)
{
Led1 = 1; // 按键松开,LED熄灭
}
else
{
Led1 = 0; // 按键按下,LED点亮
}
if (Key2)
{
Led2 = 1;
}
else
{
Led2 = 0;
}
if (Key3)
{
Led3 = 1;
}
else
{
Led3 = 0;
}
if (Key4)
{
Led4 = 1;
}
else
{
Led4 = 0;
}
}
}
5. 定时器驱动数码管动态显示
数码管动态显示利用定时器中断实现定时刷新。每隔固定时间切换一位数码管显示,利用视觉暂留效应让4位数码管看起来同时点亮。
5.1 数码管段码表
共阴极数码管显示0~9对应的段码如下:
| 数字 | 段码(十六进制) | 二进制 |
|---|---|---|
| 0 | 0x3F | 0011 1111 |
| 1 | 0x06 | 0000 0110 |
| 2 | 0x5B | 0101 1011 |
| 3 | 0x4F | 0100 1111 |
| 4 | 0x66 | 0110 0110 |
| 5 | 0x6D | 0110 1101 |
| 6 | 0x7D | 0111 1101 |
| 7 | 0x07 | 0000 0111 |
| 8 | 0x7F | 0111 1111 |
| 9 | 0x6F | 0110 1111 |
5.2 完整数码管显示程序
c
#include <reg52.h>
#include <intrins.h>
sbit Led1En = P1^0;
sbit Led2En = P1^1;
sbit Led3En = P1^2;
sbit Led4En = P1^3;
// 数码管显示对应数字的真值表
unsigned char NumCode[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
// 数码管显示内容的缓存
unsigned char NumBuffer[4] = {0x00, 0x00, 0x00, 0x00};
void DisplayNum(unsigned char wei);
// 定时器0中断服务函数:动态扫描数码管
void Timer0Handler(void) interrupt 1
{
static unsigned char n = 0;
// 定时器赋初值
TH0 = 0xEE;
TL0 = 0x00;
DisplayNum(n);
n++;
if (4 == n)
{
n = 0;
}
}
// 延时1ms
void Delay1ms(void) //@11.0592MHz
{
unsigned char data i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
// 延时1000ms
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);
}
// 显示数码管第wei位的数值
void DisplayNum(unsigned char wei)
{
P1 &= ~0xF; // 关闭所有位选
P1 |= (0x1 << wei); // 选中第wei位
P0 = NumCode[NumBuffer[wei]]; // 输出段码
}
// 将数字设置到显示缓存区中
void SetDisplayNum(int TmpNum)
{
NumBuffer[0] = TmpNum % 10;
NumBuffer[1] = TmpNum % 100 / 10;
NumBuffer[2] = TmpNum % 1000 / 100;
NumBuffer[3] = TmpNum / 1000;
}
void main(void)
{
int Num = 0;
// 设置定时器工作模式(16位定时器)
TMOD &= ~0x3;
TMOD |= 0x1;
// 定时器赋初值
TH0 = 0xEE;
TL0 = 0x00;
// 定时器中断开启
ET0 = 1;
EA = 1;
// 开启定时器0
TR0 = 1;
while (1)
{
Num++;
SetDisplayNum(Num);
Delay1000ms();
}
}
6. 按键与定时器综合应用
将按键控制与定时器显示结合,可以实现按键调节数码管显示数值的功能。例如:按下Key1数值加1,按下Key2数值减1,按下Key3清零,按下Key4暂停计数。
c
#include <reg52.h>
#include <intrins.h>
sbit Key1 = P1^4;
sbit Key2 = P1^5;
sbit Key3 = P1^6;
sbit Key4 = P1^7;
sbit Led1En = P1^0;
sbit Led2En = P1^1;
sbit Led3En = P1^2;
sbit Led4En = P1^3;
unsigned char NumCode[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
unsigned char NumBuffer[4] = {0x00, 0x00, 0x00, 0x00};
unsigned int DisplayNum = 0;
bit PauseFlag = 0;
void DisplayNumFunc(unsigned char wei);
void SetDisplayNum(int TmpNum);
void Delay10ms(void);
// 定时器0中断:数码管动态扫描
void Timer0Handler(void) interrupt 1
{
static unsigned char n = 0;
TH0 = 0xEE;
TL0 = 0x00;
DisplayNumFunc(n);
n++;
if (4 == n)
{
n = 0;
}
}
void DisplayNumFunc(unsigned char wei)
{
P1 &= ~0xF;
P1 |= (0x1 << wei);
P0 = NumCode[NumBuffer[wei]];
}
void SetDisplayNum(int TmpNum)
{
NumBuffer[0] = TmpNum % 10;
NumBuffer[1] = TmpNum % 100 / 10;
NumBuffer[2] = TmpNum % 1000 / 100;
NumBuffer[3] = TmpNum / 1000;
}
// 简易延时消抖
void Delay10ms(void)
{
unsigned char i, j;
for (i = 0; i < 20; i++)
{
for (j = 0; j < 250; j++);
}
}
void main(void)
{
TMOD &= ~0x3;
TMOD |= 0x1;
TH0 = 0xEE;
TL0 = 0x00;
ET0 = 1;
EA = 1;
TR0 = 1;
while (1)
{
// 按键1:数值加1
if (0 == Key1)
{
Delay10ms();
if (0 == Key1)
{
DisplayNum++;
SetDisplayNum(DisplayNum);
while (0 == Key1); // 等待按键松开
}
}
// 按键2:数值减1
if (0 == Key2)
{
Delay10ms();
if (0 == Key2)
{
if (DisplayNum > 0)
{
DisplayNum--;
}
SetDisplayNum(DisplayNum);
while (0 == Key2);
}
}
// 按键3:清零
if (0 == Key3)
{
Delay10ms();
if (0 == Key3)
{
DisplayNum = 0;
SetDisplayNum(DisplayNum);
while (0 == Key3);
}
}
// 按键4:暂停/继续计数
if (0 == Key4)
{
Delay10ms();
if (0 == Key4)
{
PauseFlag = ~PauseFlag;
while (0 == Key4);
}
}
// 自动计数
if (0 == PauseFlag)
{
DisplayNum++;
SetDisplayNum(DisplayNum);
Delay1000ms();
}
}
}
7. 按键消抖处理
机械按键在按下和松开的瞬间会产生抖动,表现为电平在短时间内多次跳变。如果不做消抖处理,一次按键可能被误判为多次触发。常用的消抖方法有两种:
- 软件延时消抖:检测到按键按下后延时10~20ms再次检测,若仍为按下状态则确认有效。
- 定时器扫描消抖:利用定时器中断周期性扫描按键,连续多次采样一致才确认状态变化。
本实验综合程序采用软件延时消抖,并在确认按键有效后等待按键松开,避免重复触发。
8. 总结
本实验完整演示了51单片机按键输入、LED输出、定时器中断和数码管动态显示的综合应用。晶体管作为功率驱动器件,在数码管位选和LED驱动中起到关键的电流放大作用。通过定时器中断实现精确的时序控制,配合按键输入实现人机交互,是单片机嵌入式开发的基础技能。
