数码管简介
数码管的本质:你可以把数码管想象成一组特制的LED灯组合。它们被排列成一个”8″字的形状(外加一个小数点),这样通过点亮不同的LED(段),就能组合显示出0-9的数字。
数码管的核心构成
- 段 (Segment):
- 每个数码管由7个条形LED灯(或者8个,包含小数点)组成,通常用字母a, b, c, d, e, f, g(加 dp 表示小数点)来标识。
- 这些段分别对应”8″字的笔画和小数点。控制哪些段亮起,就决定了显示哪个数字。
- 位 / 公共端 (Common):
- 这是数码管的公共连接点,所有段的LED要么共用一个阳极(+)(共阳极数码管),要么共用一个阴极(-)(共阴极数码管)。
- 区分共阳/共阴至关重要!直接决定驱动方式!

两种数码管原理及控制方式
| 特性 | 共阳极数码管 (Common Anode) | 共阴极数码管 (Common Cathode) |
| 公共端 | 所有LED的 阳极 (+) 连在一起 | 所有LED的 阴极 (-) 连在一起 |
| 工作逻辑 | 公共端给高电平 (+),需要点亮的段给低电平 (-) | 公共端给低电平 (-),需要点亮的段给高电平 (+) |
| 驱动逻辑 | “灌电流” (单片机输出电流驱动能力) | “拉电流” (单片机吸入电流驱动能力) |
| 常用场景 | 相对更常用(尤其老设计) | 也很常用 |
| 简化比喻 | “总开关打开(+)后,合上哪几个分开关(-)就亮哪几段” | “总开关关闭(-)后,打开哪几个分开关(+)就亮哪几段” |
1位数码管的显示
这里以共阳数码管为例,使用8051的P0口驱动(需加上拉电阻)。数码管有7段(a-g)和1个小数点(dp),共8个LED。共阳数码管的公共端接高电平,通过控制各段为低电平来点亮。如果使用共阴数码管,则公共端接地,需要给高电平点亮相应段。

真值表示例
以下表格包含共阳和共阴数码管的完整真值对照,支持数字、字母和常用符号。真值采用8位二进制(高位到低位:dp g f e d c b a)
| 显示内容 | 名称 | 二进制 (dp g f e d c b a) | 共阳十六进制 (0=亮) | 共阴十六进制 (1=亮) |
| 0 | 数字0 | 1100 0000 | 0xC0 | 0x3F |
| 1 | 数字1 | 1111 1001 | 0xF9 | 0x06 |
| 2 | 数字2 | 1010 0100 | 0xA4 | 0x5B |
| 3 | 数字3 | 1011 0000 | 0xB0 | 0x4F |
| 4 | 数字4 | 1001 1001 | 0x99 | 0x66 |
| 5 | 数字5 | 1001 0010 | 0x92 | 0x6D |
| 6 | 数字6 | 1000 0010 | 0x82 | 0x7D |
| 7 | 数字7 | 1111 1000 | 0xF8 | 0x07 |
| 8 | 数字8 | 1000 0000 | 0x80 | 0x7F |
| 9 | 数字9 | 1001 0000 | 0x90 | 0x6F |
| . | 小数点 | 0111 1111 | 0x7F | 0x80 |
共阳数码管连接
+5V
│
│
┌───┴───┐
│ 共阳数码管 │
└───┬───┘
├─ a ── P1.0 ─┤
├─ b ── P1.1 ─┤
├─ c ── P1.2 ─┤
├─ d ── P1.3 ─┤ 330Ω电阻
├─ e ── P1.4 ─┤(每个段独立)
├─ f ── P1.5 ─┤
├─ g ── P1.6 ─┤
└─ dp ─ P1.7 ─┤
限流电阻 (Must Have!):每个段(a, b, c, d, e, f, g, dp)都需要串联一个限流电阻(通常在220Ω – 1KΩ之间)。没有它,会瞬间烧毁LED或损坏你的单片机IO口!这是新手最容易忽略的点!
共阳数码管代码示例
#include <reg52.h> // 包含51单片机头文件
// 定义段选引脚(以P1口为例)
#define DIG_P1 P1
// 共阳数码管0-9的段码表
unsigned char code segCode[] = {
// gfedcba 顺序 (0=亮,1=灭)
0xC0, // 0: 1100 0000
0xF9, // 1: 1111 1001
0xA4, // 2: 1010 0100
0xB0, // 3: 1011 0000
0x99, // 4: 1001 1001
0x92, // 5: 1001 0010
0x82, // 6: 1000 0010
0xF8, // 7: 1111 1000
0x80, // 8: 1000 0000
0x90, // 9: 1001 0000
0x7F // .: 0111 1111
};
void delay(unsigned int t) { // 简单延时函数
while(t--);
}
void main() {
while(1) {
// 循环显示0-9
int num;
for(num=0; num<11; num++) {
DIG_P1 = segCode[num]; // 输出数字段码
delay(50000); // 延时约0.5秒
}
}
}
共阴数码管连接
GND
│
│
┌───┴───┐
│ 共阴数码管 │
└───┬───┘
├─ a ── P1.0 ─┐
├─ b ── P1.1 ─┤
... ├─> 都连接到 +5V
├─ g ── P1.6 ─┤
└─ dp ─ P1.7 ─┘
(每个P1口需要330Ω限流电阻)
共阴数码管代码示例
#include <reg52.h> // 包含51单片机头文件
// 定义段选引脚(以P1口为例)
#define DIG_P1 P1
/* 修改点1:段码表反转(共阴数码管需要高电平点亮) */
// 共阴数码管0-9和小数点的段码表
unsigned char code segCode[] = {
// gfedcba 顺序 (1=亮,0=灭)
0x3F, // 0: 0011 1111 (原0xC0取反)
0x06, // 1: 0000 0110 (原0xF9取反)
0x5B, // 2: 0101 1011 (原0xA4取反)
0x4F, // 3: 0100 1111 (原0xB0取反)
0x66, // 4: 0110 0110 (原0x99取反)
0x6D, // 5: 0110 1101 (原0x92取反)
0x7D, // 6: 0111 1101 (原0x82取反)
0x07, // 7: 0000 0111 (原0xF8取反)
0x7F, // 8: 0111 1111 (原0x80取反)
0x6F, // 9: 0110 1111 (原0x90取反)
0x80 // .: 1000 0000 (小数点需高电平点亮)
};
/* 修改点2:共阴连接方法
* 硬件连接变更:
* COM公共端 → GND
* 各段控制脚 → P1口引脚 + 330Ω限流电阻 → VCC
*/
void delay(unsigned int t) { // 简单延时函数
while(t--);
}
void main() {
while(1) {
/* 修改点3:小数点极性适配 */
sbit decimalPoint = P1^7; // 小数点控制位(P1.7)
for(int num = 0; num < 11; num++) {
// 特殊处理小数点显示
if(num == 10) {
DIG_P1 = 0x00; // 熄灭所有段
decimalPoint = 1; // 单独点亮小数点
} else {
DIG_P1 = segCode[num]; // 输出数字段码
decimalPoint = 0; // 确保小数点熄灭
}
delay(50000); // 延时约0.5秒
}
}
}
4位数码管的显示
多位数码管的原理
多位数码管的本质是共享段线 + 分时位选的结构,通过视觉暂留现象实现静态显示效果。这种设计大大减少了I/O口占用,是多位数码管的智能解决方案。
核心控制技术:动态扫描。动态扫描是解决多位数码管共享资源冲突的关键技术,其原理如下:

以4 位 7 段数码管为例,4 位 7 段数码管由四个协同工作的 7 段数码管组成。当数码管显示“1234”时,第一个7段显示“1”,不显示“234”。一段时间后,第2个7段显示“2”,第1个第3个第4个7段不显示,以此类推,四位数码管依次显示。这个过程很短(一般为5ms),由于光学余辉效应和视觉残留原理,我们可以同时看到四个字符。


连接原理图
我们以4位共阳数码管数码管为例进行讲解,模块引脚为:Vcc, D1, D2, D3, D4, A, B, C, D, E, F, G, DP。

关键连接要点:
- 位选信号:使用P2.0-P2.3分别连接D1-D4
- 段选信号:使用P0口连接A-G和DP
- 限流电阻:需要在段选线路中添加330Ω限流电阻
- 电源:Vcc接5V电源正极
4位共阳数码管代码示例
#include <reg52.h> // 包含51单片机头文件
// 定义控制端口
#define DIG_PORT P2 // 位选信号端口(D1-D4)
#define SEG_PORT P0 // 段选信号端口(A, B, C, D, E, F, G, DP)
// 共阳数码管段码表(0-9,顺序为dp g f e d c b a)
unsigned char code segCode[10] = {
0xC0, // 0: 1100 0000
0xF9, // 1: 1111 1001
0xA4, // 2: 1010 0100
0xB0, // 3: 1011 0000
0x99, // 4: 1001 1001
0x92, // 5: 1001 0010
0x82, // 6: 1000 0010
0xF8, // 7: 1111 1000
0x80, // 8: 1000 0000
0x90 // 9: 1001 0000
};
// 位选编码(第1-4位)
unsigned char code digCode[4] = {
0xFE, // 1111 1110 - 第一位(千位)
0xFD, // 1111 1101 - 第二位(百位)
0xFB, // 1111 1011 - 第三位(十位)
0xF7 // 1111 0111 - 第四位(个位)
};
// 全局变量
unsigned int counter = 0; // 计数器值(0-9999)
unsigned char displayData[4]; // 显示数据
// 毫秒级延时函数
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 114; j++);
}
// 更新显示数据
void updateDisplayData() {
// 分解数字为4位
displayData[0] = segCode[counter / 1000]; // 千位
displayData[1] = segCode[(counter / 100) % 10]; // 百位
displayData[2] = segCode[(counter / 10) % 10]; // 十位
displayData[3] = segCode[counter % 10] & 0x7F; // 个位,点亮小数点
}
// 数码管扫描函数
void displayNumber() {
unsigned char pos; // 当前扫描位置
// 依次扫描4位数码管
for(pos = 0; pos < 4; pos++) {
// 关闭所有段选,防止重影
SEG_PORT = 0xFF;
// 选择当前位
DIG_PORT = digCode[pos];
// 输出当前位的段码数据
SEG_PORT = displayData[pos];
// 保持显示1ms(优化刷新率)
delay_ms(1);
}
}
// 主函数
void main(void) {
// 初始化端口
DIG_PORT = 0xFF; // 初始关闭所有位选
SEG_PORT = 0xFF; // 初始关闭所有段选
// 初始显示0000
counter = 0;
updateDisplayData();
// 主循环
while(1) {
static unsigned int loopCount = 0;
// 显示当前计数器值
displayNumber();
// 累计循环次数(每100次循环约为1秒)
loopCount++;
// 每100次循环≈1秒后更新计数器
if(loopCount >= 100) {
loopCount = 0; // 重置循环计数器
counter++; // 秒计数器加1
// 超过9999则归零
if(counter > 9999)
counter = 0;
// 更新显示数据
updateDisplayData();
}
}
}
数码管与驱动芯片的联合使用
在51单片机系统中驱动数码管时,常需要搭配特定芯片解决电流驱动能力不足和引脚资源紧张的问题。以下是常用方案及其对比:
| 特性 | 74HC595 | TM1637 | MAX7219 | HT16K33 |
| 芯片类型 | 移位寄存器 | 专用数码管驱动 | LED显示驱动器 | LED控制器+RAM |
| 最大驱动能力 | 8位(1字节) | 4位数码管(32段) | 8位数码管(64段) | 8×16矩阵(128段) |
| 通信接口 | SPI(3线) | 类I²C(2线) | SPI(3线) | I²C(2线) |
| 级联能力 | ✅ 无限级联(动态扩展) | ❌ 不可级联 | ✅ 支持级联 | ❌ 单芯片 |
| 驱动电流 | 6mA/段(需扩流) | 10-20mA/段(内置恒流) | 40mA/段(大电流驱动) | 15mA/段(内置恒流) |
| 控制复杂度 | ★★★★ (需软件扫描) | ★★ (自动扫描) | ★ (完全自动扫描) | ★★ (自动扫描) |
| 功耗优化 | ❌ 无亮度控制 | ✅ 8级亮度调节 | ✅ 16级亮度调节 | ✅ 16级亮度调节 |
| 成本参考 | ¥0.5/片 | ¥1.5/片 | ¥5/片 | ¥3/片 |
| 典型应用 | 低成本多位数显 | 时钟/温湿度计 | LED大屏/工业仪表 | 多功能显示/自定义字符 |
带74HC595芯片的4位数码管模块

74HC595芯片简介
74HC595是一款8位串行输入/并行输出的移位寄存器,属于高速CMOS器件。它通过3线串行接口接收数据,可输出8位并行数据,广泛应用于LED显示驱动、IO扩展等场景。
引脚功能(16引脚封装)
| 引脚 | 名称 | 功能说明 |
| 1-7 | Q0-Q6 | 并行输出引脚 |
| 8 | GND | 接地 |
| 9 | Q7 | 第8位并行输出 |
| 10 | MR | 主复位(低电平有效) |
| 11 | SHCP | 移位寄存器时钟(上升沿触发) |
| 12 | STCP | 存储寄存器时钟(上升沿触发) |
| 13 | OE | 输出使能(低电平有效) |
| 14 | DS | 串行数据输入 |
| 15 | Q7′ | 串行输出(用于级联) |
| 16 | VCC | 电源正极 |
核心功能模块
- 移位寄存器
- 在SHCP上升沿时,DS数据移入寄存器
- 数据流向:Q0→Q1→…→Q7→Q7′
- 级联时:Q7’接下一级的DS
- 存储寄存器
- 在STCP上升沿时,移位寄存器内容锁存到存储寄存器
- 实现显示刷新无闪烁
- 三态输出
- OE=0时输出使能
- OE=1时输出高阻态(可总线共享)
74HC595因其简单可靠、成本低廉,在数码管显示、LED点阵、继电器控制等场景仍是首选方案。掌握其原理是嵌入式硬件设计的基本功。
带74HC595芯片的4位数码管模块的使用
硬件连接方案
该4位数码管模块通常采用双74HC595架构:
- 一个74HC595控制8段(7段+小数点)显示内容
- 另一个74HC595控制4位数码管的位选(选择显示位置)
连接示意图:
STC89C52RC 4位数码管模块
VCC ────────> VCC
GND ────────> GND
P2.0 ────────> SDI (串行数据输入)
P2.1 ────────> SCLK (移位时钟)
P2.2 ────────> LOAD (锁存时钟)
驱动程序示例
#include <REG52.H>
#include <intrins.h> // 包含_nop_()延时函数
// 引脚定义
sbit SDI = P2^0; // 串行数据输入
sbit SCLK = P2^1; // 移位时钟
sbit LOAD = P2^2; // 锁存时钟(STCP)
// 共阴数码管段码表 (0-9)
unsigned char code segTable[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};
// 延时函数(约5μs)
void delay5us() {
_nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_();
}
// 向595发送1字节数据函数
void send595Byte(unsigned char dat) {
unsigned char i;
for(i=0; i<8; i++) {
SDI = (dat & 0x80) ? 1 : 0; // 取最高位
dat <<= 1; // 左移准备下一位
// 产生移位时钟上升沿
SCLK = 0;
delay5us();
SCLK = 1;
delay5us();
}
}
// 向数码管模块发送两个字节数据
// digitPos:位选(1个字节,其中4位有效)
// segData:段选
void displaySeg(unsigned char digitPos, unsigned char segData) {
send595Byte(segData); // 先发送段选到第一片595
send595Byte(digitPos); // 再发送位选到第二片595
// 锁存数据到输出寄存器
LOAD = 0;
delay5us();
LOAD = 1;
delay5us();
}
// 动态显示函数(显示4位数)
unsigned char displayPos = 0; // 修复1:静态变量移到函数外部
void showNumber(unsigned int num) {
// 分解显示数字
unsigned char digits[4];
digits[0] = num % 10; // 个位
digits[1] = (num/10) % 10; // 十位
digits[2] = (num/100) % 10; // 百位
digits[3] = (num/1000) % 10; // 千位
// 动态扫描
displaySeg(1 << (3-displayPos), segTable[digits[displayPos]]);
// 移位显示位置
if(++displayPos >= 4) displayPos = 0;
}
// 延时函数(约1ms)
void delay1ms() {
unsigned int i, j;
for(i=0; i<1000; i++)
for(j=0; j<10; j++);
}
// 主函数:显示递增数字
void main() {
unsigned int count = 0;
unsigned char tick = 0; // 修复2:将tick移到函数开头声明
while(1) {
showNumber(count);
delay1ms(); // 刷新间隔
// 每50次刷新计数一次
if(++tick >= 50) {
tick = 0;
if(++count > 9999) count = 0;
}
}
}
带TM1637芯片的4位数码管模块
TM1637简介
TM1637是一款由台湾Titan Micro Electronics生产的LED驱动控制芯片,专为数码管显示应用设计。该芯片因其低成本、高集成度、使用简单的特点,在嵌入式显示领域广泛应用。
核心特性
- 显示能力
- 可驱动6位数码管
- 支持7段LED显示(包括小数点)
- 内置显存RAM(16字节×8位)
- 通信接口
- 两线串行接口(CLK和DIO)
- 最大支持450KHz通信速度
- 兼容I2C协议但非标准I2C
- 控制功能
- 8级亮度调节
- 扫描频率选择(1/2或1/3占空比)
- 内置时钟振荡电路(无需外部晶振)
技术参数
| 参数 | 值 | 备注 |
| 工作电压 | 3.3-5.5V | 宽电压范围 |
| 驱动电流 | ≥20mA/段 | 可驱动高亮数码管 |
| 功耗 | 0.5mA@3.3V | 低功耗模式 |
| 工作温度 | -40℃~85℃ | 工业级标准 |
| 封装 | SOP16/20 | 表面贴装 |
TM1637芯片的作用
TM1637是一种专门设计的LED数码管驱动控制芯片,在数码管显示系统中扮演着智能管理者的角色,它解决了直接驱动数码管的诸多痛点问题:
信号转换枢纽
- 将简单的串行信号转换为复杂的数码管控制信号
- 将微控制器的2根I/O线扩展为16个控制通道:
扫描与刷新引擎
- 内置动态扫描控制器:

与传统驱动方式对比
| 特性 | 传统方法(如74HC595) | TM1637方案 |
| IO需求 | 4-12个引脚 | 2个引脚 |
| 刷新机制 | 需CPU持续扫描 | 芯片自动完成 |
| 亮度控制 | 软件PWM或电阻调节 | 硬件8级控制 |
| 功耗控制 | 困难 | 休眠模式(<0.5μA) |
| 开发难度 | 高(需完整驱动程序) | 低(简单API) |
| 电路复杂度 | 高(多电阻+译码器) | 极简(直连) |
| 成本 | 中等 | 低(芯片<$0.1) |
管脚定义(SOP16封装)
A[VCC] --> 1 B[GND] --> 2 C[SEG1/GRID1] --> 3 D[SEG2/GRID2] --> 4 E[SEG3/GRID3] --> 5 F[SEG4/GRID4] --> 6 G[SEG5/GRID5] --> 7 H[SEG6/GRID6] --> 8 I[SEG7/GRID7] --> 9 J[GRID8/DIO] --> 10 K[CLK] --> 11 L[SEG8] --> 12 M[NC] --> 13-16
- 电源管脚:
- VCC:电源输入(3.3-5.5V)
- GND:接地
- 信号管脚:
- CLK:时钟输入
- DIO:双向数据线
- 驱动管脚:
- SEG1-SEG8:段驱动输出
- GRID1-GRID6:位驱动输出
内部结构框图

通信协议
通信接口
专用双线串行总线:
- CLK(时钟):上升沿有效
- DIO(数据):双向数据线
- 传输协议:起始信号 → [命令1] → [数据1] → … → [数据N] → 停止信号
基本时序

命令结构
| 命令类型 | 二进制格式 | 描述 |
| 数据命令 | 0100 AB00 | A=0:正常模式 A=1:测试模式 B=0:固定地址 B=1:自动地址 |
| 地址命令 | 1100 0000 +地址 | 设置显存地址(00H-0FH) |
| 显示控制 | 1000 PPP0 | PPP:亮度等级(000-111) 0位:1=显示ON 0=显示OFF |
带时间分隔符的四位数码管TM1637驱动方案

连接原理图
硬件特点分析
4位数码管模块具有以下特点:
- 集成时间分隔符(通常为双点冒号)
- 没有传统的小数点(DP位)
- 时间分隔符独立控制
- 标准TM1637驱动接口
接线表:
| TM1637引脚 | 51单片机引脚 | 说明 |
| CLK | P2.0 | 时钟信号线 |
| DIO | P2.1 | 数据信号线 |
| VCC | 5V | 电源正极 |
| GND | GND | 电源负极 |
完整测试程序
#include <reg52.h>
#include <intrins.h>
// 定义TM1637控制引脚
sbit TM1637_CLK = P2^0;
sbit TM1637_DIO = P2^1;
// TM1637命令定义
#define TM1637_ADDR_FIXED 0x44 // 固定地址模式
#define TM1637_ADDR_AUTO 0x40 // 自动地址模式
#define TM1637_DATA_SET 0xC0 // 数据设置命令
#define TM1637_DISPLAY_ON 0x88 // 显示开命令
#define TM1637_DISPLAY_OFF 0x80 // 显示关命令
// 数字段码表 (共阴数码管)
// 格式: gfedcba (高位到低位)
unsigned char code digitToSegment[] = {
/* 0 */ 0x3F, /* 1 */ 0x06, /* 2 */ 0x5B, /* 3 */ 0x4F,
/* 4 */ 0x66, /* 5 */ 0x6D, /* 6 */ 0x7D, /* 7 */ 0x07,
/* 8 */ 0x7F /* 9 */
};
// 全局变量声明
unsigned int i, j; // 用于延时函数的循环变量
unsigned char seconds = 0; // 秒
unsigned char minutes = 0; // 分
bit colonState = 0; // 冒号状态
// 微秒级延时函数
void delay_us(unsigned int us)
{
while(us--)
{
_nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_();
}
}
// 毫秒级延时函数
void delay_ms(unsigned int ms)
{
unsigned int x, y;
for(x = ms; x > 0; x--)
for(y = 114; y > 0; y--);
}
// 起始信号
void tm1637_start(void)
{
TM1637_CLK = 1;
TM1637_DIO = 1;
delay_us(5);
TM1637_DIO = 0;
delay_us(5);
TM1637_CLK = 0;
}
// 停止信号
void tm1637_stop(void)
{
TM1637_CLK = 0;
TM1637_DIO = 0;
delay_us(5);
TM1637_CLK = 1;
delay_us(5);
TM1637_DIO = 1;
delay_us(5);
}
// 发送字节
void tm1637_write_byte(unsigned char byte)
{
unsigned char i;
for (i = 0; i < 8; i++)
{
TM1637_CLK = 0;
delay_us(1);
TM1637_DIO = (byte & 0x01);
byte >>= 1;
delay_us(1);
TM1637_CLK = 1;
delay_us(1);
}
// 等待ACK
TM1637_CLK = 0;
TM1637_DIO = 1; // 释放数据线
delay_us(1);
TM1637_CLK = 1;
delay_us(1);
TM1637_CLK = 0;
}
// 显示数字和时间分隔符
void tm1637_display_with_colon(unsigned char d1, unsigned char d2,
unsigned char d3, unsigned char d4, bit colonOn)
{
unsigned char digit_buf[4];
unsigned char i;
// 转换数字为段码
digit_buf[0] = digitToSegment[d1];
digit_buf[1] = digitToSegment[d2];
digit_buf[2] = digitToSegment[d3];
digit_buf[3] = digitToSegment[d4];
// 设置冒号状态
if (colonOn) {
digit_buf[1] |= 0x80; // 设置最高位(DP位)
} else {
digit_buf[1] &= 0x7F; // 清除最高位(DP位)
}
// 设置显示模式
tm1637_start();
tm1637_write_byte(TM1637_ADDR_AUTO);
tm1637_stop();
// 发送显示数据
tm1637_start();
tm1637_write_byte(TM1637_DATA_SET);
for (i = 0; i < 4; i++) {
tm1637_write_byte(digit_buf[i]);
}
tm1637_stop();
// 设置亮度
tm1637_start();
tm1637_write_byte(TM1637_DISPLAY_ON | 0x07); // 最高亮度
tm1637_stop();
}
// 更新时钟显示
void display_time(void)
{
// 显示时间 (格式: MM:SS)
tm1637_display_with_colon(minutes/10, // 分钟十位
minutes%10, // 分钟个位
seconds/10, // 秒十位
seconds%10, // 秒个位
colonState); // 冒号状态
}
// 一秒计时器
void one_second_timer(void)
{
static unsigned char counter = 0;
// 每500ms调用一次,2次为1秒
counter++;
if(counter >= 2) {
counter = 0;
// 秒数增加
seconds++;
// 时间进位处理
if(seconds >= 60) {
seconds = 0;
minutes++;
if(minutes >= 60) {
minutes = 0;
}
}
}
// 切换冒号状态(闪烁效果)
colonState = !colonState;
}
// 主程序
void main()
{
// 初始化引脚
TM1637_CLK = 1;
TM1637_DIO = 1;
// 初始化时间变量
seconds = 0;
minutes = 0;
// 初始显示00:00
display_time();
delay_ms(500);
// 主循环
while(1)
{
display_time(); // 更新时间显示
one_second_timer(); // 一秒计时器
delay_ms(500); // 500ms刷新一次
}
}
带MAX7219芯片的8位数码管模块
MAX7219芯片简介
MAX7219是一款功能强大的LED显示驱动器芯片,可以控制多达8位7段数码管。每个数字均带小数点(DP),能够实现丰富的显示效果。

核心功能与架构
MAX7219是一款串行输入/输出共阴极显示驱动器,由Maxim Integrated(现ADI公司)设计。这款芯片是LED显示系统的”大脑”,包含:
- 8×8静态RAM:存储每个数码管/LED的显示数据
- B型BCD码译码器:支持BCD译码或原始数据模式
- 多路复用扫描电路:自动刷新数码管
- 段电流驱动器:提供5-40mA驱动能力
- 数字/模拟亮度控制:16级PWM调光
内部架构图解

关键技术参数
| 参数 | 值 | 说明 |
| 工作电压 | +4.0-5.5V | 支持3V/5V系统 |
| 刷新率 | 800Hz-16kHz | 可编程 |
| 通道数 | 8位数字 + 8段 | 共64个LED |
| 接口速度 | 10MHz | SPI兼容 |
| 待机电流 | 150µA | 低功耗模式 |
| 工作温度 | -40℃ ~ +85℃ | 工业级 |
主要功能特性
智能扫描刷新

灵活的显示控制
- 4种工作模式:
- 正常显示:标准8位数码管
- 译码模式:自动BCD-7段转换
- 亮度控制:16级PWM调光(占空比1/32~31/32)
- 显示测试:全亮模式
- 扫描控制:可选择显示1-8位数字
级联扩展能力

寄存器映射表
| 地址 | 寄存器 | 功能说明 | 默认值 |
| 0x01-0x08 | Digit0-Digit7 | 各数字显示数据 | 0x00 |
| 0x09 | Decode Mode | 译码模式 (0x00=无译码,0xFF=全译码) | 0x00 |
| 0x0A | Intensity | 亮度控制 (0x00-0x0F) | 0x07 |
| 0x0B | Scan Limit | 扫描位数 (0x00-0x07) | 0x07 |
| 0x0C | Shutdown | 关机模式 (0x00=关, 0x01=开) | 0x01 |
| 0x0F | Display Test | 显示测试 (0x00=正常, 0x01=测试) | 0x00 |
通信协议详解
MAX7219使用专有SPI兼容协议:
16位数据帧结构
+--------------+---------------+ | 15 14 13 12 | 11 10 9 8 | 7 6 5 4 3 2 1 0 | |----------------|------------|----------------| | 寄存器地址 | X X X X | 数据 | +--------------+---------------+
工作时序

驱动代码范例
#include <reg52.h>
#include <intrins.h>
sbit DIN = P1^0; // 数据线
sbit LOAD = P1^1; // 片选线
sbit CLK = P1^2; // 时钟线
// 数码管段码表 (0-9,A-F)
unsigned char code digitTable[17] = { // 17个元素(0-16)
0x7E, // 0: ABCDEF
0x30, // 1: BC
0x6D, // 2: ABEDG
0x79, // 3: ABCD
0x33, // 4: FBGC
0x5B, // 5: AFGCD
0x5F, // 6: AFGCED
0x70, // 7: ABC
0x7F, // 8: 全亮
0x7B, // 9: ABFGCD
0x77, // A: ABFGE
0x1F, // B: FGECD
0x4E, // C: AFED
0x3D, // D: BGECD
0x4F, // E: AFGED
0x47, // F: AFG
0x00 // 空格
};
// 函数声明
void MAX7219_Write(unsigned char addr, unsigned char dat);
void MAX7219_Init(void);
void Set_Digit(unsigned char pos, unsigned char val);
void Display_Number(unsigned long num);
void delay_ms(unsigned int ms);
// 写入寄存器函数
void MAX7219_Write(unsigned char addr, unsigned char dat) {
unsigned int frame;
unsigned char i;
// 构造16位数据帧
frame = (unsigned int)addr << 8 | dat;
LOAD = 0; // 开始传输
// 发送16位数据 (MSB先发)
for(i = 0; i < 16; i++) {
CLK = 0; // 时钟下降沿
// 设置数据位
if(frame & 0x8000) {
DIN = 1;
} else {
DIN = 0;
}
frame = frame << 1; // 准备下一位
// 短延时确保信号稳定
_nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_();
CLK = 1; // 时钟上升沿
_nop_(); _nop_(); _nop_();
}
CLK = 0; // 时钟恢复低电平
LOAD = 1; // 结束传输
}
// 初始化MAX7219
void MAX7219_Init(void) {
// 关闭显示测试
MAX7219_Write(0x0F, 0x00);
// 禁用BCD解码
MAX7219_Write(0x09, 0x00);
// 设置中等亮度
MAX7219_Write(0x0A, 0x07);
// 扫描所有8位数码管
MAX7219_Write(0x0B, 0x07);
// 正常操作模式
MAX7219_Write(0x0C, 0x01);
// 清空显示
MAX7219_Write(0x01, 0x00); // Digit 0 (最右边)
MAX7219_Write(0x02, 0x00);
MAX7219_Write(0x03, 0x00);
MAX7219_Write(0x04, 0x00);
MAX7219_Write(0x05, 0x00);
MAX7219_Write(0x06, 0x00);
MAX7219_Write(0x07, 0x00);
MAX7219_Write(0x08, 0x00); // Digit 7 (最左边)
}
// 设置数码管显示 (修正位序)
void Set_Digit(unsigned char pos, unsigned char val) {
unsigned char reg;
// 调整位序:实际数码管位置0-7对应寄存器1-8
// 位置0: 最右边 (Digit0) -> 寄存器1
// 位置7: 最左边 (Digit7) -> 寄存器8
reg = pos + 1; // 寄存器地址 = 位置 + 1
if(val < 16) {
MAX7219_Write(reg, digitTable[val]); // 显示数字
} else {
MAX7219_Write(reg, digitTable[16]); // 显示空格
}
}
// 显示整数值 (修正位序)
void Display_Number(unsigned long num) {
unsigned char i;
unsigned char digits[8]; // 存储8位数字 (右到左)
// 分离数字 (从低位到高位)
for(i = 0; i < 8; i++) {
digits[i] = num % 10; // 当前位数字
num = num / 10; // 移除已处理数字
}
// 设置数码管 (右到左显示),位置0为最右边
for(i = 0; i < 8; i++) {
// 设置第i位数码管 (从右向左)
Set_Digit(i, digits[i]);
}
}
// 毫秒级延时函数
void delay_ms(unsigned int ms) {
unsigned int x, y;
for(x = ms; x > 0; x--)
for(y = 112; y > 0; y--);
}
// 主程序
void main(void) {
unsigned long counter = 12345678; // 初始显示数字
// 初始化IO
DIN = 0;
LOAD = 1;
CLK = 0;
delay_ms(100); // 电源稳定延时
// 初始化MAX7219
MAX7219_Init();
// 主循环
while(1) {
Display_Number(counter); // 显示当前数值
counter = (counter + 1) % 100000000; // 0-99999999循环
delay_ms(100);
}
}
增强版本
#include <reg52.h>
#include <intrins.h>
#include <math.h>
sbit DIN = P1^0; // 数据线
sbit LOAD = P1^1; // 片选线
sbit CLK = P1^2; // 时钟线
// 数码管段码表 (0-9,A-F)
unsigned char code digitTable[17] = { // 17个元素(0-16)
0x7E, // 0: ABCDEF
0x30, // 1: BC
0x6D, // 2: ABEDG
0x79, // 3: ABCD
0x33, // 4: FBGC
0x5B, // 5: AFGCD
0x5F, // 6: AFGCED
0x70, // 7: ABC
0x7F, // 8: 全亮
0x7B, // 9: ABFGCD
0x77, // A: ABFGE
0x1F, // B: FGECD
0x4E, // C: AFED
0x3D, // D: BGECD
0x4F, // E: AFGED
0x47, // F: AFG
0x00 // 空格
};
// 函数声明
void MAX7219_Write(unsigned char addr, unsigned char dat);
void MAX7219_Init(void);
void Set_Digit(unsigned char pos, unsigned char val);
void Display_Number(unsigned long num);
void delay_ms(unsigned int ms);
void Set_Digit_DP(unsigned char pos, unsigned char val, unsigned char dot);
void Display_Float(float num, char dec_places);
// 写入寄存器函数
void MAX7219_Write(unsigned char addr, unsigned char dat) {
unsigned int frame;
unsigned char i;
// 构造16位数据帧
frame = (unsigned int)addr << 8 | dat;
LOAD = 0; // 开始传输
// 发送16位数据 (MSB先发)
for(i = 0; i < 16; i++) {
CLK = 0; // 时钟下降沿
// 设置数据位
if(frame & 0x8000) {
DIN = 1;
} else {
DIN = 0;
}
frame = frame << 1; // 准备下一位
// 短延时确保信号稳定
_nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_();
CLK = 1; // 时钟上升沿
_nop_(); _nop_(); _nop_();
}
CLK = 0; // 时钟恢复低电平
LOAD = 1; // 结束传输
}
// 初始化MAX7219
void MAX7219_Init(void) {
// 关闭显示测试
MAX7219_Write(0x0F, 0x00);
// 禁用BCD解码
MAX7219_Write(0x09, 0x00);
// 设置中等亮度
MAX7219_Write(0x0A, 0x07);
// 扫描所有8位数码管
MAX7219_Write(0x0B, 0x07);
// 正常操作模式
MAX7219_Write(0x0C, 0x01);
// 清空显示
MAX7219_Write(0x01, 0x00); // Digit 0 (最右边)
MAX7219_Write(0x02, 0x00);
MAX7219_Write(0x03, 0x00);
MAX7219_Write(0x04, 0x00);
MAX7219_Write(0x05, 0x00);
MAX7219_Write(0x06, 0x00);
MAX7219_Write(0x07, 0x00);
MAX7219_Write(0x08, 0x00); // Digit 7 (最左边)
}
// 设置数码管显示 (修正位序)
void Set_Digit(unsigned char pos, unsigned char val) {
unsigned char reg;
// 调整位序:实际数码管位置0-7对应寄存器1-8
// 位置0: 最右边 (Digit0) -> 寄存器1
// 位置7: 最左边 (Digit7) -> 寄存器8
reg = pos + 1; // 寄存器地址 = 位置 + 1
if(val < 16) {
MAX7219_Write(reg, digitTable[val]); // 显示数字
} else {
MAX7219_Write(reg, digitTable[16]); // 显示空格
}
}
// 显示整数值 (修正位序)
void Display_Number(unsigned long num) {
unsigned char i;
unsigned char digits[8]; // 存储8位数字 (右到左)
// 分离数字 (从低位到高位)
for(i = 0; i < 8; i++) {
digits[i] = num % 10; // 当前位数字
num = num / 10; // 移除已处理数字
}
// 设置数码管 (右到左显示),位置0为最右边
for(i = 0; i < 8; i++) {
// 设置第i位数码管 (从右向左)
Set_Digit(i, digits[i]);
}
}
// 毫秒级延时函数
void delay_ms(unsigned int ms) {
unsigned int x, y;
for(x = ms; x > 0; x--)
for(y = 112; y > 0; y--);
}
// 增强Set_Digit函数
void Set_Digit_DP(unsigned char pos, unsigned char val, unsigned char dot) {
unsigned char seg_data;
unsigned char reg = pos + 1; // 寄存器地址
if(val < 16) {
seg_data = digitTable[val]; // 获取段码
// 添加小数点 (最高位为DP)
if(dot) {
seg_data |= 0x80;
}
MAX7219_Write(reg, seg_data);
} else {
MAX7219_Write(reg, digitTable[16]); // 空格
}
}
// 显示浮点数
void Display_Float(float num, char dec_places) {
unsigned long integer = (unsigned long)(num * pow(10, dec_places));
unsigned char digits[8];
unsigned char i;
// 分离数字
for(i = 0; i < 8; i++) {
digits[i] = integer % 10;
integer /= 10;
}
// 设置数码管
for(i = 0; i < 8; i++) {
// 判断是否为小数点位置
unsigned char dot = (i == dec_places) ? 1 : 0;
Set_Digit_DP(i, digits[i], dot);
}
}
// 主程序
// 完整的调试和测试函数
void main(void) {
unsigned char i;
unsigned long counter = 0;
unsigned char test_phase = 0;
// 初始化IO
DIN = 0;
LOAD = 1;
CLK = 0;
delay_ms(100); // 电源稳定延时
// 初始化MAX7219
MAX7219_Init();
// 测试序列
while(1) {
switch(test_phase) {
case 0: // 位序测试: 显示01234567
for(i = 0; i < 8; i++) {
Set_Digit(i, i);
}
break;
case 1: // 全亮测试
for(i = 0; i < 8; i++) {
Set_Digit(i, 8);
}
break;
case 2: // 小数点测试
for(i = 0; i < 8; i++) {
Set_Digit_DP(i, 8, (i % 2)); // 交替小数点
}
break;
case 3: // 正常计数模式
Display_Number(counter);
counter = (counter + 1) % 100000000;
break;
}
// 切换测试状态
delay_ms(2000);
test_phase = (test_phase + 1) % 4;
// 每轮测试前清空显示
MAX7219_Init();
}
}



