Transmitting & Receiving DMX using nRF24L01 radio transceivers

Hi All

I have been working towards getting a TX/RX solution for sending DMX data short distances cheaply using nRF24L01 radio modules with the RF24 library. Initially I just sent 4 bytes to see if I could control a DMX fixture remotely, and it worked, so I then tweaked the code to ensure to stream was smooth and error free at best. So now I am working towards sending larger and larger amounts of 32 byte packets..

Transmitter:

  • The first Node receives DMX using Matt Hertels DMX library, I use a ATMega328 for this, the DMX is stored in an array and is received in the background automatically
  • Using a simple timer routine, I send the current DMX data out over radio every 25ms.
  • I send each 32 byte packet as [packet number] [timestamp] [30 bytes of data]
  • At present I am only using 8 packets of 30 DMX data bytes (240 bytes) so still not the full DMX512
  • The timestamp is just a sequential number so the receiver can see if there are any dropped packets and show reception quality with an LED
  • There will be an allowance for selecting Radio channel number via DIP Switches (matched with receiver)

Receiver:

  • For the receiver I use a ATMega8-16 for cheapness
  • I receive all the packets and check for dropped packets
  • Then recompile all the DMX data back into its own slots using the packet number*30 as an offset into the array
  • Once the data is compiled I manually blatt out the DMX data using some special timed code I saw on the internet

I am remaking the PCBs for TX and RX and will try out the FULL software very soon
Once I have got it working as best as I can I will submit it on here

I hope others are interested
Best Regards Bob

UPDATE: So far I have 120 channels of DMX, transmitted & received and running smoothly updating at 40hz

I still have distances tests to do and I really want to ramp this up to the full 512 channels that DMX requires

Regards

Hello Bob,

I am also dealing with sending DMX data over nRF24L01 wireless transceivers. Have you faced with data receiving? Anything you can share (code or circuit shematic ) would be very appriciated here.
mail address*************

Thank you.

OK, so I have done it, I now have a full 512 byte DMX stream running at about 25hz over wireless using these nifty little radio boards

I shall only be posting here and not really mailing individuals thanks

Outline code posted soon

Bob

CODE TO TRANSMIT DMX USING nRF24L01:
This code will take in 512 channels of DMX and transmit it in 30 byte packets over RF every 40ms

// FOR ATMEGA328 16mhz ONLY //
// connection information...
// http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
// use ATMEGA RXD pin to receive DMX data from pin 1 of SN75176 chip
// SN75176: pins 2, 3 & 5 to 0v | pin 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 //

#define MAXGROUPS 17 // 17 groups of 30 channels = 510 channels
#define MAXPAYLOAD 32 // max payload size for nrf24l01
#define BURSTTIMER 40 // 40ms between blasts of radio data
#define DMX_LED 8 // DMX monitor : LED from digital pin 8 to 0V via 470R resistor

RF24 radio(9,10);

const uint64_t pipe = 0xF0F0F0F0E1LL; //0xE8E8F0F0E1LL;
uint8_t payload[MAXPAYLOAD];
unsigned long timeslot, flashTimer;
uint8_t timeStamp, channel=0; // ensure TX & RX channels are the same

void setup(void)
{
  pinMode(DMX_LED, OUTPUT);
  digitalWrite(DMX_LED, LOW);
  DMXSerial.init(DMXReceiver);
  radio.begin();
  radio.setAutoAck(false);
  radio.setPayloadSize(MAXPAYLOAD);
  radio.setPALevel(RF24_PA_HIGH);    
  radio.setDataRate(RF24_250KBPS);
  radio.openWritingPipe(pipe);
  radio.stopListening();
  radio.setChannel(channel);
  flashTimer = millis();
}

void loop(void)
{
  if (millis() - timeslot > BURSTTIMER) {
    timeslot = millis();
    for (uint8_t group = 0; group<=MAXGROUPS; group++) {
      payload[0] = group; // set first byte to point to group (groups of 30 bytes)
      payload[1] = timeStamp++; // second byte helps us monitor consistency of reception at receiver
      for (uint8_t chan = 1; chan<31; chan++) {
        payload[1+chan] = DMXSerial.read((group*30)+chan); // fill payload with DMX data
      }
      radio.write( payload, sizeof(payload) ); // dump payload to radio
      delayMicroseconds(20); // short delay between packets to ensure radio not overloaded    
    }
  }
  unsigned long lastPacket = DMXSerial.noDataSince();
  unsigned long lastFlash = millis() - flashTimer;
  if (lastPacket < 5000) { // if continuous DMX data is received then flash LED
    if (lastFlash < 500) { digitalWrite(DMX_LED,1); } // flash on 0.5sec
    else if (lastFlash < 1000) { digitalWrite(DMX_LED,0); } // flash off 0.5sec
    else if (lastFlash > 1000) { flashTimer = millis(); } // reset timer after 1 second
  }
}

CODE TO RECEIVE DMX USING nRF24L01:
This code will take in receive 30 byte packets of data via RF and recompile it into a 512 byte DMX stream
The code will work for ATmega 328 and ATMega8 processors, I chose to adapt for ATmega8 to keep receiver cost down

// 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

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
    }
    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
      digitalWrite(DMXout, LOW); 
      delay(10);     // send the break
      shiftDmxOut(DMXout, 0);     // send the start byte
      for (int count = 0; count <= 30*MAXGROUPS; count++){
        shiftDmxOut(DMXout, DMXData[count]);
      }
    }
}

void shiftDmxOut(int pin, int theByte){
  int port_to_output[] = { NOT_A_PORT, NOT_A_PORT, _SFR_IO_ADDR(PORTB), _SFR_IO_ADDR(PORTC), _SFR_IO_ADDR(PORTD) };
  int portNumber = port_to_output[digitalPinToPort(pin)];
  int pinMask = digitalPinToBitMask(pin);
  _SFR_BYTE(_SFR_IO8(portNumber)) |= pinMask;
  cli(); 
  _SFR_BYTE(_SFR_IO8(portNumber)) &= ~pinMask;  
  fourUSdelay(); 
  for (int i = 0; i < 8; i++){
    if (theByte & 01){ _SFR_BYTE(_SFR_IO8(portNumber)) |= pinMask; }
    else { _SFR_BYTE(_SFR_IO8(portNumber)) &= ~pinMask; }
    fourUSdelay();
    theByte >>= 1;
  }
  _SFR_BYTE(_SFR_IO8(portNumber)) |= pinMask; 
  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");
}

Alot of the above code has been nicked/nobbled/adapted from stuff I have researched online, it was just the stitching together I did really, and many hours scoping to get the speed/reliability/throughput as good as I can get it

I have successfully transmitted and received 512 channels over 25ft through 2 solid brick walls at 25hz to give a very smooth result at my collection of DMX fixtures, there was little or no noticable loss of data packets as there is always a good one following up 40ms later !

The nRF24L01 boards I used were the ones with the antenna ON etched as part of the PCB at a cost of under 1GBP a go !

1 Like

Further additions to the code will be :

  1. 8 bit Channel selector for both TX & RX
  2. Better simulation of signal quality via LEDs
  3. Improved DMX processing on receiver board

I have done some further BITRATE testing on this, I can definately vouch for using the lower Bitrate of 250kbps over 2mbps, you get a much better distance (over twice) and far far fewer dropouts at the lower speed

For this application as I have written it, it would not be noticable at the lower speed as the full cycle for the refresh rate would allow everything to happen within the time period of 40ms I have allowed. You cant see any jitter when watching DMX fixtures move so I am happy

:@)

1 Like

I have now purchased the Rf modules with external antennas, I shall be trying these in due course and will post the results

Hello friend

I'm trying to implement your code on my arduino uno

I would like to know which version you are using IDE

I use to 1.05 when compiling command appear several errors ... could you help me?

RF24_v2.cpp.o: In function __static_initialization_and_destruction_0': F:\Programas\Arduino/RF24_v2.ino:19: undefined reference to RF24::RF24(unsigned char, unsigned char)'
RF24_v2.cpp.o: In function loop': F:\Programas\Arduino/RF24_v2.ino:52: undefined reference to RF24::write(void const*, unsigned char)'
RF24_v2.cpp.o: In function setup': F:\Programas\Arduino/RF24_v2.ino:31: undefined reference to RF24::begin()'
F:\Programas\Arduino/RF24_v2.ino:32: undefined reference to RF24::setAutoAck(bool)' F:\Programas\Arduino/RF24_v2.ino:33: undefined reference to RF24::setPayloadSize(unsigned char)'
F:\Programas\Arduino/RF24_v2.ino:34: undefined reference to RF24::setPALevel(rf24_pa_dbm_e)' F:\Programas\Arduino/RF24_v2.ino:35: undefined reference to RF24::setDataRate(rf24_datarate_e)'
F:\Programas\Arduino/RF24_v2.ino:36: undefined reference to RF24::openWritingPipe(unsigned long long)' F:\Programas\Arduino/RF24_v2.ino:37: undefined reference to RF24::stopListening()'
F:\Programas\Arduino/RF24_v2.ino:38: undefined reference to `RF24::setChannel(unsigned char)'

Did you install RF24 library?

Install the library at : GitHub - maniacbug/RF24: Arduino driver for nRF24L01

Hello friends

Please advise anyone how I modified the code to work with 8Mhz cpu thanks.

You have to create a new entry in boards.txt for a 8mhz CPU, once you set the F_CPU value to 8000000L it should all work ok

uno_8.name=Arduino Uno 8mhz
uno_8.upload.protocol=arduino
uno_8.upload.maximum_size=32256
uno_8.upload.speed=115200
uno_8.bootloader.low_fuses=0xff
uno_8.bootloader.high_fuses=0xde
uno_8.bootloader.extended_fuses=0x05
uno_8.bootloader.path=optiboot
uno_8.bootloader.file=optiboot_atmega328.hex
uno_8.bootloader.unlock_bits=0x3F
uno_8.bootloader.lock_bits=0x0F
uno_8.build.mcu=atmega328p
uno_8.build.f_cpu=8000000L
uno_8.build.core=arduino
uno_8.build.variant=standard

But I think this will only work if you use a 8mhz crystal, you havent said if you are using internal clock

Thanks for responding but does not want to work so using an internal clock you have any idea to solve this problem ?.

Sorry, I havent ever used the internal clock, just crystals, I cant help

Do not mind remakes my hardware for using external clock and the matter will be solved :slight_smile: I would still ask if you have any new update of finished because I noticed that in this version are some channels not working :frowning:

I think I know why the DMX Transmit doesnt work consistently, I am rewriting the routine and will publish my results. Regards

Please You can then add new sketches if they have already been repaired ?.