Moar inline docs
[dmxpainter.git] / src / dmx.c
1 #include "dmx.h"
2
3 #include "mcu.h"
4
5 #include "buf.h"
6
7 /*********************************************************************/
8 /* Declaration of private global variables.                          */
9
10 /**
11  * The DMX parser is driven by a state machine using these states.
12  */
13 enum state {
14   STATE_IDLE,
15   STATE_SYNC,
16   STATE_WAIT,
17   STATE_RECV,
18   STATE_STOR
19 };
20 /**
21  * The current state of the DMX state machine.
22  */
23 #ifndef REG_DMX_STATE
24   static volatile enum state state_;
25 #else
26   #define state_ REG_DMX_STATE
27 #endif
28
29 /**
30  * Index of current DMX frame (between 0 and 512).
31  */
32 static volatile int16_t index_;
33
34 /**
35  * Current timer start value in case we want to reset.
36  */
37 static volatile uint8_t timer_;
38
39 /**
40  * Error flag, set on error, cleared after next start byte.
41  */
42 static volatile uint8_t error_;
43
44
45 /*********************************************************************/
46 /* Declaration of private functions.                                 */
47
48 static void enable_timer(int8_t us);
49 static void disable_timer(void);
50 static void reset_timer(void);
51
52 static void enable_trigger(void);
53 static void disable_trigger(void);
54
55 static void enable_usart(void);
56 static void disable_usart(void);
57
58
59 /*********************************************************************/
60 /* Declaration of private constants.                                 */
61
62 #define DMX_RESET_TIME   88
63 #define DMX_BIT_TIME     4
64 #define DMX_CHAR_TIME    (DMX_BIT_TIME * (8 + 3))
65
66 // FIXME, this is not enough.
67 #define DMX_CHAR_TIMEOUT 127 //(DMX_CHAR_TIME * 2)
68 #if DMX_CHAR_TIMEOUT > 127
69   #error DMX_CHAR_TIMEOUT out of range
70 #endif
71
72 /*********************************************************************/
73 /* Implementation of public interrupts.                              */
74
75 /**
76  * ISR to handle an external interrupt (any edge) and act as an 
77  * input to the state machine.
78  */
79 void dmx_int_ext_edge(void)
80 {
81   switch (state_) {
82     // The line might be pulled low in Idle.
83     case STATE_IDLE: {
84       // Only trigger on fallen edge.
85       if (!pin_is_set(PIN_DMX_RXD)) {
86         // Wait for a Reset of 88 us (or more).
87         enable_timer(DMX_RESET_TIME);
88         state_ = STATE_SYNC;
89       }
90     } break;
91
92     // Got a stray edge while Reset, back to Idle.
93     case STATE_SYNC: goto err;
94
95     // The next rising edge is the Mark.
96     case STATE_WAIT: {
97       // Disable this interrupt...
98       disable_trigger();
99       // ... and start the USART and the timeout.
100       enable_usart();
101       enable_timer(DMX_CHAR_TIMEOUT);
102       state_ = STATE_RECV;
103     } break;
104
105     // Ignore edge in all other states.
106     default: break;
107   }
108   return;
109
110 err: {
111     error_ = 1;
112     disable_timer();
113     state_ = STATE_IDLE;
114     return;
115   }
116 }
117
118 /**
119  * ISR to handle an overflow of a timer and act as an 
120  * input to the state machine.
121  */
122 void dmx_int_timer_ovf(void)
123 {
124   // Disable this interrupt.
125   disable_timer();
126
127   switch (state_) {
128     // Line was low long enough for a Reset.
129     case STATE_SYNC: {
130       // All is fine.
131       state_ = STATE_WAIT;
132     } break;
133
134     // We got a timeout, back to Idle.
135     case STATE_RECV: goto err;
136     case STATE_STOR: goto err;
137
138     // Ignore timer in all other states.
139     default: break;
140   }
141   return;
142
143 err: {
144     error_ = 1;
145     disable_usart();
146     enable_trigger();
147     state_ = STATE_IDLE;
148     return;
149   }
150 }
151
152 /**
153  * ISR to handle incoming serial data and act both as an 
154  * input to the state machine and store the new data.
155  */
156 void dmx_int_usart_rxc(void)
157 {
158   // Read USART data (p146).
159   uint8_t rxd;
160   uint8_t err;
161   // Read Frame Error flag.
162   err = UCSRA & bits_value(FE);
163   // Read data byte (and clear RXC flag).
164   rxd = UDR;
165   // Bail out if start or stop bit was wrong.
166   if (err) {
167     goto err;
168   }
169
170   // Reset timeout.
171   reset_timer();
172
173   switch (state_) {
174     // The first byte is the start byte.
175     case STATE_RECV: {
176       // Bail out if the start byte is not all low.
177       if (rxd != 0x00) {
178         goto err;
179       }
180
181       // Clear error flag.
182       error_ = 0;
183
184       // Switch to data storage.
185       index_ = 0;
186       state_ = STATE_STOR;
187     } break;
188
189     // All following bytes are stored as channel data.
190     case STATE_STOR: {
191       // Write byte to buffer.
192       buf_gs__[index_] = rxd;
193       // Jump out once we received all 512 channels.
194       if (index_ == 511) {
195         goto end;
196       }
197       // Next index.
198       index_++;
199     } break;
200
201     // In all other states we shouldn't trigger anyway.
202     default: break;
203   }
204   return;
205
206 err:
207   error_ = 1;
208   // Resume with default end procedure.
209 end: {
210     // Either an invalid or the last frame appeared, stop DMX.
211     disable_timer();
212     disable_usart();
213     enable_trigger();
214     state_ = STATE_IDLE;
215     return;
216   }
217 }
218
219
220 /*********************************************************************/
221 /* Implementation of public functions.                               */
222
223 /**
224  * Initializes the peripherals (pins, timer, USART) used by the
225  * DMX parser.
226  */
227 void dmx_init(void)
228 {
229   // Initialize state (register), might be needed (p20) and 
230   // doesn't hurt.
231   state_ = STATE_IDLE;
232
233   // Configure both pins as input.
234   pin_in(PIN_DMX_INT);
235   pin_in(PIN_DMX_RXD);
236
237   // Initialize USART (p156) with
238   // 250kbaud    (UBRR = 3),
239   // 8 data bits (UCSZ = 011),
240   // 2 stop bits (USBS = 1),
241   // no parity   (UPM  = 00).
242   UBRRL = F_CPU / (16 * 250e3) - 1;
243   UBRRH = (0 << URSEL) | 0;
244   UCSRC = (1 << URSEL)
245         | bits_value_indexed(UCSZ, 1)
246         | bits_value_indexed(UCSZ, 0)
247         | bits_value(USBS);
248   // Enable USART RXD interrupt (and clear UCSZ2 and *XEN).
249   UCSRB = bits_value(RXCIE);
250
251   // Enable timer interrupt (p72).
252   bits_on(TIMSK, TOIE0);
253
254   // Trigger INT0 on any edge (ISC0 = 01, p67).
255   bits_on(MCUCR, ISC00);
256   // But explicitly disable it for now.
257   disable_trigger();
258 }
259
260 /**
261  * Starts parsing of DMX data by enabling the external interrupt.
262  */
263 void dmx_exec(void)
264 {
265   // Just enable the trigger for the pin.
266   enable_trigger();
267 }
268
269 /**
270  * Called by the main loop
271  */
272 void dmx_update(void)
273 {
274   mcu_debug_set(error_);
275 }
276
277
278 /*********************************************************************/
279 /* Implementation of private functions.                              */
280
281 static void enable_timer(int8_t us)
282 {
283   // Prepare timer counter, considering the prescaler.
284   timer_ = 0xFF - (16 / 8) * us;
285   reset_timer();
286
287   // Enable timer, use a frequency prescaler of 8 (p72).
288   bits_mask_on(TCCR0, (1 << CS01));
289 }
290
291 static void disable_timer(void)
292 {
293   // Disable timer (p72).
294   bits_mask_off(TCCR0, (1 << CS02) | (1 << CS01) | (1 << CS00));
295 }
296
297 static void reset_timer()
298 {
299   TCNT0 = timer_;
300 }
301
302
303 static void enable_trigger(void)
304 {
305   // Enable interrupt triggered by edge on pin.
306   bits_on(GICR, INT0);
307 }
308
309 static void disable_trigger(void)
310 {
311   // Disable interrupt triggered by edge on pin.
312   bits_off(GICR, INT0);
313 }
314
315 static void enable_usart(void)
316 {
317
318   // Enable RXD.
319   bits_on(UCSRB, RXEN);
320 }
321
322 static void disable_usart(void)
323 {
324   // Disable RXD.
325   bits_off(UCSRB, RXEN);
326 }