目录
[1 AT89C52单片机驱动单个数码管](#1 AT89C52单片机驱动单个数码管)
[1.1 数码管基础知识](#1.1 数码管基础知识)
[1.1.2 共阴(CC) vs 共阳(CA)](#1.1.2 共阴(CC) vs 共阳(CA))
[1.1.4驱动方式A. 直连IO(最简单,占用IO多)一个段一根线,共阴或共阳公共端固定接GND/VCC。适合单个数码管、教学实验。](#1.1.4驱动方式A. 直连IO(最简单,占用IO多)一个段一根线,共阴或共阳公共端固定接GND/VCC。适合单个数码管、教学实验。)
[1.1.6 快速排查清单](#1.1.6 快速排查清单)
[1.2 完整例子1------AT89C52单片机驱动单个数码管0~9循环计数](#1.2 完整例子1——AT89C52单片机驱动单个数码管0~9循环计数)
[1.2.1 电路原理图](#1.2.1 电路原理图)
[1.2.2 控制程序](#1.2.2 控制程序)
[1.3 完整例子2------AT89C52单片机实现比分计数器](#1.3 完整例子2——AT89C52单片机实现比分计数器)
[1.3.1 74HC573透明锁存器](#1.3.1 74HC573透明锁存器)
[1.3.2 引脚与封装](#1.3.2 引脚与封装)
[1.3.3 真值表(简化)](#1.3.3 真值表(简化))
[1.3.4 典型应用:51 单片机驱动 4 位共阴七段数码管](#1.3.4 典型应用:51 单片机驱动 4 位共阴七段数码管)
[1.3.5 最小示例(Keil C,12 MHz 晶振)](#1.3.5 最小示例(Keil C,12 MHz 晶振))
[1.3.6 常见问题速查](#1.3.6 常见问题速查)
[1.4 AT89C52单片机实现比分计数器电路原理图](#1.4 AT89C52单片机实现比分计数器电路原理图)
[1.5 控制程序](#1.5 控制程序)
**摘要:**本文介绍了AT89C52单片机驱动数码管的基础知识及实现方法。主要内容包括:1.数码管工作原理,包括共阴/共阳结构、段码表、驱动方式;2.两个具体应用实例:单个数码管0-9循环计数和比分计数器;3.详细硬件电路设计(含74HC573锁存器应用)和配套C语言程序实现;4.常见问题排查指南。通过定时器中断实现精确控制,采用动态扫描技术实现多位显示,并提供了完整的代码示例和电路原理图。
1 AT89C52单片机驱动单个数码管
1.1 数码管基础知识

7段数码管(7-segment display)是最常见的数字显示器件之一,广泛用于仪表、时钟、计数器等场合。
1.1.1外观与引脚
如上图所示,7个发光段分别命名为 a、b、c、d、e、f、g(顺时针方向排列),再加一个小数点 dp。
单个数码管常见封装:
直插 DIP-10(2.54 mm)
贴片 SMD-10
引脚排列没有统一标准,务必看数据手册或用万用表「二极管档」量:红笔固定接公共端,黑笔依次碰其它脚,某一段亮就说明该脚对应段。
1.1.2 共阴(CC) vs 共阳(CA)
共阴:所有 LED 阴极并在一起 → 公共脚接 GND,要点亮某段就给该段阳极送高电平(+V)。
共阳:所有 LED 阳极并在一起 → 公共脚接 +V,要点亮某段就给该段阴极送低电平(GND)。
记忆口诀:
共阴 = "给1亮"
共阳 = "给0亮"
1.1.3段码表(以数字1为例)
数字1需要点亮 b、c 两段。
|--------|----------------------|----------|------------|
| 类型 | a b c d e f g dp | 十六进制 | 说明 |
| 共阴 | 0 1 1 0 0 0 0 0 | 0x06 | b、c=1,其余=0 |
| 共阳 | 1 0 0 1 1 1 1 1 | 0xF9 | b、c=0,其余=1 |
完整段码速查:
0 1 2 3 4 5 6 7 8 9
共阴:0x3F 0x06 0x5B 0x4F 0x66 0x6D 0x7D 0x07 0x7F 0x6F
共阳:取反即可(~共阴 & 0xFF)
1.1.4驱动方式
A. 直连IO(最简单,占用IO多)
一个段一根线,共阴或共阳公共端固定接GND/VCC。
适合单个数码管、教学实验。
B. 译码/ 驱动芯片
74HC47(BCD→7段,共阳)
CD4511(BCD→7段,带锁存,共阴)
TM1637、MAX7219(集成扫描与亮度调节)
C. MCU+三极管/MOS(动态扫描多个位)
节省IO:段线共用,位选线轮流导通。
需要定时刷新(>100 Hz 无闪烁)。
1.1.5 多连排数码管动态扫描
以4 位共阴为例:
12个IO:8段+4位选
流程:
关所有位(位选高)
送第N位段码
打开第N位(位选低)
延时1~2 ms
回到1,循环
伪代码:
objectivec
const uint8_t seg_cc[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void display_4digit(uint16_t val){
for(uint8_t pos=0; pos<4; pos++){
set_digit_OFF();
output_SEG(seg_cc[val%10]); // 输出段码
set_digit_ON(pos); // 打开位
delay_ms(2);
val /= 10;
}
}
1.1.6 快速排查清单
不亮:检查公共端电压、限流电阻(每段330 Ω~1 kΩ)。
乱亮:段码高低反了(把共阴/共阳搞反)。
闪烁:动态扫描频率太低,提高刷新或加定时器中断。
1.2 完整例子1------AT89C52单片机驱动单个数码管0~9循环计数
1.2.1 电路原理图

如上图所示,AT89C52单片机驱动单个数码管0~9循环计数电路原理图由AT89C52单片机、上拉排阻、数码管、晶振和复位电路组成,通过AT89C52单片机控制P0的输出电平实现数码管数字显示的控制。
1.2.2 控制程序
objectivec
//头文件与位定义
# include <reg51.h>
# include <intrins.h>
//数码管对应数字
// 0:10000000 1:11110010 2:01001000 3:01100000 4:00110010 5:00100100 6:00000100 7:11110000 8:00000000 9:00100000
unsigned char display_table[] ={ 0x80, 0xf2, 0x48, 0x60, 0x32, 0x24, 0x04, 0xf0, 0x00, 0x20 }; // 共阳极数码管显示编码16进制
unsigned int i=0;
//定时器T0初始化
void inittimer()
{
TMOD = 0x01; // T0 方式 1
TH0 = 0xFC; // 1 ms
TL0 = 0x18;
TR0 = 1;
ET0 = 1;
EA = 1;//中断设置值
}
//定时器延时函数
void Delay(unsigned int ms)
{
unsigned int i;
for (i = 0; i < ms; i++)
{
while (!TF0); // 等待定时器T0溢出
TF0 = 0; // 清除溢出标志
}
}
//定时器T0模式1中断服务函数
void Timer0_ISR() interrupt 1
{
TH0 = 0xFC;
TL0 = 0x18;
}
void main() // 主程序
{
inittimer();
while(1)
{
P0 = 0x00;
for(i = 0; i < 10; i++)
{
if (i == 10 ) // 当i=10时,将0清零。就是当数码管显示9后,再从0开始
{
i = 0;
}
P0 = display_table[i];
Delay(1000);
}
}
}
如上程序所示,程序使用定时器T0的T1模式实现精确延时控制,从而实现控制数码管的显数显示。
1.3 完整例子2------AT89C52单片机实现比分计数器
1.3.1 74HC573透明锁存器

74HC573是一块"8 位透明锁存器(Octal D-Type Transparent Latch)",带三态输出,最常被拿来做"IO口扩展+数据保持",比如用3片74HC573 就能让 8 位单片机口线分时输出 24 位数据,正好驱动 8×16 LED 点阵或多位七段数码管
1.3.2 引脚与封装
封装:DIP-20、SOP-20、TSSOP-20 等
关键脚位(俯视图左下角圆点逆时针数):
1 OE̅ 输出使能(低电平有效,拉高则 Q0~Q7 高阻)
11 LE 锁存使能(高=透明,低=锁存)
2~9 D0~D7 数据输入
19~12 Q0~Q7 三态输出
20 VCC,10 GND
1.3.3 真值表(简化)
|---------|--------|-------|--------|
| OE̅ | LE | D | Q |
| 0 | 1 | X | 随D(透明) |
| 0 | 0 | X | 保持上次数据 |
| 1 | X | X | 高阻态 |
1.3.4 典型应用:51 单片机驱动 4 位共阴七段数码管
接线示意
MCU-P0 → 74HC573(段码) → 330 Ω 电阻 → a~g,dp
MCU-P2.0~P2.3 → 三极管 → 位选线(共阴端)
锁存器:
OE̅ 接地(一直使能)
LE → 单片机 P3.6(段锁存)
时序
1把段码送 P0
2给 LE 一个高脉冲(锁存段码)
3打开对应位选线,延时 2 ms
4关位选、重复扫描
1.3.5 最小示例(Keil C,12 MHz 晶振)
objectivec
#include <reg52.h>
sbit HC573_LE = P3^6; // 段锁存
unsigned char code seg7[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void delay_ms(unsigned char t)
{
unsigned int i;
while(t--) for(i=0;i<123;i++);
}
void main()
{
unsigned char i=0;
while(1)
{
P0 = seg7[i]; // 送段码
HC573_LE = 1; // 锁存
HC573_LE = 0;
P2 = ~(1<<0); // 点亮第 1 位
delay_ms(2);
P2 = 0xFF; // 关闭所有位
if(++i>9) i=0;
}
}
1.3.6 常见问题速查
输出全高阻:OE̅ 被拉高,请接地。
数据写不进去:LE 没有拉到高电平或脉冲宽度不足(>10 ns 即可)。
电流不够:段码端只能提供 ±4 mA,驱动大电流 LED 请加三极管或 ULN2803。
1.4 AT89C52单片机实现比分计数器电路原理图

如上图所示,电路原理图由AT89C52单片机、上拉排阻、控制按键、晶振和复位电路、锁存器和6个数码管组成。通过五个独立按键控制高低电平,可以实现数值增加、减少或清零,并通过数码管显示数值。
1.5 控制程序
objectivec
//头文件与位定义
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
sbit AJJ= P2^1;//A-
sbit BJJ= P2^2;//B-
sbit CLC= P2^3;//A+
sbit AJ= P2^4;//A+
sbit BJ= P2^5;//B+
sbit LE_D = P2^6;//段选
sbit LE_W = P2^7;//位选
uchar code table_D[] = {0xC0,0xF9,0xA4,0XB0,0x99,0x92,0x82,0xF8,0x80,0x98};//段值0-9
uchar code table_W[] = {0x01,0x02,0x04,0x08,0x10,0x20}; //位值1-6
uint time = 0;
uint Num_D = 0;
uint Num_W = 0;
uchar ge,shi,bai;//个 十 百位
uint num = 0;
uint num1 = 0;
void init();//定时器T0初始化
void delay(uint dtime);//延时函数
void display1(uint num);//显示函数
void display2(uint num);//显示函数
//定时器T0段选位选初始化
void init(){
LE_D = 0;
LE_W = 0;
AJ=1;
BJ=1;
TMOD = 0x01; //0000 0001
TH0 = 0xFC; //1ms
TL0 = 0x17;
EA = 1;
ET0 = 1;
TR0 = 1;
}
//定时器T0延时函数
void delay(unsigned int dtime)
{
unsigned int i;
for (i = 0; i <dtime; i++)
{
while (!TF0); // 等待定时器T0溢出
TF0 = 0; // 清除溢出标志
}
}
//定时器T0模式1中断服务函数
void Timer0_ISR() interrupt 1
{
TH0 = 0xFC;
TL0 = 0x17;
time++;
}
void main(){
init();
num1 = 0;
num1 = 0;
while(1)
{
if((time >= 300)&&(AJ==0))
{
time=0;
num1++;
}
else if((time >= 300)&&(BJ==0))
{
time=0;
num++;
}
else if((time >= 300)&&(AJJ==0)&&(num>0))
{
time=0;
num1--;
}
else if((time >= 300)&&(BJJ==0)&&(num>0))
{
time=0;
num--;
}
else if((time >= 300)&&(CLC==0))
{
time=0;
num=0;
num1=0;
}
display1(num1);
display2(num);
}
}
//后三个数码管显示数字的函数
void display1(uint num){
bai = num / 100; //百位应显示的数字
shi = num % 100 /10; //十位应显示的数字
ge = num % 10; //个位应显示的数字
//数码管显示个位数字
LE_D = 1;
P0 = table_D[ge]; //段------>位 会在段所指的位置上显示段
LE_D = 0; //位------>段 新的数字会显示到之前的位置上
LE_W = 1;
P0 = 0x20; //0010 0000
LE_W = 0;
delay(1);
LE_W = 1; //消隐
P0 = 0;
LE_W = 0;
//数码管显示十位数字
LE_D = 1;
P0 = table_D[shi];
LE_D = 0;
LE_W = 1;
P0 = 0x10; //0001 0000
LE_W = 0;
delay(1);
LE_W = 1; //消隐
P0 = 0;
LE_W = 0;
//数码管显示百位数字
LE_D = 1;
P0 = table_D[bai];
LE_D = 0;
LE_W = 1;
P0 = 0x08; //0000 1000
LE_W = 0;
delay(1);
LE_W = 1; //消隐
P0 = 0;
LE_W = 0;
}
//前三个数码管显示数字的函数
void display2(uint num){
bai = num / 100; //百位应显示的数字
shi = num % 100 /10; //十位应显示的数字
ge = num % 10; //个位应显示的数字
//数码管显示个位数字
LE_D = 1;
P0 = table_D[ge]; //段------>位 会在段所指的位置上显示段
LE_D = 0; //位------>段 新的数字会显示到之前的位置上
LE_W = 1;
P0 = 0x04; //0000 0100
LE_W = 0;
delay(1);
LE_W = 1; //消隐
P0 = 0;
LE_W = 0;
//数码管显示十位数字
LE_D = 1;
P0 = table_D[shi];
LE_D = 0;
LE_W = 1;
P0 = 0x02; //0000 0010
LE_W = 0;
delay(1);
LE_W = 1; //消隐
P0 = 0;
LE_W = 0;
//数码管显示百位数字
LE_D = 1;
P0 = table_D[bai];
LE_D = 0;
LE_W = 1;
P0 = 0x01; //0000 0001
LE_W = 0;
delay(1);
LE_W = 1; //消隐
P0 = 0;
LE_W = 0;
}