/* 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), 500); // set the clock frequency (500 kHz is the maximum per datasheet, but also because of the bit banging implementation) 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 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_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 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 }