TM1621驱动程序实现与嵌入式显示系统设计

在家电控制面板、温控器、电子秤这类产品中,你有没有遇到过这样的问题:主控单片机的GPIO不够用了?明明只是想驱动几个数码管,却要占用十几根IO线,还得写复杂的动态扫描逻辑,稍有疏忽就出现闪烁或重影。更头疼的是,一旦加上按键输入功能,软件定时中断层层嵌套,主循环几乎被拖垮。

这正是TM1621这类专用LED驱动芯片存在的意义——它把原本压在MCU肩上的重担接了过去。只需三根线,就能搞定最多32个SEG和4个COM构成的128段显示,外加13路按键扫描。听起来像“外挂”一样神奇,但其实它的原理并不复杂,关键是理解清楚通信协议和初始化流程。

我第一次调试TM1621时也踩了不少坑。比如初始化命令顺序不对导致显示不亮,亮度调节寄存器写错值让屏幕忽明忽暗,甚至因为没定期通信触发了看门狗,设备用着用着突然黑屏。这些问题背后,其实是对芯片工作机制的理解偏差。今天我们就从实战角度出发,把TM1621的驱动搞透。


TM1621是点晶科技推出的一款高集成度LED驱动控制器,广泛应用于中小规模段码式显示场景。它最大的优势在于将显示刷新、键盘扫描、恒流驱动、内部振荡全部集成在一个SOP24封装里,主控只需要通过 CS WR DATA 三根信号线进行串行通信即可完成所有操作。

具体来看,芯片内部有32个SEG输出和4个COM输出,组成4×32的矩阵结构,最多可驱动32位共阴极数码管(每位列对应一个COM)。显示RAM为16字节(地址0x00~0x0F),每个字节控制8个段,也就是两个数码位的基本单元。此外还支持8级亮度调节、内置RC振荡器(约256kHz)、自动地址递增以及去抖处理的键盘扫描功能。

供电方面兼容2.4V~5.5V宽压范围,非常适合3.3V或5V系统;段电流最大可达25mA,通过外接电阻精确控制亮度。通信速率最高支持300kHz的写时钟频率,在普通8位单片机上也能轻松跑起来。

值得注意的一点是,如果长时间不与TM1621通信,它会自动进入低功耗模式并关闭显示。因此即使你的应用不需要频繁更新内容,也要记得每隔几秒发送一次“开启显示”命令,否则用户可能会误以为设备死机了。


通信协议是驱动开发的核心。TM1621采用三线制串行接口,所有操作都基于命令+数据的帧格式。整个过程以 CS 拉低开始,拉高结束。

首先传输的是6位命令字,固定前两位为 11 ,后四位决定操作类型:

  • 1100xx :写数据到显示RAM
  • 1101xx :读取按键状态(本文暂不实现)
  • 1110xx :系统命令设置(如启停振荡器、选择COM模式等)
  • 1111xx :亮度调节(0~7级)

例如发送 0x40 (二进制 110000 )表示进入“写显示RAM”模式。接下来要发送一个6位地址头,格式为 10AAAAAA ,其中AAAAAA是6位地址(实际有效范围0x00~0x0F)。之后连续写入的数据将按地址自动递增存储。

举个例子:向RAM地址0x02写入0x55,流程如下:

CS = 0;
tm1621_write_byte(0x40);  // 写RAM命令
tm1621_write_byte(0x84);  // 地址头:10 + 000010 << 1 → 0x84
tm1621_write_byte(0x55);  // 数据
CS = 1;

这里的地址左移一位是因为低电平用于指示是否继续传输,高位补 10 作为起始标志。这种设计虽然节省了引脚,但也增加了编程时的位操作复杂度,需要特别注意掩码和移位处理。


下面是一个适用于STC、AVR、STM8等8位平台的通用C语言驱动框架。由于不依赖任何HAL库,移植性很强,只需根据实际硬件修改GPIO定义即可。

假设连接如下:

#define TM1621_CS   PB0
#define TM1621_WR   PB1
#define TM1621_DATA PB2

// IO操作宏(以51为例)
#define SET_CS()    (P_B |= (1<<TM1621_CS))
#define CLR_CS()    (P_B &= ~(1<<TM1621_CS))
#define SET_WR()    (P_B |= (1<<TM1621_WR))
#define CLR_WR()    (P_B &= ~(1<<TM1621_WR))
#define SET_DATA()  (P_B |= (1<<TM1621_DATA))
#define CLR_DATA()  (P_B &= ~(1<<TM1621_DATA))

#define OUTPUT(pin) // 若需配置方向可在此补充

延时函数用于满足基本时序要求(通常微秒级):

void delay_us(unsigned int n) {
    while(n--) __nop();  // 视主频调整,一般1~2us/nop
}

核心写入函数采用MSB优先方式逐位发送:

static void tm1621_write_byte(unsigned char dat) {
    unsigned char i;
    for(i = 0; i < 8; i++) {
        CLR_WR();
        delay_us(1);
        if(dat & 0x80)
            SET_DATA();
        else
            CLR_DATA();
        SET_WR();
        delay_us(1);
        dat <<= 1;
    }
}

命令发送封装成独立函数:

static void tm1621_send_command(unsigned char cmd) {
    CLR_CS();
    delay_us(2);
    tm1621_write_byte(cmd);
    delay_us(2);
    SET_CS();
}

批量写RAM函数是日常使用最频繁的接口:

void tm1621_write(unsigned char addr, const unsigned char* data, unsigned char len) {
    unsigned char i;

    CLR_CS();
    delay_us(2);
    tm1621_write_byte(0x40);  // 写RAM命令
    tm1621_write_byte(0x80 | ((addr & 0x0F) << 1));  // 地址头

    for(i = 0; i < len; i++) {
        tm1621_write_byte(data[i]);
    }

    SET_CS();
}

初始化部分最容易出错。必须严格按照手册规定的顺序执行:

  1. 启动内部振荡器(命令 0x24
  2. 设置为4-COM模式(命令 0x82
  3. 开启显示(命令 0xC0
  4. 可选设置亮度
void tm1621_init(void) {
    OUTPUT(TM1621_CS);
    OUTPUT(TM1621_WR);
    OUTPUT(TM1621_DATA);

    SET_CS();
    SET_WR();

    tm1621_send_command(0x24);  // 启用内部RC振荡器
    delay_us(50);
    tm1621_send_command(0x82);  // 4-COM, 1/4 duty
    tm1621_send_command(0xC0);  // 开启显示,无闪烁
    tm1621_set_brightness(3);   // 默认亮度等级3(中等)
}

void tm1621_set_brightness(unsigned char level) {
    tm1621_send_command(0x80 | (level & 0x07));
}

这里特别提醒: 0x82 这个命令很容易被误解。它属于“系统命令”类别(1110xxxx),但具体功能由低四位决定。查表可知 0x82 对应的二进制是 11100010 ,其中D1D0=10表示4-COM模式,这才是正确的配置。


对于常见的4位7段数码管应用,可以添加辅助函数简化显示调用。假设RAM[0]~[3]分别对应第1~4位数码管:

const unsigned char seg_code[10] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66,
    0x6D, 0x7D, 0x07, 0x7F, 0x6F  // 共阴极,dp在高位
};

void tm1621_display_number(int num, unsigned char pad_zero) {
    unsigned char buf[4];
    int i;

    for(i = 0; i < 4; i++) {
        buf[3-i] = seg_code[num % 10];
        num /= 10;
    }

    if(!pad_zero) {
        for(i = 0; i < 3; i++) {
            if(buf[i] == seg_code[0])
                buf[i] = 0x00;  // 前导零变空白
            else
                break;
        }
    }

    tm1621_write(0x00, buf, 4);
}

这样调用 tm1621_display_number(123, 0) 就能显示“ 123”,简洁直观。


在实际项目中,TM1621的价值体现在系统架构的优化上。以电子秤为例,主控MCU负责ADC采样、滤波计算、单位转换等核心任务,而显示刷新和按键检测全部交给TM1621处理。两者仅通过三根线交互,极大减轻了主控负担。

工作流程通常是:
1. 上电初始化TM1621
2. 主循环中根据称重结果调用显示函数
3. 轮询读取按键状态(如有需要)
4. 定期唤醒防止进入休眠

相比传统方案,这种方式有几个显著优势:
- GPIO占用少 :原来至少需要12~16个IO,现在只要3个;
- 显示稳定 :内置恒流驱动和自动扫描,彻底告别闪烁;
- 按键可靠 :硬件级去抖,避免软件延时影响响应速度;
- 功耗可控 :可通过命令关闭显示,适合电池供电设备。

当然也有一些设计细节需要注意:
- PCB布局应尽量缩短SEG/COM走线,建议芯片靠近数码管放置;
- VDD引脚务必加0.1μF去耦电容;
- 未使用的SEG或COM引脚建议悬空或接地;
- DATA线避免与其他高频信号平行走线以防干扰;
- 若启用看门狗功能,必须保证至少每秒通信一次。

初次调试时推荐先做全亮测试: tm1621_write(0, "\xFF\xFF\xxFF\xFF", 4); ,观察是否有段不亮,排查焊接或映射错误。


如今虽然有不少国产替代型号(如HT1621B、GC6208等)陆续出现,但其通信协议基本与TM1621兼容,原有驱动代码只需微调即可迁移。这也说明该协议已被业界广泛接受。

掌握TM1621的驱动不仅是为了点亮一块数码管,更重要的是建立起一种嵌入式系统设计思维: 让专用芯片做专事,主控专注业务逻辑 。这种分工协作的思想同样适用于LCD驱动、电机控制、音频编解码等场景。当你学会合理利用外设芯片,你会发现很多看似复杂的任务其实都有现成的“加速器”。

下次当你面对资源紧张的单片机项目时,不妨想想:是不是非得自己写扫描中断?有没有可能用一颗小芯片解放主控?也许答案就在那颗不起眼的TM1621里。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐