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