概述
TM1721 是一款带键盘扫描的 LED 段码驱动芯片(段码屏 = 显示数字的「日」字形笔画屏,每个数字由几段笔画拼成,类似老式计算器和数字电子钟的显示屏)。它一边驱动屏幕的每一段笔画亮灭,一边扫描按键(键扫 = 芯片轮流「摸」一遍按键,看哪个被按下了),一颗芯片就把「显示 + 键盘」两件事都干了。本教程使用官方 demo_TM1721 示例:初始化芯片、点亮屏幕全部段码,然后读取 6 个按键的状态并在串口打印短按 / 长按事件。
用大白话讲:段码屏就像数字电子钟的显示窗——「8」这个数字由 7 段笔画(横横竖竖组成「日」字形)拼成,TM1721 的活就是决定哪几笔亮、哪几笔灭,让笔画拼出 0~9 和字母。同时它还能一边显示一边「摸」键盘:它轮流检查每个按键有没有被按下,并把结果告诉单片机。本教程先让所有笔画都亮起来(相当于「8 8 8 8」全亮),再演示读按键。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/iot-solution/demo_TM1721编写,代码可在本地 SDK 中直接找到。
按官方示例接线(TM1721 用三根控制线 + 电源与单片机通信):
| Ai-WB2 引脚 | TM1721 模块 |
|---|---|
| IO17 | CLKPIN(时钟线,定通信节奏) |
| IO3 | DATAPIN(数据线,传内容) |
| IO14 | CSPIN(片选线,CS = 选中这颗芯片的「开关」,低电平选中) |
| 3V3 | VCC |
| GND | GND |
💡 三根线分别叫 CLK(时钟)、DATA(数据)、CS(片选),是一种类似 I2C 的软件模拟时序:CLK 打一个节拍、DATA 上送一个比特。片选 = 「点名」,只有被 CS 选中的芯片才会听数据线上的内容。接线时注意 IO3 同时是 SSD1306 教程 的 SDA,换外设时把线换过来即可。
打开终端进入官方 demo_TM1721 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_TM1721
说明:
cd是「进入目录」命令,这里进入 demo_TM1721 工程目录;后续的make编译、make flash烧录命令都必须先在这个目录里执行。
工程目录结构说明:
| 文件 | 作用 |
|---|---|
main/main.c |
主程序源码,本教程主要修改的文件 |
components/tm1721_driver/tm1721_driver.c / .h |
TM1721 驱动(自研),封装初始化、写段码数据、读按键等接口 |
Makefile |
编译入口,一般无需修改 |
打开 main/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/iot-solution/demo_TM1721/main/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
TM1721_init() |
初始化三个控制引脚并给芯片发送开机命令,不初始化芯片不会响应 |
TM1721_write_data_conytinous(0, Dbyte, 14) |
从地址 0 连续写 14 字节全 0xFF,把全部段码笔画点亮(8 段全亮 = 显示「8」) |
TM1721_read_key_status(key_read) |
读 4 字节键值,0x08/0x80 等组合对应 6 个不同按键 |
key_count 计数 + 判断区间 |
每次按键状态相同就计数,按 80~400ms 判短按、超 2.8s 判长按,只判一次会分不清长短按 |
vTaskDelay(portTICK_RATE_MS * 10) |
每 10ms 扫一次键,太快读不准、太慢按键会被「吞」 |
在工程目录执行编译:
make -j8
说明:
make是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8表示用 8 个核并行编译,速度更快。
编译成功后生成固件 build_out/demo_TM1721.bin。
开发板保持 USB 连接,确认串口设备号(Linux 下通常为 /dev/ttyUSB0),执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件写进开发板芯片。p=后面是串口设备号(要改成你电脑上实际的串口,可用ls /dev/ttyUSB*查看),b=是烧录波特率(传输速度)。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式(部分开发板自动进入),等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行:
- 段码屏全部笔画点亮(程序把 14 个显示地址全写了
0xFF,看起来像「8 8 8 8」全亮)。 - 打开串口助手(波特率 921600,波特率 = 串口传输速度,收发两端必须设成一样),按下模块上的按键:
- 短按(约 0.1~0.4 秒内按下并松开):串口打印
TIMER_KEY SHORT PRESS等(哪个键就打印哪个键名); - 长按(按住超过约 2.8 秒):串口打印
TIMER_KEY LONG PRESS; - 松开瞬间还会打印一行
get key_press value: 01(键值,每 10ms 打印一次)。
- 短按(约 0.1~0.4 秒内按下并松开):串口打印
💡 6 个按键分别对应
TIMER_KEY/CONFIRM_KEY/SWITCH_KEY/MODE_KEY/UP_KEY/DOWN_KEY。如果按键后打印prevent from wrong detect,说明按键时长没落在判定区间(太快或太慢),多试几次即可。
看到屏幕全亮、按键后串口打印对应键名和 SHORT/LONG PRESS 即为成功;如果屏幕不亮或按键无打印,说明还没成功,对照文末「常见问题与踩坑提示」排查。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
TM1721_init()
初始化 TM1721 芯片:把 CLK(IO17)、DATA(IO3)、CS(IO14)三个引脚配置为输出,并向芯片发送开机/设置命令(驱动自研接口,源码见 components/tm1721_driver/tm1721_driver.c)。
参数:无
返回值:无
TM1721_write_data_conytinous(first_addr, Dbyte, addr_num)
从指定地址开始连续写入多个地址的段码显示数据(驱动自研接口,源码见 components/tm1721_driver/tm1721_driver.c)。每个地址 1 字节,对应 8 段笔画(位 a~h),写 0xFF 即 8 段全亮。
参数:
first_addr:起始显示地址,可选值:0x00~0x0F,本教程0Dbyte:要写入的数据数组指针(每元素 1 字节段码),本教程全0xFFaddr_num:连续写入的地址个数,可选值:1~14,本教程14(首地址 + 数量不能超过0x0F)
返回值:成功返回 1;参数非法(地址越界等)返回 0
TM1721_read_key_status(data)
读取按键扫描结果(驱动自研接口,源码见 components/tm1721_driver/tm1721_driver.c)。芯片把 6 个按键的状态编码在 4 字节里返回,程序通过比较 0x08 / 0x80 等键值判断是哪个键。
参数:
data:4 字节接收缓冲区指针,返回后含键值(如data[0] == 0x08表示 TIMER 键)
返回值:读取成功返回 1
vTaskDelay(ms)
让当前任务挂起指定毫秒数,期间让出 CPU 给其他任务(FreeRTOS 系统 API)。
参数:
ms:延时毫秒数,可选值:任意非负整数(本教程按键扫描周期portTICK_RATE_MS * 10= 10ms)
返回值:无
完整代码
以下为 main/main.c 完整源码,与官方示例(applications/iot-solution/demo_TM1721/main/main.c)完全一致:
📜 点击展开 main/main.c 完整代码
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include "bl_sys.h"
#include "tm1721_driver.h"
#include <aos/kernel.h>
#include <bl_gpio.h>
#include <bl602_gpio.h>
#include <hosal_gpio.h>
#define TM1721_OPERAT_CMD 0x04
#define TM1721_DATA_CMD 0x40
#define TM1721_ADDR_CMD 0xC0
#define TM1721_SHOW_CMD 0x9F
#define SHORT_PRESS_TIMES_MIN 8
#define SHORT_PRESS_TIMES_MAX 40
#define LONG_PRESS_TIMES 280
typedef enum
{
NONE_KEY = 0,
TIMER_KEY = 1,
CONFIRM_KEY,
SWITCH_KEY,
MODE_KEY,
UP_KEY,
DOWN_KEY
} key_confirm_t;
// LED----------------------------- a b c d e f g h
static uint8_t NumberToLED[][8] = {{1, 1, 1, 1, 1, 1, 0, 0}, // 0
{0, 1, 1, 0, 0, 0, 0, 0}, // 1
{1, 1, 0, 1, 1, 0, 1, 0}, // 2
{1, 1, 1, 1, 0, 0, 1, 0}, // 3
{0, 1, 1, 0, 0, 1, 1, 0}, // 4
{1, 0, 1, 1, 0, 1, 1, 0}, // 5
{1, 0, 1, 1, 1, 1, 1, 0}, // 6
{1, 1, 1, 0, 0, 0, 0, 0}, // 7
{1, 1, 1, 1, 1, 1, 1, 0}, // 8
{1, 1, 1, 1, 0, 1, 1, 0}, // 9
{1, 0, 0, 1, 1, 1, 1, 0}, // E
{0, 0, 0, 0, 0, 0, 1, 0}}; // -
void screen_bit_show_step_to_step()
{
TM1721_init();
uint8_t byte = 0xFF;
uint8_t *Dbyte = calloc(1, sizeof(uint8_t) * 14);
uint8_t *ADDR = calloc(1, sizeof(uint8_t) * 15);
for (int i = 0; i < 16; i++)
{
ADDR[i] = i;
}
memset(Dbyte, 0, sizeof(uint8_t) * 14);
TM1721_write_data_conytinous(0, Dbyte, 14);
TM1721_write_data_conytinous(14, Dbyte, 2);
vTaskDelay(portTICK_RATE_MS * 1000);
byte = 0;
for (int i = 0; i < 15; i++)
{
for (int j = 0; j < 8; j++)
{
byte = byte + (1 << j);
TM1721_write_data_conytinous(i, &byte, 1);
vTaskDelay(portTICK_RATE_MS * 500);
}
byte = 0;
}
byte = 0x0F;
TM1721_write_data_conytinous(15, &byte, 1);
}
void main(void)
{
uint8_t *key_read = calloc(1, sizeof(uint8_t) * 4);
uint8_t *Dbyte = calloc(1, sizeof(uint8_t) * 16);
key_confirm_t key_press = NONE_KEY;
key_confirm_t key_front = NONE_KEY;
uint8_t key_count = 0;
TM1721_init();
memset(Dbyte, 0xFF, 16);
TM1721_write_data_conytinous(0, Dbyte, 14);
TM1721_write_data_conytinous(14, Dbyte + 14, 2);
vTaskDelay(portTICK_RATE_MS * 100);
while (1)
{
TM1721_read_key_status(key_read);
if (key_read[0] == 0x08)
{
key_press = TIMER_KEY;
if (TIMER_KEY == key_front)
{
key_count++;
if (key_count > LONG_PRESS_TIMES)
{
printf("TIMER_KEY LONG PRESS\r\n");
}
}
}
else if (key_read[0] == 0x80)
{
key_press = CONFIRM_KEY;
if (CONFIRM_KEY == key_front)
{
key_count++;
if (key_count > LONG_PRESS_TIMES)
{
printf("CONFIRM_KEY LONG PRESS\r\n");
}
}
}
else if (key_read[2] == 0x80)
{
key_press = SWITCH_KEY;
if (SWITCH_KEY == key_front)
{
key_count++;
if (key_count > LONG_PRESS_TIMES)
{
printf("SWITCH_KEY LONG PRESS\r\n");
}
}
}
else if (key_read[2] == 0x08)
{
key_press = MODE_KEY;
if (MODE_KEY == key_front)
{
key_count++;
if (key_count > LONG_PRESS_TIMES)
{
printf("MODE_KEY LONG PRESS\r\n");
}
}
}
else if (key_read[1] == 0x80)
{
key_press = UP_KEY;
if (UP_KEY == key_front)
{
key_count++;
if (key_count > LONG_PRESS_TIMES)
{
printf("UP_KEY LONG PRESS\r\n");
}
}
}
else if (key_read[1] == 0x08)
{
key_press = DOWN_KEY;
if (DOWN_KEY == key_front)
{
key_count++;
if (key_count > LONG_PRESS_TIMES)
{
printf("DOWN_KEY LONG PRESS\r\n");
}
}
}
else
{
key_press = 0;
if (key_count > SHORT_PRESS_TIMES_MIN && key_count < SHORT_PRESS_TIMES_MAX)
{
switch (key_front)
{
case TIMER_KEY:
printf("TIMER_KEY SHORT PRESS\r\n");
break;
case CONFIRM_KEY:
printf("CONFIRM_KEY SHORT PRESS\r\n");
break;
case SWITCH_KEY:
printf("SWITCH_KEY SHORT PRESS\r\n");
break;
case MODE_KEY:
printf("MODE_KEY SHORT PRESS\r\n");
break;
case UP_KEY:
printf("UP_KEY SHORT PRESS\r\n");
break;
case DOWN_KEY:
printf("DOWN_KEY SHORT PRESS\r\n");
break;
default:
break;
}
}
else
{
printf("prevent from wrong detect\r\n");
}
}
key_count = 0;
key_front = key_press;
vTaskDelay(portTICK_RATE_MS * 10);
printf("get key_press value: %02X\r\n", key_press);
}
}常见问题与踩坑提示
⚠️ 段码屏完全不亮
原因:CLK/DATA/CS 三根线接反、未共地或供电不足
解决:按官方接线 IO17=CLKPIN、IO3=DATAPIN、IO14=CSPIN,VCC 接 3V3 且 GND 共地;杜邦线重新插紧
⚠️ 按键后打印 prevent from wrong detect
原因:按键时长不在判定区间——太短(<80ms)或太长(超过 400ms 又被判成别的)
解决:用「快速按下立刻松开」的方式操作;想调整灵敏度可改 main.c 里 SHORT_PRESS_TIMES_MIN/MAX、LONG_PRESS_TIMES 三个宏(宏 = 给数字起的有名字的代号)
⚠️ 想显示数字而不是全亮
原因:main.c 里把所有地址写成了 0xFF(全亮),官方把 0~9 的段码表 NumberToLED 也放在 main.c 里但未启用
解决:参考 NumberToLED 表,把对应数字的 8 段笔画拼成字节(如数字 0 = 段 a~f 亮 = 0x3F)再调用 TM1721_write_data_conytinous 写入
⚠️ 编译报错找不到 tm1721_driver.h
原因:驱动在 components/ 子目录,编译配置未包含或目录路径不对
解决:确认在工程根目录 ~/Ai-Thinker-WB2/applications/iot-solution/demo_TM1721 执行 make -j8,make 会自动编译 components 下的驱动,不要手动 cd 进 main/ 目录
⚠️ 烧录时提示无法打开串口 / 一直卡住等待
原因:串口设备号不对、权限不足,或未进入下载模式、数据线只能充电
解决:确认设备号 ls /dev/ttyUSB*,权限不足执行 sudo usermod -aG dialout $USER;烧录时按提示长按 EN 键,换一根能传数据的 Type-C 数据线后重试
运行自检
段码屏全部笔画点亮(呈「8 8 8 8」全亮效果),短按/长按模块按键时串口分别打印 XXX_KEY SHORT PRESS / XXX_KEY LONG PRESS,即 TM1721 驱动验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

