14f1f459e420966277b8c139277d570795eebb52
[dmxpainter.git] / src / tlc.c
1 #include <avr/io.h>\r
2 \r
3 #include "tlc.h"\r
4 \r
5 #include "mcu.h"\r
6 #include "pins.h"\r
7 \r
8 #include "sched.h"\r
9 #include "buffer.h"\r
10 \r
11 /////////////////////////////////////////\r
12 \r
13 sched_res_t wait_for_data(void);\r
14 \r
15 volatile uint8_t g_data_available;\r
16 \r
17 /////////////////////////////////////////\r
18 \r
19 volatile uint8_t g_data_wait;\r
20 void tlc_wait_for_data()\r
21 {\r
22   if (!g_data_wait) return;\r
23   g_data_wait = wait_for_data() != SCHED_OK;\r
24 }\r
25 void set_wait_for_data(void)\r
26 {\r
27   //sched_put(&wait_for_data);\r
28   g_data_wait = 1;\r
29 }\r
30 \r
31 /////////////////////////////////////////\r
32 \r
33 // XLAT pulse to apply data to internal register.\r
34 void clock_xlat(void)\r
35 {\r
36   pin_on(PIN_TLC_XLAT);\r
37   pin_off(PIN_TLC_XLAT);\r
38 }\r
39 \r
40 // SCLK pulse to clock in serial data from SIN.\r
41 void clock_sclk(void)\r
42 {\r
43   pin_on(PIN_TLC_SCLK);\r
44   pin_off(PIN_TLC_SCLK);\r
45 }\r
46 \r
47 void set_blnk_on(void)\r
48 {\r
49   pin_on(PIN_TLC_BLNK);\r
50 }\r
51 \r
52 void set_blnk_off(void)\r
53 {\r
54   pin_off(PIN_TLC_BLNK);\r
55 }\r
56 \r
57 void set_vprg_gs_mode(void)\r
58 {\r
59   pin_off(PIN_TLC_VPRG);\r
60 }\r
61 \r
62 void set_vprg_dc_mode(void)\r
63 {\r
64   pin_on(PIN_TLC_VPRG);\r
65 }\r
66 \r
67 /////////////////////////////////////////\r
68 \r
69 void tlc_init(void)\r
70 {\r
71   // Initialize blanked (ie. LEDs off).\r
72   pin_out(PIN_TLC_BLNK);\r
73   pin_on( PIN_TLC_BLNK);\r
74 \r
75   // Timer 1 is for our GSCLK:  We refresh with a GS cycle of\r
76   // about 100 Hz (cf. Timer 2), for each full cycle we need to\r
77   // clock the PWM 4096 times.\r
78   // We need about 38 clocks to get 4096 cycles at 100 Hz.\r
79   mcu_set_timer1_ic(38);\r
80   // 50% duty cycle.\r
81   mcu_set_timer1_ocma(38 / 2);\r
82   // * CS1 = 0001:  No prescaler. (p100)\r
83   // * WGM1 = 1110: Fast PWM, TOP at ICR1\r
84   // * COM1A = 10: Set at 0, clear at Output Compare Match)\r
85   TCCR1B = bits_value(CS10) | bits_value(WGM13) | bits_value(WGM12);\r
86   TCCR1A = bits_value(WGM11) | bits_value(COM1A1);\r
87 \r
88   /* Timer 2: Refresh-Timer */\r
89   // * AS2 = 0: Use IO-clock (16 MHz) for base frequency (p119)\r
90   // * Mode: Normal mode, no PWM, count upwards (WGM21:0 = 00) (p117)\r
91   // * Disable Output on OC2, needed for SPI (COM21:0 = 00) (p117)\r
92   // * Prescaler: 1024 (CS22:0 = 111) => 15625 Hz\r
93   TCCR2 = bits_value(CS22) | bits_value(CS21) | bits_value(CS20);\r
94   // To get a 100 Hz clock we need to count 157 times (~ 99.5 Hz).\r
95   mcu_set_timer2_ocm(156);\r
96 \r
97   // All these pins write to the painter.\r
98   pin_out(PIN_TLC_GSCK);\r
99   pin_out(PIN_TLC_VPRG);\r
100   pin_out(PIN_TLC_XLAT);\r
101   pin_out(PIN_TLC_SCLK);\r
102   pin_out(PIN_TLC_SIN);\r
103 \r
104   // Wait for first DMX packet.\r
105   set_wait_for_data();\r
106 }\r
107 \r
108 void tlc_set_data_done(void)\r
109 {\r
110   g_data_available = 1;\r
111 }\r
112 \r
113 /////////////////////////////////////////\r
114 \r
115 void start_gscycle(void)\r
116 {\r
117   // Start counter with next GS pulse.\r
118   mcu_int_timer1_ocma_enable();\r
119 }\r
120 \r
121 void tlc_int_timer1_ocma(void)\r
122 {\r
123   // First, disable this interrupt.\r
124   mcu_int_timer1_ocma_disable();\r
125 \r
126   // Restart and enable timeout timer.\r
127   mcu_set_timer2_cnt(0);\r
128   mcu_int_timer2_ocm_enable();\r
129 \r
130   // Switch off BLNK.\r
131   set_blnk_off();\r
132 }\r
133 \r
134 void tlc_int_timer2_ocm(void)\r
135 {\r
136   // Go into BLNK mode (switch off LEDs and reset GSCLK counter)\r
137   set_blnk_on();\r
138 \r
139   // Disable GS cycle timeout timer.\r
140   mcu_int_timer2_ocm_disable();\r
141 \r
142   // Wait for next DMX packet.\r
143   set_wait_for_data();\r
144 }\r
145 \r
146 /////////////////////////////////////////\r
147 \r
148 void shift8(uint8_t byte)\r
149 {\r
150   // Shift out all eight bits.\r
151   for (uint8_t bit = bits_uint8(1, 0, 0, 0, 0, 0, 0, 0); bit; bit >>= 1) {\r
152     if (byte & bit) {\r
153       pin_on(PIN_TLC_SIN);\r
154     } else {\r
155       pin_off(PIN_TLC_SIN);\r
156     }\r
157     clock_sclk();\r
158   }\r
159 }\r
160 \r
161 void shift12(uint8_t byte)\r
162 {\r
163   // The data in the upper 8 bits.\r
164   shift8(byte);\r
165 \r
166   // Plus 4 zero bits (makes a shift by 4).\r
167   pin_off(PIN_TLC_SIN);\r
168   for (uint8_t bit = 4; bit; bit--) {\r
169     clock_sclk();\r
170   }\r
171 }\r
172 \r
173 /////////////////////////////////////////\r
174 \r
175 void send_gs_data(void)\r
176 {\r
177   // Set VPRG to GS mode.\r
178   set_vprg_gs_mode();\r
179   // Because the TLCs are daisy-chained, we have to shift out the RGB data\r
180   // starting at the end.  Each painter has 3 TLCs (with 16 channels each), \r
181   // for the colors red, green, blue.  So we've got to shift out the 16 blue\r
182   // channels of the last TLC first, then 16 green ones and finally 16 red \r
183   // ones.  The last data we shift out is thus the first red of the first\r
184   // painter.\r
185   // This will always point to the start of the current painter data, \r
186   // starting with the last one.\r
187   char * painter_gs = gg_buffer_gs\r
188                     + TLC_N_CHANNELS\r
189                     - TLC_N_CHANNELS_PER_PAINTER;\r
190   // Find the current data byte to shift out, starting with the last one.\r
191   // Its signed so we can determine when we reached the end/start, eight\r
192   // bit are enough to index 48 channels per painter.\r
193 #if TLC_N_CHANNELS_PER_PAINTER != 48\r
194 #error What a weird painter...\r
195 #endif\r
196   while (1) {\r
197     int8_t index = TLC_N_CHANNELS_PER_PAINTER - 1;\r
198     while (1) {\r
199       // Shift out current channel.\r
200       shift12(painter_gs[index]);\r
201 \r
202       // Skip two colors.\r
203       index -= 3;\r
204 \r
205       // If we reached the start, we jump to the next color.\r
206       if (index < 0) {\r
207         // Did we just finish the last (ie. red) channel?\r
208         if (index == -3)\r
209           break;\r
210 \r
211         // Jump to end again and skip to next color.\r
212         index += TLC_N_CHANNELS_PER_PAINTER - 1;\r
213       }\r
214     }\r
215 \r
216     // Did we just finish the last (ie. first) painter?\r
217     if (painter_gs == gg_buffer_gs)\r
218       break;\r
219 \r
220     // Move to next painter.\r
221     painter_gs -= TLC_N_CHANNELS_PER_PAINTER;\r
222   }\r
223 }\r
224 \r
225 void send_dc_data(void)\r
226 {\r
227   // Set VPRG to DC mode. \r
228   set_vprg_dc_mode();  \r
229 \r
230   // All TLCs on all the connected painters will get the same DC value.\r
231   // That makes it easy to generate the 6-Bit format we need:  We just\r
232   // create a constant buffer for the packed rgb values, containing four\r
233   // values for each color.\r
234   uint8_t dc_out[3][3];\r
235   for (int8_t rgb = 2; rgb >= 0; rgb--) {\r
236     uint8_t dc_data = gg_buffer_dc[rgb] & bits_uint8(1, 1, 1, 1, 1, 1, 0, 0);\r
237     dc_out[rgb][2] = (dc_data << 0) | (dc_data >> 6);\r
238     dc_out[rgb][1] = (dc_data << 2) | (dc_data >> 4);\r
239     dc_out[rgb][0] = (dc_data << 4) | (dc_data >> 2);\r
240   }\r
241 \r
242   // Now, shift out the dc-data like we do it with the gs-data:  First the\r
243   // last blue, then green and red of the last painter, until we reach the\r
244   // first red.\r
245   int8_t painter = N_PAINTER;\r
246   do {\r
247     int8_t rgb = 3 - 1;\r
248     do {\r
249       int8_t index = (TLC_N_CHANNELS_PER_TLC / 4) * 3 - 1;\r
250       do {\r
251         shift8(dc_out[rgb][index % 3]);\r
252         index--;\r
253       } while (index != -1);\r
254       rgb--;\r
255     } while (rgb != -1);\r
256     painter--;\r
257   } while (painter != 0);\r
258 }\r
259 \r
260 void send_data(void)\r
261 {\r
262   pin_in(PIN_TLC_GSCK);\r
263 \r
264   // Always shift out DC first.\r
265   send_dc_data();\r
266   clock_xlat();\r
267 \r
268   // No extra SCLK needed, just shift out all GS data.\r
269   send_gs_data();\r
270   clock_xlat();\r
271 \r
272   pin_out(PIN_TLC_GSCK);\r
273 }\r
274 \r
275 \r
276 /////////////////////////////////////////\r
277 \r
278 sched_res_t wait_for_data(void)\r
279 {\r
280   if (!g_data_available) return SCHED_RE;\r
281 \r
282   send_data();\r
283   start_gscycle();\r
284 \r
285   // TODO: next data\r
286   buffer_next();\r
287 \r
288   return SCHED_OK;\r
289 }\r
290 \r
291 /////////////////////////////////////////\r