/* 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 (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 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_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 */ 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(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 (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 (data==NULL || length==0) { // 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(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 (size_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 to send next byte } // send stop condition gpio_set_mode(GPIO(LED_TM1637_DIO_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(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(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; } 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) { uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal 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'-' '] }; // 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_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; }