DMX RS485 Interface

After having configured a Color Kinetics RGB LED driver I need a way to generate a DMX stream to validate it worked as expected. I searched my project boxes for some RS485 USB interfaces in my workshop, but as usual I had put them in a “special” place. So there was nothing for it but to crack out an Arduino and the soldering iron.

This isn’t the first RS485 interface I’ve built for an Arduino, but if you haven’t done it for DMX then I suggest reading this article on Instructables (click) that explains why the standard RS485 shields available are not suitable. However this interface (click) is one that I have found to work, just note that the TX and RX should be Pins 1 & 0 respectively and the enable input (ENB) should be connected to Pin 2.

I like to use the Freetronics Basic Protoshields when building with Arduino’s they are different around the connectors and seem to have more clearance. The photo of the interface (right) was taken just prior to the RS485 safety resistors being mounted across the output of the RS485 bus.

I’d caution anyone against building RS485 interfaces without these safety resistors. With new RS485 receivers you can get away with out using them, but with older receivers they are mandatory. In a nutshell the safety resistors ensure there is always a 200mV differential across the receiver input that ensures the output of the receiver sits in the “UART idle” state. Otherwise what you find is the receiver will either not toggle it’s output or oscillate, in either case it doesn’t work. You just never know if the device you’re talking too has an old or new receiver, so it’s safer (boom boom) to have them than to not. YMMV.

Once the basic interface was built I then realised that this wasn’t going to work with your standard Arduino Uno. You can’t share the UART with both the USB programming and serial debug interface with the RS485 driver without their being bus contention problems, steering resistors can be used with varying success. So the simple solution to this problem was to switch to a Arduino Leonardo, since they have both an on-chip USB interface for debugging and programming and a separate UART connected to pins 0 and 1. This was not without it’s own problems, so much so there is separate blog post to deal with these (click).

So all that was missing was a DMX library. Now this is something of a mine field, there are so many DMX libraries it got confusing finding the right one. After a lot of searching, downloading and fiddling I settled on the “DMX Library for Arduino” written by William van der Meeren (click). What set this library apart was it would transmit a full DMX 512 byte frame every 26ms from a much smaller buffer held in RAM that was non-blocking. It also has a mechanism to signal to the main loop when it had finished a full frame (click) allowing the firmware to quickly update the DMX channels and then allow it to carry on. This meant that colour washes could be kept atomic and smooth.

Below is one of the test programs that I wrote using the DMX library to test a few ideas on what may be possible with this interface. I have also included the PlatformIO config file that includes the necessary build flags;

#include <Arduino.h>
#include "Conceptinetics.h"
#include "math.h"

#define debug_output_enable()	Serial.begin(115200); while (!Serial){}
#define debug_output_f(...)    	Serial.print(F(__VA_ARGS__))
#define debug_msg_f(...) 	Serial.println(F(__VA_ARGS__))
#define debug_output(...)      	Serial.print(__VA_ARGS__)
#define debug_msg(...) 	      	Serial.println(__VA_ARGS__)

// structure to hold RGB colour information
typedef struct
{
  uint8_t red;
  uint8_t green;
  uint8_t blue;
} mycolour_t;

// DMX addressing information
const uint8_t DMX_DEVICES = 6;
const uint8_t DMX_CHANNELS_PER_DEVICE = 3;
const uint8_t DMX_CHANNELS_MIN = 1;
const uint8_t DMX_CHANNELS_MAX = (DMX_DEVICES*DMX_CHANNELS_PER_DEVICE);
const uint8_t DMX_BREAK_USEC = 200;
const uint8_t DMX_TXRX_PIN = 2;       // pin 2 used for RS485 TX/RX pin

// setup DMX master control object
DMX_Master dmx_master( DMX_CHANNELS_MAX , DMX_TXRX_PIN );

// convert sine wave to half wave, clamp value to zero for non-negative
// sine ouptut
float non_negative( float degrees )
{
  if( sin( degrees * DEG_TO_RAD ) < 0.0 )
    return 0.0;
  else
    return degrees;
};

// Initialise our hardware
void setup( void ) 
{
  /* Leonardo has separate USB serial port which is enabled when 
     plugged in.
  */
  debug_output_enable();

  // display something to user when deubgging
  debug_output_f("Mallee DMX Test\r\n Initialising...");

  // setup our DMX master
  dmx_master.enable();
  dmx_master.setChannelRange( DMX_CHANNELS_MIN, DMX_CHANNELS_MAX, 0 );
  dmx_master.setManualBreakMode();
  debug_msg_f(" Done!");

  // all done
  return;
}

// our main loop that will do stuff
void loop( void )
{
  /* Check if the DMX master is waiting for a break to happen, 
     the function below runs every 26ms (44Hz) which is the DMX
     maximum frame rate.
  */
  if( dmx_master.waitingBreak() )
  {      
    // Temp storage for RGB information
    mycolour_t colour;

    // local count variable
    static float angle = 0.0;

    // how much we'll increment our counter per frame
    angle += 0.25;

    //if we overflow wrap back to zero
    if( angle > 360.0 )
      angle = 0.0;

    /* calculate out our RGB values, including our phase offsets;
       not a lookup table in sight !
    */
    colour.red = (uint8_t)( 255 * sin( non_negative(angle + 120.0) * \ 
                            DEG_TO_RAD) );
    colour.green = (uint8_t)( 255 * sin( non_negative(angle + 0.0) * \
                              DEG_TO_RAD) );
    colour.blue = (uint8_t)( 255 * sin( non_negative(angle + 240.0) * \
                             DEG_TO_RAD) );
	
    /* Now we can update all of the attached devices, note that the DMX
       channels and the lamps are configured by the Color Kinetics 
       QuickPro software separately.
    */
    for( uint8_t i = 0; i < DMX_DEVICES; i++ )
    {
      //calculate DMX address for each lamp device
      uint8_t addr = (i * DMX_CHANNELS_PER_DEVICE) + DMX_CHANNELS_MIN;

      //update individual channels are sequentially addressed
      dmx_master.setChannelValue(addr+0, colour.red );
      dmx_master.setChannelValue(addr+1, colour.green );
      dmx_master.setChannelValue(addr+2, colour.blue );
    }    

    // Generate break and continue transmitting the next frame
    dmx_master.breakAndContinue ( DMX_BREAK_USEC );
  }
}
;PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:leonardo]
platform = atmelavr
board = leonardo
framework = arduino
monitor_speed = 115200
build_flags =
  -D USE_DMX_SERIAL_1
  -D __DEBUG_ENABLED__