Беспроводной DMX512 приемник/передатчик

В данной статье я расскажу вам как создать передатчик и приемник DMX512 сигнала. Выложена принципиальная схема, схема платы, и вся разводка. Апаратная часть построена на микроконтролере avr ATMEGA 328 от компании Atmel. Приемник и передатчик могут передавать все 512 каналов управления. Без антенны базовая конфигурация работает до 100 метров в прямой видимости(сможет работать до 1км при установке антенны)


Сборка состоит из микроконтролера

Беспроводной модуль в сборе

Для подключения радиомодуля использованы ножки аппаратного SPI микроконтроллера, поэтому разъемы подключения модуля и подключения программатор дублируют друг друга. Это сделано, чтобы удобней было прошивать микроконтроллер на отладночной платке, например, если использовать программатор который подает на схему 5 вольт, а для NRF24L01 это слишком большое напряжение. Чтобы перепрошить управляющий микроконтроллер, достаточно выдернуть трансивер с платы, перепрошить и всунуть его обратно - без лишней возни с перепайкой.

Готовая плата


Существуют ситуации, когда где DMX кабель невозможно протянуть, или использование его просто не практично. Этот проект реализует беспроводную передачу сигнала DMX в диапазоне 2,4 ГГц, используя платформу Arduino и популярного модуля приемопередатчика NRF24L01 + из нордического полупроводника.

Протокол передачи

Прием и передача по протоколу DMX осуществляется на контроллере ATmega с помощью библиотеки DMXSerial. Реализация использует прерывания и является хорошей базой для реализации второго протокола на одном чипе. 2,4 Радиомодуль подключен к плате Arduino или микроконтроллеру ATmega с помощью интерфейса SPI. Библиотека для использования чипа находится здесь https://github.com/gcopeland/RF24 . После инициализации библиотеки можно передавать и принимать данные, используя прилагаемые функции библиотеки. Библиотека передает все команды и данные микросхемы nRF24L01 + также будет обрабатывать все детали передачи в фоновом режиме.

ВНИМАНИЕ: перед компиляцией и заливкой скетча в микроконтроллер, необходимо установить и добавить в компилятор библиотеки nRF24L01 и RF24
DMX512device.uno
// FOR ATMEGA328 16mhz ONLY //
// connection information...
// http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
// SN75176 configuration : 
//   DMXrx NRFtx : pin 2 = 0v | pin 3 = 0v
//   NRFrx DMXtx : pin 2 = 0v | pin 3 = 5v
//   pin 6 = DMX+ | pin7 = DMX-
// Ensure 3.3v is used to supply NRF24L01 board !!
// 
// the Nrf24L01 board can only transmit from channel 0 to 83 in the USA, therefore we shall limit it to 0-80 in steps of 5
 
#include <EEPROM.h>
#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 MAX_DMX_CHANNELS 512 // full [[:wiki:dmx_512|DMX]]
#define BYTES_PER_PACKET 16 // usable bytes per packet
#define PACKET_OVERHEAD 2 // group and time stamp
#define MAXPAYLOAD (BYTES_PER_PACKET+PACKET_OVERHEAD) // max payload size for nrf24l01
#define MAXGROUPS (MAX_DMX_CHANNELS/BYTES_PER_PACKET) // 32 groups of 16 channels = 512 DMX channels
 
// 16way hex channel selector - hex switch mounted with zero at top (see PCB layout)
#define HEX_0 A4 
#define HEX_1 A1 
#define HEX_2 A2 
#define HEX_3 A5 
// nRF24L01 control
#define nRF_CE 9 // Chip enable
#define nRF_CSN 10 // Chip select not
// LEDs
#define LED_BLUE 6 // BLUE PWM
#define LED_RED 5 // RED PWM
// sn76175 control
#define DMX_NOT_EN 2
#define DMX_MODE 3
// other
#define ROLE A0 // if low (jumper on) then RX otherwise TX
 
RF24 radio(nRF_CE,nRF_CSN);
 
unsigned long RXtimer, flashTimer, taskTimer, lastFlash, refreshTimer, radioTimer, rePairTimer; 
uint64_t RXTXaddress, pairingAddress = 0xF0F0F0F0F0LL;
uint16_t rfQuality; 
uint8_t payload[MAXPAYLOAD], shadow_DMX[MAX_DMX_CHANNELS];
uint8_t timeStamp, lastStamp, bestChannel;
uint8_t HEXchannel, _HEXchannel, RXTXchannel, pairingChannel = 80;
boolean role, _role, group_send;
 
void setup(void)
{
  // set DMX/rs485 interface
  pinMode(DMX_NOT_EN, OUTPUT); 
  digitalWrite(DMX_NOT_EN, LOW); 
  pinMode(DMX_MODE, OUTPUT);
  // set bicolour LED
  pinMode(LED_BLUE, OUTPUT); 
  digitalWrite(LED_BLUE, LOW);
  pinMode(LED_RED, OUTPUT); 
  digitalWrite(LED_RED, LOW);
  // set up ROLE switch
  pinMode(ROLE, INPUT_PULLUP); 
  // set up HEX channel select switch
  pinMode(HEX_0, INPUT_PULLUP);
  pinMode(HEX_1, INPUT_PULLUP);
  pinMode(HEX_2, INPUT_PULLUP);
  pinMode(HEX_3, INPUT_PULLUP);
  if ( digitalRead(ROLE) ) { // jumper off : transmitter : initialise & read EEPROM
    init_EEPROM();
  }
  radio.begin();
 
// test nRF board, if it doesnt reply then flash LED indefinately, or until it does reply
  while (!radio.isPVariant() ) { // check if board connected
    if (digitalRead(ROLE)) { blue(); delay(500); off(); delay(500); }  // Wireless TX : if no rf comms then flash BLUE 50%
    else { red(); delay(500); off(); delay(500); }  // Wireless RX : if no rf comms then flash RED 50%
  }
 
  // generic radio stuff    
  radio.setAutoAck(false);
  radio.setPayloadSize(MAXPAYLOAD);
  radio.setPALevel(RF24_PA_HIGH);    
  radio.setDataRate(RF24_250KBPS); 
  radio.setRetries(0,0);
  //radio.setDataRate(RF24_1MBPS); 
  //radio.setDataRate(RF24_2MBPS); 
 
  HEXchannel = HEXread();
 
// if this is a transmitter, do a channel scan, transmit network RXTXaddress and clean RXTXchannel during rePair (every 2 secs)
// only send clean channel data if channel selector is set to zero (send 0xFF if channel other than zero : manualPair)
  if ( digitalRead(ROLE) ) { // jumper off : transmitter
    bestChannelScan(); // only do channel scan if this is a transmitter - bestChannel is required for manualPair and autoPair modes
    if (HEXchannel) { //  
      bestChannelDisplay(); // if HEX switch not zero display best channel number
    }
  }
// if this is a receiver, go into pairing mode and wait (indefinately) for pairing & channel data 
// only use received (clean) channel data if channel selector is set to zero
  else  {
    receivePairingAddress();
  }
  rePairTimer = flashTimer = taskTimer = millis(); // TXtimer =
  DMXSerial.maxChannel(MAX_DMX_CHANNELS);
  rfQuality = 0;
  // read and initialise HEX switch and Role jumper
  readPins(true);
} // end of init
 
 
void loop(void) {
// once per second, test for changes in HEX Switch and Role jumper (rx/tx)
  if (millis() - taskTimer > 1000) {
    taskTimer = millis();
    readPins(false);
  }
 
// JUMPER OFF : [[:wiki:dmx_512|DMX]] --> WIRELESS Tx    
// LED Wireless TX : Solid BLUE, broken with very short flash of RED only if [[:wiki:dmx_512|DMX]] data is being received.
  if (digitalRead(ROLE)) { // if a wireless transmitter
  // SEND OUT BURSTS OF RADIO DATA EVERY 30ms....
    for (uint8_t group = 0; group < MAXGROUPS; group++) { // scan through the groups of DMX data, 30 bytes at a time
      uint16_t group_ptr = group*BYTES_PER_PACKET; //30; // create group pointer for array
      if (millis() - refreshTimer > 1000) { // allow ALL radio data (full [[:wiki:dmx_512|DMX]] array) to be send once per second, regardless of changes
        group_send = true; // force ALL send
        refreshTimer = millis();
      } //if refresh timer
      else { 
        group_send = false; // preset flag to false, only set it if there has been a data change since last time
      } // not refresh timer
      for (uint8_t chan = 1; chan < BYTES_PER_PACKET+1; chan++) {
        if ( DMXSerial.read(group_ptr+chan-1) != shadow_DMX[group_ptr+chan-1] ) { // compare test : current DMX against old DMX 
          shadow_DMX[group_ptr+chan-1] = DMXSerial.read(group_ptr+chan-1); // if changed, update shadow array of [[:wiki:dmx_512|DMX]] data and payload
          group_send = true; // set flag so that THIS group packet gets sent
        } // if data compare
        payload[chan+1] = DMXSerial.read(group_ptr+chan-1); // ensure ALL up-to-date data gets through on this packet
      } // for chan
      if (group_send) { // only send the data that has changed, any data change in a group will result in whole group send
        payload[0] = group; // set first byte to point to group number (groups of 30 bytes)
        payload[1] = timeStamp++; // second byte helps us monitor consistency of reception at receiver with a continuous frame counter
        radio.write( payload, sizeof(payload) ); // dump payload to radio
      } // if group_send
    } // for group
    if ( (DMXSerial.noDataSince() < 2000) && (millis() > 1000) ) { // if [[:wiki:dmx_512|DMX]] has been received within the last 2 seconds make LED flash
      if (!(millis() & 0b1111000000)) { 
        red(); 
      } 
      else { // SIMPLE [[:wiki:dmx_512|DMX]] FLASHER
        digitalWrite(LED_RED,0); 
        analogWrite(LED_BLUE,5); // low level Blue
      }  
    }
    else { // no [[:wiki:dmx_512|DMX]] data present, no flash
      digitalWrite(LED_RED,0); 
      analogWrite(LED_BLUE,5); 
    }
    // trigger rePair information send, just once every 2 seconds
    if (millis() - rePairTimer > 2000) {
      rePairTimer = millis();
      rePairTX(); // single burst of pairing information on pairing channel using pairing network address
    }
  } // if role
 
// JUMPER ON
// WIRELESS --> DMX
// LED Wireless RX : Solid RED, broken with flash of BLUE if wireless is being received (channel match)
//                   the gaps between the BLUE flashes are dependant on consistency of consecutive packets, relating to timeStamp
  else {
    if (millis() - RXtimer > 20000) { // after 20 seconds without receving any radio data, try and rePair 
      receivePairingAddress();
    } 
    else if (millis() - RXtimer > 2000) { // after 2 seconds of no radio data, stop blinking
      rfQuality = 0; 
    } 
    if ( radio.available() ) {
      if (!rfQuality) { 
        rfQuality = 50; // just to show that channel and address are locked on !
      } 
      RXtimer = millis();
      radio.read( payload, sizeof(payload) ); // get data packet from radio 
      if ( payload[1] == (lastStamp+1) ) { // check for continuous timestamps, increase quality indicator if good
        if (rfQuality < 255) { rfQuality++; } 
      } 
      else { // if incontinuous, reduce quality indicator/LED brightness
       if (rfQuality > 50) { rfQuality--; }
      }  
      lastStamp = payload[1]; // reset last time stamp to current for checking next time
      for (uint8_t i = 0; i <BYTES_PER_PACKET; i++) {
        DMXSerial.write((BYTES_PER_PACKET*payload[0])+i, payload[i+2]); // spill radio data into dmx data array
      } 
    } // if radio avail
    lastFlash = millis() - flashTimer;
    if (lastFlash < 64) { 
      digitalWrite(LED_RED,0);
      analogWrite(LED_BLUE,rfQuality); // show rfQuality as brightness of BLUE LED 
    } 
    else if (lastFlash >= 64) { 
      digitalWrite(LED_BLUE,0);
      analogWrite(LED_RED,5); // switch back to RED at low level
    } 
    if (lastFlash > 1024) { 
      flashTimer = millis(); // reset timer after 1 second(ish)
    } 
  } // else
} // loop
 
// read HEX switch and Role jumper, if there has been a change in settings then an update can be made
void readPins(boolean initialise) {
  _HEXchannel = HEXread(); // get temp HEXchannel
  _role = digitalRead(ROLE); // get temporary Role
  if (_role != role || initialise) { // has the ROLE changed ?? - initialise will override any change in settings
    role = _role;
    if (role) { // JUMPER OFF : this is a wireless transmitter
      digitalWrite(DMX_MODE,LOW); // enable rs485 for input
      DMXSerial.init(DMXReceiver); // enable [[:wiki:dmx_512|DMX]] to receive
      radio.openWritingPipe(RXTXaddress); // set network address
      radio.stopListening(); // start talking !
      digitalWrite(LED_RED,0);
      analogWrite(LED_BLUE,5); // low level BLUE to start...
    }
    else { // JUMPER ON : this is a wireless receiver
      digitalWrite(DMX_MODE,HIGH); // enable rs485 for output
      DMXSerial.init(DMXController); // enable [[:wiki:dmx_512|DMX]] for output only
      radio.openReadingPipe(1,RXTXaddress); // set network address
      radio.startListening(); // start listening for data
      digitalWrite(LED_BLUE,0);
      analogWrite(LED_RED,5); // low level RED to start...
    }
  }
  if (_HEXchannel != HEXchannel || initialise) { // has the CHANNEL changed ? - 'initialise' overrides any change in settings
    HEXchannel = _HEXchannel;
    rfQuality = 0; // reset this so that led stops flashing
    if (!role) {
      radio.stopListening(); // if this is a receiver...
    }
    if (HEXchannel) { 
      RXTXchannel = HEXchannel*5; // create real channel number from HEX read
    } 
    radio.setChannel(RXTXchannel); // set the channel
    if (!role) {
      radio.startListening(); // if this is a receiver...
    }
  }
}
 
void red(void) { 
  digitalWrite(LED_BLUE,0); 
  digitalWrite(LED_RED,1); 
}
void blue(void) { 
  digitalWrite(LED_BLUE,1); 
  digitalWrite(LED_RED,0); 
}
void off(void) { 
  digitalWrite(LED_BLUE,0); 
  digitalWrite(LED_RED,0); 
}
 
// manualPair & autoPair information
// selecting HEXchannel 0 instigates "autoPair" mode on transmitter and receivers, therefore channel zero cannot be used in manualPair mode 
// The transmitter and receivers MUST be set to either ALL autoPair(0) or ALL manualPair(1-15) to work correctly
// manualPair mode can only select RXTXchannels 5 to 75 (via hex switch positions 1-15) 
// manualPair & autoPair (on the Transmitter) has a 'bestChannelScan' on power-up
// autoPair : The best(quietest)channel will be transmitted with the network address on pairing
// manualPair : The best(quietest)channel is shown by number of blinks (shown as 3 groups of blinks) (bestChannelDisplay)
// The RXTXchannel number is 5 times that indicated - example 5 blinks = channel 60
// In manualPair, the transmitter and all receivers must be manually set to that channel via the hex switch
 
// Transmitter : every 2 seconds, the transmitter sends its network address/pipe number as data on channel 80, using a a pairing address/pipe
// Receivers : on power up, receiver(s) are in pairing mode, receiver goes to channel 80 and listens on the pairing address/pipe for the network address and channel number to go to
//             receivers change to the network channel and wait for DMX/Radio data to arrive. If any receiver loses pairing, after 20 seconds it will start to repair again.
 
void bestChannelScan(void) {
 uint8_t carriers[16], reps, i, best=200;
 for (reps=0; reps<200; reps++)  { // scan all channels 200 times
   for (i=1; i<16; i++) { 
      radio.setChannel(i*5); // scan from channel 5 to 75 in steps of 5
      radio.startListening();
      delayMicroseconds(128);
      radio.stopListening();
      if ( radio.testCarrier() )
        ++carriers[i]; // if there is any signal present, increase value in array
    } // i
  } // reps
  for (i=15; i>0; i--) { //  find quietest channel, starting from highest channel
    if (carriers[i] < best) {
      best = carriers[i]; // save quietest value
      RXTXchannel = i*5; //and save corresponding channel number
    }
  }
}
 
// BestChannel will only display if transmitter is not on HEX SWITCH 0
void bestChannelDisplay(void) {
  uint8_t i, x;
  for (x=0; x<3; x++) {
    for (i=0; i<RXTXchannel/5; i++) {
      blue();
      delay(25);
      off();
      delay(375);
    }
  delay(1000);
  }
}
 
// read the value of the Hex Switch
uint8_t HEXread(void) {
    return (!digitalRead(HEX_0)) | (!digitalRead(HEX_1) << 1) | (!digitalRead(HEX_2) << 2) | (!digitalRead(HEX_3) << 3);
}
 
// reguarily, the transmitter sends out a short burst of the config data on the pairing channel and using the pairing address
void rePairTX(void) { 
  radio.openWritingPipe(pairingAddress);
  radio.setChannel(pairingChannel); 
  radio.stopListening();
  payload[0] = (uint8_t) (RXTXaddress);
  payload[1] = (uint8_t) (RXTXaddress >> 8);
  payload[2] = (uint8_t) (RXTXaddress >> 16);
  payload[3] = (uint8_t) (RXTXaddress >> 24);
  payload[4] = (uint8_t) (RXTXaddress >> 32);
  if (HEXread()) { payload[5] = 0xFF; } 
  else { payload[5] = RXTXchannel; }
  radio.write( payload, sizeof(payload) ); // dump pairing data to radio
  radio.openWritingPipe(RXTXaddress);
  radio.setChannel(RXTXchannel); 
  radio.stopListening();
}
 
void receivePairingAddress(void) { 
  uint8_t p, q;
  radio.stopListening();
  radio.openReadingPipe(1,pairingAddress);
  radio.setChannel(pairingChannel);
  radio.startListening();
  digitalWrite(LED_BLUE,LOW);
  while ( !radio.available() ) { 
    analogWrite(LED_RED,p++); delay(2); // slowly pulse RED LED while awaiting pairing connection
  } 
  radio.read( payload, sizeof(payload) ); // get data packet from radio if available
  RXTXaddress = (uint64_t)(payload[0]) | (uint64_t)(payload[1] << 8) | (uint64_t)(payload[2] << 16);
  RXTXaddress |= (uint64_t)(payload[3] << 24) | (uint64_t)(payload[4] << 32); // get RXTX address from payload
  if ( payload[5] == 0xFF ) {
    RXTXchannel = HEXread()*5; // use channel from hex switch
  } 
  else { 
    RXTXchannel = payload[5]; // use best channel from scan
  } 
  radio.stopListening();
  radio.openReadingPipe(1,RXTXaddress); 
  radio.setChannel(RXTXchannel);
  radio.startListening();
  RXtimer = millis();
} 
 
void init_EEPROM(void) {
// do this only if a transmitter 
// if the EEPROM hasnt already been set (all bytes at 0xFF) then set an address using 2 random numbers to make up unique 40bit address
// the address will be used in pairing. Once it has been set the first time it wont need to be set again
  if ( digitalRead(ROLE) ) { // jumper off : transmitter
    if ( (EEPROM.read(0) == 0xff) && (EEPROM.read(1) == 0xff) ) {// check for uninitialised/used eeprom memory
      randomSeed(analogRead(A3)); // seed from unused floating analog port
      EEPROM.write(0,random(255)); // unique lowest byte only
      EEPROM.write(1,random(255)); // initialise 40 bit address number into eeprom, this makes up bytes 1-4 in address
    }
    RXTXaddress = (uint64_t) (EEPROM.read(0)) | (uint64_t)(EEPROM.read(1) << 8) | (uint64_t)(EEPROM.read(1) << 16);
    RXTXaddress |= (uint64_t)(EEPROM.read(1) << 24) | (uint64_t)(EEPROM.read(1) << 32); // reconstruct RXTXaddress from eeprom, byte 0 is unique, bytes 1-4 are the same
  }
}

#define ROLE A0 - Эта строка отвечает за прием и ли передачу. Если нужен приемник то A0, передатчик A1

Следите за правильным напряжением, радиомодули очень чувствительны, напряжение должно быть в пределах 3.3v

WillaBroome 2016/08/13 04:34

, 455044, Челябинская обл.,г. Магнитогорск,пр.Карла Маркса, д.101 кор2, кв.43, 2021/09/10 11:29

Собрал два модуля, сопряжения не смог получить, пока не откорректировал эту строку в скетче radio.setDataRate(RF24_2MBPS); Надо убрать . После этого все заработало как надо. Приведенная схема не соответствует приложенной печатной плате. Я использовал в качестве микроконтроллера AtMega 328P 32 TQFP Top View, поэтому разработал свою печатную плату. Вместо 16 позиционного переключателя использовал 4 джампера. На приведенной схеме переключатель вообще не показан. Дальность связи в квартире, с модулями без внешней антенны, низкая. В соседней комнате связь пропадает. Связь сопряжения без входного DMX сигнала не устанавливается. Это нужно знать. Очень жаль, что описание работы схемы очень скудное. Разобраться можно только по коментариям в скетче. А так схема хорошая. Спасибо автору!

, г. Магнитогорск, 2021/09/10 11:35

Перед этой строкой radio.setDataRate(RF24_2MBPS); надо удалить слэши

, Россия, 2020/06/09 23:38

Понял. Это передатчик и приемник на 512 каналов. А можно ли посмотреть данные по какалам дмх? Как назыаается массив куда принимаются даные? Можно ли дописать прогррамму чтобы смотреть эти данные?

, Rossia, 2020/06/09 23:29

В дмх есть адрес и данные. По каким адресаи тоесть дмх каналам идет передача и на какой начальный адрес настроен приемник? Можно ли поменять дмх адрес приемника? Можно ли увеличить количество принимаемых дмх адресов с 2х на более большое количество?

, Rossia, 2020/06/09 23:28

В дмх есть адрес и данные. По каким адресаи тоесть дмх каналам идет передача и на какой начальный адрес настроен приемник? Можно ли поменять дмх адрес приемника? Можно ли увеличить количество принимаемых дмх адресов с 2х на более большое количество?

, Vasilija Ostroskog 2, 2018/07/24 09:35

Hello from Bosnia. Can I use Arduino clones as direct replacement for Atmega MPU or have to use only Atmega328?

, 2018/11/23 01:26

Yep, all clones support on this chip.

, Г. Тамбов, 2018/02/03 12:00

добрый день. подскажите какую роль выполняет переключатель каналов ? и какой используется в данной схеме ?

, 2018/02/06 23:46

Можно использовать обычный джампер. Если перемычки нет, устройство работает как передатчик. Если есть то как приемник, также джампер используется для переключения каналов, если у вас несколько устройств, как приемников так и передатчиков

, г. Тамбов, 2018/08/30 14:19

скажите а насколько критичны расхождения схемы и платы ? например в количестве светодиодов и в местах их подключения.

Ваш комментарий. Вики-синтаксис разрешён: