Overview
The TM1721 is an LED segment display driver chip with keyboard scanning (a segment display = a "日"-shaped stroke screen for numbers, each digit made of a few strokes — like the display on old calculators and digital clocks). On one side it drives each stroke of the screen on and off, on the other it scans the keys (key scanning = the chip takes turns "feeling" the keys to see which one is pressed) — one chip handles both "display + keyboard". This tutorial uses the official demo_TM1721 example: initializes the chip, lights all segments of the screen, then reads the state of 6 keys and prints short-press / long-press events on the serial.
In plain words: a segment display is like a digital clock's display window — the digit "8" is made of 7 strokes (horizontal and vertical lines forming a "日" shape), and TM1721's job is to decide which strokes light and which don't, so the strokes form 0~9 and letters. At the same time it can feel the keyboard while displaying: it checks each key in turn to see if it's pressed, and tells the microcontroller. This tutorial first lights every stroke (like "8 8 8 8" all lit), then demonstrates reading the keys.
This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version
release_bl_iot_sdk_1.6.40) exampleapplications/iot-solution/demo_TM1721; the code can be found directly in the local SDK.
Wire per the official example (the TM1721 talks to the microcontroller with three control lines plus power):
| Ai-WB2 Pin | TM1721 Module |
|---|---|
| IO17 | CLKPIN (clock line, sets the communication rhythm) |
| IO3 | DATAPIN (data line, carries the content) |
| IO14 | CSPIN (chip select line — the “switch” that selects this chip, active low) |
| 3V3 | VCC |
| GND | GND |
💡 The three lines are called CLK (clock), DATA (data), CS (chip select) — a software-emulated timing similar to I2C: one beat on CLK, one bit up on DATA. Chip select = “roll call”; only the chip selected by CS listens to the content on the data line. Note IO3 is also the SDA in the SSD1306 tutorial — when swapping peripherals, just move the wire over.
Open a terminal and enter the official demo_TM1721 example project directory:
cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_TM1721
Note:
cdis the “change directory” command — entering the demo_TM1721 project directory; all subsequentmakebuild andmake flashflash commands must run in this directory first.
Project structure:
| File | Purpose |
|---|---|
main/main.c |
Main program source, the main file this tutorial looks at |
components/tm1721_driver/tm1721_driver.c / .h |
TM1721 driver (self-developed), wraps init, write segment data, read keys and other interfaces |
Makefile |
Build entry, usually no changes needed |
Open main/main.c — the complete code for this step has been moved to the end of this page:
📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (
applications/iot-solution/demo_TM1721/main/main.c).
Code highlights:
| Code | Purpose |
|---|---|
TM1721_init() |
Initializes the three control pins and sends the power-on command to the chip; uninitialized, the chip won’t respond |
TM1721_write_data_conytinous(0, Dbyte, 14) |
Continuously writes 14 bytes of all 0xFF from address 0, lighting every segment stroke (all 8 segments lit = displays “8”) |
TM1721_read_key_status(key_read) |
Reads 4 bytes of key values; combos like 0x08/0x80 correspond to the 6 different keys |
key_count counting + range judging |
Counts while the key state stays the same; 80~400ms judges short press, over 2.8s judges long press — judging only once can’t tell short from long |
vTaskDelay(portTICK_RATE_MS * 10) |
Scans keys every 10ms; too fast reads inaccurately, too slow swallows presses |
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program file) the board can run;-j8builds with 8 parallel CPU cores, faster.
On success a firmware build_out/demo_TM1721.bin is generated.
Keep the board connected via USB, confirm the serial device (usually /dev/ttyUSB0 on Linux), and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board’s chip. Afterp=comes the serial device (change it to your computer’s actual one — check withls /dev/ttyUSB*),b=is the flash baud rate (transfer speed).
⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode (some boards enter automatically); wait for the progress bar to complete — that means the flash succeeded.
After flashing, the board automatically restarts and runs:
- Every segment of the screen lights up (the program writes all 14 display addresses with
0xFF, looking like “8 8 8 8” all lit). - Open the serial assistant (baud rate 921600 — the baud rate is the serial transfer speed, both ends must be set the same) and press a key on the module:
- Short press (press and release within about 0.1~0.4 seconds): the serial prints
TIMER_KEY SHORT PRESSetc. (whichever key was pressed prints that key name); - Long press (hold for over about 2.8 seconds): the serial prints
TIMER_KEY LONG PRESS; - At the moment of release it also prints a line
get key_press value: 01(the key value, printed every 10ms).
- Short press (press and release within about 0.1~0.4 seconds): the serial prints
💡 The 6 keys correspond to
TIMER_KEY/CONFIRM_KEY/SWITCH_KEY/MODE_KEY/UP_KEY/DOWN_KEY. If pressing printsprevent from wrong detect, the press duration didn’t land in the judging range (too fast or too slow) — try a few more times.
Seeing the screen fully lit and the serial printing the matching key name with SHORT/LONG PRESS after pressing means success; if the screen doesn’t light or pressing prints nothing, it hasn’t succeeded yet — check the FAQ at the end.
API Summary for This Tutorial
TM1721_init()
Initializes the TM1721 chip: configures the three pins CLK (IO17), DATA (IO3), CS (IO14) as outputs and sends the power-on/setup commands to the chip (self-developed driver interface, source in components/tm1721_driver/tm1721_driver.c).
Parameters: none
Return: none
TM1721_write_data_conytinous(first_addr, Dbyte, addr_num)
Continuously writes segment display data to multiple addresses starting from the given address (self-developed driver interface, source in components/tm1721_driver/tm1721_driver.c). Each address is 1 byte, corresponding to 8 segment strokes (bits a~h); writing 0xFF lights all 8 segments.
Parameters:
first_addr: starting display address, values:0x00~0x0F, this tutorial0Dbyte: data array pointer to write (each element is 1 byte of segment data), this tutorial all0xFFaddr_num: number of consecutive addresses to write, values:1~14, this tutorial14(first address + count must not exceed0x0F)
Return: 1 on success; 0 on invalid parameters (address out of range, etc.)
TM1721_read_key_status(data)
Reads the key scanning result (self-developed driver interface, source in components/tm1721_driver/tm1721_driver.c). The chip encodes the 6 keys' states into 4 bytes; the program identifies which key by comparing key values like 0x08 / 0x80.
Parameters:
data: 4-byte receive buffer pointer; after return it holds the key values (e.g.data[0] == 0x08means the TIMER key)
Return: 1 on successful read
vTaskDelay(ms)
Suspends the current task for the given milliseconds, yielding the CPU to other tasks (FreeRTOS system API).
Parameters:
ms: delay in milliseconds, values: any non-negative integer (this tutorial's key scan periodportTICK_RATE_MS * 10= 10ms)
Return: none
Full Code
Below is the complete main/main.c source, identical to the official example (applications/iot-solution/demo_TM1721/main/main.c):
📜 Click to expand the full main/main.c code
#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);
}
}FAQ & Troubleshooting
⚠️ The segment screen doesn't light at all
Cause: the three lines CLK/DATA/CS are swapped, no common ground, or insufficient supply
Fix: per the official wiring IO17=CLKPIN, IO3=DATAPIN, IO14=CSPIN; VCC to 3V3 with GND common ground; re-seat the dupont wires
⚠️ Pressing prints prevent from wrong detect
Cause: the press duration isn't in the judging range — too short (<80ms) or too long (over 400ms, then judged otherwise)
Fix: press with a quick "press and release immediately" motion; to adjust sensitivity, edit the three macros SHORT_PRESS_TIMES_MIN/MAX and LONG_PRESS_TIMES in main.c (a macro = a named alias for a number)
⚠️ Want to display digits instead of all lit
Cause: main.c writes all addresses as 0xFF (all lit); the official 0~9 segment table NumberToLED is also in main.c but not enabled
Fix: refer to the NumberToLED table, pack the 8 strokes of the digit into a byte (e.g. digit 0 = segments a~f lit = 0x3F) and write it with TM1721_write_data_conytinous
⚠️ Build reports tm1721_driver.h not found
Cause: the driver lives in the components/ subdirectory — the build config doesn't include it or the directory path is wrong
Fix: run make -j8 at the project root ~/Ai-Thinker-WB2/applications/iot-solution/demo_TM1721; make compiles the driver under components automatically — don't cd into main/ manually
⚠️ Flashing reports cannot open the serial port / keeps waiting
Cause: wrong serial device, insufficient permission, download mode not entered, or the cable only charges
Fix: confirm the device with ls /dev/ttyUSB*; if permission denied run sudo usermod -aG dialout $USER; press and hold EN during flashing as prompted; try a Type-C data-capable cable
Self-Check
Every segment of the screen lights up (looking like "8 8 8 8" all lit), and short/long pressing the module's keys prints XXX_KEY SHORT PRESS / XXX_KEY LONG PRESS on the serial — the TM1721 driver is verified.

