hello-dmx: working basic dmx output using DmxSimple's frame-timing inline assembler code
authorTero Marttila <terom@paivola.fi>
Sun, 06 Apr 2014 18:25:55 +0300
changeset 62 2d68a76322cb
parent 61 a960cf5981f7
child 63 e9b478c817df
hello-dmx: working basic dmx output using DmxSimple's frame-timing inline assembler code
Makefile
dmx.s
docs/dmx.txt
src/dmx_frame.c
src/hello-dmx.c
--- a/Makefile	Sat Apr 05 02:53:17 2014 +0300
+++ b/Makefile	Sun Apr 06 18:25:55 2014 +0300
@@ -1,4 +1,4 @@
-PROG = hello-lkm
+PROG = hello-dmx
 
 ELF = build/src/$(PROG).elf
 HEX = build/src/$(PROG).hex
@@ -8,6 +8,8 @@
 build:
 	mkdir -p build/src
 
+dmx: dmx.hex
+
 ## Compiler
 MCU = atmega328p
 
--- a/dmx.s	Sat Apr 05 02:53:17 2014 +0300
+++ b/dmx.s	Sun Apr 06 18:25:55 2014 +0300
@@ -49,8 +49,8 @@
 
 ; Clock select
 .set DMX_TIMER_CS_STOP = 0b000
-;.set DMX_TIMER_CS = 0b001      ; 1/1
-.set DMX_TIMER_CS = 0b111       ; 1/1024
+.set DMX_TIMER_CS = 0b001      ; 1/1
+;.set DMX_TIMER_CS = 0b111       ; 1/1024
 
 ; Counted value
 .set DMX_TIMER_TOP = DMX_CYCLES     ; number of cycles for baud
@@ -59,7 +59,7 @@
 .set LED_DDR = DDRB
 .set LED_PORT = PORTB
 .set LED_PIN = PINB
-.set LED_BIT = PORTB5
+.set LED_BIT = PORTB0
 
 ;; Set up DMX output
 DMX_Init:
@@ -145,7 +145,7 @@
         sbic    TIFR2, OCF2A     
         rjmp    _dmx_bit_wait   
 
-sbi         LED_PIN, LED_BIT
+;sbi         LED_PORT, LED_BIT
 
     ; Output bit
         ; XXX: ugly bit-testing, can't we do this using something more nifty?
@@ -250,14 +250,14 @@
     ; Init
         rcall       DMX_Init
 
-sbi         LED_PORT, LED_BIT
+cbi         LED_PORT, LED_BIT
 
     ; Send; value
 _main_loop:
         ldi         r17, 255
         rcall       DMX_Flood
 
-;sbi         LED_PORT, LED_BIT
+cbi         LED_PORT, LED_BIT
 
         ; never returns..
         rjmp        _main_loop
--- a/docs/dmx.txt	Sat Apr 05 02:53:17 2014 +0300
+++ b/docs/dmx.txt	Sun Apr 06 18:25:55 2014 +0300
@@ -1,11 +1,18 @@
+
+## Specs:
+
 * 250 kHz clock
     * 250000 bits / s
     * 4µs / bit
 * idle line = high (transmitter/master drives line high)
 
-        
+## Pinout
 
-Packet structure:
+    1       0       GND
+    2       -       B
+    3       +       A
+
+## Protocol
 
 * paket format
     * break
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dmx_frame.c	Sun Apr 06 18:25:55 2014 +0300
@@ -0,0 +1,49 @@
+/*
+ * Per-frame timing-critical bit-banging.
+ *
+ * Shamelessly stolen from
+ *  http://code.google.com/p/tinkerit/source/browse/trunk/DmxSimple/DmxSimple.cpp
+ *
+ *  Copyright (c) 2008-2009 Peter Knight, Tinker.it!
+ *
+ * Consumes approx. 11 * 64 ~= 705 clock cycles
+ * Leaves the line idle (high) on return.
+ */
+void dmx_frame (volatile byte value)
+{
+  uint8_t bitCount, delCount;
+  __asm__ volatile (
+    "cli\n"
+    "ld __tmp_reg__,%a[dmxPort]\n"
+    "and __tmp_reg__,%[outMask]\n"
+    "st %a[dmxPort],__tmp_reg__\n"
+    "ldi %[bitCount],11\n" // 11 bit intervals per transmitted byte
+    "rjmp bitLoop%=\n"     // Delay 2 clock cycles.
+  "bitLoop%=:\n"\
+    "ldi %[delCount],%[delCountVal]\n"
+  "delLoop%=:\n"
+    "nop\n"
+    "dec %[delCount]\n"
+    "brne delLoop%=\n"
+    "ld __tmp_reg__,%a[dmxPort]\n"
+    "and __tmp_reg__,%[outMask]\n"
+    "sec\n"
+    "ror %[value]\n"
+    "brcc sendzero%=\n"
+    "or __tmp_reg__,%[outBit]\n"
+  "sendzero%=:\n"
+    "st %a[dmxPort],__tmp_reg__\n"
+    "dec %[bitCount]\n"
+    "brne bitLoop%=\n"
+    "sei\n"
+    :
+      [bitCount] "=&d" (bitCount),
+      [delCount] "=&d" (delCount)
+    :
+      [dmxPort] "e" (&DMX_PORT),
+      [outMask] "r" (~(1 << DMX_DATA)),
+      [outBit] "r" ((1 << DMX_DATA)),
+      [delCountVal] "M" (F_CPU/1000000-3),
+      [value] "r" (value)
+  );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hello-dmx.c	Sun Apr 06 18:25:55 2014 +0300
@@ -0,0 +1,105 @@
+/*
+ * DMX output.
+ *
+ * See doc/dmx.txt for the RX-485 bus protocol details...
+ */
+
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "stdlib.h"
+
+// debug
+#define DEBUG_DDR   DDRB
+#define DEBUG_PORT  PORTB
+#define DEBUG_LED   0
+
+void led_init ()
+{
+    sbi(&DEBUG_DDR, DEBUG_LED);
+}
+
+void led_on ()
+{
+    sbi(&DEBUG_PORT, DEBUG_LED);
+}
+
+void led_off ()
+{
+    cbi(&DEBUG_PORT, DEBUG_LED);
+}
+
+void led_toggle ()
+{
+    xbi(&DEBUG_PORT, DEBUG_LED);
+}
+
+// DMX
+#define DMX_DDR     DDRB
+#define DMX_PORT    PORTB
+#define DMX_DATA    3       // SPI MOSI
+
+// baud rate: 250k = 4µs / bit
+#define DMX_BAUD    (250 * 1000)
+#define DMX_US      4
+
+// CPU cycles / bit: 64 @ 16Mhz
+#define DMX_CYCLES  (F_CPU / DMX_BAUD)
+
+void dmx_init ()
+{
+    // dmx data out: idle (high)
+    sbi(&DMX_PORT, DMX_DATA);
+    sbi(&DMX_DDR, DMX_DATA);
+}
+
+static inline void dmx_high ()
+{
+    sbi(&DMX_PORT, DMX_DATA);
+}
+
+static inline void dmx_low ()
+{
+    cbi(&DMX_PORT, DMX_DATA);
+}
+
+#define dmx_pause(bits) _delay_us((DMX_US * bits))
+
+static void dmx_break ()
+{
+    // break
+    dmx_low();
+    dmx_pause(22);
+
+    // mark-after-break (MAB)
+    dmx_high();
+    dmx_pause(2);
+}
+
+#include "dmx_frame.c"
+
+static void dmx_packet (byte r, byte g, byte b)
+{
+    dmx_break();
+    dmx_frame(0);
+
+    dmx_frame(0);       // control
+    dmx_frame(r);
+    dmx_frame(g);
+    dmx_frame(b);
+    dmx_frame(0);       // madness
+}
+
+void main ()
+{
+    led_init();
+    dmx_init();
+
+    // dmx
+    while (true) {
+        dmx_packet(0x00, 0x00, 0xff);
+        dmx_pause(100);
+
+        led_toggle();
+    }
+}