b6453aecb541b31249a0986c5a78f5695b5c247b
[cortex-from-scratch] / drivers / tm1637.c
1 /* (CC-BY-NC-SA) ROBIN KRENS - ROBIN @ ROBINKRENS.NL
2  * 
3  * $LOG$
4  * 2019/8/14 - ROBIN KRENS      
5  * Initial version 
6  * 
7  * $DESCRIPTION$
8  * Driver for the TM1637 for STM32 boards. 
9  * The TM1637 is (4 digit) 8 segment ledclock peripheral. Communication 
10  * is similar to I2C, but not completely. There is no device address
11  * selecting. Instead, you can directly send a command / write data. 
12  *
13  * Most libraries for this chip that I have seen use 'Bit banging'.
14  * Manually adjusting the clock and data pin (not using the I2C)
15  *
16  * This driver is different and uses STM32 I2C hardware. A problem of the
17  * TM1637 chip, however, is that commands sometimes set bit 1 (LSB). Setting
18  * the LSB will make the STM32 MCU switch to receiver mode. (A better design 
19  * choice for commands would have to keep the LSB reserved). 
20  *
21  * Another thing to note is that this chip uses BIG-ENDIANESS
22  *
23  * */
24
25 #include <stdbool.h>
26 #include <stddef.h>
27 #include <stdint.h>
28
29 #include <sys/mmap.h>
30 #include <sys/robsys.h>
31
32 #include <lib/regfunc.h>
33 #include <lib/string.h>
34 #include <lib/tinyprintf.h>
35
36 #include <drivers/tm1637.h>
37
38 #define TIMEOUT 5000
39
40 #define DOT true
41 #define NODOT false
42
43 #define WRITE_CMD 0x20
44
45 /* STM32F1 microcontrollers do not provide the ability to pull-up SDA and SCL lines. Their
46 GPIOs must be configured as open-drain. So, you have to add two additional resistors to
47 pull-up I2C lines. Something between 4K and 10K is a proven value.
48 */
49
50 void tm1637_init() {
51
52  /* Program the peripheral input clock generate correct timings.
53   * Configure the clock control registers CCR
54   * Configure the rise time register TRIS
55   * Program the I2C_CR1 register to enable the peripheral
56   * Enable GPIOB6 and B7*/
57  
58  rsetbit(RCC_APB1ENR, 21); // enable GPIOB
59  rsetbit(RCC_APB2ENR, 3); // enable I2C
60  rwrite(GPIOB_CRL, 0xEE444444); // open-drain
61  rsetbitsfrom(I2C_CR2, 0, 0x2); // 2 MHz 
62  rwrite(I2C_TRISE, 0x3); // MAX = 1000ns, TPCLK1 = 500ns (+1)
63  rwrite(I2C_CCR, 0x000A); // standard mode
64  rsetbit(I2C_CR1, 0); // enable
65
66 }
67
68 /* Reset the I2C channels and reinitialize */
69 void tm1637_reset() {
70
71  rsetbit(I2C_CR1, 15); // master is busy, reset
72  rsetbit(RCC_APB1RSTR, 21); // reset I2C
73  rwrite(RCC_APB1RSTR, 0x00000000); // clear reset
74  rsetbit(RCC_APB1ENR, 21);
75  rsetbitsfrom(I2C_CR2, 0, 0x2); // 2 MHz 
76  rwrite(I2C_TRISE, 0x3); // MAX = 1000ns, TPCLK1 = 500ns (+1)
77  rwrite(I2C_CCR, 0x000A); // standard mode
78  rsetbit(I2C_CR1, 0); // enable
79
80
81 }
82
83 /* Write value to grid, if no offset is given it writes to the first
84  * grid. Grid 1 an 2 have an additional dot that can be set */
85 int set_grid(uint8_t offset, char value, bool dot) {
86
87         //int (*ack)() = ack_recv; /* Scary function pointer :D */
88
89         if (offset > 3) {
90                 printf("Offset incorrect");
91         }
92
93         // enable dot on display if desired
94         if (dot && (offset == 1 || offset == 2)) {
95                 value = value | 0x1;
96         }
97
98         int start_pos_cmd  = 0x03  | (offset & 0x01) << 7 | (offset & 0x2) << 5 ;
99
100         /* Initiate writing routine */
101         start_condition();
102         rwrite(I2C_DR, WRITE_CMD); 
103         if(!ack_recv())
104                 return -1;
105
106         stop_condition();
107         
108         if(!idle())
109                 return -1;
110
111         /* Set GRID offset */
112         start_condition();
113         rwrite(I2C_DR, start_pos_cmd); 
114         if(!ack_recv())
115                 return -1;
116
117         stop_condition();
118
119         tm1637_reset();
120
121         /* Write value to segments */
122         start_condition();
123         rwrite(I2C_DR, value);
124         if(!ack_recv())
125                 return -1;
126         stop_condition(); 
127
128         tm1637_reset();
129
130         return 0;
131 }
132
133 /* Turns on/off the led display. The degree of brightness can set [0..7]*/
134 int set_display(bool on, uint8_t degree) {
135
136         int disp_cmd = 0x1; // off
137         if (on) {
138                 disp_cmd = 0xF1; // TODO 
139         }       
140
141         start_condition();
142         rwrite(I2C_DR, disp_cmd);
143         if(!ack_recv()) {
144                 printf("Can't switch on display!");
145                 return -1;
146         }
147         stop_condition();
148
149         tm1637_reset();
150
151         return 0;
152
153 }
154
155
156 void tm1637_example() {
157
158 unsigned char display_number[10] = {0xFC, 0x60, 0xDA, 0xF2, 0x66, 0xB6, 0xBE, 0xE0, 0xFE, 0xF6};
159
160
161         char love[4]  = { 0x1C, 0xFC, 0x7C, 0x9E };
162         
163         for (int i = 0; i < 4; i++) {
164                 set_grid(i, love[i], NODOT);
165         }
166
167         set_display(true, 0);
168
169 }
170
171 /* HELPER ROUTINES */
172
173 static void start_condition() {
174         rsetbit(I2C_CR1, 8); //start bit
175 }
176
177 static void stop_condition() {
178         rsetbit(I2C_CR1, 9); //stop bit
179 }
180
181 /* Wait for an acknowledge from the peripheral */
182 int ack_recv() {
183         int cnt = 0;
184         while(!(*I2C_SR1 & 0x2)) {
185                 cnt++;
186                 if (cnt > TIMEOUT) {
187                         printf("Error: no ack (addr bit) received \n");
188                         return 0;
189                 }
190         }
191         uint32_t a = *I2C_SR2; // need to read SR2 register!
192         return 1;
193
194 }
195
196 /* Similar, but SR2 register is not read */
197 int ack10_recv() {
198         int cnt = 0;
199         while(!(*I2C_SR1 & 0x8)) {
200                 cnt++;
201                 if (cnt > TIMEOUT) {
202                         printf("Error: no ack (addr10 bit) received \n");
203                         return 0;
204                 }
205         }
206         return 1;
207
208 }
209
210 /* Check if lines are idle (i.e. stop condition finished)*/
211 int idle() {
212         int cnt = 0;
213         while(*I2C_SR2 & 0x2) {
214                 cnt++;
215                 if (cnt > TIMEOUT) {
216                         printf("Error: busy state\n");
217                         return 0;
218                 }
219         }
220
221         return 1;
222 }
223
224