Check up DMX timeout, remember the FIXME.
[dmxpainter.git] / src / dmx.c
index 4b66655..1260426 100644 (file)
--- a/src/dmx.c
+++ b/src/dmx.c
 
 #include "mcu.h"
 
-#include "sched.h"
+#include "buf.h"
+
+/*********************************************************************/
+/* Declaration of private global variables.                          */
+
+/**
+ * The DMX parser is driven by a state machine using these states.
+ */
+enum state {
+  STATE_IDLE,
+  STATE_SYNC,
+  STATE_WAIT,
+  STATE_RECV,
+  STATE_STOR
+};
+/**
+ * The current state of the DMX state machine.
+ */
+#ifndef REG_DMX_STATE
+  static volatile enum state state_;
+#else
+  #define state_ REG_DMX_STATE
+#endif
 
-#if 0
+/**
+ * Index of current DMX frame (between 0 and 512).
+ */
+static volatile int16_t index_;
 
-uint8_t g_frame_count;
-uint8_t g_dmx_state;
-#define STATE_IDLE 0
-#define STATE_WAIT 1
-#define STATE_MARK 2
+/**
+ * Current timer start value in case we want to reset.
+ */
+static volatile uint8_t timer_;
 
-void dmx_start(void)
-{
-  g_dmx_state   = STATE_WAIT;
-  g_frame_count = 0;
-}
+/**
+ * Error flag, set on error, cleared after next start byte.
+ */
+static volatile uint8_t error_;
 
 
+/*********************************************************************/
+/* Declaration of private functions.                                 */
 
-sched_res_t wait_for_mark(void)
-{
-  if (g_frame_count < 2) return SCHED_RE;
-  
-  return SCHED_OK;
-}
+static void enable_timer(int8_t us);
+static void disable_timer(void);
+static void reset_timer(void);
 
-typedef void (*state_func_t)(void);
-state_func_t g_int_handler;
-state_func_t g_sch_handler;
-uint8_t g_data;
+static void enable_trigger(void);
+static void disable_trigger(void);
 
-inline void set_state(state_func_t i, state_func_t s)
-{
-  g_int_handler = i;
-  g_sch_handler = s;
-}
+static void enable_usart(void);
+static void disable_usart(void);
 
 
-// http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
-// http://blogs.sun.com/nike/entry/fast_interpreter_using_gcc_s
-// http://pramode.net/2006/01/18/stackless-protothreads-using-computed-gotos/
-// http://www.sics.se/~adam/pt/
-// http://www.reddit.com/r/programming/comments/6ltfo/squirrelfish_webkits_new_javascript_interpreter/c047sus
+/*********************************************************************/
+/* Declaration of private constants.                                 */
 
-enum dmx_state {
-  state_idle_wait,
-  state_reset_sync,
-  state_reset_wait,
-  state_mark_sync,
-  state_mark_wait,
-  state_nop
-};
-enum dmx_state g_state;
+#define DMX_RESET_TIME   88
+#define DMX_BIT_TIME     4
+#define DMX_CHAR_TIME    (DMX_BIT_TIME * (8 + 3))
+
+// FIXME, this is not enough.
+#define DMX_CHAR_TIMEOUT 127 //(DMX_CHAR_TIME * 2)
+#if DMX_CHAR_TIMEOUT > 127
+  #error DMX_CHAR_TIMEOUT out of range
+#endif
 
-#define DJ(a, b) &&state_ ## b
-#define DT(a, b, c, d) state_ ## a: if (d) { g_state = state_ ## b; do c while(0); } return
+/*********************************************************************/
+/* Implementation of public interrupts.                              */
 
-void dmx_int_handler(void)
+void dmx_int_ext_edge(void)
 {
-  static void *jump[] = {
-    DJ(idle_wait, idle_wait),
-    DJ(reset_sync, reset_sync),
-    DJ(reset_wait, reset_wait),
-    DJ(mark_sync,  mark_sync),
-    DJ(mark_wait,  mark_wait),
-    DJ(data_start, data_start),
-    DJ(data_store, nop),
-    &&state_nop };
-  goto *jump[g_state];
-DT(idle_wait,  reset_sync, {}, 1);
-DT(reset_sync, idle_wait,  {}, 1);
-DT(reset_wait, mark_sync,  {}, 1);
-DT(mark_sync,  idle_wait,  {}, 1);
-DT(mark_wait,  data_start, {}, 1);
-DT(data_start, data_store, {}, 1);
-state_nop:
+  switch (state_) {
+    // The line might be pulled low in Idle.
+    case STATE_IDLE: {
+      // Only trigger on fallen edge.
+      if (!pin_is_set(PIN_DMX_RXD)) {
+        // Wait for a Reset of 88 us (or more).
+        enable_timer(DMX_RESET_TIME);
+        state_ = STATE_SYNC;
+      }
+    } break;
+
+    // Got a stray edge while Reset, back to Idle.
+    case STATE_SYNC: goto err;
+
+    // The next rising edge is the Mark.
+    case STATE_WAIT: {
+      // Disable this interrupt...
+      disable_trigger();
+      // ... and start the USART and the timeout.
+      enable_usart();
+      enable_timer(DMX_CHAR_TIMEOUT);
+      state_ = STATE_RECV;
+    } break;
+
+    // Ignore edge in all other states.
+    default: break;
+  }
+  return;
+
+err: {
+    error_ = 1;
+    disable_timer();
+    state_ = STATE_IDLE;
+    return;
+  }
 }
 
-sched_res_t sched_handler(void)
+void dmx_int_timer_ovf(void)
 {
-  static void *jump[] = {
-    DJ(idle_wait,  nop),
-    DJ(reset_sync, reset_sync),
-    DJ(reset_wait, nop),
-    DJ(mark_sync,  mark_sync),
-    DJ(mark_wait,  nop),
-    DJ(data_start, data_start),
-    DJ(data_store, nop),
-    &&state_nop };
-  goto *jump[g_state];
-DT(reset_sync, reset_wait, {}, (g_frame_count >= 22), 1);
-DT(mark_sync,  mark_wait,  {}, (g_frame_count >= 2), 1);
-DT(data_store, data_store, { g_data = (g_data << 1) | pin_get(PIN_DMX); }, 1);
-state_nop:
+  // Disable this interrupt.
+  disable_timer();
+
+  switch (state_) {
+    // Line was low long enough for a Reset.
+    case STATE_SYNC: {
+      // All is fine.
+      state_ = STATE_WAIT;
+    } break;
+
+    // We got a timeout, back to Idle.
+    case STATE_RECV: goto err;
+    case STATE_STOR: goto err;
+
+    // Ignore timer in all other states.
+    default: break;
+  }
+  return;
+
+err: {
+    error_ = 1;
+    disable_usart();
+    enable_trigger();
+    state_ = STATE_IDLE;
+    return;
+  }
 }
 
-
-void dmx_init(void)
+void dmx_int_usart_rxc(void)
 {
-  // Configure as input.
-  pin_in(PIN_DMX);
+  // Read USART data (p146).
+  uint8_t rxd;
+  uint8_t err;
+  // Read Frame Error flag.
+  err = UCSRA & bits_value(FE);
+  // Read data byte (and clear RXC flag).
+  rxd = UDR;
+  // Bail out if start or stop bit was wrong.
+  if (err) {
+    goto err;
+  }
+
+  // Reset timeout.
+  reset_timer();
+
+  switch (state_) {
+    // The first byte is the start byte.
+    case STATE_RECV: {
+      // Bail out if the start byte is not all low.
+      if (rxd != 0x00) {
+        goto err;
+      }
+
+      // Clear error flag.
+      error_ = 0;
+
+      // Switch to data storage.
+      index_ = 0;
+      state_ = STATE_STOR;
+    } break;
+
+    // All following bytes are stored as channel data.
+    case STATE_STOR: {
+      // Write byte to buffer.
+      buf_gs__[index_] = rxd;
+      // Jump out once we received all 512 channels.
+      if (index_ == 511) {
+        goto end;
+      }
+      // Next index.
+      index_++;
+    } break;
+
+    // In all other states we shouldn't trigger anyway.
+    default: break;
+  }
+  return;
+
+err:
+  error_ = 1;
+  // Resume with default end procedure.
+end: {
+    // Either an invalid or the last frame appeared, stop DMX.
+    disable_timer();
+    disable_usart();
+    enable_trigger();
+    state_ = STATE_IDLE;
+    return;
+  }
+}
 
-  g_int_handler = &wait_state;
-  g_sch_handler = NULL;
 
-  // Trigger INT0 on any edge (p67)
-  _BC(MCUCR, _BV(ISC01));
-  _BS(MCUCR, _BV(ISC00));
+/*********************************************************************/
+/* Implementation of public functions.                               */
 
-  sched_put(&sched_handler);
+void dmx_init(void)
+{
+  // Initialize state (register), might be needed (p20) and 
+  // doesn't hurt.
+  state_ = STATE_IDLE;
+
+  // Configure both pins as input.
+  pin_in(PIN_DMX_INT);
+  pin_in(PIN_DMX_RXD);
+
+  // Initialize USART (p156) with
+  // 250kbaud    (UBRR = 3),
+  // 8 data bits (UCSZ = 011),
+  // 2 stop bits (USBS = 1),
+  // no parity   (UPM  = 00).
+  UBRRL = F_CPU / (16 * 250e3) - 1;
+  UBRRH = (0 << URSEL) | 0;
+  UCSRC = (1 << URSEL)
+        | bits_value_indexed(UCSZ, 1)
+        | bits_value_indexed(UCSZ, 0)
+        | bits_value(USBS);
+  // Enable USART RXD interrupt (and clear UCSZ2 and *XEN).
+  UCSRB = bits_value(RXCIE);
+
+  // Enable timer interrupt (p72).
+  bits_on(TIMSK, TOIE0);
+
+  // Trigger INT0 on any edge (ISC0 = 01, p67).
+  bits_on(MCUCR, ISC00);
+  // But explicitly disable it for now.
+  disable_trigger();
 }
 
-#define TIMER_BOTTOM (0xFF - 16 * 4)
-void dmx_count_frame(void)
+void dmx_exec(void)
 {
-  g_frame_count++;
-  mcu_set_timer0_cnt(TIMER_BOTTOM);
+  // Just enable the trigger for the pin.
+  enable_trigger();
 }
 
-
-void start_timer(void)
+void dmx_update(void)
 {
-  // 
-  mcu_set_timer0_cnt(TIMER_BOTTOM);
-  // CS0 = 001: Start timer 0 without prescaler (p72)
-  _BS(TCCR0, _BV(CS00));
+  mcu_debug_set(error_);
 }
 
 
-void dmx_int_enable()
+/*********************************************************************/
+/* Implementation of private functions.                              */
+
+static void enable_timer(int8_t us)
 {
-  // Enable INT0 (p67)
-  _BS(GICR, _BV(INT0));
+  // Prepare timer counter, considering the prescaler.
+  timer_ = 0xFF - (16 / 8) * us;
+  reset_timer();
+
+  // Enable timer, use a frequency prescaler of 8 (p72).
+  bits_mask_on(TCCR0, (1 << CS01));
 }
 
-void dmx_int_disable()
+static void disable_timer(void)
 {
-  // Disable INT0 (p67)
-  _BC(GICR, _BV(INT0));
+  // Disable timer (p72).
+  bits_mask_off(TCCR0, (1 << CS02) | (1 << CS01) | (1 << CS00));
 }
 
+static void reset_timer()
+{
+  TCNT0 = timer_;
+}
 
 
-#else
-void dmx_init(void) {}
+static void enable_trigger(void)
+{
+  // Enable interrupt triggered by edge on pin.
+  bits_on(GICR, INT0);
+}
 
-void dmx_int_timer0_ovf(void)
+static void disable_trigger(void)
 {
+  // Disable interrupt triggered by edge on pin.
+  bits_off(GICR, INT0);
 }
 
-void dmx_int_ext(void)
+static void enable_usart(void)
 {
+
+  // Enable RXD.
+  bits_on(UCSRB, RXEN);
+}
+
+static void disable_usart(void)
+{
+  // Disable RXD.
+  bits_off(UCSRB, RXEN);
 }
-#endif