d36d742d0b6a7674accdeba6e3644989b3a9abcb
[dmxpainter.git] / src / tlc.c
1 #include <avr/io.h>
2
3 #include "tlc.h"
4
5 #include "mcu.h"
6
7 #include "buf.h"
8
9
10 /*********************************************************************/
11 /* Declaration of private constants.                                 */
12
13 #define TLC_N_CHANNELS_PER_TLC 16
14 #define TLC_N_TLCS_PER_PAINTER  3
15
16 #define TLC_N_CHANNELS_PER_PAINTER (TLC_N_TLCS_PER_PAINTER * TLC_N_CHANNELS_PER_TLC)
17
18 #define TLC_N_CHANNELS (N_PAINTER * TLC_N_CHANNELS_PER_PAINTER)
19
20
21 /*********************************************************************/
22 /* Declaration of private global variables.                          */
23
24 /**
25  * Flag to indicate that data is currently shifted out.
26  */
27 static volatile uint8_t shifting_;
28
29
30 /*********************************************************************/
31 /* Declaration of private functions.                                 */
32
33 static void send_dc_data(void);
34 static void send_gs_data(void);
35
36 static void clock_xlat(void);
37 static void clock_sclk(void);
38 static void set_blnk_on(void);
39 static void set_blnk_off(void);
40 static void set_vprg_gs_mode(void);
41 static void set_vprg_dc_mode(void);
42
43
44 /*********************************************************************/
45 /* Implementation of public interrupts.                              */
46
47 /**
48  * Handler for Output-Compare-Match interrupt on 16-bit timer:
49  * Syncs on GSCLK to start GS cycle.
50  */
51 void tlc_int_timer1_ocma(void)
52 {
53   // First, disable this interrupt.
54   mcu_int_timer1_ocma_disable();
55
56   // Leave BLNK mode (switch on LEDs and start GS cycle).
57   set_blnk_off();
58 }
59
60 /**
61  * Handler for Output-Compare-Match interrupt on 8-bit timer:
62  * Disables PWM when a full GSCK cycle is done.
63  */
64 void tlc_int_timer2_ocm(void)
65 {
66   // Go into BLNK mode (switch off LEDs and reset GSCLK counter)
67   set_blnk_on();
68
69   // Disable GS cycle timeout timer.
70   mcu_int_timer2_ocm_disable();
71
72   // Wait for next DMX packet.
73   shifting_ = 0;
74 }
75
76
77 /*********************************************************************/
78 /* Implementation of public functions.                               */
79
80 void tlc_init(void)
81 {
82   // We have to use the 16-bit timer for the GSCLK and the 8-bit
83   // timer for the timeout even though it would be better the other
84   // way round:  We need the OC2 pin for SPI and thus can generate 
85   // a PWM on OC1A (A because it can use ICR1 for TOP, p85) only.
86   // Do this setup before enabling the output (p86).
87
88   // Timer 1 is for our GSCLK:  We refresh with a GS cycle of
89   // about 100 Hz (cf. Timer 2), ie. each 10 ms.  For each full 
90   // cycle we needed to clock the PWM 4096 times which would be 
91   // a count of about 39 times between the pules:
92   //   n = 16 MHz / (4096 * 100 Hz) = 39.0625
93   // But we have to consider the time it takes to shift out the data
94   // so we don't have 10 ms for 4096 pulses, so we've got to speed 
95   // up, ie. count less clocks.  The count is adapted on demand later
96   // on, we'll initialize it with a reasonable default.
97   mcu_set_timer1_ic(38);
98   // Duty cycle as short as possible (see COM1A below).
99   mcu_set_timer1_ocma(1);
100   // * CS1   =  001: No prescaler. (p100)
101   // * WGM1  = 1110: Fast PWM, TOP at ICR1 (p78, p89, p98)
102   // * COM1A =   10: Set OC1A at 0, clear at OCM1A (p97)
103   // * COM1B =   00: No output on OC1B (p97)
104   TCCR1B = bits_value_indexed(CS1, 0)
105          | bits_value_indexed(WGM1, 3)
106          | bits_value_indexed(WGM1, 2);
107   TCCR1A = bits_value_indexed(WGM1, 1)
108          | bits_value_indexed(COM1A, 1);
109
110   // Timer 2 is the refresh timer which determines the time one GS
111   // cycle is finished; triggers Output Compare Match.
112   // * AS2  =   0: Use IO-clock (16 MHz) for base frequency (p119)
113   // * CS2  = 111: Use a prescaler of 1024 (p119)
114   // * WGM2 =  00: Normal mode, no PWM, count upwards (p117)
115   // * COM2 =  00: Disable Output on OC2, needed for SPI (p117)
116   TCCR2 = bits_value_indexed(CS2, 2)
117         | bits_value_indexed(CS2, 1)
118         | bits_value_indexed(CS2, 0);
119   // With a prescaler of 1024 this timer counts at 15.625 kHz,
120   // to get a 100 Hz clock we need to count 157 times (~ 99.5 Hz)
121   // and refresh after that (that equals to 4 PWM pulses when
122   // ignoring the shifting time).
123   //   n = (16 MHz / 1024) / 100 Hz = 156.25
124   mcu_set_timer2_ocm(156);
125
126   // All these pins write to the painter.
127   pin_out(PIN_TLC_GSCK);
128   pin_out(PIN_TLC_VPRG);
129   pin_out(PIN_TLC_XLAT);
130   pin_out(PIN_TLC_SCLK);
131   pin_out(PIN_TLC_SIN);
132
133   // This one writes too, but has to be initialized blanked
134   // (ie. LEDs off).  The external pullup took care against
135   // flickering on boot.
136   pin_out(PIN_TLC_BLNK);
137   set_blnk_on();
138
139   // Here we could read the return from the painter.
140   pin_in( PIN_TLC_SRTN);
141 }
142
143 void tlc_exec(void)
144 {
145   // If enabled, shift out DC once.
146   #ifdef TLC_DC_ONCE 
147     mcu_debug_on();
148     send_dc_data();
149     clock_xlat();
150   #endif
151 }
152
153 void tlc_update(void)
154 {
155   // TODO: Make this routine iterative.
156   // TODO: Is it possible to shift while GS cycle is active and 
157   //       XLAT when cycle is done?
158
159   // Don't send anything if PWM is still active.
160   if (shifting_) return;
161   shifting_ = 1;
162
163   // Restart and enable 100 Hz-timeout timer now so
164   // it includes the time we need to shift out data.
165   mcu_set_timer2_cnt(0);
166   mcu_int_timer2_ocm_enable();
167
168   // If not disabled, always shift out DC first.
169   #ifndef TLC_DC_ONCE
170     send_dc_data();
171     clock_xlat();
172   #endif
173
174   // No extra SCLK needed, just shift out all GS data.
175   send_gs_data();
176   clock_xlat();
177
178   // A final SCLK to notify 
179   clock_sclk();
180
181   // Start PWM with next GS pulse and continue in background...
182   mcu_int_timer1_ocma_enable();
183 }
184
185
186 /*********************************************************************/
187 /* Implementation of private functions.                              */
188
189 static void shift8(uint8_t byte)
190 {
191   // Shift out all eight bits.
192   for (uint8_t bit = bits_uint8(1, 0, 0, 0, 0, 0, 0, 0); bit; bit >>= 1) {
193     if (byte & bit) {
194       pin_on(PIN_TLC_SIN);
195     } else {
196       pin_off(PIN_TLC_SIN);
197     }
198     clock_sclk();
199   }
200 }
201
202 static void shift12(uint8_t byte)
203 {
204   // The data in the upper 8 bits.
205   shift8(byte);
206
207   // Plus 4 zero bits (makes a shift by 4).
208   pin_off(PIN_TLC_SIN);
209   for (uint8_t bit = 4; bit; bit--) {
210     clock_sclk();
211   }
212 }
213
214 /*********************************************************************/
215
216 static void send_gs_data(void)
217 {
218   // Set VPRG to GS mode.
219   set_vprg_gs_mode();
220
221   // Because the TLCs are daisy-chained, we have to shift out the RGB data
222   // starting at the end.  Each painter has 3 TLCs (with 16 channels each), 
223   // for the colors red, green, blue.  So we've got to shift out the 16 blue
224   // channels of the last TLC first, then 16 green ones and finally 16 red 
225   // ones.  The last data we shift out is thus the first red of the first
226   // painter.
227   // This will always point to the start of the current painter data, 
228   // starting with the last one.
229   char * painter_gs = buf_gs__
230                     + TLC_N_CHANNELS
231                     - TLC_N_CHANNELS_PER_PAINTER;
232   // Find the current data byte to shift out, starting with the last one.
233   // Its signed so we can determine when we reached the end/start, eight
234   // bit are enough to index 48 channels per painter.
235 #if TLC_N_CHANNELS_PER_PAINTER != 48
236 #error What a weird painter...
237 #endif
238   while (1) {
239     int8_t index = TLC_N_CHANNELS_PER_PAINTER - 1;
240     while (1) {
241       // Shift out current channel.
242       shift12(painter_gs[index]);
243
244       // Skip two colors.
245       index -= 3;
246
247       // If we reached the start, we jump to the next color.
248       if (index < 0) {
249         // Did we just finish the last (ie. red) channel?
250         if (index == -3)
251           break;
252
253         // Jump to end again and skip to next color.
254         index += TLC_N_CHANNELS_PER_PAINTER - 1;
255       }
256     }
257
258     // Did we just finish the last (ie. first) painter?
259     if (painter_gs == buf_gs__)
260       break;
261
262     // Move to next painter.
263     painter_gs -= TLC_N_CHANNELS_PER_PAINTER;
264   }
265 }
266
267 static void send_dc_data(void)
268 {
269   // Set VPRG to DC mode. 
270   set_vprg_dc_mode();
271
272   // All TLCs on all the connected painters will get the same DC value.
273   // That makes it easy to generate the 6-Bit format we need:  We just
274   // create a constant buffer for the packed rgb values, containing four
275   // values for each color.
276   uint8_t dc_out[3][3];
277   for (int8_t rgb = 2; rgb >= 0; rgb--) {
278     uint8_t dc_data = buf_dc__[rgb] & bits_uint8(1, 1, 1, 1, 1, 1, 0, 0);
279     dc_out[rgb][2] = (dc_data << 0) | (dc_data >> 6);
280     dc_out[rgb][1] = (dc_data << 2) | (dc_data >> 4);
281     dc_out[rgb][0] = (dc_data << 4) | (dc_data >> 2);
282   }
283
284   // Now, shift out the dc-data like we do it with the gs-data:  First the
285   // last blue, then green and red of the last painter, until we reach the
286   // first red.
287   int8_t painter = N_PAINTER;
288   do {
289     int8_t rgb = 3 - 1;
290     do {
291       int8_t index = (TLC_N_CHANNELS_PER_TLC / 4) * 3 - 1;
292       do {
293         shift8(dc_out[rgb][index % 3]);
294         index--;
295       } while (index != -1);
296       rgb--;
297     } while (rgb != -1);
298     painter--;
299   } while (painter != 0);
300 }
301
302 /*********************************************************************/
303
304
305 // XLAT pulse to apply data to internal register.
306 static void clock_xlat(void)
307 {
308   pin_on(PIN_TLC_XLAT);
309   pin_off(PIN_TLC_XLAT);
310 }
311
312 // SCLK pulse to clock in serial data from SIN.
313 static void clock_sclk(void)
314 {
315   pin_on(PIN_TLC_SCLK);
316   pin_off(PIN_TLC_SCLK);
317 }
318
319 static void set_blnk_on(void)
320 {
321   pin_on(PIN_TLC_BLNK);
322 }
323
324 static void set_blnk_off(void)
325 {
326   pin_off(PIN_TLC_BLNK);
327 }
328
329 static void set_vprg_gs_mode(void)
330 {
331   pin_off(PIN_TLC_VPRG);
332 }
333
334 static void set_vprg_dc_mode(void)
335 {
336   pin_on(PIN_TLC_VPRG);
337 }
338
339 /*********************************************************************/