器→工具, 电子电路

51单片机学习之操作数码管

钱魏Way · · 4 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

数码管简介

数码管的本质:你可以把数码管想象成一组特制的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。

关键连接要点:

  • 位选信号:使用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();
    }
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注