/** library to communicate with a Titan Micro TM1637 IC attached to a 4-digit 7-segment * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2017-2020 * @note peripherals used: GPIO @ref led_tm1637_gpio, timer @ref led_tm1637_timer * @note the protocol is very similar to I2C but incompatible for the following reasons: the capacitance is too large for open-drain type output with weak pull-up resistors (push-pull needs to be used, preventing to get ACKs since no indication of the ACK timing is provided); the devices doesn't use addresses; the STM32 I2C will switch to receiver mode when the first sent byte (the I2C address) has last bit set to 1 (such as for address commands with B7=1 where B7 is transmitted last), preventing to send further bytes (the data byte after the address) * @warning all calls are blocking * * bit vs segment: 0bpgfedcba * +a+ * f b p * +g+ * e c p * +d+ */ /* standard libraries */ #include // standard integer types #include // general utilities #include // string utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // real-time control clock library #include // general purpose input output library #include // timer library #include "global.h" // global utilities #include "led_tm1637.h" // TM1637 header and definitions /** @defgroup led_tm1637_gpio GPIO used to communication with TM1637 IC * @{ */ #define LED_TM1637_CLK_PIN PB6 /**< pin for CLK signal */ #define LED_TM1637_DIO_PIN PB7 /**< pin for DIO signal */ /** @} */ /** @defgroup led_tm1637_timer timer used to communication with TM1637 IC * @{ */ #define LED_TM1637_TIMER 3 /**< timer to create signal */ /** @} */ /** display brightness */ static enum led_tm1637_brightness_t display_brightness = LED_TM1637_14DIV16; /** if display is on */ static bool display_on = false; /** display buffer */ static uint8_t led_tm1637_digits[4] = {0}; /** if the display is upside down */ bool led_tm1637_updown = false; /** ASCII characters encoded for the 7 segments digit block * @note starts with space */ static const uint8_t ascii_7segments[] = { 0x00, // 0b00000000 space 0x30, // 0b00110000 ! (I) 0x22, // 0b00100010 " 0x5c, // 0b01011100 # (o) 0x6d, // 0b01101101 $ (s) 0x52, // 0b01010010 % (/) 0x7d, // 0b01111101 & (6) 0x20, // 0b00100000 ' 0x39, // 0b00111001 ( ([) 0x0f, // 0b00001111 ) 0x70, // 0b01110000 * 0x46, // 0b01000110 + 0x10, // 0b00010000 , 0x40, // 0b01000000 - 0x10, // 0b00010000 . (,) 0x52, // 0b01010010 / 0x3f, // 0b00111111 0 0x06, // 0b00000110 1 0x5b, // 0b01011011 2 0x4f, // 0b01001111 3 0x66, // 0b01100110 4 0x6d, // 0b01101101 5 0x7d, // 0b01111101 6 0x07, // 0b00000111 7 0x7f, // 0b01111111 8 0x6f, // 0b01101111 9 0x48, // 0b01001000 : (=) 0x48, // 0b01001000 ; (=) 0x58, // 0b01011000 < 0x48, // 0b01001000 = 0x4c, // 0b01001100 > 0x53, // 0b01010011 ? 0x7b, // 0b01111011 @ 0x77, // 0b01110111 A 0x7f, // 0b01111111 B 0x39, // 0b00111001 C 0x5e, // 0b01011110 D 0x79, // 0b01111001 E 0x71, // 0b01110001 F 0x3d, // 0b00111101 G 0x76, // 0b01110110 H 0x30, // 0b00110000 I 0x1e, // 0b00011110 J 0x76, // 0b01110110 K 0x38, // 0b00111000 L 0x37, // 0b00110111 M 0x37, // 0b00110111 N 0x3f, // 0b00111111 O 0x73, // 0b01110011 P 0x6b, // 0b01101011 Q 0x33, // 0b00110011 R 0x6d, // 0b01101101 S 0x78, // 0b01111000 T 0x3e, // 0b00111110 U 0x3e, // 0b00111110 V (U) 0x3e, // 0b00111110 W (U) 0x76, // 0b01110110 X (H) 0x6e, // 0b01101110 Y 0x5b, // 0b01011011 Z 0x39, // 0b00111001 [ 0x64, // 0b01100100 '\' 0x0f, // 0b00001111 / 0x23, // 0b00100011 ^ 0x08, // 0b00001000 _ 0x02, // 0b00000010 ` 0x5f, // 0b01011111 a 0x7c, // 0b01111100 b 0x58, // 0b01011000 c 0x5e, // 0b01011110 d 0x7b, // 0b01111011 e 0x71, // 0b01110001 f 0x6f, // 0b01101111 g 0x74, // 0b01110100 h 0x10, // 0b00010000 i 0x0c, // 0b00001100 j 0x76, // 0b01110110 k 0x30, // 0b00110000 l 0x54, // 0b01010100 m 0x54, // 0b01010100 n 0x5c, // 0b01011100 o 0x73, // 0b01110011 p 0x67, // 0b01100111 q 0x50, // 0b01010000 r 0x6d, // 0b01101101 s 0x78, // 0b01111000 t 0x1c, // 0b00011100 u 0x1c, // 0b00011100 v (u) 0x1c, // 0b00011100 w (u) 0x76, // 0b01110110 x 0x6e, // 0b01101110 y 0x5b, // 0b01011011 z 0x39, // 0b00111001 { ([) 0x30, // 0b00110000 | 0x0f, // 0b00001111 } ([) 0x40, // 0b01000000 ~ }; /** convert the data for upside down displays * @param[in] data the data to be converted * @return the upside down data */ static uint8_t led_tm1637_rotate(uint8_t data) { uint8_t converted = 0; converted |= (data & 0xc0); // keep g and dot converted |= (data << 3) & 0x38; // rotate abc converted |= (data >> 3) & 0x07; // rotate def return converted; } void led_tm1637_setup(bool updown) { led_tm1637_updown = updown; // remember if the display is upside down // configure GPIO for CLK and DIO signals rcc_periph_clock_enable(GPIO_RCC(LED_TM1637_CLK_PIN)); // enable clock for GPIO peripheral gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // idle high gpio_set_mode(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_CLK_PIN)); // master start the communication (capacitance is to large for open drain), only switch to input for ack from slave rcc_periph_clock_enable(GPIO_RCC(LED_TM1637_DIO_PIN)); // enable clock for GPIO peripheral gpio_set(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // idle high gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_DIO_PIN)); // master start the communication (capacitance is to large for open drain), only switch to input for ack from slave // first clock then data high also stands for stop condition // setup timer to create signal timing (each tick is used for a single GPIO transition) rcc_periph_clock_enable(RCC_TIM(LED_TM1637_TIMER)); // enable clock for timer block rcc_periph_reset_pulse(RST_TIM(LED_TM1637_TIMER)); // reset timer state timer_set_mode(TIM(LED_TM1637_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up timer_set_prescaler(TIM(LED_TM1637_TIMER), 0); // don't prescale to get most precise timing ( 1/(72E6/1/(2**16))=0.91 ms > 0.5 us ) timer_set_period(TIM(LED_TM1637_TIMER), 500); // set the clock frequency (empirical value until the signal starts to look bad) timer_clear_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF); // clear flag timer_update_on_overflow(TIM(LED_TM1637_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout) } /** wait until clock tick (timer overflow) occurred */ static inline void led_tm1637_tick(void) { while (!timer_get_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF)); // wait until counter overflow update event happens timer_clear_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF); // clear event flag } /** write data on bus * @param[in] data bytes to write * @param[in] length number of bytes to write * @return if write succeeded * @note includes start and stop conditions */ static bool led_tm1637_write(const uint8_t* data, size_t length) { bool to_return = true; // return if write succeeded if (NULL == data || 0 == length) { // verify there it data to be read return false; } // enable timer for signal generation timer_set_counter(TIM(LED_TM1637_TIMER), 0); // reset timer counter timer_enable_counter(TIM(LED_TM1637_TIMER)); // enable timer to generate timing led_tm1637_tick(); // wait to enforce minimum time since last write // send start condition (DIO then CLK low) gpio_clear(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO low led_tm1637_tick(); // wait for next tick gpio_clear(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK low // send data bytes (MSb first) for (size_t i = 0; i < length; i++) { // send all bytes uint8_t byte = data[i]; for (uint8_t b = 0; b < 8; b++) { // send all bits if (byte & 0x1) { // send a 1 gpio_set(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO high } else { gpio_clear(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO low } byte >>= 1; // shift data led_tm1637_tick(); // wait for next tick gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK high led_tm1637_tick(); // wait for next tick (no DIO transition when CLK is high) gpio_clear(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK low } gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LED_TM1637_DIO_PIN)); // switch DIO as input to read ACK led_tm1637_tick(); // wait for next tick (when the slave should ACK) gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK high if (gpio_get(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN))) { // no ACK received to_return = false; // remember there was an error break; // stop sending bytes } led_tm1637_tick(); // wait for next tick gpio_clear(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK low gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_DIO_PIN)); // switch DIO back to output to send next byte } // send stop condition gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_DIO_PIN)); // ensure DIO is output (in case no ACK as been received led_tm1637_tick(); // wait for next tick gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK high led_tm1637_tick(); // wait for next tick gpio_set(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO high timer_disable_counter(TIM(LED_TM1637_TIMER)); // stop timer since it's not used anymore return to_return; } /** write all data to SRAM (automatic address adding, normal) and control display * @return if write succeeded */ static bool led_tm1637_update(void) { const uint8_t write[] = { 0x40 }; // command: write data, automatic address adding, normal uint8_t data[] = { 0xc0, led_tm1637_digits[0], led_tm1637_digits[1], led_tm1637_digits[2], led_tm1637_digits[3] }; // set digits (start at address 0) if (led_tm1637_updown) { // rotate data data[1] = led_tm1637_rotate(led_tm1637_digits[3]); data[2] = led_tm1637_rotate(led_tm1637_digits[2]) | (led_tm1637_digits[1] & 0x80); // keep the : for the time data[3] = led_tm1637_rotate(led_tm1637_digits[1]) & 0x7f; // remove the : for the time data[4] = led_tm1637_rotate(led_tm1637_digits[0]); } const uint8_t control[] = { (display_on ? 0x88 : 0x80) + (display_brightness & 0x7) }; // command to turn display on/off and set brightness return led_tm1637_write(write, LENGTH(write)) && led_tm1637_write(data, LENGTH(data)) && led_tm1637_write(control, LENGTH(control)); // send commands } bool led_tm1637_on(void) { display_on = true; // remember display is on return led_tm1637_update(); } bool led_tm1637_off(void) { display_on = false; // remember display is off return led_tm1637_update(); } bool led_tm1637_brightness(enum led_tm1637_brightness_t brightness) { display_brightness = brightness; // save brightness return led_tm1637_update(); } bool led_tm1637_number(uint16_t number, bool zero) { const uint8_t digits[] = { // digits to display (number / 1000) % 10, (number / 100) % 10, (number / 10) % 10, (number / 1) % 10, }; // convert digits to text to be displayed if (0 == digits[0] && !zero) { led_tm1637_digits[0] = ascii_7segments[' ' - ' ']; } else { led_tm1637_digits[0] = ascii_7segments[digits[0] + '0' - ' ']; } if (0 == digits[0] && 0 == digits[1] && !zero) { led_tm1637_digits[1] = ascii_7segments[' ' - ' ']; } else { led_tm1637_digits[1] = ascii_7segments[digits[1] + '0' - ' ']; } if (0 == digits[0] && 0 == digits[1] && 0 == digits[2] && !zero) { led_tm1637_digits[2] = ascii_7segments[' ' - ' ']; } else { led_tm1637_digits[2] = ascii_7segments[digits[2] + '0' - ' ']; } led_tm1637_digits[3] = ascii_7segments[digits[3] + '0' - ' ']; return led_tm1637_update(); // display number } bool led_tm1637_time(uint8_t hours, uint8_t minutes) { // set digits led_tm1637_digits[0] = ascii_7segments[((hours / 10) % 10) + '0' - ' ']; led_tm1637_digits[1] = ascii_7segments[((hours / 1) % 10) + '0' - ' '] | 0x80; led_tm1637_digits[2] = ascii_7segments[((minutes / 10) % 10) + '0' - ' ']; led_tm1637_digits[3] = ascii_7segments[((minutes / 1) % 10) + '0' - ' ']; return led_tm1637_update(); // display time } bool led_tm1637_text(char* text) { if (strlen(text) != 4) { // input text should have exactly 4 characters return false; } for (uint8_t i = 0; i < 4; i++) { // input text should only contain printable character (8th bit is used for dots) if ((text[i] & 0x7f) < ' ' || (text[i] & 0x7f) >= ' ' + LENGTH(ascii_7segments)) { return false; } } // set digits led_tm1637_digits[0] = ascii_7segments[(text[0] & 0x7f) - ' '] | (text[0] & 0x80); led_tm1637_digits[1] = ascii_7segments[(text[1] & 0x7f) - ' '] | (text[1] & 0x80); led_tm1637_digits[2] = ascii_7segments[(text[2] & 0x7f) - ' '] | (text[2] & 0x80); led_tm1637_digits[3] = ascii_7segments[(text[3] & 0x7f) - ' '] | (text[3] & 0x80); return led_tm1637_update(); // display test }