/* 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 7-segment displays (code) * @file led_tm1637.c * @author King Kévin * @date 2017 * @note peripherals used: GPIO @ref led_tm1637_gpio, timer @ref led_tm1637_timer * @note only 4-digit 7-segment displays are considered as this is the most common case * @warning all calls are blocking * @note the protocol is very similar to I2C but incompatible for the following reasons: the pin 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) * * bit vs segment: 0bpgfedcba * +a+ * f b p * +g+ * e c p * +d+ */ /* standard libraries */ #include // standard integer types #include // memory 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_PORT B /**< port for CLK signal */ #define LED_TM1637_CLK_PIN 6 /**< pin for CLK signal */ #define LED_TM1637_DIO_PORT B /**< port for DIO signal */ #define LED_TM1637_DIO_PIN 7 /**< 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 */ enum led_tm1637_brightness_t display_brightness = LED_TM1637_14DIV16; /** 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(RCC_GPIO(LED_TM1637_CLK_PORT)); // enable clock for GPIO peripheral gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // idle high gpio_set_mode(GPIO(LED_TM1637_CLK_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(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(RCC_GPIO(LED_TM1637_DIO_PORT)); // enable clock for GPIO peripheral gpio_set(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // idle high gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(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 timer_reset(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), rcc_ahb_frequency/50000/2); // set the clock frequency to 50 kHz (500 kHz is the maximum per datasheet, but 50 kHz is empirically more stable due to the line capacitance of the multiple displays) 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, uint8_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 gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_TM1637_DIO_PIN)); // ensure we can output to sent start condition 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(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO low led_tm1637_tick(); // wait for next tick gpio_clear(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK low // send data bytes (MSb first) for (uint8_t i=0; i>= 1; // shift data led_tm1637_tick(); // wait for next tick gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK high led_tm1637_tick(); // wait for next tick (no DIO transition when CLK is high) gpio_clear(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK low } gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO(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(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK high if (gpio_get(GPIO(LED_TM1637_DIO_PORT), GPIO(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(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK low gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_TM1637_DIO_PIN)); // switch DIO back to output } // send stop condition led_tm1637_tick(); // wait for next tick gpio_set(GPIO(LED_TM1637_CLK_PORT), GPIO(LED_TM1637_CLK_PIN)); // put CLK high led_tm1637_tick(); // wait for next tick gpio_set(GPIO(LED_TM1637_DIO_PORT), GPIO(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 commands on bus to send data * @param[in] address_command data data to send (including address as first byte) * @param[in] length length of data * @return if send succeeded */ static bool led_tm1637_send_data(uint8_t* address_command, uint8_t length) { // sanity check if (0==length && NULL==address_command) { return false; } uint8_t data_command[] = { 0x40 }; // data command: write data, automatic address adding, normal uint8_t display_command[] = { 0x88+(display_brightness&0x0f) }; // display command: turn display on and set brightness if (led_tm1637_write(data_command, LENGTH(data_command)) && led_tm1637_write(address_command, length) && led_tm1637_write(display_command, LENGTH(display_command))) { // send commands return true; } return false; } bool led_tm1637_off(void) { // note: while the TM1637 could recognize the display command thanks to the first two bits, we still need to send the data and address command first uint8_t data_command[] = { 0x40 }; // data command: write data, automatic address adding, normal uint8_t address_command[] = { 0xc0, 0x00, 0x00, 0x00, 0x00 }; // address command: clear all (if bit 7 of byte 4 is set switching off doesn't work correctly, I don't know why) uint8_t display_command[] = { 0x80 }; // display command: turn display off (we don't care about the brightness since it's set when turned on) if (led_tm1637_write(data_command,LENGTH(data_command)) && led_tm1637_write(address_command, LENGTH(address_command)) && led_tm1637_write(display_command, LENGTH(display_command))) { // send commands return true; } return false; } void led_tm1637_brightness(enum led_tm1637_brightness_t brightness) { display_brightness = (brightness&0x0f); // save brightness } bool led_tm1637_number(uint16_t number) { uint8_t data[] = { 0xc0, ascii_7segments[((number/1000)%10)+'0'-' '], ascii_7segments[((number/100)%10)+'0'-' '], ascii_7segments[((number/10)%10)+'0'-' '], ascii_7segments[((number/1)%10)+'0'-' '] }; // encode number on 4 digits return led_tm1637_send_data(data, LENGTH(data)); // transmit data } bool led_tm1637_time(uint8_t hours, uint8_t minutes) { 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'-' '] }; // encode time on 4 digits return led_tm1637_send_data(data, LENGTH(data)); // transmit data } 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 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) }; // encode text on 7 digits return led_tm1637_send_data(data, LENGTH(data)); // transmit data }