/* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** library to communicate with a Titan Micro TM1637 IC attached to a 4-digit 7-segment * @file * @author King Kévin * @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 PB14 /**< pin for CLK signal */ #define LED_TM1637_DIO_PIN PB13 /**< 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; /** 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 ~ }; void led_tm1637_setup(void) { // 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 (emprical 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; } bool led_tm1637_on(void) { uint8_t data[] = { 0x88 + display_brightness }; // command to turn display on (use set brightness) bool to_return = false; // result to return if (led_tm1637_write(data, LENGTH(data))) { // send command display_on = true; // remember display is on to_return = true; // command succeeded } return to_return; // return result } bool led_tm1637_off(void) { uint8_t data[] = { 0x80 + display_brightness }; // command to turn display off (use set brightness) if (led_tm1637_write(data, LENGTH(data))) { // send command display_on = false; // remember display is off return true; // command succeeded } return false; // return result } bool led_tm1637_brightness(enum led_tm1637_brightness_t brightness) { display_brightness = brightness; // save brightness if (display_on) { // adjust brightness if display is on return led_tm1637_on(); // adjust brightness } else { return true; // command succeeded } return false; } bool led_tm1637_number(uint16_t number, bool zero) { const uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal const uint8_t digits[] = { // digits to display (number / 1000) % 10, (number / 100) % 10, (number / 10) % 10, (number / 1) % 10, }; uint8_t data[] = { 0xc0, 0, 0, 0, 0}; // number to be displayed // convert digits to text to be displayed if (0 == digits[0] && !zero) { data[1] = ascii_7segments[' ' - ' ']; } else { data[1] = ascii_7segments[digits[0] + '0' - ' ']; } if (0 == digits[0] && 0 == digits[1] && !zero) { data[2] = ascii_7segments[' ' - ' ']; } else { data[2] = ascii_7segments[digits[1] + '0' - ' ']; } if (0 == digits[0] && 0 == digits[1] && 0 == digits[2] && !zero) { data[3] = ascii_7segments[' ' - ' ']; } else { data[3] = ascii_7segments[digits[2] + '0' - ' ']; } data[4] = ascii_7segments[digits[3] + '0' - ' ']; // display number return led_tm1637_write(write_data, LENGTH(write_data)) && led_tm1637_write(data, LENGTH(data)); // send commands } bool led_tm1637_time(uint8_t hours, uint8_t minutes) { uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal uint8_t data[] = { 0xc0, ascii_7segments[((hours / 10) % 10) + '0' - ' '], ascii_7segments[((hours / 1) % 10) + '0' - ' '] | 0x80, ascii_7segments[((minutes / 10) % 10) + '0' - ' '], ascii_7segments[((minutes / 1) % 10) + '0' - ' '] }; // set address C0H and add data if (led_tm1637_write(write_data, LENGTH(write_data)) && led_tm1637_write(data, LENGTH(data))) { // send commands return true; } return false; } 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; } } uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal uint8_t data[] = { 0xc0, ascii_7segments[(text[0] & 0x7f) - ' '] | (text[0] & 0x80), ascii_7segments[(text[1] & 0x7f) - ' ']|(text[1] & 0x80), ascii_7segments[(text[2] & 0x7f) - ' ']|(text[2] & 0x80), ascii_7segments[(text[3] & 0x7f) - ' ']|(text[3] & 0x80) }; // set address C0H and add data if (led_tm1637_write(write_data, LENGTH(write_data)) && led_tm1637_write(data, LENGTH(data))) { // send commands return true; } return false; }