Transmitting & Receiving DMX using nRF24L01 radio transceivers

I havent properly tested this, but I have changed the code that takes the RF data and recompiles it into DMX data

  • still "work-in-progress" :slight_smile:
// DMX RECEIVE USES ATMEGA328 or ATMEGA8-16PU at 16mhz
// connection information...
// http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
// use ATMEGA DigitalPin 1 to send DMX data to pin 4 of SN75176 chip
// SN75176: pins 2 & 5 to 0v | pin 3 & 8 to 5v | pin 6 to DMX+ | pin7 to DMX-
// Ensure 3.3v is used to supply NRF24L01 board !!
//
#include <SPI.h>
#include "nRF24L01.h" // library: https://github.com/maniacbug/RF24
#include "RF24.h"
#include <Wire.h>
#include "pins_arduino.h";

RF24 radio(9,10);

const uint64_t pipe = 0xF0F0F0F0E1LL;
#define MAXPAYLOAD 32 // max payload size for nrf24l01
#define DMXout 1      // DMX signal output pin (same as TX pin)
#define ERRout 8      // LED to show signal strength : digitalpin 8 to LED via 470 resistor to 0v
#define MAXGROUPS 17 // 17 groups of 30 channels = 510 channels

unsigned long time;
uint8_t payload[MAXPAYLOAD], DMXData[30*MAXGROUPS];
uint8_t lastStamp, channel=0, i;
unsigned long timer;
uint8_t port_to_output[] = { NOT_A_PORT, NOT_A_PORT, _SFR_IO_ADDR(PORTB), _SFR_IO_ADDR(PORTC), _SFR_IO_ADDR(PORTD) };
uint8_t portNumber, pinMask;

void setup(void)
{
  pinMode(DMXout, OUTPUT);
  digitalWrite(DMXout, HIGH);
  pinMode(ERRout, OUTPUT);
  digitalWrite(ERRout, LOW);
  radio.begin();
  radio.setAutoAck(false);
  radio.setPayloadSize(MAXPAYLOAD); // 9 for 6 byte packet, 12 for 6 byte data + SSD, needs to be 14 to stop failing !!
  radio.setPALevel(RF24_PA_HIGH); // try and set PA to max !   
  radio.setDataRate(RF24_250KBPS); // 250kbps for best tx/rx
  radio.openReadingPipe(1,pipe);
  radio.startListening();
  radio.setChannel(channel);
  portNumber = port_to_output[digitalPinToPort(DMXout)];
  pinMask = digitalPinToBitMask(DMXout);
}

void loop(void)
{
    if ( radio.available() ) {
      radio.read( payload, sizeof(payload) ); // get data packet from radio if available
    }
    if ( payload[1] == (lastStamp+1) ) { digitalWrite(ERRout,1); } // check for incontinuous timestamps, LED HI if good
    else { digitalWrite(ERRout,0); } // if time stamp error set LED low
    lastStamp = payload[1]; // reset last time stamp
    for (i = 0; i <30; i++) {
      DMXData[(30*payload[0])+i] = payload[i+2]; // recompile DMX data from RF data
    }
    if (payload[0] == MAXGROUPS-1) { // send DMX once last packet has been received
      _SFR_BYTE(_SFR_IO8(portNumber)) &= ~pinMask;  
      delayMicroseconds(100); // 100us break (92us min)
      _SFR_BYTE(_SFR_IO8(portNumber)) |= pinMask;
      delayMicroseconds(12); // 12us mark after break
      shiftDmxOut(0);     // send the start byte
      for (int count = 0; count <= 30*MAXGROUPS; count++){
        shiftDmxOut(DMXData[count]);
      }
    }
}

void shiftDmxOut(uint8_t data){
  cli(); 
  _SFR_BYTE(_SFR_IO8(portNumber)) &= ~pinMask;  
  fourUSdelay();
  for (i = 0; i < 8; i++){
    if (data & 1){ _SFR_BYTE(_SFR_IO8(portNumber)) |= pinMask; }
    else { _SFR_BYTE(_SFR_IO8(portNumber)) &= ~pinMask; }
    fourUSdelay();
    data >>= 1;
  }
  _SFR_BYTE(_SFR_IO8(portNumber)) |= pinMask; 
  fourUSdelay();
  fourUSdelay();
  sei();  
}

void fourUSdelay(void) {
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n");
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n");
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); 
    //asm("nop\n nop\n nop\n nop\n"); // you may want to try and add this line in if you need to
}

There is a better version of this ONLY for ATMega328 (UNO/Nano etc) which uses Mat Hertels DMX code rather than the awful bit bashing one I have here with nasty delays in

This is the version that uses MatHertels code...
its much simpler, and automatically sends the DMX stream out on an interrupt
Once again, untested as I am not in the Lab !! :roll_eyes:

// DMX RECEIVE USES ATMEGA328 or ATMEGA8-16PU
// connection information...
// http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
// use ATMEGA DigitalPin 1 to send DMX data to pin 4 of SN75176 chip
// SN75176: pins 2 & 5 to 0v | pin 3 & 8 to 5v | pin 6 to DMX+ | pin7 to DMX-
// Ensure 3.3v is used to supply NRF24L01 board !!
//
#include <SPI.h>
#include "nRF24L01.h" // library: https://github.com/maniacbug/RF24
#include "RF24.h"
#include <Wire.h>
#include <DMXSerial.h> // library: http://www.mathertel.de/Arduino/DMXSerial.aspx //

RF24 radio(9,10);

const uint64_t pipe = 0xF0F0F0F0E1LL;
#define MAXPAYLOAD 32 // max payload size for nrf24l01
#define DMXout 1      // DMX signal output pin (same as TX pin)
#define ERRout 8      // LED to show signal strength : digitalpin 8 to LED via 470 resistor to 0v
#define MAXGROUPS 17 // 17 groups of 30 channels = 510 channels

unsigned long time;
uint8_t payload[MAXPAYLOAD], DMXData[30*MAXGROUPS];
uint8_t lastStamp, channel=0, i;
unsigned long timer;

void setup(void)
{
  pinMode(ERRout, OUTPUT);
  digitalWrite(ERRout, LOW);
  radio.begin();
  radio.setAutoAck(false);
  radio.setPayloadSize(MAXPAYLOAD); // 9 for 6 byte packet, 12 for 6 byte data + SSD, needs to be 14 to stop failing !!
  radio.setPALevel(RF24_PA_HIGH); // try and set PA to max !   
  radio.setDataRate(RF24_250KBPS); // 250kbps for best tx/rx
  radio.openReadingPipe(1,pipe);
  radio.startListening();
  radio.setChannel(channel);
  DMXSerial.init(DMXController); 
  DMXSerial.maxChannel(512);  
}

void loop(void)
{
    if ( radio.available() ) {
      radio.read( payload, sizeof(payload) ); // get data packet from radio if available
    }
    if ( payload[1] == (lastStamp+1) ) { digitalWrite(ERRout,1); } // check for incontinuous timestamps, LED HI if good
    else { digitalWrite(ERRout,0); } // if time stamp error set LED low
    lastStamp = payload[1]; // reset last time stamp
    for (i = 0; i <30; i++) {
      DMXData[(30*payload[0])+i] = payload[i+2]; // recompile DMX data from RF data
    }
}

Hello

I tried both your codes but none of them does not work, Error LED indicates incoming signal but the DMX output pin 1 there is no DMX signal. Do not know where the problem may be ?.

The output comes out of DIGITAL PIN 1 (TXD), which is Pin 3 on the chip, try that

Hello

For testing I use Arduino Duemilanove and there's TX pin 1 only problem with the sketch I used for testing DMX tester and shows no DMX signal and both the principal nor the lights do not work when I connect them to the receiver.

I have done some more work on this, and added a 16 way (hex) channel selector switch, the 16 channels are spread over the first 64 channels, for example .. channel = channel << 2; You should avoid using channels over 83 because these are not legal to use in some countries.

I shall be improving both softwares so that it all works much better and good status indication is shown

DMX TX RX.bmp (1.97 MB)

Hello

Great job already looking forward when I will be able to test your new sketch. Add sketch here as prior or just send it to request the report ?.

I havent changed anything about the sketch, except (1) adding the hex-switch decoder to select the channel (2) probably a few changes to the LED to make it a bidirectional Blue/Red LED to show statii (3) I have also added the Enables on the SN75176 chip

SN75176 Enable pins :
If you are receiving DMX, connect both pins 2 & 3 to ground
if you are transmitting DMX, pins 2 goes to ground, pin 3 goes to 5v

I will be making the sketch align with the CCT Diag (with the few changes above) and posting them here
It will be a single sketch, with just a #define to decide its gender

Regards, Bob

1 Like

This will be perfect, they are all great ideas, I hope that everything will work as it should :slight_smile:

At present, my whole system is working fine, I intend to build several more receivers, one for each lighting stand I have, that way all I need to do is plug the power into the nearest socket and not worry about DMX leads all over the stage

To me it does not yet work correctly, your last sketch does not work and I do not know why but while I use your first sketch that works but only for 510 DMX channels.

HI, now I am working on this I shall have a look at the problem for you, hopefully come back later with a solution. Thanks for your patience

I will be posting a ATTiny85 (210 channel) version of this software soon... its an incredibly cheap option !!

1 Like

So I am trying to Implement your code to make my "own fixture". I am looking to use DMX to control servos and relays for a haunted house that I work at for a fundraiser. I have two Arduino Duemilanove with ATMega328. I also have two NRF24L01+ similar that you mentioned. I am looking to make the DMX receiver take "channels" and put those values into a int variable.

etc.
Set Start address = 1

Of channels = 5

int channel1 = 0
int channel2 =0
int channel3= 0
int channel4= 0
int channel5= 0

and I will map these values as PWM for servos and such.
If there is anyway at all you could help me out with coding that would be AMAZING! Thank you so much.

I see that there is a Example on the DMXSerial Lib that does this. I just do not know how to take the NRF24L01+ signal and make it use the code.

If you use the code similar to what I posted, you could make the receive like this...

    if ( radio.available() ) {
      radio.read( payload, sizeof(payload) ); // get data packet from radio if available
      myservo.write(payload[1]);                  // sets the servo position according to the received value

    }

obviously you would have to set up your servo code and initialise as per your library

Thank you so much for helping!

Where and what would I replace in your code with the code you gave me? Also is the payload[channel number]? Like this?

   if ( radio.available() ) {
      radio.read( payload, sizeof(payload) ); // get data packet from radio if available
channel1= payload[1];  // DMX address 1
channel2=payload[2];
channel3=payload[3];
//....
channel56=payload[56]; //DMX address 56

    }

Actually, it wasnt as simple as I first suggested - the DMX channels are broken into packets of 30 channels each - you would need to recompile the received radio data into the correct channels slot.. like this...

void loop(void)
{
    if ( radio.available() ) {
      radio.read( payload, sizeof(payload) ); // get data packet from radio if available
      for (i = 0; i <30; i++) {
        DMXData[(30*payload[0])+i] = payload[i+2]; // recompile DMX data from RF data
        /// your channels are now in the correct place to send to the servos...
        myservo.write(DMXData[1]); // set servo to the value of DMX channel 1
    }
}

Better yet something like this?

// DMX RECEIVE USES ATMEGA328 or ATMEGA8-16PU
// connection information...
// http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
// use ATMEGA DigitalPin 1 to send DMX data to pin 4 of SN75176 chip
// SN75176: pins 2 & 5 to 0v | pin 3 & 8 to 5v | pin 6 to DMX+ | pin7 to DMX-
// Ensure 3.3v is used to supply NRF24L01 board !!
//
#include <SPI.h>
#include "nRF24L01.h" // library: https://github.com/maniacbug/RF24
#include "RF24.h"
#include <Wire.h>
#include "pins_arduino.h";

RF24 radio(9,10);

const uint64_t pipe = 0xF0F0F0F0E1LL;
#define MAXPAYLOAD 32 // max payload size for nrf24l01
#define DMXout 1      // DMX signal output pin (same as TX pin)
#define ERRout 8      // LED to show signal strength : digitalpin 8 to LED via 470 resistor to 0v
#define MAXGROUPS 17 // 17 groups of 30 channels = 510 channels
int ch1;
int ch2;
int ch3;

unsigned long time;
uint8_t payload[MAXPAYLOAD], DMXData[30*MAXGROUPS];
uint8_t lastStamp, channel=0, i;
unsigned long timer;

void setup(void)
{
  pinMode(DMXout, OUTPUT);
  digitalWrite(DMXout, HIGH);
  pinMode(ERRout, OUTPUT);
  digitalWrite(ERRout, LOW);
  radio.begin();
  radio.setAutoAck(false);
  radio.setPayloadSize(MAXPAYLOAD); // 9 for 6 byte packet, 12 for 6 byte data + SSD, needs to be 14 to stop failing !!
  radio.setPALevel(RF24_PA_HIGH); // try and set PA to max !   
  radio.setDataRate(RF24_250KBPS); // 250kbps for best tx/rx
  radio.openReadingPipe(1,pipe);
  radio.startListening();
  radio.setChannel(channel);
}

void loop(void)
{
    if ( radio.available() ) {
      radio.read( payload, sizeof(payload) ); // get data packet from radio if available
      for (i = 0; i <30; i++) {
        DMXData[(30*payload[0])+i] = payload[i+2]; // recompile DMX data from RF data
        /// your channels are now in the correct place to send to the servos...
        ch1 = (DMXData[1]);
        ch2 = (DMXData[2]);
        ch3 = (DMXData[3]);

    }
}
}
void fourUSdelay(void) {
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n");
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n");
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n");
}

Also could a MAX485 be substituted for the SN75176? I believe they are the same thing.

I think you can get rid of the 4us delay since you aren't recompiling the DMX for re-transmission,and there is probably some other superfluous code in there too, but essentially yes you have it right there. I only use sn75176 devices but I do believe you can use a max485 as a substitute if you like :slight_smile: