Read/write DMX with ESP32-C6

I would like to build a DMX backup box. I. e. I want to read DMX signals from one channel (RX) and write DMX signals to another channel (TX). For this project I would like to use an ESP32-C6 but ran into some difficulties.

I tried
esp_dmx but wasn't able to get the program execute anything after
dmx_driver_install(...) . It feels like the controller "hangs", but it might also be some FreeRTOS magic I don't understand.

Therefore I switched over to EasyDMX. With this library I could receive DMX values, but I had to add delay(500) at the end of the main loop. With different delay times the DMX values were assigned to the wrong channels.

Transmitting DMX with EasyDMX worked, but I wasn't able to set more than one channel. Any additions to the program (even as small as setting an LED output pin) led to a state where I couldn't set any DMX channel at all.

Another try with the DMX Transceiver library showed me that this library requires avr/io.h and avr/interrupt.h (at least in the example), so I wasn't able to compile it at all.

I am using ArduinoIDE under Linux. My embedded programming skills are rather "basic". Now I am stuck and would be glad if anyone might help me to make progress with reading/writing DMX with my ESP32-C6.

It likely depends on the lines before that.

It likely depends on the code You didn’t post.

Thank you, Railroader, for your response and your guidance! Yes, I missed to mention: The code mentioned before was taken from the original examples found in the according Github repos. I modified it slightly to match my pinning (Rx=17, Tx=16) and set the DMX channels different from the example. For the sake of readability of this thread I removed some comments regarding copyright and circuit before posting:

/*
 * Credits to EasyDMX https://github.com/tesa-klebeband/EasyDMX
 * Copyright (c) 2024 tesa-klebeband
 * - see full copyright note in the original projekt on Github
 */
#include <Arduino.h>
#include <easydmx.h>

// Create an instance of the EasyDMX class, so we can use its functions
EasyDMX dmx;

void setup() {
    Serial.begin(115200);
    dmx.begin(DMXMode::Transmit, DMXPin::NoRx, 16);
}

void loop() {   
   // Set channel 2 to 170
    dmx.setChannel(2, 170);
    delay(1000);
    // Set channel 1 to 200
    dmx.setChannel(1, 200);
}

Channel 2 is set to 170, channel 1 remains 0 (checked by means of an oscilloscope on the Tx pin).

This is the simplest example. I am trying not to mess up this thread with superfluous information. Therefore, please let me know when you are interested in the code of my tries with esp_dmx as well.

Pin 16 is referenced in dmx.begin but nowhere pin 17.

I am at the very beginning of this project. Therefore I tried to manage sending and receiving with minimal examples before integrating them into a more complex project. The code posted above is just the transmit example. Do you see a reason why not assigning the receive pin might cause the code to fail in this case?

If you look at the transmit example that came with the library. TX is on pin 17, not pin 16 which is the Tx pin of Serial2

from: EasyDMX/examples/Transmit/main.cpp at master · tesa-klebeband/EasyDMX · GitHub

void setup() {
    Serial.begin(115200);
    /**
     * Start the DMX driver in transmit mode on Serial2 (pin 17)
     * The MAX485's DI pin should be connected to the TX pin of the ESP32
     *  and RE and DE should be connected to 3.3V.
    */
    dmx.begin(DMXMode::Transmit, DMXPin::NoRx, DMXPin::Serial2Tx);
...

No, the detailed knowledge is too low but reply #6 comes from a much more knowing helper.

I wrote Dmx_ESP32.
Transmission on an ESP32 is fairly straightforward, reception can be a different matter. Most libraries use the Serial event Queue to detect the break, Dmx_ESP32 uses the RMT idle callback.
On ESP32 core version 3.x.x the minimum requirement is 3.3.x
I don't own a C6 and so i haven't tested it on that particular board, but it compiles just fine.

For that you do need 2 separate UARTs. I think the ESPC6 does have them, but i am not sure if you can assigned exposed pins to both. As i said, i don't own one. EDIT: Of course all peripheral pins are free assignable on all ESP32 boards.

Most DMX libraries use the same trick to generate the frame reset break, switching the baud-rate from 250kbps to a much lower rate (less than 100kbps) and sending a '0'. With the baud-rate switching you can not do reception on that same UART.

On an ESP32C6 ?

I don't know for that specific board. I was just referring to the example code. It uses pin 17 for Tx where to OP used pin 16. I was pointing out the difference since OP said they started with the example code.

@Railroader: Thank you again for your guidance to clarify the issue.

@blh64: Thanks for your comment. According to Getting Started with Seeed Studio XIAO ESP32C6 | Seeed Studio Wiki pin 16 is Tx and pin 17 is Rx on this specific board, which is just the other way round than I found it in some examples. It looks like different

@Deva_Rishi: Next time I find some time with my ESP32-C6, I will try Dmx_ESP32. Thank you for the hint! I will let you know if it worked.

1 Like

First success with Dmx_ESP32.

Thanks @Deva_Rishi!
In order to match my hardware, I simply adapted two lines in the transmit example of your Dmx_ESP32 project:
#define TX_PIN 16 (instead of 32)
#define DMX_PORT &Serial1 (instead of &Serial1)
and copied the files Dmx_ESP32.cpp and Dmx_ESP32.h to the folder of Dmx_transmit_example.ino.
Transmission works perfect, the loop is executed every 45 ms.
[edit] I measured the cycle time on the wrong pin. The corect cycle time is 22.8 ms [/edit]

My next step will be to combine receive + transmit in one project.

For that as i said before (i think) you will need to use 2 different UARTs.

Now i am not fully familiar with the C6, but from the datasheet is says it has 2 UARTs and 2 LP(Low Power) UARTs, but to be honest i don't actually know how these are implemented in the Arduino core, since there is also the USB TX/Rx available.

That would be instead of '&Serial2' I actually suggest you keep it the way it was. Peripherals are freely assignable to pins on an ESP32, and with '&Serial2' it compiles, which probably suggests it works, but i am curious to hear from you about that. (&Serial3 doesn't, Serial3 is not defined, so i figure Serial2 is also a normal UART)

The reception example uses Serial1. In a way it really doesn't matter which is which, but they can not be the same.

That should be near 25ms. 513 bytes (of 11 bits, so 5643 bits at 250.000 bps comes down to 22,572 ms + the breaktime. I am not even sure what you mean by that. How did you measure this ?

I suspect the USB is defined as 'Serial' (UART0), but i haven't looked through the datasheet and the implementation in the core that closely, so i also don't know how to easily access the LP-UARTs. They would suffice for both transmission and reception, though their FIFO is quite a bit smaller.

As i said the datasheet implies 2 more Low Power UARTs, which theoretically could also be used.

The normal procedure is to download the zip file, extract it in you sketchbook->libraries folder and the main folder will then have the examples folder which will make examples show up from the IDE File->Examples. (once you close and re-open the IDE)

1 Like

Thank you for all the valuable input.

Sorry, in #11 I measured the cycle time at the wrong pin. The actual cycle time was 22.8 ms (measured on the "white" LED pin).

In contrast to Serial1, where the DMX transmission example works as expected, it does not work with Serial2.

With Serial2

Currently I am limited to "LED debugging", as the serial monitor doesn't work for me with this project (no idea why). My next try would still be to test the receive example. Maybe this one will operate with Serial2(?). As you see, my understanding of ESP32 UART is very limited.

Update:
The Rx example works with Serial1 / Pin 17 and does not with Serial2.
Fun fact: With this example the serial monitor is working.

Ok, well what if you remove the debug messages and try each with '&Serial'

There are 2 UARTs on the C6 but if one is used for USB then there aren't 2 available for reception and transmission, which would be a problem, since 2 separate UARTs are required.

I replaced #define DMX_PORT &Serial2 with #define DMX_PORT &Serial and removed all Serial.print(ln) lines. The project does not compile because...

~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_transmit_Serial.ino:14:58: error: no matching function for call to 'dmxTx::dmxTx(HWCDC*, int, int, int, int)'
   14 | dmxTx dmxSend(DMX_PORT, TX_PIN, TX_ENABLE, LED_GREEN, LOW);
      |                                                          ^
In file included from ~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_transmit_Serial.ino:1:
~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_ESP32.h:106:5: note: candidate: 'dmxTx::dmxTx(HardwareSerial*, int8_t, int8_t, int8_t, int8_t)'
  106 |     dmxTx(HardwareSerial* port, int8_t pinTx, int8_t pinEnable = -1, int8_t pinToggle = -1, int8_t ledOn = HIGH);
      |     ^~~~~
~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_ESP32.h:106:27: note:   no known conversion for argument 1 from 'HWCDC*' to 'HardwareSerial*'
  106 |     dmxTx(HardwareSerial* port, int8_t pinTx, int8_t pinEnable = -1, int8_t pinToggle = -1, int8_t ledOn = HIGH);
      |           ~~~~~~~~~~~~~~~~^~~~
~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_ESP32.h:102:7: note: candidate: 'constexpr dmxTx::dmxTx(const dmxTx&)'
  102 | class dmxTx {
      |       ^~~~~
~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_ESP32.h:102:7: note:   candidate expects 1 argument, 5 provided
~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_ESP32.h:102:7: note: candidate: 'constexpr dmxTx::dmxTx(dmxTx&&)'
~/Arduino/DMX/dmx-esp32/Dmx_transmit_Serial/Dmx_ESP32.h:102:7: note:   candidate expects 1 argument, 5 provided
exit status 1

Compilation error: no matching function for call to 'dmxTx::dmxTx(HWCDC*, int, int, int, int)'

Additionally I have to make a correction:
Last time I mentioned the Rx eample was working with Serial1. I was just too happy to see the serial monitor output "DMX Configured". Now I have to admit that this is the latest console output. I should expect another line "DMX reception Started", but it doesn't turn up. Now I inserted digitalWrite(LED_WHITE, HIGH) right at the beginning of the main loop() and removed the analogWrite(LED_WHITE... statement, but the according pin did not go high. Which means to me that the main loop is never entered.

Any more ideas?

Yeah so Serial is not a definable UART. it is just USB-Serial (or just, i mean it has merit having one, but it is not usable for DMX reception or transmission)
It does compile when you use

#define DMX_PORT &Serial0  

Also the defined GPIO RX-pin in the example doesn't exist on the C6, so i guess you should try a different pin.

  if (dmxReceive.start()) {
    Serial.println("DMX reception Started");
  }
  else {
    Serial.println("DMX aborted");
  }

Well it should be either one or the other. If it doesn't do anything then it must have crashed.
Can you add a

delay(100);

after that statement to given some time to transmit either of those statements.

also you can try the basic receive example with debug level 'verbose'

There will be some warning coming up about the sharing of the pin between the RMT and the UART and the freeing of the bus not being supported anymore.

You can also try using 2 separate pins for the UART and the RMT and tie them together with a 1K current limiting resistor in between just to be safe.

It does feel a little unpractical to do this without having a C6 myself, i did order an S2 the other day for a different reason, but no C6 on the way either.

Edit: Just in case, which core version are you using ?

#define DMX_PORT &Serial0 did the job :+1:. Thank you very much!

I am using a board (with RS485 interfaces), where the receive pin is 17. I chose this pin, because it is marked as Rx pin in the ESP-C6 data sheet. I didn't see a warning regarding the combination of UART and RMT pin - which doesn't mean there is none. Should I have a closer look (and where)?

Once I tried to assign pin 20 to RMT, but the program still crashed.

With additional delays and the output of the core version my setup() function is now

void setup() {
  Serial.begin(500000);
  Serial.println("\n");
  Serial.println("DMX Reception using RMT for break detection.");
  uint32_t cv = ESP_ARDUINO_VERSION; // added to find out core version
  Serial.printf("ESP32 Core Version: %d.%d.%d\n", cv >> 16, (cv >> 8) & 0xFF, cv & 0xFF);
  if (!dmxReceive.configure()) {  // configures and starts the UART (with default parameters)
    Serial.println("DMX Configure failed.");
  }
  else {
    Serial.println("DMX Configured.");
  }
  delay(100); // delay increased from 10, just in case
  if (dmxReceive.start()) {
    Serial.println("DMX reception Started");
    delay(100); // added to give more time for serial output
  }
  else {
    Serial.println("DMX aborted");
    delay(100); // added to give more time for serial output
  }
  pinMode(LED_WHITE, OUTPUT);  // the indicator LED
}

The console prints

DMX Reception using RMT for break detection.
ESP32 Core Version: 3.3.6
DMX Configured.

So I have core version 3.3.6, but the program still crashes right after DMX Configured.

Below you find an extract of the verbose debugging output.

(...)
DMX Configured.
Guru Meditation Error: Core  0 panic'ed (Stack protection fault). 

Detected in task "BreakDetector" at 0x40035b92
Stack pointer: 0x40815a80
Stack bounds: 0x40815abc - 0x408160b0
(...) 

The entire output is in this file:
debug_verbose.txt (240,8 KB)