Hi All,
I've been working on a solution to send DMX data cheaply and reliably using NRF24L01+ modules (RF24 lib). The goal of the project is to control 25 costumes equipped with WS2812B LEDs in a dance show. I was inspired by Bob's work—thanks for your great contribution! @ mcnobby mentioned something about a time-controlled code - unfortunately couldn't find anything
Transmitter:
- Hardware:
- RP2040-Stamp
- E01-ML01SP2 (NRF24L01+ Module with PA)
- Isolated DMX Input
- 128x64 Pixel OLED
- 21x WS2812B LEDs (to aid in debugging)
- Rotary Encoder with Pushbutton
- Micro-SD Card (optional for recording and sending)
- Software:
- DMX data is received using the Pico-DMX library with PIO support on the RP2040. Since the DMX bus runs at 250k baud, I'm expecting up to a 44Hz refresh rate.
- When data is received, a flag is set, and the data is immediately buffered into a second array.
- The data is then sent in 18 blocks of 32 bytes each (Byte 1: Packet ID; Bytes 2-31: Data; Byte 32: CRC8).
- Optionally, I have provisioned for sending the data a second time - if some data are lost in air.
Receiver (currently 25 units):
- Hardware:
- RP2040 Zero
- 18650 Battery
- Charger
- 5V 3A Boost Converter (for longer WS2812B strips)
- E01-ML01SP2 (NRF24L01+ Module with PA)
- 1x WS2812B Status LED
- 1x Blue Status LED
- Software:
- The receiver accepts the packet, checks the CRC8, and writes the data into the DMX array.
- It then sends the data to the WS2812B LEDs via PIO (NeoPixelBus).
My Problem:
When I input a slow stroboscope effect via DMX, only one receiver performs exactly as expected (DMX channels 1 to 30). However, the others are slightly delayed—unfortunately, the delay is noticeable.
I'm looking for a way to achieve a more stable broadcast transmission and minimize the delay between receivers. A slight delay between the sender and receivers is acceptable, but the delay between receivers should be minimal or, ideally, not noticeable at all.
Some additional info - to optimize the code for this special case:
Each costume has 30 DMX channels (10 RGB LED groups) => Costume 1: DMX 1 to 30, Costume 2: DMX 31 to 60, ...
Hope someone can help.
I'll be happy to share the plans once everything is running smoothly.
Best Regards, Thomas
Code:
- Transmitter:
Sender_F1.ino (24,2 KB)
bool sendDMXData() {
// Paketstruktur
const uint8_t DATA_SIZE = 30;
const uint8_t PACKET_SIZE = 32; // 1 Byte Paketnummer + 30 Bytes Daten + 1 Bytes CRC8
uint8_t packet[PACKET_SIZE];
uint8_t packet_id = 0;
uint16_t dmx_index = 0;
bool fifo_sent = false;
bool all_ok = true;
unsigned long micros_start = micros();
#ifdef THOB_DEBUG_TX
Serial.println("--- Send Block ---");
#endif
radio.flush_tx();
packet_id = 0;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 1;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 2;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
fifo_sent = radio.txStandBy(1); // Returns 0 if failed after x milliseconds of retries. 1 if success.
all_ok = all_ok && fifo_sent;
#ifdef THOB_DEBUG_TX
Serial.print("Paket: "); Serial.print(packet_id - 2); Serial.print(" - "); Serial.print(packet_id);
Serial.print(" FIFO sent (1): "); Serial.print(fifo_sent);
Serial.print(" FIFO empty (1): "); Serial.println(radio.isFifo(true));
#endif
packet_id = 3;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 4;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 5;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
fifo_sent = radio.txStandBy(1); // Returns 0 if failed after x milliseconds of retries. 1 if success.
all_ok = all_ok && fifo_sent;
#ifdef THOB_DEBUG_TX
Serial.print("Paket: "); Serial.print(packet_id - 2); Serial.print(" - "); Serial.print(packet_id);
Serial.print(" FIFO sent (1): "); Serial.print(fifo_sent);
Serial.print(" FIFO empty (1): "); Serial.println(radio.isFifo(true));
#endif
packet_id = 6;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 7;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 8;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
fifo_sent = radio.txStandBy(1); // Returns 0 if failed after x milliseconds of retries. 1 if success.
all_ok = all_ok && fifo_sent;
#ifdef THOB_DEBUG_TX
Serial.print("Paket: "); Serial.print(packet_id - 2); Serial.print(" - "); Serial.print(packet_id);
Serial.print(" FIFO sent (1): "); Serial.print(fifo_sent);
Serial.print(" FIFO empty (1): "); Serial.println(radio.isFifo(true));
#endif
packet_id = 9;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 10;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 11;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
fifo_sent = radio.txStandBy(1); // Returns 0 if failed after x milliseconds of retries. 1 if success.
all_ok = all_ok && fifo_sent;
#ifdef THOB_DEBUG_TX
Serial.print("Paket: "); Serial.print(packet_id - 2); Serial.print(" - "); Serial.print(packet_id);
Serial.print(" FIFO sent (1): "); Serial.print(fifo_sent);
Serial.print(" FIFO empty (1): "); Serial.println(radio.isFifo(true));
#endif
packet_id = 12;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 13;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 14;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
fifo_sent = radio.txStandBy(1); // Returns 0 if failed after x milliseconds of retries. 1 if success.
all_ok = all_ok && fifo_sent;
#ifdef THOB_DEBUG_TX
Serial.print("Paket: "); Serial.print(packet_id - 2); Serial.print(" - "); Serial.print(packet_id);
Serial.print(" FIFO sent (1): "); Serial.print(fifo_sent);
Serial.print(" FIFO empty (1): "); Serial.println(radio.isFifo(true));
#endif
packet_id = 15;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 16;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], DATA_SIZE);
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
packet_id = 17;
dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
packet[0] = packet_id;
memcpy(&packet[1], &dmx_data_buffer[dmx_index], 2);
memset(&packet[3], 0x00, 28); // Setze packet[3] bis packet[30] auf 0x00
packet[31] = crc8(packet, 31);
all_ok = all_ok && radio.writeFast(&packet, PACKET_SIZE);
fifo_sent = radio.txStandBy(1); // Returns 0 if failed after x milliseconds of retries. 1 if success.
all_ok = all_ok && fifo_sent;
#ifdef THOB_DEBUG_TX
Serial.print("Paket: "); Serial.print(packet_id - 2); Serial.print(" - "); Serial.print(packet_id);
Serial.print(" FIFO sent (1): "); Serial.print(fifo_sent);
Serial.print(" FIFO empty (1): "); Serial.println(radio.isFifo(true));
#endif
#ifdef THOB_DEBUG_TX
Serial.print("--- "); Serial.print(micros() - micros_start); Serial.println(" ---");
#endif
return all_ok; // Returns 0 if failed, 1 if success.
}
- Receiver:
Empfaenger_5.ino (5,9 KB)
// Paketstruktur
const uint8_t DATA_SIZE = 30;
const uint8_t PACKET_SIZE = 32; // 1 Byte Paketnummer + 30 Bytes Daten + 1 Bytes CRC8
while (radio.available()) {
uint8_t packet[PACKET_SIZE];
radio.read(&packet, PACKET_SIZE);
uint8_t packet_id = packet[0];
uint8_t* data = &packet[1];
uint8_t received_crc = packet[31];
uint16_t dmx_index = DMX_CHANNEL_START + packet_id * DATA_SIZE;
uint16_t dataLength = 30;
if (packet_id ==17) {
dataLength = 2;
}
memcpy(&dmx_data_buffer[dmx_index], data, dataLength);
}