TM1637 Interface with STM32 Part 2: Firmware Development

In Part 2, we shall develop the core firmware and driver logic required to translate time data into the TM1637’s custom serial protocol. This section focuses on implementing the precise bit-banging timing and segment mapping necessary to display live clock digits on the hardware.

In this guide, we shall cover the following:

  • Developing microseconds delay.
  • Developing TM1637 firmware.
  • main.c code.
  • Results.

5. Developing Microseconds Delay:

First, we need to create new source and header files.

To create Source file, right click on Src folder and add new source file as following:

Give a name for the source file, we shall name it as delay_us.c as following:

In similar manner, right click on Inc folder and add new header file with name of delay_us.h as follows:

Open delay_us.h header file. We start by including the following:

#include "tim.h"
#include "stdint.h"

Next, declare the following two functions:

void delay_us_start(void);
void delay_us(uint32_t us);

That all for the header file.

Next, open delay_us.c source file. Include the following header file as follows:

#include "delay_us.h"

Next, the functions:

void delay_us_start(void)
{
	HAL_TIM_Base_Start(&htim2);
}


void delay_us(uint32_t us)
{
	__HAL_TIM_SET_COUNTER(&htim2,0);  // set the counter value a 0
	while ((uint32_t)__HAL_TIM_GET_COUNTER(&htim2) < us);  // wait for the counter to reach the us input in the p
}

For more details how these functions have been developed, please refer to this guide.

6. Developing TM1637 Firmware:

First, we need to create new source and header files.

To create Source file, right click on Src folder and add new source file as following:

Give a name for the source file, we shall name it as TM1637.c as following:

In similar manner, right click on Inc folder and add new header file with name of TM1637.h as follows:

In the header file, we start by including the following header files:

#include "main.h"
#include "gpio.h"
#include "delay_us.h"

Next, declare the following function:

void tm1637Init(void);

It is a one-time configuration step that prepares the TM1637 to accept single-digit (fixed-address) writes.

Next,

void tm1637SetDigit(uint8_t position, uint8_t value, uint8_t dot);

This is a function prototype that declares a function used to update one digit on a 4-digit TM1637 display.

  • position (0–3): selects which digit to update
  • value (0–15): selects the segment pattern (0–9, A–F)
  • dot (0 or 1): turns the decimal point on or off

Next,

void tm1637SetBrightness(char brightness);

This function manages the internal Pulse Width Modulation (PWM) of the TM1637 chip to control how much current flows through the LEDs.

Last function:

void tm1637SetColon(uint8_t state);

This is a function prototype used to control the colon (:) indicator on a TM1637 clock-style display.

Thats all for the header file.

Next, open TM1637.c source file.

We start by including the following header file:

#include "TM1637.h"

First, declare a buffer to hold the digit to be displayed as follows:

uint8_t displayBuffer[4]={0};

Then, declare segment map as follows:

const char segmentMap[] = {
    0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, // 0-7
    0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, // 8-9, A-F
    0x00
};

Next, declare macros to handle the state of CLK and DIO pins as follows:

#define _tm1637ClkHigh() HAL_GPIO_WritePin(CLK_GPIO_Port,CLK_Pin,GPIO_PIN_SET);
#define _tm1637ClkLow()  HAL_GPIO_WritePin(CLK_GPIO_Port,CLK_Pin,GPIO_PIN_RESET);

#define _tm1637DioHigh() HAL_GPIO_WritePin(DIO_GPIO_Port,DIO_Pin,GPIO_PIN_SET);
#define _tm1637DioLow()  HAL_GPIO_WritePin(DIO_GPIO_Port,DIO_Pin,GPIO_PIN_RESET);

Since my module has time separator rather than decimal points, I defined the following statement to handle this:

#define COLON_POS 1  // or 2 depending on your module

Next, declare the function to handle the delay in uS for the module as follows:

static void _tm1637DelayUsec(unsigned int i)
{
	delay_us(i);
}

Next, for start condition:

static void _tm1637Start(void)
{
    _tm1637ClkHigh();
    _tm1637DioHigh();
    _tm1637DelayUsec(20);
    _tm1637DioLow();
}

Next, stop condition:

static void _tm1637Stop(void)
{
    _tm1637ClkLow();
    _tm1637DelayUsec(20);
    _tm1637DioLow();
    _tm1637DelayUsec(20);
    _tm1637ClkHigh();
    _tm1637DelayUsec(20);
    _tm1637DioHigh();
}

Next, reading the results:

static void _tm1637ReadResult(void)
{
    _tm1637ClkLow();
    _tm1637DelayUsec(50);
    // while (dio); // We're cheating here and not actually reading back the response.
    _tm1637ClkHigh();
    _tm1637DelayUsec(20);
    _tm1637ClkLow();
}

This is just to follow the requirement and wast a little bit of time.

Next, write single byte to the module:

static void _tm1637WriteByte(unsigned char b)
{
    for (int i = 0; i < 8; ++i) {
        _tm1637ClkLow();
        if (b & 0x01) {
            _tm1637DioHigh();
        }
        else {
            _tm1637DioLow();
        }
        _tm1637DelayUsec(30);
        b >>= 1;
        _tm1637ClkHigh();
        _tm1637DelayUsec(30);
    }
}

Next, for the initialization of the module:

void tm1637Init(void)
{
    _tm1637Start();
    _tm1637WriteByte(0x44); // fixed address mode
    _tm1637ReadResult();
    _tm1637Stop();
}

Next, write to a digit:

void tm1637SetDigit(uint8_t position, uint8_t value, uint8_t dot)
{
    if (position > 3 || value > 15) return;

    uint8_t seg = segmentMap[value];

    if (dot)
        seg |= 0x80;

    displayBuffer[position] = seg;

    // Write single digit
    _tm1637Start();
    _tm1637WriteByte(0xC0 | position);
    _tm1637ReadResult();

    _tm1637WriteByte(seg);
    _tm1637ReadResult();

    _tm1637Stop();
}

Set the brightness:

void tm1637SetBrightness(char brightness)
{
    // Brightness command:
    // 1000 0XXX = display off
    // 1000 1BBB = display on, brightness 0-7
    // X = don't care
    // B = brightness
    _tm1637Start();
    _tm1637WriteByte(0x87 + brightness);
    _tm1637ReadResult();
    _tm1637Stop();
}

This function for displaying the separator as follows:

void tm1637SetColon(uint8_t state)
{
    if (state)
        displayBuffer[COLON_POS] |= 0x80;
    else
        displayBuffer[COLON_POS] &= ~0x80;

    // Rewrite only that digit
    _tm1637Start();
    _tm1637WriteByte(0xC0 | COLON_POS);
    _tm1637ReadResult();

    _tm1637WriteByte(displayBuffer[COLON_POS]);
    _tm1637ReadResult();

    _tm1637Stop();
}

This function will maintain the current value and update the separator only.

Thats all for the firmware.

7. Main.c Code:

Open main.c.

In user code includes, include the following header files:

#include "TM1637.h"
#include "delay_us.h"

In user code begin PV, declare the following variables to display the time:

uint8_t hours = 12;
uint8_t minutes = 0;

uint8_t colonState = 0;

uint32_t lastTick = 0;

In user code begin 0:

void updateDisplay(void)
{
    tm1637SetDigit(0, hours / 10, 0);
    tm1637SetDigit(1, hours % 10, colonState); // separator here
    tm1637SetDigit(2, minutes / 10, 0);
    tm1637SetDigit(3, minutes % 10, 0);
}

In user code begin 2, start the microsecond timer as follows:

delay_us_start();

Initialize the display:

tm1637Init();

Set the brightness:

tm1637SetBrightness(5);

In user code begin 3 in while 1 loop:

if (HAL_GetTick() - lastTick >= 1000)
{
  lastTick = HAL_GetTick();

  // Toggle colon
  colonState ^= 1;

  // Increment time
  minutes++;

  if (minutes >= 60)
  {
    minutes = 0;
    hours++;

    if (hours >= 24)
      hours = 0;
  }

  updateDisplay();
}

Thats all for the guide.

Save, build the project and run it as follows:

You may download the code below:

8. Results:

You should get the following:

Please note that in the demo, I am increasing the minutes each second for the demo purposes.

Happy coding 😉

Add Comment

Your email address will not be published. Required fields are marked *