Tutorial: TM1637-based 7-Segment Display Module#


TM1637 IC#

Time-multiplexing is a technique that helps reduce the number of control pins needed for multi-digit 7-segment displays, while keeping full control over each individual digit. Nowadays, there are specialized LED driver/controller ICs designed for these displays, like the TM1637 and TM1640, which can also handle dot-LED matrices.

In this tutorial, we will use the TM1637 IC to drive a 4-digit 7-segment display module, which is usually set up in a common-anode configuration (not common-cathode). The TM1637 connects with a microcontroller or an FPGA using just two signal lines: a clock line (CLK) and a bidirectional data line (DIO). Don’t forget, both lines need external pull-up resistors, typically around 10kΩ.

 


TM1637-based 4-Digit 7-Segment Display Module#

The figure below shows a 4-digit 7-segment display module that operates with the TM1637 chip as its controller or driver. Depending on the model, there are different digit sizes (like 0.36" or 0.56"), and some versions come with decimal points (.) for each digit or colon symbols (:), which are quite handy for showing time in a digital clock format.

Figure: TM1637-based 4-digit 7-segment module

Figure: Schematic example of a TM1637-based 7-segment display module

Note:

  • Although the VCC of the TM1637-based display module is rated at "+5V", it often operates reliably at +3.3V as well (but with less brightness).

 


TM1637 Programming#

The figure below shows the sequence of sending command and data bytes to the TM1637 IC. This process is used to write display data into the TM1637’s internal SRAM, starting from a specified address and automatically incrementing for subsequent data bytes.

Figure: Write SRAM data process

The protocol follows a structured sequence of start conditions, commands, data transfers, and acknowledgments (ACKs) over the CLK and DIO lines.

  • Start Condition: The host (MCU or FPGA) begins communication with a start condition (falling edge of DIO while CLK is high).
  • Command1 = Set Data Command (0x40): This step sends a data command byte that enables auto-increment mode.
  • Command2 = Set Address Command (0xC0): This step sends an address command byte to specify the starting SRAM address where display data will be written.
  • Display Data Bytes: This step sequentially writes 1 to N data bytes to the display. Each byte corresponds to the segment pattern for one digit. The TM1637 auto-increments the address after each byte.
  • Command3 = Display Control Command This sends a command to set the brightness level and turn display on.
  • Stop Condition: A stop condition is sent by the host to finish communication. This occurs when DIO goes high while CLK is high.

Figure: Options for data set command

Figure: Options for address set command

Figure: Options for the display control command

 


VHDL Coding Examples for FPGA#

This VHDL module (tm1637_driver) implements a low-level driver for the TM1637 LED controller IC. It has the following control/status signals.

  • START: Begins a write transaction
  • STOP: Indicates if the current byte is the last
  • DC: Select either data or command byte to transmit
  • DONE: Signals the end of the transaction
  • ACK: Indicates acknowledgment from the TM1637

The CLK and DIO are bidirectional I/O signals for the TM1637 IC.

TM_CLK : INOUT STD_LOGIC;
TM_DIO : INOUT STD_LOGIC;

These lines are open-drain (open-collector), they can only be driven low or released (high-Z). Pull-up resistors pull them high when not driven.

-- File: tm1637_driver.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

ENTITY tm1637_driver IS
    PORT (
        CLK    : IN STD_LOGIC;
        RST_N  : IN STD_LOGIC;
        START  : IN STD_LOGIC; -- start write operation
        STOP   : IN STD_LOGIC;
        DC     : IN STD_LOGIC_VECTOR(7 DOWNTO 0); -- data or cmd byte
        TM_CLK : INOUT STD_LOGIC;
        TM_DIO : INOUT STD_LOGIC;
        DONE   : OUT STD_LOGIC;
        ACK    : OUT STD_LOGIC -- ACK or NACK status
    );
END tm1637_driver;

ARCHITECTURE behavioral OF tm1637_driver IS

    TYPE state_type IS (
        ST_IDLE,
        ST_START,
        ST_SHIFT_CLK_L,
        ST_SHIFT_CLK_H,
        ST_ACK,
        ST_READ_ACK,
        ST_STOP,
        ST_DONE
    );

    SIGNAL state      : state_type := ST_IDLE;
    SIGNAL next_state : state_type := ST_IDLE;

    SIGNAL bit_cnt    : INTEGER RANGE 0 TO 7 := 0;
    SIGNAL shift_reg  : STD_LOGIC_VECTOR(7 DOWNTO 0) := (OTHERS => '0');

    -- Output control signals
    SIGNAL tm_clk_oe  : STD_LOGIC := '0'; -- 1 = drive, 0 = high-Z
    SIGNAL tm_dio_oe  : STD_LOGIC := '0'; -- 1 = drive, 0 = high-Z

    -- Clock divider: adjust for target frequency
    --CONSTANT CLK_DIV : INTEGER := 125;
    CONSTANT CLK_DIV   : INTEGER := 1000;
    --CONSTANT CLK_DIV : INTEGER := 5000;
    SIGNAL clk_count   : INTEGER RANGE 0 TO CLK_DIV - 1 := 0;
    SIGNAL clk_en      : STD_LOGIC := '0';

    -- ACK read
    SIGNAL ack_bit     : STD_LOGIC := '1';
    SIGNAL stop_bit    : STD_LOGIC := '1';

BEGIN
    -- Assign outputs
    TM_CLK <= '0' WHEN tm_clk_oe = '1' ELSE 'Z';
    TM_DIO <= '0' WHEN tm_dio_oe = '1' ELSE 'Z';
    ACK <= ack_bit;

    -- Clock Divider: Generate clk_en pulse
    clk_div_proc : PROCESS (RST_N, CLK)
    BEGIN
        IF RST_N = '0' THEN
            clk_count <= 0;
            clk_en <= '0';
        ELSIF rising_edge(CLK) THEN
            IF clk_count = CLK_DIV - 1 THEN
                clk_count <= 0;
                clk_en <= '1';
            ELSE
                clk_count <= clk_count + 1;
                clk_en <= '0';
            END IF;
        END IF;
    END PROCESS;

    -- FSM Process
    fsm_proc : PROCESS (RST_N, CLK)
    BEGIN
        IF RST_N = '0' THEN
            state     <= ST_IDLE;
            tm_clk_oe <= '0';
            tm_dio_oe <= '0';
            bit_cnt   <= 0;
            ack_bit   <= '1';
            stop_bit  <= '1';
            DONE      <= '1';

        ELSIF rising_edge(CLK) THEN
            IF clk_en = '1' THEN
                CASE state IS
                    WHEN ST_IDLE =>
                        DONE <= '1';
                        tm_clk_oe <= '0'; -- CLK 'H'
                        tm_dio_oe <= '0'; -- DIO 'H'
                        IF START = '1' THEN
                            state <= ST_START;
                        END IF;

                    WHEN ST_START =>
                        DONE <= '0';
                        stop_bit <= STOP;
                        shift_reg <= DC;
                        tm_dio_oe <= '1'; -- DIO '0'
                        bit_cnt <= 0;
                        state <= ST_SHIFT_CLK_L;

                    WHEN ST_SHIFT_CLK_L =>
                        tm_clk_oe <= '1'; -- CLK '0'
                        state <= ST_SHIFT_CLK_H;
                        tm_dio_oe <= NOT shift_reg(0); -- LSB first
                        shift_reg <= '0' & shift_reg(7 DOWNTO 1);

                    WHEN ST_SHIFT_CLK_H =>
                        tm_clk_oe <= '0'; -- CLK 'H'
                        IF bit_cnt = 7 THEN
                            state <= ST_ACK;
                        ELSE
                            bit_cnt <= bit_cnt + 1;
                            state <= ST_SHIFT_CLK_L;
                        END IF;

                    WHEN ST_ACK =>
                        tm_clk_oe <= '1'; -- CLK '0'
                        tm_dio_oe <= '0'; -- release line
                        DONE <= '1';
                        state <= ST_READ_ACK;

                    WHEN ST_READ_ACK =>
                        ack_bit <= TM_DIO; -- read ACK
                        tm_dio_oe <= '1'; -- DIO '0'
                        tm_clk_oe <= '0'; -- CLK 'H'
                        state <= ST_DONE;

                    WHEN ST_DONE =>
                        tm_dio_oe <= '1'; -- DIO '0'
                        tm_clk_oe <= '1'; -- CLK '0'
                        state <= ST_STOP;

                    WHEN ST_STOP =>
                        IF stop_bit = '0' THEN
                            tm_clk_oe <= '1'; -- CLK '0'
                            state <= ST_START;
                        ELSE
                            tm_clk_oe <= '0'; -- CLK 'H'
                            state <= ST_IDLE;
                        END IF;

                    WHEN OTHERS =>
                        state <= ST_IDLE;

                END CASE;
            END IF;
        END IF;
    END PROCESS;

END behavioral;

 

This VHDL module demonstrates how to control a 4-digit 7-segment display using the TM1637 driver IC. It instantiates a lower-level module (tm1637_driver) to manage the serial communication protocol, and implements a simple decimal counter (ranging from 0 to 9999) that updates periodically. The counter value is converted to 7-segment format and sent to the TM1637 display module.

-- File: tm1637_driver_demo.vhd
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

ENTITY tm1637_driver_demo IS
    PORT (
        CLK    : IN STD_LOGIC;
        RST_N  : IN STD_LOGIC;
        TM_CLK : INOUT STD_LOGIC;
        TM_DIO : INOUT STD_LOGIC
    );
END tm1637_driver_demo;

ARCHITECTURE behavioral OF tm1637_driver_demo IS

    -- TM1637 driver interface
    SIGNAL start : STD_LOGIC := '0';
    SIGNAL stop  : STD_LOGIC := '1';
    SIGNAL done  : STD_LOGIC;
    SIGNAL ack   : STD_LOGIC;
    SIGNAL dc    : STD_LOGIC_VECTOR(7 DOWNTO 0); -- DATA / CMD byte

    -- FSM
    TYPE state_type IS (
       ST_INIT, ST_LOAD, ST_SEND, ST_WAIT_START, ST_WAIT_DONE, ST_DELAY
    );

    SIGNAL state : state_type := ST_INIT;
    SIGNAL index : INTEGER RANGE 0 TO 6 := 0;

    CONSTANT DELAY_CNT_MAX : INTEGER := 5_000_000 - 1;
    SIGNAL delay_cnt : INTEGER RANGE 0 TO DELAY_CNT_MAX := 0;

    -- Counter for display
    SIGNAL count  : INTEGER RANGE 0 TO 9999 := 0;
    SIGNAL digit0 : INTEGER RANGE 0 TO 9 := 0; -- Units
    SIGNAL digit1 : INTEGER RANGE 0 TO 9 := 0; -- Tens
    SIGNAL digit2 : INTEGER RANGE 0 TO 9 := 0; -- Hundreds
    SIGNAL digit3 : INTEGER RANGE 0 TO 9 := 0; -- Thousands

    -- TM1637 data sequence (7 commands total)
    TYPE data_array IS ARRAY (0 TO 6) OF STD_LOGIC_VECTOR(7 DOWNTO 0);
    SIGNAL data_seq : data_array;

    -- 7-segment encoding function
    FUNCTION to_7seg(val : INTEGER) RETURN STD_LOGIC_VECTOR IS
    BEGIN
        CASE val IS
            WHEN 0 => RETURN "00111111";
            WHEN 1 => RETURN "00000110";
            WHEN 2 => RETURN "01011011";
            WHEN 3 => RETURN "01001111";
            WHEN 4 => RETURN "01100110";
            WHEN 5 => RETURN "01101101";
            WHEN 6 => RETURN "01111101";
            WHEN 7 => RETURN "00000111";
            WHEN 8 => RETURN "01111111";
            WHEN 9 => RETURN "01101111";
            WHEN OTHERS => RETURN "00000000";
        END CASE;
    END FUNCTION;

BEGIN
    -- TM1637 Driver Instance
    driver_inst : ENTITY work.tm1637_driver
        PORT MAP(
            CLK    => CLK,
            RST_N  => RST_N,
            START  => start,
            STOP   => stop,
            DC     => dc,
            TM_CLK => TM_CLK,
            TM_DIO => TM_DIO,
            DONE   => done,
            ACK => ack
        );

    -- FSM Process
    PROCESS (CLK, RST_N)
    BEGIN
        IF RST_N = '0' THEN
            state <= ST_INIT;
            start <= '0';
            stop  <= '1';
            index <= 0;
            delay_cnt <= 0;
            count <= 0;

        ELSIF rising_edge(CLK) THEN
            CASE state IS

                WHEN ST_INIT =>
                    start <= '0';
                    stop  <= '1';
                    index <= 0;
                    state <= ST_LOAD;

                WHEN ST_LOAD =>
                    -- Split count into BCD digits
                    digit0 <= count MOD 10;
                    digit1 <= (count / 10) MOD 10;
                    digit2 <= (count / 100) MOD 10;
                    digit3 <= (count / 1000) MOD 10;

                    -- Prepare TM1637 command/data sequence
                    data_seq(0) <= x"40"; -- CMD1: normal write
                    data_seq(1) <= x"C0"; -- CMD2: start address
                    data_seq(2) <= to_7seg(digit3); -- leftmost digit
                    data_seq(3) <= to_7seg(digit2);
                    data_seq(4) <= to_7seg(digit1);
                    data_seq(5) <= to_7seg(digit0); -- rightmost digit        
                    data_seq(6) <= x"8F"; -- CMD3: display ON (max. brightness)

                    state <= ST_SEND;

                WHEN ST_SEND =>
                    dc <= data_seq(index);
                    start <= '1';

                    -- Only stop for command or last byte
                    IF index = 0 OR index = 5 OR index = 6 THEN
                        stop <= '1';
                    ELSE
                        stop <= '0';
                    END IF;

                    state <= ST_WAIT_START;

                WHEN ST_WAIT_START =>
                    IF done = '0' THEN
                        start <= '0';
                        state <= ST_WAIT_DONE;
                    END IF;

                WHEN ST_WAIT_DONE =>
                    IF done = '1' THEN
                        IF index = 6 THEN
                            state <= ST_DELAY;
                            delay_cnt <= DELAY_CNT_MAX;
                        ELSE
                            index <= index + 1;
                            state <= ST_SEND;
                        END IF;
                    END IF;

                WHEN ST_DELAY =>
                    IF delay_cnt = 0 THEN
                        IF count = 9999 THEN
                            count <= 0;
                        ELSE
                            count <= count + 1;
                        END IF;
                        state <= ST_INIT;
                    ELSE
                        delay_cnt <= delay_cnt - 1;
                    END IF;

                WHEN OTHERS =>
                    state <= ST_INIT;

            END CASE;
        END IF;
    END PROCESS;

END behavioral;

 

The following Tcl script defines the pin assignments for the DE10-Lite FPGA board.

# File: assignments.tcl
#============================================================
# CLOCK (MAX10_CLK1_50)
#============================================================
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to CLK
set_location_assignment PIN_P11 -to CLK

#============================================================
# Push Buttons (KEY)
#============================================================

set_instance_assignment -name IO_STANDARD "3.3 V SCHMITT TRIGGER" -to RST_N
set_location_assignment PIN_B8 -to RST_N

#============================================================
# Arduino I/O 14 & 15 
#============================================================

set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to TM_CLK
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to TM_DIO
set_location_assignment PIN_AB21 -to TM_CLK
set_location_assignment PIN_AA20 -to TM_DIO

#set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to TM_CLK
#set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to TM_DIO

#============================================================

The following are the sample waveforms of CLK and DIO measured by a digital oscilloscope.

Figure: Waveforms measured by a digital oscilloscope

The following are sample waveforms of the CLK and DIO signals, captured using a digital oscilloscope.

Note that the clock rate of the TM1637 interface is limited (up to 450kHz), and it also depends on the values of the pull-up resistors and any filtering capacitors used on the lines.

Figure: DE10 Lite FPGA board + TM1637-based 7-segment 4-digit display module


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2025-06-18 | Last Updated: 2025-06-18