Saturday, 21 August 2021

DMX to control NeoPixels and an Isolated DMX Shield

I've been playing about with RS485 and DMX for some time now.  I am particularly interested in being able to control NeoPixels via DMX.  The amount of software and experience available for using DMX to control lights and LEDS in particular is vast.  YouTube is full of lighting shows and displays which range from humble but functional to truly epic and vast.  Here are a couple of videos which I thought were amazing:

Submergence - Squidsoup
  

Tom Betgeorge's Excellent 2020 Christmas Light Show

I must admit I watched all 35 minutes of that Christmas Show...It is incredible work.  Any way this is achieved is by controlling lots of LEDS in pre-programmed sequences using DMX control software and LED lights.  DMX is a lighting protocol based upon RS485.  Here is a brief primer on DMX:


Here is another which goes into more technical detail:


So basically lighting technicians control the lights in theatres, discos and concerts using the DMX protocol to control the brightness, colour and state (off/on) of a light or lighting fixture (many lights in one package).

DMX uses RS485 which is a differential serial protocol that electronic equipment has been using for communication since the standard was approved in 1998 and probably earlier than that.


I'm not going to go into too much detail on R485 or DMX for now other than to say it's very useful stuff for electronics and lighting fans to know about.

I have a confession to make: I'd like to be able to control lights...not just one or two but many.  If I had my way every light in my house would be sequenced and remote controlled.  Unfortunately not everyone I live with approves of this plan and as such most of my lights are still just regular lights....(boring!!!).

I have been asked in the past to assist with light shows and recently I have been asked to assist with the technical development of a light show using NeoPixels.

To that end I started to look at the DMX shields available for the Arduino and have a play.  I noticed that most of them have a flaw - they aren't isolated...that means that the electrical supply used to power  the lights is also connected via the return (or ground) to the controller.  In most cases this isn't an issue but it can be in very large lighting displays with lots of wiring and fixtures because of something referred to as a 'ground loop'.


Here are some of the DMX shields designed for use with the Arduino R3:



There is nothing functionally wrong with them and I bought two of the Concineptics variants to play with.  They arrived in good order and worked as described once I understood the code and the jumper settings.  

In order to actually use DMX properly with the software one needs a controller.  Something I was  aware of but not completely in understanding of at all.  I'd always used a hardware controller in the past.

So to really make use of DMX you will either need to make your own controller or buy one off Ebay or Amazon or wherever you might wish to.  I wanted to use an external computer and software to control my lights as I don't own or have access to a hardware DMX controller.

I bought a USB to RS485 dongle off Amazon which works perfectly...It didn't at first but that's because I cannot wire things up for love nor money sometimes.  I then bought a USB to DMX dongle and that worked straight out of the box without any issues whatsoever!  Don't tell anyone but I'm very disappointed in my engineering abilities sometimes...Not being able to wire up simple circuits competently is worrisome for somebody who works in electronic engineering!

Here are the two devices I bought which I heartily recommend:



Once you have a method of controlling DMX lights it's time to either buy some or make some...user's choice.  I was trying to make some so I got hold of some NeoPixel Tape and then connected everything up.

Before doing anything I tend to do some research...Planning is everything so they say....I watched several videos on YouTube and read some blogs on DMX and in particular this one stood out:

Gadget Reboot has an excellent channel and I recommend subscribing!

In it the narrator discusses exactly what I'm trying to achieve and provides all of the equipment required and some code.  The only issue I had was that it didn't work for me!  I didn't have the exact same setup and tried to rush things...never a good situation.  Their setup uses an Arduino mega and some potentiometers, an RS485 breakout module, Buttons and some wire for the connections - for the hardware controller.  For the receiver they use an Arduino uno and an RS485 breakout and some WS2812B LEDS.  I suspect armed with what I know now I could probably get this setup working... 

Here is my setup that worked for me:
 
A DMX to NeoPixel Receiver Setup!

The equipment needed:
1x Arduino R3 or suitable clone:  

1x RS485 breakout PCB:  
Suitable connection wire for making the connections between the arduino and the breakouts etc as well as some 4 core 120 Ohm signal cable for the RS485 communications.  For testing any wire would do but once something longer distance or more permanent is required get proper cable.  Belden 9842 is a pretty good option.  At a pinch some old ethernet cable would work...cut the R45 connectors off and use four of the cores...

The actual setup!


You will need to flash the following arduino code onto your Arduino R3:

// Target Hardware:  Arduino Uno

#include <DMXSerial.h>
#include "ws2812.h"                // a specific LED controller that disables interrupts to work better

#define NUM_LEDS 30                // number of RGB LEDs on strip - 3 LEDS in one W2182B9
#define DMXSTART 1                 // first DMX channel
#define DMXLENGTH (NUM_LEDS*3)     // number of DMX channels used (3*30 LEDs)

void setup () {

  DMXSerial.init(DMXProbe);        // initialize DMX bus in manual access mode
  DMXSerial.maxChannel(DMXLENGTH); // "onUpdate" will be called when all new ch data has arrived

  setupNeopixel();                 // setup the LED output hardcoded to pin 12 in ws2812.h

}


void loop() {
  // wait for an incomming DMX packet and write
  // the RGB data for 60 LEDs on the strip
  if (DMXSerial.receive()) {
    updateNeopixel(DMXSerial.getBuffer() + DMXSTART, NUM_LEDS);
  }

}

You will need to download a copy of the DMXSerial Library. It is available here:


Here is the source for ws2812.h - Cut and paste it into a text file called ws2812.h and store it in the same directory as your arduino sketch.

// neopixel.h


/*
  The Neopixel driving routines are taken from the article and sketch from bigjosh
  http://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
  where the interrupt cli() and sei() are included in the sendBit function.
  At the sources from his github this is not the case but it's important for the usage with DMXSerial library.
  (see https://github.com/bigjosh/SimpleNeoPixelDemo )
  These routines fit very good to the DMXSerial implementation because they switch on and off the
  Interrupt
  On DMX usual channels are used in the red then green then blue order.
  Neopixel wants colors in green then red then blue order so the 2 channels are switched.
*/

// ----- global defines from josh: -----

// These values are for the pin that connects to the Data Input pin on the LED strip. They correspond to...

#define PIXEL_PORT  PORTB  // Port of the pin the pixels are connected to
#define PIXEL_DDR   DDRB   // Port of the pin the pixels are connected to
#define PIXEL_BIT   4      // Bit of the pin the pixels are connected to

// This re3sults in the following Arduino Pins:
// Arduino Yun:     Digital Pin 8
// DueMilinove/UNO: Digital Pin 12
// Arduino Mega     PWM Pin 4

// You'll need to look up the port/bit combination for other boards.
// Note that you could also include the DigitalWriteFast header file to not need to to this lookup.

// These are the timing constraints taken mostly from the WS2812 datasheets
// These are chosen to be conservative and avoid problems rather than for maximum throughput

#define T1H  900    // Width of a 1 bit in ns
#define T1L  600    // Width of a 1 bit in ns

#define T0H  400    // Width of a 0 bit in ns
#define T0L  900    // Width of a 0 bit in ns

#define RES 6000    // Width of the low gap between bits to cause a frame to latch

// Here are some convience defines for using nanoseconds specs to generate actual CPU delays

#define NS_PER_SEC (1000000000L)          // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives

#define CYCLES_PER_SEC (F_CPU)

#define NS_PER_CYCLE ( NS_PER_SEC / CYCLES_PER_SEC )

#define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE )

#define DELAY_CYCLES(n) ( ((n)>0) ? __builtin_avr_delay_cycles( n ) :  __builtin_avr_delay_cycles( 0 ) )  // Make sure we never have a delay less than zero

// Low level function with mixed in assembler code.

// Actually send a bit to the string. We turn off optimizations to make sure the compile does
// not reorder things and make it so the delay happens in the wrong place.
inline void sendBit( bool bitVal )
{
  if (bitVal) {        // 0 bit
    asm volatile (
      "sbi %[port], %[bit] \n\t"        // Set the output bit
      ".rept %[onCycles] \n\t"                                // Execute NOPs to delay exactly the specified number of cycles
      "nop \n\t"
      ".endr \n\t"
      "cbi %[port], %[bit] \n\t"                              // Clear the output bit
      ".rept %[offCycles] \n\t"                               // Execute NOPs to delay exactly the specified number of cycles
      "nop \n\t"
      ".endr \n\t"
      ::
      [port]    "I" (_SFR_IO_ADDR(PIXEL_PORT)),
      [bit]   "I" (PIXEL_BIT),
      [onCycles]  "I" (NS_TO_CYCLES(T1H) - 2),    // 1-bit width less overhead  for the actual bit setting, note that this delay could be longer and everything would still work
      [offCycles]   "I" (NS_TO_CYCLES(T1L) - 2)     // Minimum interbit delay. Note that we probably don't need this at all since the loop overhead will be enough, but here for correctness
    );
  
  } else {          // 1 bit
    // **************************************************************************
    // This line is really the only tight goldilocks timing in the whole program!
    // **************************************************************************
    asm volatile (
      "sbi %[port], %[bit] \n\t"        // Set the output bit
      ".rept %[onCycles] \n\t"        // Now timing actually matters. The 0-bit must be long enough to be detected but not too long or it will be a 1-bit
      "nop \n\t"                                              // Execute NOPs to delay exactly the specified number of cycles
      ".endr \n\t"
      "cbi %[port], %[bit] \n\t"                              // Clear the output bit
      ".rept %[offCycles] \n\t"                               // Execute NOPs to delay exactly the specified number of cycles
      "nop \n\t"
      ".endr \n\t"
      ::
      [port]    "I" (_SFR_IO_ADDR(PIXEL_PORT)),
      [bit]   "I" (PIXEL_BIT),
      [onCycles]  "I" (NS_TO_CYCLES(T0H) - 2),
      [offCycles] "I" (NS_TO_CYCLES(T0L) - 2)
    );
  } // if
  
  // Note that the inter-bit gap can be as long as you want as long as it doesn't exceed the 5us reset timeout (which is A long time)
  // Here I have been generous and not tried to squeeze the gap tight but instead erred on the side of lots of extra time.
  // This has thenice side effect of avoid glitches on very long strings becuase
} // sendBit()

// Neopixel wants bit in highest-to-lowest order
// so send highest bit (bit #7 in an 8-bit byte since they start at 0)
inline void sendByte(uint8_t byte)
{
  for (uint8_t bit = 0; bit < 8; bit++) {
    sendBit(byte & 0x80);
    byte <<=
        1; // and then shift left so bit 6 moves into 7, 5 moves into 6, etc
  } // for
} // sendByte()

/*
  The following three functions are the public API:
  ledSetup() - set up the pin that is connected to the string. Call once at the begining of the program.
  sendPixel( r g , b ) - send a single pixel to the string. Call this once for each pixel in a frame.
  show() - show the recently sent pixel on the LEDs . Call once per frame.
*/

// Set the specified pin up as digital out

void sendPixel(uint8_t r, uint8_t g, uint8_t b)  {
  sendByte(g);          // Neopixel wants colors in green then red then blue order
  sendByte(r);
  sendByte(b);
} // sendPixel


// ----- defines and routines from josh - End -----

void setupNeopixel() {
  bitSet( PIXEL_DDR , PIXEL_BIT );
} // setupNeopixel()


// read data from the DMX buffer (RGB) and send it to the neopixels...
void updateNeopixel(uint8_t *ptr, uint8_t pixels) {
  uint8_t  r, g, b;

  // no interrupt is welcome.
  cli();

  for (int p = 0; p < pixels; p++ ) {
    r = *ptr++;
    g = *ptr++;
    b = *ptr++;
    // send to Neopixels
    // sendPixel(r, g , b);
    sendPixel(r >> 2, g >> 2, b >> 2);
  } // for

  // interrupt may come.
  sei();

  // Just wait long enough without sending any bots to cause the pixels to latch and display the last sent frame
  _delay_us((RES / 1000UL) + 1);
} // updateNeopixel()

// End

Once you have everything setup you will need to download some DMX software.  There is a great deal to choose from.  Some of it is very complicated and expensive and some are open source, still complicated but free for fair use.

I chose to use Q Light Controller Plus.  I have heard good things about Xlight and JinX.  

You can down load Q Light Controller Plus from here:


Once installed on the operating system of your choice, load up the software:

You will be presented with the following screen:


Click on the Inputs/Outputs button in the bottom middle of the screen.


Ensure that DMX USB is selected and that it relates to the USB Dongle.  It was already selected for me.

Next it is time to add a fixture.  Click on the fixtures icon in the bottom left corner.  You will be presented with the following screen:


Click on the green '+' Icon in the top left corner to add a DMX controlled lighting fixture.


I called mine 'LED_strip' but yours can be anything you wish.  I chose 90 channels as that is what was set in the code and I have 30 NeoPixels on my strip.  Each colour = one channel.  I noticed that Q Light Plus can only control 99 channels in one universe...Start from address 1 as there is only one fixture and therefore quantity = 1 and address gap = 0.

Anyway...Click OK and then Click on the Simple Desk button in the bottom middle of the screen.  The following window will be presented:


Now we are getting somewhere!  If you look at the display it looks quite like an audio mixing desk and in a way it is...just for lights...If you increase the slider on channel 1 you should see your first LED light up and it will be RED.  If you were to increase channel 2 It would light up up the GREEN LED in the first NeoPixel.  Channel 3 = BLUE LED etc.  The big red slider at the left hand side of the screen controls the master brightness.

Here is what I did to quickly test things:


It gives the following responses on the LED strip:


 That is about as much as I currently know on how to control Q Light Plus!  There are a million tutorials available however and a very active forum.  Go check them out!

I also had a quick play with JinX which can be downloaded from here:


I'm not going to go into how it works as the manual is OK but here is a quick video showing it in action:


That's all for now.  The next post will be about how I designed an isolated DMX shield and go into how the code works and probably a bit more on how to use DMX software.  I need to research more into the software and setting up scenes etc.

Take care everyone - Langster!