ArDMX - Simple DMX Receiver Code

ArDMX provides a simple snippet of C code that you can embed into your own Arduino Project files (no need for additional C/CPP files altought this setup is also supported).

It basically consists of some lines of code in the setup routine, and an Interrupt Service Routine, which is called when new data arrives at the serial port of the Arduino.

The code is heavily based upon a app note by Henn (Henne's Site, German) and was simply adapted for arduino platforms.

Introduction + Drawbacks

As with the other approach by Max Pierson here, there are also two major drawbacks with this one, especially for the unexperienced Arduino/AVR programmer:

1. The USART register and ISR names are absolutely dependent on the hardware that is being used!

This means that the names of the registers and the name of the ISR vector used may differ if you use other hardware or arduino compatible clones. In some cases, it is sufficient to swap the digit contained within the register names (0 for USART0, 1 for USART1,...). When in doubt, the datasheets of the processor in use may help you out. Also, a look into the HardwareSerial.cpp file within your used platform directory can help to gain more information about the ISRs and registers being used. The code below is built to use the first (and only on most platforms) UART.

2. A core file needs to be modified

To avoid a redefinition error upon compile, you have to slightly modify your HardwareSerial.cpp when compiling the DMX receiver code. Don't forget to save the original one before! Now just do a search for "ISR(USARTn_RX_vect)" where n should be replaced by the number of the USART you actually use, e.g. "USART0_RX_vect". When you found the definition, just comment the whole block between the {..} out (it should be about 5 lines long or so).

Besides this drawbacks, you'll also need to have a suitable DMX shield or build a simple DMX receiver circuit on your own. These are mostly based on a 75176 or MAX485 integrated circuit, which is basically a transceiver for RS-485 lines. The RE pin of the MAX485 or 75176 needs to be connected to the RX pin of your arduino, and the IC needs to be powered correctly and have its "Receive Enable" pin set to LOW/GND.

The Code

Variables and #defines

Following variables and defines need to be added to your sketch:

  1. #define RX_STATUS_PIN 2
  3. volatile uint8_t  DmxRxField[8]; //array of DMX vals (raw)
  4. volatile uint16_t DmxAddress;    //start address
  6. enum {IDLE, BREAK, STARTB, STARTADR}; //DMX states
  8. volatile uint8_t gDmxState;

Additions to the setup() function

The following code needs to be added to the setup function to configure the serial port correctly and set up serial communication:

  1. Serial.begin(250000); //Enable serial reception with a 250k rate
  2. pinMode(RX_STATUS_PIN, OUTPUT); //RX STATUS LED pin, blinks on incoming DMX data
  3. gDmxState= IDLE; // initial state
  4. DmxAddress = 3; // The desired DMX Start Address

The main interrupt service routine (ISR)

The interrupt service routine gets called whenever new data arrives at the USART pin. With the high data rate DMX runs at, this will likely happen very often, so it is crucial to just do the most urgent stuff within that routine, and leave the rest for the main loop.

  1. ISR(USART0_RX_vect) {
  2.         digitalWrite(rxStatusPin, HIGH);
  3.         static  uint16_t DmxCount;
  4.         uint8_t  USARTstate= UCSR1A;    //get state before data!
  5.         uint8_t  DmxByte   = UDR1;          //get data
  6.         uint8_t  DmxState  = gDmxState; //just load once from SRAM to increase speed
  8.         if (USARTstate &(1<<FE1))               //check for break
  9.         {
  10.                 DmxCount =  DmxAddress;         //reset channel counter (count channels before start address)
  11.                 gDmxState= BREAK;
  12.         }
  14.         else if (DmxState == BREAK)
  15.         {
  16.                 if (DmxByte == 0) gDmxState= STARTB;  //normal start code detected
  17.                 else                      gDmxState= IDLE;
  18.         }
  20.         else if (DmxState == STARTB)
  21.         {
  22.                 if (--DmxCount == 0)    //start address reached?
  23.                 {
  24.                         DmxCount= 1;            //set up counter for required channels
  25.                         DmxRxField[0]= DmxByte; //get 1st DMX channel of device
  26.                         gDmxState= STARTADR;
  27.                 }
  28.         }
  30.         else if (DmxState == STARTADR)
  31.         {
  32.                 DmxRxField[DmxCount++]= DmxByte;        //get channel
  33.                 if (DmxCount >= sizeof(DmxRxField)) //all ch received?
  34.                 {
  35.                         gDmxState= IDLE;        //wait for next break
  36.                 }
  37.         }
  38.         digitalWrite(rxStatusPin, LOW);                                                
  39. }

Working with the received DMX values

If all of the above is working correctly, and the RX status LED is blinking happily, you can do the main work with your received DMX values. They are stored in the array called "DmxRxField" and can be used in the main loop, as the ISR does its work on its own and does not need to be called externally.

A simple example for fading a LED according to the DMX value on the first received address might look like this:

  1. void loop() {
  2.     analogWrite(14, DmxRxField[0]);
  3. }