TM1637芯片从入门到精通

TM1637 芯片从入门到精通


目录

  1. [TM1637 是什么](#TM1637 是什么)
  2. 芯片内部架构与引脚详解
  3. 电气特性与极限参数
  4. 通信协议深度解析
  5. 命令集与寄存器
  6. 硬件电路设计指南
  7. 编程实战:多平台驱动库
  8. 高级应用技巧
  9. [TM1637 vs TM1638 vs TM1650 vs MAX7219](#TM1637 vs TM1638 vs TM1650 vs MAX7219)
  10. 常见故障与排错手册
  11. [项目实战:温湿度显示器 + 按键交互](#项目实战:温湿度显示器 + 按键交互)
  12. 总结与资源推荐

1. TM1637 是什么

1.1 芯片概述

TM1637 是深圳天微电子(Titan Micro Electronics)推出的一款专用 LED 数码管驱动芯片,内部集成了:

  • MCU 数字接口(类 I2C 两线串行通信)
  • 8×6 位 RAM 显存(最大驱动 6 个 8 段数码管)
  • 键盘扫描电路(最多 16 个按键)
  • 内部 RC 振荡器(±2.5% 精度,无需外部晶振)
  • 8 级亮度 PWM 调节电路
  • 上电复位电路(POR)

一句话总结: TM1637 用两根 IO 线就能驱动最多 6 位 7 段数码管 + 读取 16 个按键,极大简化了数码管显示系统的硬件与软件开发。

适用对象: 嵌入式开发初学者、电子爱好者、Arduino/STM32/ESP32 开发者

芯片定位: LED 数码管驱动 + 按键扫描专用芯片

涵盖平台: Arduino · STM32 · ESP32 · 51单片机 · Raspberry Pi

通信协议: 类 I2C 两线串行接口(非标准 I2C)

1.2 TM1637 家族

TM1637 是天微"TM16xx"系列中的一员,该系列全家福如下:

型号 显示位数 按键数 接口 特殊功能
TM1637 6 位 × 8 段 16 键 (2×8) 2 线 最流行,性价比之王
TM1638 8 位 × 10 段 24 键 (3×8) 3 线 更多段数
TM1640 8 位 × 16 段 --- 2 线 16 段驱动
TM1650 4 位 × 8 段 28 键 (4×7) 2 线 更小封装
TM1652 4 位 × 8 段 --- 2 线 I2C 真标准协议
TM1668 7 位 × 10 段 可扩展 3 线 大电流驱动
MAX7219 8 位 × 8 段 --- SPI (3 线) 非天微,作为对比

1.3 典型应用场景

复制代码
┌─────────────────────────────────────────────────┐
│  电饭煲 / 电磁炉 / 微波炉      数码管显示倒计时   │
│  空调 / 热水器 / 温控器         温度 + 模式显示   │
│  电子秤 / 万用表 / 示波器       测量数值显示      │
│  DIY 数字时钟 / 温湿度计        时间/温湿度       │
│  智能家居面板                   状态参数显示      │
│  工业仪表 / PLC 人机界面        计数/频率/电压    │
└─────────────────────────────────────────────────┘

1.4 为什么 TM1637 如此流行

  1. 成本极低 --- 芯片零售低于 ¥0.5,成品模块 ¥2~5
  2. IO 占用少 --- 仅 2 个 MCU IO 口(CLK + DIO)
  3. 无外部元件 --- 内部 RC 振荡,无需晶振
  4. 即插即用 --- 淘宝模块四线引出,5 分钟点亮
  5. 生态完善 --- Arduino / MicroPython / STM32 都有现成库
  6. 电压宽范围 --- 3.3V ~ 5.5V,兼容主流 MCU

2. 芯片内部架构与引脚详解

2.1 内部框图

复制代码
                    ┌──────────────────────────────────┐
                    │            TM1637                │
                    │                                  │
    CLK ──────────► │  ┌─────────┐    ┌───────────┐   │
    DIO ◄────────► │  │ 串行接口 │───►│ 命令解码   │   │
                    │  │ 控制器   │    └─────┬─────┘   │
                    │  └─────────┘          │         │
                    │                       ▼         │
                    │  ┌─────────┐    ┌───────────┐   │
                    │  │ 内部 RC │    │  显示     │───► SEG1~SEG8
                    │  │ 振荡器   │    │ 寄存器    │───► GRID1~GRID6
                    │  └─────────┘    │ (6×8bit)  │   │
                    │                 └───────────┘   │
                    │                       │         │
                    │  ┌─────────┐    ┌─────▼─────┐   │
                    │  │  上电   │    │  亮度PWM   │   │
                    │  │ 复位POR │    │  控制器    │   │
                    │  └─────────┘    └─────┬─────┘   │
                    │                       │         │
                    │  ┌─────────┐    ┌─────▼─────┐   │
                    │  │ 按键扫描│◄───┤  位选    │   │
    KS1 ◄─────────► │  │ (2×8)  │    │ 驱动器    │───► SEG1/KS1 (复用)
    KS2 ◄─────────► │  │        │    │           │───► SEG2/KS2 (复用)
    KS3~KS8 ◄─────► │  └─────────┘    └───────────┘   │
                    │                                  │
                    └──────────────────────────────────┘

2.2 引脚定义

TM1637 常见封装有 DIP-20 (直插)和 SOP-20(贴片):

复制代码
              DIP-20 / SOP-20 (顶视图)
             ┌───○───────────────┐
    SEG1/KS1 │ 1              20 │ SEG2/KS2
        SEG3 │ 2              19 │ SEG4
        SEG5 │ 3              18 │ SEG6
        SEG7 │ 4              17 │ SEG8
         GND │ 5              16 │ VDD
        GRID1│ 6              15 │ KS3
        GRID2│ 7              14 │ KS4
        GRID3│ 8              13 │ KS5
        GRID4│ 9              12 │ KS6
        GRID5│10              11 │ GRID6
             └──────────────────┘
引脚号 名称 I/O 功能说明
1 SEG1 / KS1 I/O 段输出 1 / 按键扫描输入 1(复用)
2 SEG3 O 段输出 3
3 SEG5 O 段输出 5
4 SEG7 O 段输出 7
5 GND P 电源地
6 GRID1 O 位选输出 1(低位 / 第 1 位)
7 GRID2 O 位选输出 2
8 GRID3 O 位选输出 3
9 GRID4 O 位选输出 4
10 GRID5 O 位选输出 5
11 GRID6 O 位选输出 6(高位 / 第 6 位)
12 KS6 I 按键扫描输入 6
13 KS5 I 按键扫描输入 5
14 KS4 I 按键扫描输入 4
15 KS3 I 按键扫描输入 3
16 VDD P 电源正(3.3V ~ 5.5V)
17 SEG8 O 段输出 8
18 SEG6 O 段输出 6
19 SEG4 O 段输出 4
20 SEG2 / KS2 I/O 段输出 2 / 按键扫描输入 2(复用)

重要提示: SEG1/KS1 和 SEG2/KS2 是复用引脚。在显示模式下作为段输出(SEG1、SEG2),在按键扫描模式下作为按键输入(KS1、KS2)。芯片内部自动分时切换,用户无需干预。

2.3 引脚功能分组

复制代码
段输出组 (SEG1~SEG8):
  → 每个段输出可驱动 1 个 LED 段的阳极(共阴极接法)
  → 高电平有效(推挽输出,最大 20mA)

位选输出组 (GRID1~GRID6):
  → 每个位选输出通过 NMOS 管吸收电流
  → 低电平有效(开漏输出,最大 200mA)
  → 动态扫描时依次拉低

按键扫描组 (KS1~KS6):
  → KS1/KS2 与 SEG1/SEG2 复用引脚
  → KS3~KS6 独立输入引脚
  → 按键矩阵:KS × GRID

通信接口:
  → CLK (时钟) 和 DIO (数据) 复用 SEG1/KS1 和 SEG2/KS2
  → 但成品模块中 CLK/DIO 已独立引出,不需关心复用

2.4 成品模块引脚(用户最关心的)

市售 TM1637 成品模块已封装好,仅引出 4 个引脚

复制代码
       ┌─────────────────────┐
       │    8  8  :  8  8    │  ← 4位0.56"数码管
       │     (TM1637)        │
       │                     │
       │  GND  VCC  DIO  CLK │  ← 仅 4 个引脚!
       └─────────────────────┘

    GND  ---  电源地
    VCC  ---  电源正 (3.3V ~ 5.5V)
    DIO  ---  数据线(双向,需上拉)
    CLK  ---  时钟线(单向,MCU → TM1637)

3. 电气特性与极限参数

3.1 极限参数(超出即可能永久损坏)

参数 符号 最小值 最大值 单位
电源电压 VDD -0.5 6.0 V
输入电压 VI -0.5 VDD + 0.5 V
段输出电流 I_SEG --- 25 mA
位输出电流 I_GRID --- 250 mA
工作温度 T_OPR -40 +85 °C
存储温度 T_STG -65 +150 °C

3.2 推荐工作条件

参数 符号 最小值 典型值 最大值 单位
电源电压 VDD 3.3 5.0 5.5 V
高电平输入 VIH 0.7×VDD --- VDD V
低电平输入 VIL 0 --- 0.3×VDD V
段输出电流 I_SEG --- 15 20 mA
位输出电流 I_GRID --- 150 200 mA
工作温度 T_OPR -20 25 70 °C

3.3 直流电气特性 (VDD = 5.0V, TA = 25°C)

参数 条件 最小值 典型值 最大值 单位
工作时电流 显示全开,8/8 亮度 --- 80 120 mA
待机电流 显示关 (0x80) --- 1 5 μA
段输出高电平 IOH = -15mA 4.2 4.5 --- V
段输出低电平 IOL = 15mA --- 0.3 0.7 V
位输出低电平 IOL = 150mA --- 0.5 0.8 V
CLK 频率 --- --- --- 500 kHz

3.4 上电复位时序

复制代码
    VDD
     ↑
     │     ╭────────────
     │    ╱
     │   ╱
     │  ╱
     │ ╱
     └─┴─────────────────► t
        │◄──────────────►│
        t_POR ≈ 150μs (典型值)
        
    上电后约 150μs,芯片内部复位完成,可接受命令。
    建议:上电后等待 1ms 再发送第一条命令(留足余量)。

4. 通信协议深度解析

⚠️ 这可能是 TM1637 最重要的章节。 90% 的通信问题都源于对协议的误解。

4.1 协议概述

TM1637 使用的是类 I2C 两线串行协议,但不是标准 I2C:

特性 标准 I2C TM1637
设备地址 有(7 位)
寄存器寻址 显式 隐式(命令字节中编码)
ACK/NACK 第 9 位 第 9 位(仅对数据字节响应)
起始/停止条件 标准 与 I2C 相同
从机地址响应 无 --- 不检查设备地址
多设备总线 支持 不支持

关键结论:TM1637 不能用硬件 I2C 外设驱动! 必须用 GPIO 软件模拟协议时序。

4.2 起始与停止条件

复制代码
起始条件 (Start Condition):
  CLK = 1 期间,DIO 从 高 → 低   (下降沿)
  
   CLK  ──────┐         ┌────────
               │         │
               └─────────┘
   DIO  ──────────┐
                   │
                   └────────────────
         ◄────────► 起始条件

停止条件 (Stop Condition):
  CLK = 1 期间,DIO 从 低 → 高   (上升沿)
  
   CLK  ──────┐         ┌────────
               │         │
               └─────────┘
   DIO  ──┐
           │
           └────────────────────
                       ◄────────► 停止条件

4.3 数据位传输

低位优先(LSB first) --- 这是 TM1637 与很多其他协议的关键区别!

复制代码
发送一个字节 0x41 (0100 0001b):

  位序:  LSB (bit0) ....................... MSB (bit7)
  数据:    1      0      0      0      0      0      1      0
  
  CLK    ┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐
         │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │  │
         └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──
  
  DIO    ──────────                    ┌─────────────
                     └──────────────────┘
            bit0=1   bit1=0   bit2=0 ...         bit7=0
                    ↑ 上升沿采样
                    
  规则:CLK 低 → 设 DIO → CLK 高(上升沿锁存数据)
  
  第 9 个 CLK 周期(ACK 位):
  CLK    ┐                 ┌──┐
         │                 │  │
         └─────────────────┘  └────────
  
  DIO    (主机释放,从机 TM1637 拉低表示 ACK)
         ──────────────────┐
                            └───────────
                            ◄──► ACK=0

写一个字节的伪代码:

python 复制代码
def tm1637_write_byte(byte):
    for bit in range(8):
        clk_low()
        if byte & 0x01:    # LSB first
            dio_high()
        else:
            dio_low()
        delay_us(1)
        clk_high()          # 上升沿写入
        delay_us(1)
        byte >>= 1

    # 第 9 位:ACK
    clk_low()
    dio_high()              # 主机释放 DIO
    set_dio_input()         # 切换为输入模式
    delay_us(1)
    clk_high()
    ack = read_dio()        # 0=ACK, 1=NACK
    delay_us(1)
    clk_low()
    set_dio_output()        # 切回输出模式
    return ack

4.4 完整的通信序列

TM1637 每次通信遵循 "命令 → 数据 → 命令" 的三段式结构:

复制代码
┌───────┐    ┌───────┐    ┌──────────────────┐    ┌───────┐    ┌───────┐
│ Start │ → │ 命令1 │ → │ Stop (可选)        │ → │ Start │ → │ 命令2 │ → ...
└───────┘    └───────┘    └──────────────────┘    └───────┘    └───────┘

典型显示数据写入序列:

  Start → Cmd1(0x40) → Stop → Start → Cmd2(0xC0) → Data[0] → Data[1] → ... → Data[N] → Stop → Start → Cmd3(0x8F) → Stop
  
  ┌──────────────────────────────────────────────────────────────────────────────┐
  │  Start │ 0x40 │ Stop │ Start │ 0xC0 │ 0x3F │ 0x06 │ 0x5B │ 0x4F │ Stop     │
  │        数据模式      │       地址0   │  '0'   '1'    '2'    '3'            │
  │                     │                                                       │
  │  Start │ 0x8F │ Stop │                                                     │
  │        显示开+最亮                                                           │
  └──────────────────────────────────────────────────────────────────────────────┘

为什么 Stop 之后马上 Start?

有些命令之间必须通过 Stop 分隔才能生效。根据官方数据手册,写完数据后必须先 Stop 再 Start 发显示控制命令 。但实际测试中,写完命令1后直接发命令2(不加 Stop)有时也能工作 ------ 取决于芯片批次。为保证 100% 兼容性,严格按 Stop/Start 分隔。

4.5 时序参数

复制代码
参数        符号    最小值    典型值    最大值    单位
─────────────────────────────────────────────────────
CLK 频率    fCLK     ---         ---        500       kHz
CLK 高电平  tCH     1         ---         ---         μs
CLK 低电平  tCL     1         ---         ---         μs
数据建立    tSU     100       ---         ---         ns
数据保持    tHD     100       ---         ---         ns
起始保持    tSTH    1         ---         ---         μs
停止建立    tSTS    1         ---         ---         μs

一般情况下 1~2μs 的延时足够,不建议过小(< 500ns)或过大(> 100μs)以免出现问题。


5. 命令集与寄存器

5.1 命令结构

TM1637 的每个命令都是 1 个字节,高 2 位 (Bit7, Bit6) 决定命令类型:

复制代码
Bit [7:6]    命令类型
  00         保留(未使用)
  01         数据命令
  10         地址命令  
  11         显示控制命令

5.2 数据命令 (01xxxxxx)

设置数据写入模式:

复制代码
  0  1  X  X  X  X  X  X
  │  │  │  └──┴──┴──┴──┴── 保留(填 0)
  │  │  └── 数据方向:0=写, 1=读
  │  └───── 固定地址:0=地址自增, 1=固定地址
  └──────── 命令标识 = 01
命令字节 含义
0x40 写数据到显示寄存器,地址自动加 1(最常用)
0x44 写数据到显示寄存器,固定地址
0x42 读按键扫描数据(不需要地址命令前置)

地址自增模式 (0x40):

发完地址命令 0xC0 后,每写一个数据字节,内部地址指针自动 +1:

复制代码
Start → 0x40 → Stop → Start → 0xC0 → Data_C0 → Data_C1 → Data_C2 → ... → Stop

固定地址模式 (0x44):

每次写数据前需要重新发一次地址命令:

复制代码
Start → 0x44 → Stop → Start → 0xC0 → Data[0] → Stop
Start → 0x44 → Stop → Start → 0xC1 → Data[1] → Stop
...(每次都要 Stop + Start + 地址命令)

99% 的场景使用 0x40(自增模式) 就够了。

5.3 地址命令 (10xxxxxx)

设置要读/写的显示寄存器起始地址:

复制代码
  1  0  X  X  X  X  X  X
  │  │  └──┴──┴──┴──┴──┴── 地址:0x00 ~ 0x05 (对应 C0H ~ C5H)
  └──┴── 命令标识 = 10
命令字节 对应显示位
0xC0 GRID1(第 1 位,通常是最低位/个位)
0xC1 GRID2(第 2 位)
0xC2 GRID3(第 3 位)
0xC3 GRID4(第 4 位)
0xC4 GRID5(第 5 位)
0xC5 GRID6(第 6 位)

5.4 显示控制命令 (11xxxxxx)

控制显示开关和 PWM 亮度:

复制代码
  1  1  X  X  X  B2 B1 B0
  │  │  │  └──┴──┴──┴──┴── 亮度等级 (0~7)
  │  │  └── 保留(填 0)
  │  └───── 显示开关:0=关, 1=开
  └──────── 命令标识 = 11
亮度值 命令字节 PWM 占空比 视觉效果
0 0x88 1/16 最暗
1 0x89 2/16
2 0x8A 4/16
3 0x8B 10/16
4 0x8C 11/16
5 0x8D 12/16
6 0x8E 13/16
7 0x8F 14/16 最亮

关闭显示: 发送 0x80(Bit3 = 0,亮度值无效)

开启显示: 发送 0x88 | brightness(Bit3 = 1,Bit0~2 = 亮度)

注意: 亮度等级不是线性的!Level 0~2 变化剧烈,Level 3~7 人眼感受差异较小。

5.5 按键扫描命令 (0x42)

读取按键数据:

复制代码
Start → 0x42 → B1 → B2 → B3 → B4 → ... → Bn → Stop

每 1 个数据字节的 8 个 bit 对应 8 个按键。  
n 由 GRID 数量决定(最多 6 字节)。

按键矩阵:

复制代码
                KS1   KS2   KS3   KS4   KS5   KS6   KS7   KS8
    GRID1  →    K1    K2    K3    K4    K5    K6    K7    K8
    GRID2  →    K9   K10   K11   K12   K13   K14   K15   K16
    GRID3  →    ...
    GRID4  →    ...
    GRID5  →    ...
    GRID6  →    ...

**TM1637 成品模块(4 位数码管)**只接入了 GRID1~GRID4 和 SEG1~SEG8,因此只能扫描 4 × 8 = 32 个按键。但通常模块上没有按键焊盘,需要自行飞线。


6. 硬件电路设计指南

6.1 最小系统电路

复制代码
                    VCC (3.3V ~ 5V)
                     │
                     ├────┬──────┬──────┐
                     │    │      │      │
                    ┌┴┐  ┌┴┐    ┌┴┐    ┌┴┐
                    │ │  │ │    │ │    │ │  4.7KΩ × 4(上拉)
                    │ │  │ │    │ │    │ │
                    └┬┘  └┬┘    └┬┘    └┬┘
                     │    │      │      │
                     │    │      │      │
    MCU_CLK ─────────┼────┼──────┼──────┼─── CLK (TM1637)
    MCU_DIO ─────────┼────┼──────┼──────┼─── DIO (TM1637)
                     │    │      │      │
                   ──┴────┴──────┴──────┴─── VDD (16)
                                             │
                                           ═══ 100nF  去耦电容
                                             │
                                           ──┴── GND (5)

6.2 段选与位选电路

TM1637 驱动共阴极数码管的典型电路:

复制代码
    TM1637                             共阴数码管 (4位8段)
    ┌──────┐                          ┌──────────────┐
    │      │── SEG1 ──────── a ───────┤ a            │
    │      │── SEG2 ──────── b ───────┤ b            │
    │      │── SEG3 ──────── c ───────┤ c            │
    │      │── SEG4 ──────── d ───────┤ d            │
    │      │── SEG5 ──────── e ───────┤ e            │
    │      │── SEG6 ──────── f ───────┤ f            │
    │      │── SEG7 ──────── g ───────┤ g            │
    │      │── SEG8 ──────── dp ──────┤ dp           │
    │      │                          └──────┬───────┘
    │      │── GRID1 ──────── COM1 ──────────┘ (位1)
    │      │── GRID2 ──────── COM2 ─────────── (位2)
    │      │── GRID3 ──────── COM3 ─────────── (位3)
    │      │── GRID4 ──────── COM4 ─────────── (位4)
    │      │
    │      │── GRID5 ──────── NC (未用)
    │      │── GRID6 ──────── NC (未用)
    └──────┘

    注意:
    - SEG 引脚直接连接到各段阳极(内置限流驱动,无需外加电阻)
    - GRID 引脚直接连接到各位的公共阴极
    - 成品模块内部已接好,用户无需关心此部分

6.3 段输出限流问题

TM1637 的 SEG 输出是推挽驱动,不是恒流源! 如果 VDD = 5V,LED 段压降约 2V,驱动管压降约 0.5V,则 LED 两端电压 ≈ 2.5V,电流可能达到 20mA+。

两种设计方式:

方式 做法 优点 缺点
芯片直驱(推荐) SEG 直接接 LED,不加限流电阻 电路简单,成本最低 5V 供电时 LED 电流偏大
外加限流 SEG 经电阻(10Ω~47Ω)接 LED 精确控制亮度,保护 LED 增加元件

成品模块通常不加限流电阻,靠 3.3V 供电或调低亮度等级来控制。如果 5V 供电且亮度太高,可在 VCC 端串一个二极管(如 1N4007,压降 0.7V)降压。

6.4 上拉电阻选择

TM1637 的 DIO 是双向 I/O,CLK 是单向输入(从 MCU 看是输出)。DIO 需要上拉:

VDD 推荐上拉 说明
5.0V 4.7KΩ ~ 10KΩ 最常见的配置
3.3V 2.2KΩ ~ 4.7KΩ 低电压时减小上拉以保证上升速度

绝大多数 TM1637 成品模块已经内置了上拉电阻,用户接线时无需外加。

6.5 电源去耦

复制代码
    VCC ──┬──────┬── VDD (TM1637)
          │      │
         ─┴─    ═══ 100nF (MLCC, 紧靠芯片)
         ─┬─     │
          │      │
         ═══    ─┴─ GND
      10μF 电解  │
          │      │
    GND ─┴──────┴── GND (TM1637)

100nF 去耦电容必须紧靠芯片 VDD-GND 引脚(< 5mm)! 否则数码管切换时的浪涌电流会导致通信异常。

6.6 3.3V 与 5V 混用注意事项

MCU 电压 TM1637 供电 兼容性
3.3V 3.3V ✅ 完美
3.3V 5V ⚠️ DIO 信号需上拉到 3.3V,或使用电平转换
5V 3.3V ⚠️ 检查 TM1637 是否识别 MCU 3.3V 高电平(VIH ≥ 0.7×3.3V = 2.31V,5V MCU 输出满足)
5V 5V ✅ 完美

最容易踩的坑: 3.3V 的 ESP32 直连 5V 供电的 TM1637 模块。TM1637 的 DIO 上拉到了 5V,会通过 IO 口漏电流到 ESP32,长时间可能损坏 ESP32。解决: 将模块改 3.3V 供电,或加两颗 MOSFET 做电平转换。


7. 编程实战:多平台驱动库

7.1 Arduino 平台(TM1637Display 库)

安装: Arduino IDE → 库管理 → 搜索 "TM1637Display" → 安装(作者:Avishay Orpaz)

cpp 复制代码
/**
 * Arduino TM1637 快速上手
 * 功能:计数显示、时间显示、亮度切换
 */

#include <TM1637Display.h>

#define CLK 3
#define DIO 2

TM1637Display display(CLK, DIO);

void setup() {
    Serial.begin(9600);
    display.setBrightness(7);  // 最大亮度
    display.clear();
    Serial.println("TM1637 初始化完成");
}

void loop() {
    // 1. 显示数字 (0~9999)
    for (int i = 0; i <= 9999; i += 111) {
        display.showNumberDec(i, false);  // false = 不显示前导0
        delay(500);
    }

    // 2. 显示带前导零的数字 (如 0025)
    display.showNumberDec(25, true);  // true = 4位全显示
    delay(2000);

    // 3. 显示十六进制 (0xABCD → 显示 "ABCD")
    display.showNumberHexEx(0xABCD);
    delay(2000);

    // 4. 显示时间 "12:59"(冒号亮)
    uint8_t timeData[] = { 0xFF, 0xFF, 0xFF, 0xFF };
    timeData[0] = display.encodeDigit(1);
    timeData[1] = display.encodeDigit(2) | 0x80;  // 冒号 = bit7
    timeData[2] = display.encodeDigit(5);
    timeData[3] = display.encodeDigit(9);
    display.setSegments(timeData);
    delay(2000);

    // 5. 调节亮度 (0~7)
    for (int b = 0; b <= 7; b++) {
        display.setBrightness(b);
        display.showNumberDec(b * 1111, true);
        delay(500);
    }
}

7.2 Arduino 裸驱动(不依赖库,理解协议)

cpp 复制代码
/**
 * Arduino TM1637 裸驱动 --- 手写协议层
 * 仅依赖 Arduino.h 基础函数
 */

#define TM_CLK 3
#define TM_DIO 2

// 共阴极段码表
const uint8_t SEG_CODE[] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, // 0~6
    0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, // 7~d
    0x79, 0x71, 0x40, 0x00                    // E, F, -, 灭
};

// ==================== 底层时序 ====================

void tm_delay_us() {
    delayMicroseconds(5);  // 安全时序
}

void tm_start() {
    digitalWrite(TM_DIO, HIGH);
    digitalWrite(TM_CLK, HIGH);
    tm_delay_us();
    digitalWrite(TM_DIO, LOW);  // CLK=H 时 DIO ↓
    tm_delay_us();
    digitalWrite(TM_CLK, LOW);
}

void tm_stop() {
    digitalWrite(TM_CLK, LOW);
    digitalWrite(TM_DIO, LOW);
    tm_delay_us();
    digitalWrite(TM_CLK, HIGH);
    tm_delay_us();
    digitalWrite(TM_DIO, HIGH);  // CLK=H 时 DIO ↑
    tm_delay_us();
}

uint8_t tm_write_byte(uint8_t data) {
    // LSB first
    for (uint8_t i = 0; i < 8; i++) {
        digitalWrite(TM_CLK, LOW);
        digitalWrite(TM_DIO, (data & 0x01) ? HIGH : LOW);
        tm_delay_us();
        digitalWrite(TM_CLK, HIGH);  // 上升沿写入
        tm_delay_us();
        data >>= 1;
    }

    // 第9位: ACK
    digitalWrite(TM_CLK, LOW);
    digitalWrite(TM_DIO, HIGH);
    pinMode(TM_DIO, INPUT);     // 释放 DIO,等待从机应答
    tm_delay_us();
    digitalWrite(TM_CLK, HIGH);
    tm_delay_us();
    uint8_t ack = digitalRead(TM_DIO);  // 0=ACK
    digitalWrite(TM_CLK, LOW);
    pinMode(TM_DIO, OUTPUT);    // 切回输出
    tm_delay_us();
    return ack;
}

// ==================== 中层 API ====================

void tm_display_raw(const uint8_t *data, uint8_t len) {
    tm_start();
    tm_write_byte(0x40);  // 数据命令:自增写
    tm_stop();

    tm_start();
    tm_write_byte(0xC0);  // 地址:从 GRID1 开始
    for (uint8_t i = 0; i < len; i++) {
        tm_write_byte(data[i]);
    }
    tm_stop();

    tm_start();
    tm_write_byte(0x8F);  // 显示开,最大亮度
    tm_stop();
}

void tm_display_number(int16_t num) {
    uint8_t data[4];
    bool negative = (num < 0);
    if (negative) num = -num;
    if (num > 9999) num = 9999;

    for (int8_t i = 3; i >= 0; i--) {
        if (num > 0 || i == 3) {
            data[i] = SEG_CODE[num % 10];
            num /= 10;
        } else if (negative) {
            data[i] = SEG_CODE[16];  // '-'
            negative = false;
        } else {
            data[i] = 0x00;  // 灭(不显示前导零)
        }
    }
    tm_display_raw(data, 4);
}

void tm_set_brightness(uint8_t level) {
    if (level > 7) level = 7;
    tm_start();
    tm_write_byte(0x88 | level);  // 显示开 + 亮度
    tm_stop();
}

void tm_clear() {
    uint8_t zero[6] = {0};
    tm_display_raw(zero, 6);
}

// ==================== 初始化 ====================

void setup() {
    pinMode(TM_CLK, OUTPUT);
    pinMode(TM_DIO, OUTPUT);
    digitalWrite(TM_CLK, LOW);
    digitalWrite(TM_DIO, HIGH);

    tm_clear();
    tm_set_brightness(7);
}

void loop() {
    static uint16_t count = 0;
    tm_display_number(count++);
    delay(100);
}

7.3 STM32 HAL 库驱动

c 复制代码
/**
 * tm1637_stm32.h
 * STM32 HAL 库 TM1637 驱动头文件
 */
#ifndef __TM1637_STM32_H__
#define __TM1637_STM32_H__

#include "stm32f1xx_hal.h"
#include <stdint.h>
#include <stdbool.h>

/* 引脚配置(按需修改) */
#define TM_PORT     GPIOB
#define TM_CLK_PIN  GPIO_PIN_6
#define TM_DIO_PIN  GPIO_PIN_7

/* 段码 ------ 共阴极 */
extern const uint8_t TM_SEG_CODE[];

/* API */
void TM_Init(void);
void TM_Clear(void);
void TM_SetBrightness(uint8_t level);        // 0~7
void TM_DisplayRaw(const uint8_t *data, uint8_t len);
void TM_DisplayNumber(int16_t num);           // -999 ~ 9999
void TM_DisplayTime(uint8_t h, uint8_t m, bool colon);
void TM_DisplayFloat(float val, uint8_t decimals);
void TM_PowerDown(void);                      // 关显示
void TM_PowerUp(void);                        // 开显示

#endif
c 复制代码
/**
 * tm1637_stm32.c
 * STM32 HAL 库 TM1637 驱动实现
 * 使用开漏输出 + 内部上拉,可直连 TM1637 模块
 */

#include "tm1637_stm32.h"

static uint8_t _brightness = 7;
static bool _displayOn = true;

/* 共阴极段码: 0~9, A~F, -, ' ' */
const uint8_t TM_SEG_CODE[] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D,  // 0~6
    0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E,  // 7~d
    0x79, 0x71, 0x40, 0x00                     // E, F, -, ' '
};

/* ---- 微秒延迟 ---- */
static void delay_us(uint32_t us) {
    /* 基于系统时钟的简易延迟(72MHz 时 1us ≈ 72 个 NOP) */
    for (volatile uint32_t i = 0; i < us * 8; i++) {
        __NOP();
    }
}

/* ---- IO 宏 ---- */
#define CLK_H()  HAL_GPIO_WritePin(TM_PORT, TM_CLK_PIN, GPIO_PIN_SET)
#define CLK_L()  HAL_GPIO_WritePin(TM_PORT, TM_CLK_PIN, GPIO_PIN_RESET)
#define DIO_H()  HAL_GPIO_WritePin(TM_PORT, TM_DIO_PIN, GPIO_PIN_SET)
#define DIO_L()  HAL_GPIO_WritePin(TM_PORT, TM_DIO_PIN, GPIO_PIN_RESET)

/* DIO 方向切换 */
static void DIO_Output(void) {
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = TM_DIO_PIN;
    gpio.Mode = GPIO_MODE_OUTPUT_OD;
    gpio.Pull = GPIO_NOPULL;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(TM_PORT, &gpio);
}

static void DIO_Input(void) {
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = TM_DIO_PIN;
    gpio.Mode = GPIO_MODE_INPUT;
    gpio.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(TM_PORT, &gpio);
}

static uint8_t DIO_Read(void) {
    return HAL_GPIO_ReadPin(TM_PORT, TM_DIO_PIN);
}

/* ---- 协议层 ---- */

static void tm_start(void) {
    CLK_H();
    DIO_H();
    delay_us(2);
    DIO_L();
    delay_us(2);
    CLK_L();
}

static void tm_stop(void) {
    CLK_L();
    DIO_L();
    delay_us(2);
    CLK_H();
    delay_us(2);
    DIO_H();
    delay_us(2);
}

static uint8_t tm_write_byte(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) {
        CLK_L();
        if (data & 0x01) DIO_H(); else DIO_L();
        delay_us(1);
        CLK_H();
        delay_us(1);
        data >>= 1;
    }

    /* ACK */
    CLK_L();
    DIO_H();
    DIO_Input();
    delay_us(1);
    CLK_H();
    delay_us(1);
    uint8_t ack = DIO_Read();
    CLK_L();
    DIO_Output();
    delay_us(1);
    return ack;
}

/* ---- API 实现 ---- */

void TM_Init(void) {
    GPIO_InitTypeDef gpio = {0};

    /* CLK: 开漏输出 */
    gpio.Pin = TM_CLK_PIN;
    gpio.Mode = GPIO_MODE_OUTPUT_OD;
    gpio.Pull = GPIO_NOPULL;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(TM_PORT, &gpio);

    /* DIO: 开漏输出 */
    DIO_Output();

    CLK_L();
    DIO_H();

    TM_Clear();
    TM_SetBrightness(7);
}

void TM_Clear(void) {
    uint8_t zeros[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    TM_DisplayRaw(zeros, 6);
}

void TM_SetBrightness(uint8_t level) {
    _brightness = (level > 7) ? 7 : level;
    if (_displayOn) {
        tm_start();
        tm_write_byte(0x88 | _brightness);
        tm_stop();
    }
}

void TM_PowerDown(void) {
    _displayOn = false;
    tm_start();
    tm_write_byte(0x80);
    tm_stop();
}

void TM_PowerUp(void) {
    _displayOn = true;
    TM_SetBrightness(_brightness);
}

void TM_DisplayRaw(const uint8_t *data, uint8_t len) {
    if (len > 6) len = 6;

    tm_start();
    tm_write_byte(0x40);
    tm_stop();

    tm_start();
    tm_write_byte(0xC0);
    for (uint8_t i = 0; i < len; i++) {
        tm_write_byte(data[i]);
    }
    tm_stop();

    tm_start();
    tm_write_byte(_displayOn ? (0x88 | _brightness) : 0x80);
    tm_stop();
}

void TM_DisplayNumber(int16_t num) {
    uint8_t data[4];
    bool neg = false;

    if (num < -999) num = -999;
    if (num > 9999) num = 9999;

    if (num < 0) { neg = true; num = -num; }

    for (int8_t i = 3; i >= 0; i--) {
        if (num > 0 || i == 3) {
            data[i] = TM_SEG_CODE[num % 10];
            num /= 10;
        } else if (neg) {
            data[i] = TM_SEG_CODE[16]; // '-'
            neg = false;
        } else {
            data[i] = 0x00; // 前导零不显示
        }
    }

    TM_DisplayRaw(data, 4);
}

void TM_DisplayTime(uint8_t hour, uint8_t minute, bool colon) {
    uint8_t data[4];
    data[0] = TM_SEG_CODE[hour / 10];
    data[1] = TM_SEG_CODE[hour % 10];
    data[2] = TM_SEG_CODE[minute / 10];
    data[3] = TM_SEG_CODE[minute % 10];

    if (colon) data[1] |= 0x80;  // 冒号

    TM_DisplayRaw(data, 4);
}

void TM_DisplayFloat(float val, uint8_t decimals) {
    // 显示浮点数(如 25.5 显示为 25°5 或 2.55)
    int16_t scaled = (int16_t)(val * (decimals == 1 ? 10 : 100) + 0.5f);

    uint8_t data[4];
    for (int8_t i = 3; i >= 0; i--) {
        if (scaled > 0 || i == 3) {
            data[i] = TM_SEG_CODE[scaled % 10];
            scaled /= 10;
        } else {
            data[i] = 0x00;
        }
    }

    // 第 (3 - decimals) 位加小数点
    if (decimals > 0 && decimals <= 3) {
        data[3 - decimals] |= 0x80;
    }

    TM_DisplayRaw(data, 4);
}

7.4 ESP32 MicroPython 驱动

python 复制代码
"""
tm1637.py --- ESP32 MicroPython 驱动
兼容 ESP8266 / Raspberry Pi Pico
"""
from machine import Pin
from time import sleep_us, sleep_ms

class TM1637:
    # 共阴极段码表
    _SEG = [
        0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D,  # 0~6
        0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E,  # 7~d
        0x79, 0x71, 0x40, 0x00                      # E, F, -, (space)
    ]

    def __init__(self, clk_pin, dio_pin, brightness=5):
        self._clk = Pin(clk_pin, Pin.OUT)
        self._dio = Pin(dio_pin, Pin.OUT)
        self._brightness = min(brightness, 7)
        self._on = True

        self._clk.value(0)
        self._dio.value(1)
        sleep_us(10)

    def _start(self):
        self._dio.value(1)
        self._clk.value(1)
        sleep_us(2)
        self._dio.value(0)
        sleep_us(2)
        self._clk.value(0)

    def _stop(self):
        self._clk.value(0)
        self._dio.value(0)
        sleep_us(2)
        self._clk.value(1)
        sleep_us(2)
        self._dio.value(1)
        sleep_us(2)

    def _write_byte(self, data):
        for _ in range(8):
            self._clk.value(0)
            self._dio.value(data & 0x01)
            sleep_us(1)
            self._clk.value(1)
            sleep_us(1)
            data >>= 1

        # ACK
        self._clk.value(0)
        self._dio.value(1)
        self._dio = Pin(self._dio, Pin.IN)  # 释放DIO
        sleep_us(1)
        self._clk.value(1)
        ack = self._dio.value()
        sleep_us(1)
        self._clk.value(0)
        self._dio = Pin(self._dio, Pin.OUT)  # 切回输出
        return ack

    def _write_raw(self, data):
        """底层:写入段码数据到显存"""
        self._start()
        self._write_byte(0x40)  # 自增写
        self._stop()

        self._start()
        self._write_byte(0xC0)  # 地址 0
        for d in data:
            self._write_byte(d)
        self._stop()

        self._start()
        cmd = 0x88 | self._brightness if self._on else 0x80
        self._write_byte(cmd)
        self._stop()

    def number(self, num):
        """显示整数 -999 ~ 9999"""
        neg = num < 0
        num = abs(num)
        num = min(num, 9999)

        digits = []
        for i in range(3, -1, -1):
            if num > 0 or i == 3:
                digits.append(self._SEG[num % 10])
                num //= 10
            elif neg:
                digits.append(self._SEG[16])  # '-'
                neg = False
            else:
                digits.append(0x00)  # 灭
        digits.reverse()
        self._write_raw(digits)

    def temperature(self, temp):
        """显示温度(带 °C)"""
        temp = max(-9, min(99, int(temp)))
        digits = [0] * 4
        if temp < 0:
            digits[0] = self._SEG[16]  # '-'
            temp = -temp
        digits[-3] = self._SEG[temp // 10 % 10] if temp >= 10 else 0x00
        digits[-2] = self._SEG[temp % 10]
        digits[-1] = 0x63  # '°' 符号: SEG_A|SEG_B|SEG_F|SEG_G|SEG_E|SEG_D
        self._write_raw(digits)

    def time(self, hour, minute, colon=True):
        """显示时间 HH:MM"""
        digits = [
            self._SEG[hour // 10],
            self._SEG[hour % 10],
            self._SEG[minute // 10],
            self._SEG[minute % 10]
        ]
        if colon:
            digits[1] |= 0x80  # 冒号
        self._write_raw(digits)

    def brightness(self, level):
        """设置亮度 0~7"""
        self._brightness = min(level, 7)
        if self._on:
            self._start()
            self._write_byte(0x88 | self._brightness)
            self._stop()

    def power_off(self):
        self._on = False
        self._start()
        self._write_byte(0x80)
        self._stop()

    def power_on(self):
        self._on = True
        self.brightness(self._brightness)

    def clear(self):
        self._write_raw([0, 0, 0, 0])


# ===== 使用示例 =====
if __name__ == '__main__':
    tm = TM1637(clk_pin=4, dio_pin=5, brightness=5)

    # 数字显示
    for i in range(0, 1000, 50):
        tm.number(i)
        sleep_ms(100)

    # 时间显示
    tm.time(12, 59)
    sleep_ms(3000)

    # 温度显示
    tm.temperature(25)
    sleep_ms(3000)

    tm.clear()

7.5 51单片机(Keil C51)

c 复制代码
/**
 * TM1637 51单片机驱动
 * 晶振: 11.0592MHz
 * 接线: CLK → P1^0, DIO → P1^1
 */

#include <reg51.h>
#include <intrins.h>

sbit TM_CLK = P1^0;
sbit TM_DIO = P1^1;

// 共阴极段码表 (code 放在 ROM)
unsigned char code SEG_CODE[] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D,
    0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E,
    0x79, 0x71, 0x40, 0x00
};

unsigned char _brightness = 7;
bit _displayOn = 1;

/* 微秒延时 (11.0592MHz, 1T模式需调整) */
void delay_us(unsigned char us) {
    while (us--) {
        _nop_(); _nop_(); _nop_(); _nop_();
    }
}

/* 起始条件 */
void tm_start(void) {
    TM_DIO = 1;
    TM_CLK = 1;
    delay_us(2);
    TM_DIO = 0;
    delay_us(2);
    TM_CLK = 0;
}

/* 停止条件 */
void tm_stop(void) {
    TM_CLK = 0;
    TM_DIO = 0;
    delay_us(2);
    TM_CLK = 1;
    delay_us(2);
    TM_DIO = 1;
    delay_us(2);
}

/* 写一个字节,返回 ACK (0=成功) */
bit tm_write_byte(unsigned char dat) {
    unsigned char i;
    for (i = 0; i < 8; i++) {
        TM_CLK = 0;
        TM_DIO = (dat & 0x01) ? 1 : 0;
        delay_us(1);
        TM_CLK = 1;
        delay_us(1);
        dat >>= 1;
    }

    /* ACK */
    TM_CLK = 0;
    TM_DIO = 1;           // 释放
    delay_us(1);
    TM_CLK = 1;
    delay_us(1);
    bit ack = TM_DIO;     // 读 ACK
    TM_CLK = 0;
    delay_us(1);
    return ack;
}

/* 显示段码数据 */
void tm_display(unsigned char *data, unsigned char len) {
    unsigned char i;
    tm_start();
    tm_write_byte(0x40);
    tm_stop();

    tm_start();
    tm_write_byte(0xC0);
    for (i = 0; i < len; i++) {
        tm_write_byte(data[i]);
    }
    tm_stop();

    tm_start();
    tm_write_byte(_displayOn ? (0x88 | _brightness) : 0x80);
    tm_stop();
}

/* 显示数字 */
void tm_show_num(int num) {
    unsigned char data[4];
    unsigned char i;
    bit neg = 0;

    if (num < 0) { neg = 1; num = -num; }
    if (num > 9999) num = 9999;

    for (i = 4; i > 0; i--) {
        if (num > 0 || i == 1) {
            data[i-1] = SEG_CODE[num % 10];
            num /= 10;
        } else if (neg) {
            data[i-1] = SEG_CODE[16];  // '-'
            neg = 0;
        } else {
            data[i-1] = 0x00;
        }
    }
    tm_display(data, 4);
}

/* 初始化 */
void tm_init(void) {
    TM_CLK = 0;
    TM_DIO = 1;
    tm_display((unsigned char *)"\x00\x00\x00\x00\x00\x00", 6);
}

/* 主函数 */
void main(void) {
    unsigned int count = 0;
    tm_init();

    while (1) {
        tm_show_num(count++);
        // 软件延时约 1 秒 (11.0592MHz)
        unsigned int i;
        for (i = 0; i < 30000; i++) {
            unsigned char j;
            for (j = 0; j < 10; j++);
        }
    }
}

8. 高级应用技巧

8.1 按键扫描与显示复用

TM1637 的按键扫描和显示是自动分时复用的,不需要用户干预。在显示扫描的间隙,芯片自动切换 SEG1/KS1 和 SEG2/KS2 为输入模式,扫描按键矩阵。

读取按键的方法:

cpp 复制代码
/**
 * Arduino TM1637 按键读取
 * 注意:TM1637Display 库不包含按键读取功能
 */

uint8_t tm_read_keys() {
    uint8_t keys[6] = {0};  // 最多 6 字节

    tm_start();
    tm_write_byte(0x42);  // 读按键命令
    for (uint8_t i = 0; i < 6; i++) {
        keys[i] = tm_read_byte();  // 从机发送,主机读
    }
    tm_stop();

    return keys[0];  // 返回第 1 个字节(GRID1 对应的 8 个按键)
}

/*
   按键对应关系:
   keys[0] bit0 → K1 (SEG1 + GRID1)
   keys[0] bit1 → K2 (SEG2 + GRID1)
   keys[0] bit2 → K3 (SEG3 + GRID1)
   ...
   keys[1] bit0 → K9 (SEG1 + GRID2)
   ...

   成品模块中,K3~K8 对应 SEG3/KS3 ~ SEG8/KS8
   即:KS3~KS8 是独立引脚(Pin 15,14,13,12),不是复用的
*/

注意: 成品 TM1637 数码管模块的 KS3~KS8 通常没有引出!如需要按键扫描,必须自己焊接飞线到对应引脚。

8.2 自定义段码动画

呼吸灯效果(亮度渐变):

cpp 复制代码
void breathing_effect() {
    static int8_t dir = 1;
    static uint8_t brightness = 0;

    display.setBrightness(brightness);

    brightness += dir;
    if (brightness >= 7) { brightness = 7; dir = -1; }
    if (brightness == 0)  { brightness = 0; dir = 1;  }

    delay(50);
}

数字滚动切换效果:

cpp 复制代码
void scroll_to_number(TM1637Display &disp, int target) {
    int current = 0;
    // 快速滚动到目标值
    for (int i = 0; i <= target; i += max(1, (target - i) / 10)) {
        disp.showNumberDec(i, true);
        delay(20);
    }
    disp.showNumberDec(target, true);
}

跑马灯效果(字符向左滚动):

cpp 复制代码
void marquee(TM1637Display &disp, const char *text, int len, int speed_ms) {
    uint8_t buf[4];
    for (int pos = len + 4; pos >= -4; pos--) {
        for (int i = 0; i < 4; i++) {
            int ch = pos - i;
            if (ch >= 0 && ch < len) {
                buf[i] = char_to_seg(text[ch]);
            } else {
                buf[i] = 0x00;
            }
        }
        disp.setSegments(buf);
        delay(speed_ms);
    }
}

8.3 多模块驱动

TM1637 不支持总线级联 (没有设备地址概念),但可以多个模块各占独立 IO

cpp 复制代码
// 多模块驱动示例:两个 TM1637 显示 8 位数字
TM1637Display disp1(2, 3);  // CLK=D2, DIO=D3
TM1637Display disp2(4, 5);  // CLK=D4, DIO=D5

void show_8digit(long num) {
    disp1.showNumberDec(num / 10000, true);  // 高 4 位
    disp2.showNumberDec(num % 10000, true);  // 低 4 位
}

如果需要驱动更多数码管,考虑:

  • MAX7219 --- 支持 SPI 菊花链级联,3 根线驱动 N×8 位
  • 74HC595 级联 --- 成本低,但需要外部三极管驱动
  • TM1640 --- 天微家族中可驱动 16 段的型号

8.4 低功耗设计

cpp 复制代码
// 进入低功耗模式
void sleep_display() {
    tm_start();
    tm_write_byte(0x80);  // 显示关 → 芯片进入微功耗状态 (~1μA)
    tm_stop();

    // 将 CLK 和 DIO 设为高阻输入(防止漏电)
    pinMode(TM_CLK, INPUT);
    pinMode(TM_DIO, INPUT);
}

// 唤醒
void wake_display() {
    pinMode(TM_CLK, OUTPUT);
    pinMode(TM_DIO, OUTPUT);

    tm_start();
    tm_write_byte(0x8F);  // 显示开
    tm_stop();
}

8.5 非阻塞刷新(中断/定时器驱动)

在 RTOS 或需要实时响应的场景中,避免在主循环中阻塞写 TM1637:

cpp 复制代码
/**
 * 使用定时器中断定时刷新 TM1637
 * 优势:不阻塞主循环,适合 FreeRTOS 等场景
 */
#include <Ticker.h>  // ESP8266/ESP32 定时器库

Ticker displayTicker;
volatile uint8_t dispBuffer[4] = {0};
volatile bool dispDirty = false;

void displayISR() {
    if (!dispDirty) return;
    dispDirty = false;

    // 在 ISR 中直接写 TM1637(确保 ISR 中不调用延迟敏感的库函数)
    tm_start();
    tm_write_byte(0x40);
    tm_stop();
    tm_start();
    tm_write_byte(0xC0);
    for (int i = 0; i < 4; i++) {
        tm_write_byte(dispBuffer[i]);
    }
    tm_stop();
    tm_start();
    tm_write_byte(0x8F);
    tm_stop();
}

void setup() {
    tm_init();
    displayTicker.attach_ms(100, displayISR);  // 每 100ms 刷新
}

void loop() {
    // 主循环只更新缓冲区,不阻塞
    updateDispBuffer(sensorValue);
    dispDirty = true;
    delay(1000);
}

9. TM1637 vs TM1638 vs TM1650 vs MAX7219

9.1 功能对比

特性 TM1637 TM1638 TM1650 MAX7219
制造商 天微 Titan 天微 Titan 天微 Titan Maxim (ADI)
最大段数 6 位 × 8 段 8 位 × 10 段 4 位 × 8 段 8 位 × 8 段
通信接口 2 线(类 I2C) 3 线(类 SPI) 2 线(类 I2C) 3 线 SPI
按键扫描 最多 16 键 最多 24 键 最多 28 键 ❌ 无
亮度等级 8 级 8 级 8 级 16 级
级联支持 ✅ SPI 菊花链
段电流 20mA 20mA 20mA 40mA(可调)
封装 DIP-20/SOP-20 SOP-28 SOP-16 DIP-24/SO-24
成品模块 4 位 0.56" 常见 8 位 + 8 键常见 4 位小体积 8 位 + 点阵
模块价格 ¥2~5 ¥5~10 ¥3~6 ¥10~30
芯片单价 ~¥0.5 ~¥1.0 ~¥0.8 ~¥8.0

9.2 选型决策树

复制代码
需求: 驱动数码管显示
│
├── 只需 4 位及以下? ─── 是 ─── TM1650(封装小)或 TM1637 模块(淘宝现货)
├── 需要按键扫描?
│   ├── 是,按键多(> 16 键)─── TM1650 (28键) 或 TM1638 (24键)
│   └── 是,按键少 ─── TM1637 (16键)
├── 需要 6 位以上?
│   ├── 是 ─── TM1638 (8位) 或 MAX7219 (8位×N 级联)
│   └── 否 ─── TM1637 (6位)
├── 需要级联扩展?
│   └── MAX7219(唯一支持级联的选择)
├── 预算极敏感?
│   └── TM1637(成本最低)
└── 需要驱动共阳极数码管?
    └── TM1637/38/50 都只支持共阴极!共阳极需 MAX7219 或另加反相

9.3 一句话总结

  • TM1637 --- 性价比之王,4 位数码管显示的首选
  • TM1638 --- TM1637 的升级版,更多段数和按键,带 3 线接口
  • TM1650 --- 超小封装,适合体积受限的 4 位显示
  • MAX7219 --- 专业级,支持级联,驱动能力强,但不带按键扫描,价格高

10. 常见故障与排错手册

故障 1:完全不亮

复制代码
现象: 上电后数码管无任何显示

排查步骤:
  □ VCC-GND 电压是否在 3.3V ~ 5.5V 之间?
  □ CLK/DIO 接线是否与代码中定义一致?
  □ 万用表测 CLK/DIO 是否有波形(初始化时应有脉冲)?
  □ 是否调用了显示开命令 (0x8F) ?
  □ 是否写入了非零段码数据?
  □ 模块上的数码管是否正常(单独给段引脚供电测试)?

常见原因:
  → DIO 和 CLK 接反(90% 的问题)
  → 忘了发 0x8F 显示开命令
  → 3.3V MCU 直连 5V 模块,IO 漏电

故障 2:显示乱码或笔画残缺

复制代码
现象: 数字形状不对、某些段不亮

排查步骤:
  □ 段码表用对了吗?(共阴 vs 共阳 段码正好相反)
  □ 数据字节的位序对吗?(TM1637 是 LSB first)
  □ 通信是否完整(示波器看波形是否 8+1 个 CLK)?
  □ DIO 上拉电阻是否合适(过大导致上升沿太慢)?

常见原因:
  → 段码表拿反(共阳段码写给了共阴数码管)
  → 延时太短(< 1μs),信号质量差

故障 3:闪烁或抖动

复制代码
现象: 数码管可见闪烁、偶尔乱跳

排查步骤:
  □ 主循环中是否有长延时(delay > 10ms)阻塞了刷新?
  □ 是否有频繁的 Stop→Start 重新初始化?
  □ 电源是否稳定(加 100nF 去耦电容)?
  □ 中断是否干扰了通信时序?

常见原因:
  → 没有去耦电容,数码管开关噪声耦合到 VCC
  → 在中断中操作 TM1637,被更高优先级中断打断

故障 4:ACK 始终返回 1(NACK)

复制代码
现象: tm_write_byte() 的 ACK 位永远为 1(不应答)

排查步骤:
  □ 供电够不够?VDD ≥ 3.3V?
  □ DIO 引脚是否配置为开漏输出(STM32 特别注意)?
  □ DIO 是否有上拉电阻?
  □ 时序是否太快?尝试将 delay_us 增加到 10μs
  □ CLK/DIO 是否接反?
  □ 芯片是否损坏(更换模块测试)?

常见原因:
  → STM32 用推挽输出而不是开漏输出 → DIO 无法释放
  → 缺少上拉电阻,DIO 高电平到不了 VIH
  → 芯片已烧毁(反向供电、过压等)

故障 5:亮度异常

复制代码
现象: 亮度太高、太低或不均匀

排查步骤:
  □ 亮度等级是否设置正确?
  □ 不同位的亮度是否一致?
  □ 供电电压是否稳定(大电流时 VCC 跌落)?
  □ 段输出是否有限流电阻(如果自搭电路)?

常见原因:
  → 5V 供电且没限流 → 亮度过高 → 加限流电阻或降压
  → VCC 走线太细 → 远端的位供电不足 → 加粗 VCC/GND 走线

故障 6:3.3V MCU 驱动 5V 模块异常

复制代码
问题: ESP32 (3.3V) 接 5V 供电的 TM1637 模块,偶尔通信失败或损坏芯片

原因:
  - TM1637 模块的 DIO/CLK 有上拉电阻到 5V
  - MCU 的 3.3V IO 口承受 5V 电压 ← 可能损坏!
  - 同时 MCU 输出 3.3V 高电平,TM1637 的 VIH 需 ≥ 0.7×5V = 3.5V ← 不够!

解决方案:
  方案A:TM1637 也接 3.3V → 最简单,亮度略降但可用
  方案B:用 MOSFET 做双向电平转换 (2N7002 + 上拉)
  方案C:用专用电平转换芯片 (TXS0102)

故障 7:成品模块个别段不亮

复制代码
现象: 某个数字的某一笔划始终不亮

排查:
  → 换一个数字,同一笔划不亮 → 该段 LED 损坏 → 更换模块
  → 某个位置看不到但其他位置正常 → 该位对应的 GRID 引脚虚焊

验证方法:
  用万用表二极管档,红笔接 SEGx,黑笔接 GRIDx(共阴模块)
  该段应微亮 → 不亮 = LED 损坏

11. 项目实战:温湿度显示器 + 按键交互

11.1 项目概述

基于 STM32 + TM1637 + DHT22 + 按键,打造一个可切换显示温湿度的桌面小仪表。

11.2 系统框图

复制代码
┌──────────────────────────────────────────┐
│                  STM32                    │
│                                           │
│  ┌──────────┐    ┌──────────┐            │
│  │  DHT22   │◄──►│  GPIO    │            │
│  │温湿度传感器│    │          │            │
│  └──────────┘    │          │            │
│                  │   CPU    │──► TM1637  │──► 数码管
│  ┌──────────┐    │          │    4位显示  │
│  │  按键×2  │──►│ GPIO     │            │
│  │ 模式/切换 │    │          │            │
│  └──────────┘    └──────────┘            │
│                                           │
└──────────────────────────────────────────┘

显示模式:
  模式 0: 温度显示 (例: 25°C)
  模式 1: 湿度显示 (例: 60%H)

11.3 完整代码

c 复制代码
/**
 * main.c --- STM32 + DHT22 + TM1637 温湿度显示器
 * 平台: STM32F103C8T6 (Blue Pill)
 * 接线:
 *   TM1637: CLK→PB6, DIO→PB7
 *   DHT22:  DATA→PA0
 *   按键1:  MODE→PA1 (切换显示模式)
 *   按键2:  ---   →PA2 (保留)
 */

#include "main.h"
#include "tm1637_stm32.h"
#include "dht22.h"            // 你自己的 DHT22 驱动

/* 全局变量 */
typedef enum {
    DISP_TEMPERATURE = 0,
    DISP_HUMIDITY,
    DISP_TIME,
    DISP_MODE_MAX
} DisplayMode;

volatile DisplayMode mode = DISP_TEMPERATURE;
volatile float temperature = 0.0f;
volatile float humidity = 0.0f;
volatile uint8_t sensor_updated = 0;

/* 按键消抖 */
static uint32_t last_btn_tick = 0;

/* ---- 按键扫描 ---- */
void check_button(void) {
    if (HAL_GetTick() - last_btn_tick < 200) return;  // 200ms 去抖

    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET) {
        /* 低电平 = 按下(内部上拉) */
        mode = (mode + 1) % DISP_MODE_MAX;
        last_btn_tick = HAL_GetTick();
    }
}

/* ---- DHT22 读取任务(每 2 秒) ---- */
void read_sensor(void) {
    static uint32_t last_read = 0;
    if (HAL_GetTick() - last_read < 2000) return;
    last_read = HAL_GetTick();

    float t, h;
    if (DHT22_Read(&t, &h) == 0) {
        temperature = t;
        humidity = h;
        sensor_updated = 1;
    }
}

/* ---- 显示刷新 ---- */
void update_display(void) {
    if (!sensor_updated) return;
    sensor_updated = 0;

    switch (mode) {
        case DISP_TEMPERATURE: {
            /* 温度: 显示 25°C (用小数点代替°) */
            int16_t temp10 = (int16_t)(temperature * 10 + 0.5f);
            uint8_t data[4] = {0};

            data[0] = TM_SEG_CODE[(temp10 / 100) % 10];  // 十位
            data[1] = TM_SEG_CODE[(temp10 / 10) % 10];   // 个位
            data[2] = TM_SEG_CODE[temp10 % 10] | 0x80;   // 小数 (带小数点)
            // 第4位显示自定义 °C 符号
            data[3] = 0x39;  // SEG_A|SEG_D|SEG_E|SEG_F = 'C'

            // 消除前导零
            if ((temp10 / 100) == 0) data[0] = 0x00;

            TM_DisplayRaw(data, 4);
            break;
        }

        case DISP_HUMIDITY: {
            /* 湿度: 显示 60.H (H 代表 %RH) */
            int16_t hum10 = (int16_t)(humidity * 10 + 0.5f);
            uint8_t data[4] = {0};

            data[0] = TM_SEG_CODE[(hum10 / 100) % 10];
            data[1] = TM_SEG_CODE[(hum10 / 10) % 10] | 0x80; // 小数点
            data[2] = TM_SEG_CODE[hum10 % 10];
            data[3] = 0x74;  // 'H'

            if ((hum10 / 100) == 0) data[0] = 0x00;

            TM_DisplayRaw(data, 4);
            break;
        }

        case DISP_TIME: {
            /* 显示时间(使用 SysTick) */
            uint32_t t = HAL_GetTick() / 1000;
            uint8_t min = (t / 60) % 60;
            uint8_t sec = t % 60;
            TM_DisplayTime(min, sec, (t % 2 == 0));  // 冒号闪烁
            sensor_updated = 1;  // 持续更新
            break;
        }

        default:
            break;
    }
}

/* ---- 主函数 ---- */
int main(void) {
    HAL_Init();
    SystemClock_Config();  // CubeMX 生成的时钟配置

    /* GPIO 初始化 */
    MX_GPIO_Init();  // CubeMX 生成(包含按键上拉配置)

    /* TM1637 初始化 */
    TM_Init();
    TM_DisplayNumber(0);
    HAL_Delay(500);

    /* DHT22 初始化 */
    DHT22_Init(GPIOA, GPIO_PIN_0);

    /* 主循环 */
    while (1) {
        check_button();    // 非阻塞按键扫描
        read_sensor();     // 每 2 秒读一次传感器
        update_display();  // 刷新显示
        HAL_Delay(50);     // 主循环周期 50ms
    }
}

11.4 扩展思路

  • 加入 WiFi 校时(ESP8266 方案) --- 通过 NTP 获取网络时间显示
  • 加入光照传感器(BH1750) --- 根据环境光自动调节 TM1637 亮度
  • 加入 EEPROM --- 存储报警阈值,超限蜂鸣器提醒
  • 加入蓝牙(HM-10) --- 手机 APP 远程查看温湿度数据

12. 总结与资源推荐

📝 核心要点回顾

要点 说明
芯片本质 天微 Titan 的 LED 数码管驱动 + 按键扫描 ASIC
通信协议 类 I2C 两线,LSB first,有 ACK,无设备地址
驱动方式 芯片内置动态扫描,MCU 只需发段码数据
最常用命令 0x40(自增写)+ 0xC0(起始地址)+ 0x8F(显示开/最亮)
最大能力 6 位 × 8 段 + 16 键,8 级亮度
IO 开销 仅 2 个 MCU IO 口(DIO + CLK)
关键注意事项 开漏 IO + 上拉、LSB first、共阴极段码、去耦电容

🚀 学习路线图

复制代码
第 1 天:买一个 TM1637 模块 (¥3)
        → Arduino + TM1637Display 库 → 显示数字 1234
        → 修改亮度、显示时间
        ✅ 入门

第 2 天:手写协议层代码
        → 从零实现 Start/Stop/WriteByte
        → 不用库点亮 TM1637
        ✅ 理解协议

第 3 天:移植到 STM32/ESP32
        → 理解开漏输出的意义
        → 适配 3.3V/5V 电平
        ✅ 跨平台

第 4 天:项目实战
        → DS3231 时钟 / DHT22 温湿度计
        → 加入按键交互
        → 加入低功耗休眠
        ✅ 工程能力

📚 官方与社区资源

资源 说明
TM1637 数据手册 天微电子官网下载 PDF(必读)
Arduino TM1637Display GitHub: avishorp/TM1637
MicroPython TM1637 GitHub: mcauser/micropython-tm1637
ESP-IDF TM1637 GitHub 搜索 esp32 tm1637
天微官网 www.titanmec.com --- TM16xx 系列全数据手册
嘉立创 / 立创商城 搜索 TM1637 可查库存、封装、价格

🔧 调试工具推荐

  • 逻辑分析仪(Saleae 兼容) --- ¥20 的 8 通道 USB 逻辑分析仪,配合 PulseView 软件抓取 CLK/DIO 波形,调试通信问题的终极利器
  • 万用表 --- 测电压、测连通性、LED 二极管档测段好坏
  • 示波器 --- 观察信号完整性和电源纹波(进阶)

写在最后: TM1637 这个不到 5 毛钱的芯片,可能是嵌入式开发历史上性价比最高的数码管驱动方案。它用极简的 2 线接口隐藏了动态扫描、PWM 调光、按键扫描的全部复杂性。从 Arduino 到 STM32、从 ESP32 到 51 单片机,TM1637 横跨所有主流平台,是你通往嵌入式显示世界的第一块敲门砖。


本文由作者原创编写,发布于 CSDN。数据手册截图及部分参数来源:Titan Micro TM1637 Datasheet V2.4。转载请注明出处,欢迎留言交流。

相关推荐
Dr.kangder2 小时前
嵌入式处理器仿真技术——Hypervisor 原理与实践
服务器·嵌入式硬件·架构·嵌入式
智购科技自动售卖机厂家8 小时前
从STM32到RK3588——自动售货机嵌入式主控方案的架构演进~YH
stm32·嵌入式硬件·物联网·架构·lua·零售·symfony
振南的单片机世界9 小时前
2MHz/50MHz玄机:GPIO速度档选错,信号直接“烂尾”
arm开发·stm32·单片机·嵌入式硬件
Run_Teenage9 小时前
嵌入式:理解STM32文档和社区生态,工具链的选择与安装
stm32·单片机·嵌入式硬件
ACP广源盛139246256739 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
铅笔小新z9 小时前
【stm32】RCC 与时钟树
stm32·单片机·嵌入式硬件
时空自由民.10 小时前
STM32 IIC HAL库,LL库
stm32·单片机·嵌入式硬件
txh050710 小时前
不用变压器也能把220V交流电变为5V直流电?
单片机·嵌入式硬件
辰哥单片机设计11 小时前
STM32智能门禁系统(语音版)
stm32·单片机·嵌入式硬件