TM1637 芯片从入门到精通
目录
- [TM1637 是什么](#TM1637 是什么)
- 芯片内部架构与引脚详解
- 电气特性与极限参数
- 通信协议深度解析
- 命令集与寄存器
- 硬件电路设计指南
- 编程实战:多平台驱动库
- 高级应用技巧
- [TM1637 vs TM1638 vs TM1650 vs MAX7219](#TM1637 vs TM1638 vs TM1650 vs MAX7219)
- 常见故障与排错手册
- [项目实战:温湿度显示器 + 按键交互](#项目实战:温湿度显示器 + 按键交互)
- 总结与资源推荐
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 如此流行
- 成本极低 --- 芯片零售低于 ¥0.5,成品模块 ¥2~5
- IO 占用少 --- 仅 2 个 MCU IO 口(CLK + DIO)
- 无外部元件 --- 内部 RC 振荡,无需晶振
- 即插即用 --- 淘宝模块四线引出,5 分钟点亮
- 生态完善 --- Arduino / MicroPython / STM32 都有现成库
- 电压宽范围 --- 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。转载请注明出处,欢迎留言交流。