2020-02-17 14:23:15 +01:00
/** library to communicate with a Titan Micro TM1637 IC attached to a 4-digit 7-segment
* @ file
2017-04-03 13:08:49 +02:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
2020-06-06 14:35:55 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2020-02-17 14:23:15 +01:00
* @ date 2017 - 2020
2017-04-03 13:08:49 +02:00
* @ 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 : 0 bpgfedcba
* + a +
* f b p
* + g +
* e c p
* + d +
*/
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // general utilities
# include <string.h> // string utilities
/* STM32 (including CM3) libraries */
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
# include <libopencm3/stm32/rcc.h> // real-time control clock library
# include <libopencm3/stm32/gpio.h> // general purpose input output library
# include <libopencm3/stm32/timer.h> // 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
* @ {
*/
2020-02-17 14:23:15 +01:00
# define LED_TM1637_CLK_PIN PB6 /**< pin for CLK signal */
# define LED_TM1637_DIO_PIN PB7 /**< pin for DIO signal */
2017-04-03 13:08:49 +02:00
/** @} */
/** @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 ;
2020-08-20 13:52:16 +02:00
/** display buffer */
static uint8_t led_tm1637_digits [ 4 ] = { 0 } ;
2020-09-27 11:56:31 +02:00
/** if the display is upside down */
bool led_tm1637_updown = false ;
2017-04-03 13:08:49 +02:00
/** 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 ~
} ;
2020-09-27 11:56:31 +02:00
/** 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 )
2017-04-03 13:08:49 +02:00
{
2020-09-27 11:56:31 +02:00
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
2017-04-03 13:08:49 +02:00
// configure GPIO for CLK and DIO signals
2020-02-17 14:23:15 +01:00
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
2017-04-03 13:08:49 +02:00
// 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
2020-02-17 13:59:49 +01:00
rcc_periph_reset_pulse ( RST_TIM ( LED_TM1637_TIMER ) ) ; // reset timer state
2017-04-03 13:08:49 +02:00
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 )
2020-08-20 13:52:16 +02:00
timer_set_period ( TIM ( LED_TM1637_TIMER ) , 500 ) ; // set the clock frequency (empirical value until the signal starts to look bad)
2017-04-03 13:08:49 +02:00
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
2020-02-17 14:27:16 +01:00
if ( NULL = = data | | 0 = = length ) { // verify there it data to be read
2017-04-03 13:08:49 +02:00
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)
2020-02-17 14:23:15 +01:00
gpio_clear ( GPIO_PORT ( LED_TM1637_DIO_PIN ) , GPIO_PIN ( LED_TM1637_DIO_PIN ) ) ; // put DIO low
2017-04-03 13:08:49 +02:00
led_tm1637_tick ( ) ; // wait for next tick
2020-02-17 14:23:15 +01:00
gpio_clear ( GPIO_PORT ( LED_TM1637_CLK_PIN ) , GPIO_PIN ( LED_TM1637_CLK_PIN ) ) ; // put CLK low
2017-04-03 13:08:49 +02:00
// send data bytes (MSb first)
2020-02-17 14:27:16 +01:00
for ( size_t i = 0 ; i < length ; i + + ) { // send all bytes
2017-04-03 13:08:49 +02:00
uint8_t byte = data [ i ] ;
2020-02-17 14:27:16 +01:00
for ( uint8_t b = 0 ; b < 8 ; b + + ) { // send all bits
if ( byte & 0x1 ) { // send a 1
2020-02-17 14:23:15 +01:00
gpio_set ( GPIO_PORT ( LED_TM1637_DIO_PIN ) , GPIO_PIN ( LED_TM1637_DIO_PIN ) ) ; // put DIO high
2017-04-03 13:08:49 +02:00
} else {
2020-02-17 14:23:15 +01:00
gpio_clear ( GPIO_PORT ( LED_TM1637_DIO_PIN ) , GPIO_PIN ( LED_TM1637_DIO_PIN ) ) ; // put DIO low
2017-04-03 13:08:49 +02:00
}
byte > > = 1 ; // shift data
led_tm1637_tick ( ) ; // wait for next tick
2020-02-17 14:23:15 +01:00
gpio_set ( GPIO_PORT ( LED_TM1637_CLK_PIN ) , GPIO_PIN ( LED_TM1637_CLK_PIN ) ) ; // put CLK high
2017-04-03 13:08:49 +02:00
led_tm1637_tick ( ) ; // wait for next tick (no DIO transition when CLK is high)
2020-02-17 14:23:15 +01:00
gpio_clear ( GPIO_PORT ( LED_TM1637_CLK_PIN ) , GPIO_PIN ( LED_TM1637_CLK_PIN ) ) ; // put CLK low
2017-04-03 13:08:49 +02:00
}
2020-02-17 14:23:15 +01:00
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
2017-04-03 13:08:49 +02:00
led_tm1637_tick ( ) ; // wait for next tick (when the slave should ACK)
2020-02-17 14:23:15 +01:00
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
2017-04-03 13:08:49 +02:00
to_return = false ; // remember there was an error
break ; // stop sending bytes
}
led_tm1637_tick ( ) ; // wait for next tick
2020-02-17 14:23:15 +01:00
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
2017-04-03 13:08:49 +02:00
}
// send stop condition
2020-02-17 14:23:15 +01:00
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
2017-04-03 13:08:49 +02:00
led_tm1637_tick ( ) ; // wait for next tick
2020-02-17 14:23:15 +01:00
gpio_set ( GPIO_PORT ( LED_TM1637_CLK_PIN ) , GPIO_PIN ( LED_TM1637_CLK_PIN ) ) ; // put CLK high
2017-04-03 13:08:49 +02:00
led_tm1637_tick ( ) ; // wait for next tick
2020-02-17 14:23:15 +01:00
gpio_set ( GPIO_PORT ( LED_TM1637_DIO_PIN ) , GPIO_PIN ( LED_TM1637_DIO_PIN ) ) ; // put DIO high
2017-04-03 13:08:49 +02:00
timer_disable_counter ( TIM ( LED_TM1637_TIMER ) ) ; // stop timer since it's not used anymore
return to_return ;
}
2020-08-20 13:52:16 +02:00
/** 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
2020-09-27 11:56:31 +02:00
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 ] ) ;
}
2020-08-20 13:52:16 +02:00
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
}
2017-04-03 13:08:49 +02:00
bool led_tm1637_on ( void )
{
2020-08-20 13:52:16 +02:00
display_on = true ; // remember display is on
return led_tm1637_update ( ) ;
2017-04-03 13:08:49 +02:00
}
bool led_tm1637_off ( void )
{
2020-08-20 13:52:16 +02:00
display_on = false ; // remember display is off
return led_tm1637_update ( ) ;
2017-04-03 13:08:49 +02:00
}
bool led_tm1637_brightness ( enum led_tm1637_brightness_t brightness )
{
display_brightness = brightness ; // save brightness
2020-08-20 13:52:16 +02:00
return led_tm1637_update ( ) ;
2017-04-03 13:08:49 +02:00
}
2020-02-18 17:28:34 +01:00
bool led_tm1637_number ( uint16_t number , bool zero )
2017-04-03 13:08:49 +02:00
{
2020-02-18 17:28:34 +01:00
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 ) {
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 0 ] = ascii_7segments [ ' ' - ' ' ] ;
2020-02-18 17:28:34 +01:00
} else {
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 0 ] = ascii_7segments [ digits [ 0 ] + ' 0 ' - ' ' ] ;
2017-04-03 13:08:49 +02:00
}
2020-02-18 17:28:34 +01:00
if ( 0 = = digits [ 0 ] & & 0 = = digits [ 1 ] & & ! zero ) {
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 1 ] = ascii_7segments [ ' ' - ' ' ] ;
2020-02-18 17:28:34 +01:00
} else {
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 1 ] = ascii_7segments [ digits [ 1 ] + ' 0 ' - ' ' ] ;
2020-02-18 17:28:34 +01:00
}
if ( 0 = = digits [ 0 ] & & 0 = = digits [ 1 ] & & 0 = = digits [ 2 ] & & ! zero ) {
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 2 ] = ascii_7segments [ ' ' - ' ' ] ;
2020-02-18 17:28:34 +01:00
} else {
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 2 ] = ascii_7segments [ digits [ 2 ] + ' 0 ' - ' ' ] ;
2020-02-18 17:28:34 +01:00
}
2020-08-20 13:52:16 +02:00
led_tm1637_digits [ 3 ] = ascii_7segments [ digits [ 3 ] + ' 0 ' - ' ' ] ;
2020-02-18 17:28:34 +01:00
2020-08-20 13:52:16 +02:00
return led_tm1637_update ( ) ; // display number
2017-04-03 13:08:49 +02:00
}
bool led_tm1637_time ( uint8_t hours , uint8_t minutes )
{
2020-08-20 13:52:16 +02:00
// 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 ' - ' ' ] ;
2019-03-26 19:27:40 +01:00
2020-08-20 13:52:16 +02:00
return led_tm1637_update ( ) ; // display time
2017-04-03 13:08:49 +02:00
}
bool led_tm1637_text ( char * text )
{
2020-02-17 14:27:16 +01:00
if ( strlen ( text ) ! = 4 ) { // input text should have exactly 4 characters
2017-04-03 13:08:49 +02:00
return false ;
}
2020-02-17 14:27:16 +01:00
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 ) ) {
2017-04-03 13:08:49 +02:00
return false ;
}
}
2019-03-26 19:27:40 +01:00
2020-08-20 13:52:16 +02:00
// 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
2017-04-03 13:08:49 +02:00
}