clapperboard/lib/led_tm1637.c

319 lines
13 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with a Titan Micro TM1637 IC attached to a 7-segment displays (code)
* @file led_tm1637.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @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 <stdint.h> // standard integer types
#include <stdlib.h> // memory 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
* @{
*/
#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<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(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO high
} else {
gpio_clear(GPIO(LED_TM1637_DIO_PORT), GPIO(LED_TM1637_DIO_PIN)); // put DIO low
}
byte >>= 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
}