aboutsummaryrefslogtreecommitdiff
path: root/lib/led_tm1637.c
blob: 2d11b116ad12757050ae871af4fb27657dccdfd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/* 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 4-digit 7-segment (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 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 <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
 *  @{
 */
#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<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 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;
}