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