简介:这是一份针对TM1621数码管控制器的C语言驱动程序包,面向嵌入式开发者和电子爱好者,用于解决通过串行外设接口或并行接口控制七段数码管显示数字与字符的实际问题。该芯片常见于智能家居设备、仪器仪表和嵌入式系统的人机交互界面,驱动代码已经通过硬件实测,注释完整,能够清晰解释初始化、发送数据、设置亮度、显示数字等关键函数的实现思路,有助于阅读者快速上手并避免调试困惑。压缩包共两个文件,包含一个头文件和一个源程序文件,前者用于定义引脚映射、通信协议常量、数据结构及函数原型,后者则实现了与芯片交互的具体逻辑。整个压缩包体量仅两KB,小巧易读,适合直接移植到各类单片机项目中。目前已有三千一百二十三人学习下载,该驱动包结构简洁、注释到位,开发者可借助其绕过底层时序细节,专注于上层显示功能与个性化扩展,对入门学习和项目集成都有实际参考价值。
1. tm1621 驱动程序:显示死活点不亮时的真正出路
某个温控器项目的 LCD 屏上电全黑,代码写了两版,示波器量 CS、WR、DA 三个脚都有波形,IC 供电也正常,可屏幕就是不出字,最后查出来是初始化命令漏了一条。TM1621 这颗芯片在电饭煲、热水器、仪表盘里出现频率很高,属于段式 LCD 驱动里绕不开的一类,三线串口接口看着简单,但命令顺序、RAM 映射、偏压配置任何一个地方出错,现象都是黑屏或者乱码,而且很难从波形上看出问题。如果你正在写 TM1621 的驱动程序,或者刚拿到一块带段式液晶的板子不知道怎么点亮,这篇笔记把硬件接线、命令时序、初始化顺序、常见坑和验证方法一次讲完,照着走基本能跑通。
2. 硬件连接:先把 CS、DA、WR 和 VLCD 搞清楚
2.1 引脚功能:八个脚里哪些必须接对
TM1621 常见封装是 SOP-24 或者裸片邦定,但真正和单片机打交道的引脚很少,核心就三个:CS(片选)、WR(写时钟)、DA(数据)。剩下的 VLCD、VDD、GND、OSCI/OSCO 各自有分工,接错或者不接,屏幕就给你脸色看。
我习惯先把引脚功能列成一张表,画板子之前对照一遍:
| 引脚 | 方向 | 作用 | 接法 |
|---|---|---|---|
| CS | 输入 | 片选,低电平有效 | 接单片机 GPIO,空闲拉高 |
| WR | 输入 | 写时钟,数据在上升沿锁存 | 接 GPIO,模拟时序用 |
| DA | 双向 | 串行数据输入/输出 | 接 GPIO,推挽输出为主 |
| VLCD | 输入 | LCD 偏压输入 | 接 VDD 或者经过 RC 滤波,决定对比度 |
| VDD | 电源 | 芯片供电 | 2.4~5.2V,根据屏的偏压要求选 |
| GND | 电源 | 地 | 共地 |
| OSCI/OSCO | 振荡器 | 外接晶振或 RC | 多数情况悬空,用内部 RC 振荡器 |
这里最容易被忽略的是 VLCD。有的模块上 VLCD 直接并了一个 0.1uF 电容到 VDD,有的手册建议 VLCD 和 VDD 之间加 RC 滤波,原因很简单:LCD 的偏压是从 VLCD 分出来的,如果这个脚纹波大或者电压偏低,屏幕会整体发暗,甚至什么都看不见。我第一次调一块 3.3V 供电的屏,VLCD 悬空,结果全亮测试时数字淡到几乎看不出来。
2.2 接线方案:GPIO 模拟三线接口
TM1621 本身是串行接口,最高工作频率手册上一般写到 1MHz 量级,但实际上用 GPIO 模拟完全够用。单片机这边只需要三个 IO,不占用硬件 SPI,也不挑引脚,这是它比 SPI 接口的 HT1622 之类芯片更省事的地方。
接线方案我一般这么定:CS 接一个普通输出口,WR 接一个普通输出口,DA 接一个推挽输出口。如果用到读按键或者读 LCD 状态,DA 要配置成输入输出切换,但驱动显示的话,DA 一直当输出用就行。
/* 以 STM32 的 HAL 库为例,示意 GPIO 初始化 */ void tm1621_gpio_init(void) { GPIO_InitTypeDef gpio = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); gpio.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出,带载能力好 */ gpio.Pull = GPIO_PULLUP; /* 空闲时默认高电平 */ gpio.Speed = GPIO_SPEED_FREQ_HIGH; /* 快速翻转,减小波形边沿畸变 */ gpio.Pin = TM1621_CS_PIN; HAL_GPIO_Init(TM1621_CS_PORT, &gpio); gpio.Pin = TM1621_WR_PIN; HAL_GPIO_Init(TM1621_WR_PORT, &gpio); gpio.Pin = TM1621_DA_PIN; HAL_GPIO_Init(TM1621_DA_PORT, &gpio); TM1621_CS_HIGH(); TM1621_WR_LOW(); }这段代码把三个引脚全部配成推挽输出,初始状态下 CS 拉高、WR 拉低,保证芯片处于未选中状态。推挽输出对时序模拟很重要,开漏输出不加外部上拉的话,DA 拉高时上升沿太缓,芯片容易误判数据。
2.3 上电第一步:先确认 VLCD 和偏压电容
上电之后不要急着写代码,先把万用表打到直流电压档,量一下 VLCD 和 VDD 之间的压差。正常情况 VLCD 接近 VDD,差个几十毫伏没问题。如果 VLCD 比 VDD 低超过 0.3V,先查偏压电容是不是漏电,再查 PCB 上 VLCD 走线是不是被别的信号干扰。
偏压电容的位置也有讲究,越靠近 VLCD 引脚越好。我见过一块板子把 0.1uF 电容放在 2 厘米外,结果 4 COM 屏的对比度明显不均匀,把电容挪到引脚旁边就好了。这类问题示波器不一定看得出来,因为杂散电容在高阻节点上产生的压降很小,但 LCD 的对比度对偏压极敏感。
3. 驱动命令与代码实现:初始化时序和 RAM 映射一起写
3.1 命令字与初始化顺序:先使能还是先开振荡器
TM1621 的命令分两类:命令模式和写数据模式。命令模式的首位是 1,写数据模式的首位是 0,这是三线协议判别两类操作的依据。命令字本身只有 8 位,加上首位的标志位,实际发送 9 位。
初始化顺序是 TM1621 驱动里最容易翻车的点。我踩过最深的坑是上来先发 LCD_ON,屏幕没反应,后来把顺序调整成下面这样才点亮:
- 发送系统使能命令(SYS EN),让内部逻辑开始工作
- 设置偏压和 COM 数(BIAS/COM)
- 打开系统振荡器(SYS OSC)
- 打开 LCD 显示(LCD ON)
这个顺序背后的逻辑很简单:LCD 驱动需要振荡器提供扫描时序,而振荡器要在系统使能之后才会稳定运行;偏压配置必须在 LCD 开显示之前写好,否则屏幕会以默认偏压工作,对比度不对。很多人直接抄别人代码,只看到四条命令,没注意到顺序,结果黑屏了都不知道是为什么。
3.2 驱动代码骨架:三线时序和命令收发
三线时序本身不复杂,核心是:CS 拉低选中芯片,DA 上准备好一个 bit,WR 给一个上升沿,芯片在这个沿锁存数据;发完所有 bit 后 CS 拉高。每发 9 位命令或 11 位数据,CS 要完整地拉低再拉高一次,不能把多个命令连续塞在一个片选周期里。
/* tm1621.c 驱动实现核心部分 */ #define TM1621_CMD_SYS_EN 0x01 /* 系统使能 */ #define TM1621_CMD_BIAS_COM 0x02 /* 偏压设置:1/3 bias + 4 COM,详见手册 */ #define TM1621_CMD_SYS_OSC 0x03 /* 系统振荡器开启 */ #define TM1621_CMD_LCD_ON 0x04 /* LCD 显示开启 */ #define TM1621_CMD_LCD_OFF 0x05 /* LCD 显示关闭 */ static void tm1621_delay_us(uint32_t us) { /* 移植时换成你自己的延时 */ } static void tm1621_send_bit(uint8_t bit) { TM1621_DA_WRITE(bit); /* DA 先准备好数据 */ tm1621_delay_us(1); TM1621_WR_HIGH(); /* WR 上升沿,芯片锁存这一位 */ tm1621_delay_us(1); TM1621_WR_LOW(); tm1621_delay_us(1); } static void tm1621_send_bits(uint16_t value, uint8_t count) { int8_t i; for (i = count - 1; i >= 0; i--) /* 高位先发 */ tm1621_send_bit((value >> i) & 0x01); } static void tm1621_write_command(uint8_t cmd) { TM1621_CS_LOW(); tm1621_send_bits(0x100 | cmd, 9); /* 第 1 位固定 1:命令标志 */ TM1621_CS_HIGH(); } static void tm1621_write_data(uint8_t addr, uint8_t data) { TM1621_CS_LOW(); tm1621_send_bits(0x00, 1); /* 第 1 位固定 0:数据标志 */ tm1621_send_bits(addr & 0x3F, 6); /* 6 位地址,最大 0x3F */ tm1621_send_bits(data & 0x0F, 4); /* 4 位数据,对应 4 个 SEG */ TM1621_CS_HIGH(); }逻辑说明:write_command发送的是 9 位数据,最高位固定为 1,告诉芯片「接下来是命令而不是显示数据」;write_data发送的是 11 位数据,最高位固定为 0,后面跟 6 位 RAM 地址和 4 位数据。4 位数据对应 RAM 里一个地址的 4 个 bit,每个 bit 控制一个 SEG 段的亮灭。WR 的上升沿锁存数据,所以代码里先写 DA 再拉高 WR,顺序不能反过来。
3.3 RAM 映射:地址高三位是 SEG 还是 COM
TM1621 的显示 RAM 是整块映射的,地址一共 6 位,可以到 0x3F。但实际能用的地址范围取决于 COM 数和 SEG 数:4 COM 屏一般用 0x00~0x1F,8 COM 屏用 0x00~0x3F。每个地址存 4 bit,这 4 bit 对应 4 个 SEG。
具体到每个 bit 管哪个段,不同封装的手册画法不太一样,但最常用的布局是:地址的低位是 COM 编号,数据位的低位是 SEG 编号的低位。
/* 把某个 SEG 段点亮/熄灭的封装函数 */ void tm1621_set_seg(uint8_t com, uint8_t seg, uint8_t on) { uint8_t addr, data, bit_pos; if (com > 3 || seg > 31) return; /* 4 COM 屏,seg 按 32 段算 */ addr = ((seg >> 2) << 2) | (com & 0x03); /* 地址:高几位是 SEG 组,低 2 位是 COM */ bit_pos = seg & 0x03; /* 组内第几个 SEG,对应 4 bit 的哪一位 */ data = tm1621_read_ram(addr); /* 先读回原值,避免影响同一地址其他段 */ if (on) data |= (0x01 << bit_pos); else data &= ~(0x01 << bit_pos); tm1621_write_data(addr, data); }这里(seg >> 2) << 2的意思是把 SEG 编号按每 4 个一组分成组,每组对应 RAM 的一个地址;com & 0x03是 4 COM 屏的 COM 编号,占用地址最低 2 位。如果换成 8 COM 屏,低 3 位是 COM,地址范围扩到 0x3F,公式要相应改成addr = ((seg >> 2) << 3) | (com & 0x07)。移植的时候先看手册里的 RAM 映射表,确认高几位是 SEG、低几位是 COM,这块搞反了,屏幕显示的字符就是镜像的或者错位的。确认映射的最笨也最可靠的方法,是一次只点亮一个段,然后对照实物记录,我每次拿到新屏都这么干。
3.4 移植到自己工程:替换四个底层函数
上面代码可以直接抄进工程,但要先替换掉四个和硬件相关的操作:TM1621_CS_LOW/HIGH、TM1621_WR_LOW/HIGH、TM1621_DA_WRITE、tm1621_delay_us。前三个是 GPIO 置高置低,最后一个一般用nop循环或者 SysTick 延时。
/* 适配层示例:所有硬件操作集中在这几个宏 */ #define TM1621_CS_LOW() HAL_GPIO_WritePin(TM1621_CS_PORT, TM1621_CS_PIN, GPIO_PIN_RESET) #define TM1621_CS_HIGH() HAL_GPIO_WritePin(TM1621_CS_PORT, TM1621_CS_PIN, GPIO_PIN_SET) #define TM1621_WR_LOW() HAL_GPIO_WritePin(TM1621_WR_PORT, TM1621_WR_PIN, GPIO_PIN_RESET) #define TM1621_WR_HIGH() HAL_GPIO_WritePin(TM1621_WR_PORT, TM1621_WR_PIN, GPIO_PIN_SET) #define TM1621_DA_WRITE(v) HAL_GPIO_WritePin(TM1621_DA_PORT, TM1621_DA_PIN, (v) ? GPIO_PIN_SET : GPIO_PIN_RESET)延时这块特别注意:WR 高电平持续时间不要太短,我一般保证 1us 以上,宁可慢一点也不要卡着极限跑。GPIO 模拟的时序快了出问题不好查,慢了最多显示刷新慢一点,对段式 LCD 来说几乎无感。
4. 常见问题排查:五个我踩过的 tm1621 显示坑
4.1 全黑屏:初始化命令顺序不对导致的假死
现象:上电后屏幕全黑,用示波器看 CS、WR、DA 波形都正常,命令也确实发出去了,但屏幕什么都不显示。原因:初始化命令顺序错了。常见错误是先把 LCD_ON 发出去,再开振荡器,或者根本没开系统使能。解决办法:把 SYS_EN、BIAS/COM、SYS_OSC、LCD_ON 按顺序重来一遍,并且每两条命令之间加一个短延时,让芯片内部状态稳定下来。
4.2 乱码、重影:偏压和 COM 配置跟屏幕不匹配
现象:显示能亮,但是数字乱跳,或者一个数字带出残影,像重影一样。原因:BIAS_COM命令里设置的 COM 数跟屏的引脚实际数量不一致。比如屏是 4 COM 的,你配置成了 8 COM,RAM 扫描地址就会错位。解决办法:数一下屏的 COM 引脚数量,查手册找到对应的 BIAS/COM 配置字节,改完重新初始化。另外注意,偏压档位和 VLCD 电压是配合的,1/3 bias 和 1/4 bias 显示对比度会差很多,改配置之后要用全亮测试确认亮度均匀。
4.3 某一笔永远不亮或者常亮:RAM 地址低位和 COM 对应错了
现象:数码管的「8」字少了一横,但其他横都正常;或者某个段怎么都关不掉,一直是暗的。原因:SEG 和 COM 映射关系跟代码里算的地址对不上。我遇到过一次,手册里画的是数据位低两位是 COM,我按高两位是 COM 去算,结果 A 段亮了 B 段。解决办法:用逐段扫描函数,把每个 SEG 组都循环点亮一次,拿实物对照,把映射关系改成代码里的注释。这个过程的本质就是拿屏当调试输出,一组一组点。
4.4 偶发花屏:3.3V 单片机和 5V 屏的逻辑电平不匹配
现象:程序跑起来大部分时间正常,但每隔几分钟闪一下,或者供电电压波动时屏幕花一下。原因:TM1621 的 VDD 如果接 5V,而单片机是 3.3V IO,DA 的高电平只有 3.3V,虽然能过芯片的 VIH 门槛,但抗干扰能力差,稍有噪声就误锁存。解决办法:在 DA、WR、CS 三个线上串 100Ω 电阻,或者直接用电平转换芯片;也可以在 WR 的上升沿前后各加一点延时,避开信号抖动区间。这类问题示波器上看单次波形很难复现,串电阻最省事。
4.5 掉电之后恢复不亮:看门狗复位后寄存器没重新初始化
现象:程序里做了 LCD 掉电,进入低功耗,唤醒之后屏幕亮不回来了。原因:TM1621 的掉电命令或者系统使能关闭后,内部寄存器和 RAM 的内容不保证保持;唤醒后只是把 LCD_ON 重新发一次,偏压和振荡器没有重新配置,芯片起不来。解决办法:唤醒后把整个初始化流程完整走一遍,包括 SYS_EN、BIAS/COM、SYS_OSC、LCD_ON,再把显示数据重新刷一遍。从那以后我每次做低功耗唤醒,都把显示初始化收进同一个函数,唤醒和开机走同一套流程。
5. 装完怎么验证:点亮全段、逐段扫描与波形确认
5.1 全点亮测试与逐段扫描
驱动写完之后,第一步不是显示业务数字,而是先跑全亮和逐段扫描。全亮能验证总线通信和初始化是否正常,逐段扫描能验证 RAM 映射。
void tm1621_screen_test(void) { uint8_t addr, i; /* 第一步:所有 RAM 写 0x0F,全段点亮 */ for (addr = 0; addr < 0x20; addr++) { /* 4 COM 屏的地址范围 */ tm1621_write_data(addr, 0x0F); } delay_ms(500); /* 第二步:全部熄灭 */ for (addr = 0; addr < 0x20; addr++) { tm1621_write_data(addr, 0x00); } delay_ms(500); /* 第三步:逐段点亮,每次只亮一个段,观察是否和预期 SEG/COM 对应 */ for (addr = 0; addr < 0x20; addr++) { for (i = 0; i < 4; i++) { tm1621_write_data(addr, (uint8_t)(0x01 << i)); delay_ms(50); } } /* 第四步:再全亮一次,保持常亮方便看对比度 */ for (addr = 0; addr < 0x20; addr++) { tm1621_write_data(addr, 0x0F); } }注意这里 4 COM 屏的地址范围是 0x00~0x1F,如果屏是 8 COM 的,这个循环要扩到 0x3F。全亮状态下,检查每个段亮度是否一致,特别留意屏幕四个角有没有偏暗,有偏暗说明 VLCD 或者偏压电容有问题。
5.2 数据手册反查:码段亮灭与 COM/SEG 对应
逐段扫描的 50ms 间隔够让肉眼看清每次亮的段在屏幕的哪个位置。拿一张纸,把每次addr和i对应的亮段位置画下来,再跟手册里的 RAM 映射表对比。如果发现第 3 个地址的第 1 位实际亮的是屏的最左边一段,而不是表格里写的中间一段,说明地址计算公式需要调整。
5.3 时序波形与稳定性检查
全亮和逐段都通过后,把示波器夹在 WR 和 DA 上,抓一次完整的命令发送过程。确认 WR 上升沿处 DA 数据已经稳定,且高电平幅值不低于芯片 VIH。然后跑一个 10 分钟的压力测试,反复开关 LCD、反复写入全亮和全灭,看看有没有偶发花屏。这类问题在 GPIO 模拟里最常见的原因是 DA 翻转太靠近 WR 上升沿,解决办法是增大tm1621_send_bit里的延时。从那以后,我每接一块 TM1621 的屏,都会先跑一遍全亮和逐段扫描,再去做业务码段,这个习惯帮我少加了很多班。希望帮到你。
本文还有配套的精品资源,点击获取