someweisguy / esp_dmx

Espressif ESP32 implementation of ANSI-ESTA E1.11 DMX-512A and E1.20 RDM
MIT License
335 stars 35 forks source link

DMX Output randomly stops for a few seconds #118

Closed jlohmoeller closed 8 months ago

jlohmoeller commented 9 months ago

I've built an ArtNet to DMX converter based on this library. At first, it seemed to work quite well, but I'm noticing short random breaks in the DMX output every few minutes (every 30s to 5m at random). Has anyone experienced a similar issue so far?

I've tested with a Showtec Compact Par 7 Q4 and Futurelight SlimPar 18. Both turn dark for a few seconds during the "breaks".

I've experimented with setting various DMX break times (from 176 to 10000), switching UARTs (currently using 1 and 2), and disabling the second universe, none of these removes or reduces the random breaks. The ESP32 (OLIMEX ESP32-POE module) runs ESP-IDF with two user tasks (dmx send and artnet receive). Network is Ethernet, WiFi was left uninitialized.

DMX logic

const dmx_port_t dmx_a_num = DMX_NUM_1;
const dmx_port_t dmx_b_num = DMX_NUM_2;

void dmx_task(void*){
  dmx_config_t config = DMX_CONFIG_DEFAULT;
  dmx_driver_install(dmx_a_num, &config, DMX_INTR_FLAGS_DEFAULT);
  dmx_set_pin(dmx_a_num, PIN_A_TX, PIN_A_RX, PIN_A_EN);

  dmx_driver_install(dmx_b_num, &config, DMX_INTR_FLAGS_DEFAULT);
  dmx_set_pin(dmx_b_num, PIN_B_TX, PIN_B_RX, PIN_B_EN);
  dmx_loop();
}

void IRAM_ATTR dmx_loop(){
  while (true) {
    if(!recvDMXA && !recvDMXB){
      vTaskDelay(1000);
      continue;
    }
    if(recvDMXA)
      dmx_send(dmx_a_num, DMX_PACKET_SIZE);
    if(recvDMXB)
      dmx_send(dmx_b_num, DMX_PACKET_SIZE);
  }
}

extern "C" void app_main() {
   //...
  artnet_begin();
   xTaskCreate(dmx_task, "dmx", 4096, NULL, 10, NULL);
}

part of ArtNet logic that sets actual DMX data (called from separate task that loops over incoming packets)

//callback from artnet.parsePacket on opDMX
void IRAM_ATTR updateArtnetData(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data, sockaddr_in *remote) {
  int64_t time = esp_timer_get_time()/1000;
  in_addr_t src = remote->sin_addr.s_addr;
  if (universe == portA.universe &&
      ((sequence - portA.sequence) % 0xFF < 0x40)) {
    dmx_write(dmx_a_num, data, min((int)length, DMX_PACKET_SIZE));
    recvDMXA = true;
    portA.sequence = sequence;
  }
  if (universe == portB.universe &&
      ((sequence - portB.sequence) % 0xFF < 0x40 )) {
    dmx_write(dmx_b_num, data, min((int)length, DMX_PACKET_SIZE));
    recvDMXB = true;
    portB.sequence = sequence;
  }
}

void IRAM_ATTR artnet_recv_loop(int sockfd, Artnet *artnet) {
  char rx_buffer[550];
  struct sockaddr_storage source_addr;
  socklen_t socklen = sizeof(source_addr);
  unsigned long last = 0;
  while (1) {
    int len = recvfrom(sockfd, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
    if (len < 0) {
      ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
      break;
    } else {
      artnet->parsePacket(rx_buffer, len, (struct sockaddr_in *)&source_addr);
    }
    if (esp_timer_get_time()/1000 - last > 30000) {
      last = esp_timer_get_time()/1000;
      artnet->sendPollReply();
    }
  }
}

void artnet_begin(){
  //...
    xTaskCreate(artnet_server_task, "artnet_server_task", 4096, this, 4, NULL);
}
someweisguy commented 9 months ago

Thanks for reaching out and thanks for your patience while I got back to you!

I haven't heard any reports from users about blackouts while sending DMX. I'm mildly surprised to hear about issues with DMX sending in particular because the DMX send logic is much simpler than the DMX receive logic in the codebase for the library. The bug reports I get are from people who found my mistakes in my DMX receive code.

A few things pop in my mind to check. I apologize in advance if you're already tried any of this - just trying to be thorough! :)

What happens to your PARs when you set them to full and disconnect the DMX cable? In my experience, lights generally hold the last DMX value they received for a few seconds but I am not familiar with these particular PARs. If they do indeed hold the last DMX value they received, I am wondering if somehow the DMX code is sending a blackout for a few seconds.

What is the return value of dmx_send() during the blackouts? If no DMX data was being sent, dmx_send() should return 0.

Let me know your thoughts and if you have any questions!

EDIT: As soon as I wrote the above text, I noticed a potential issue with the DMX send task:

void IRAM_ATTR dmx_loop(){
  while (true) {
    if(!recvDMXA && !recvDMXB){
      vTaskDelay(1000);
      continue;
    }
    if(recvDMXA)
      dmx_send(dmx_a_num, DMX_PACKET_SIZE);
    if(recvDMXB)
      dmx_send(dmx_b_num, DMX_PACKET_SIZE);
  }
}

Do I understand correctly that if ArtNet is not received on neither universe A nor B, that the task will delay for 10 seconds (1 tick == 10ms, 1000 ticks * 10ms == 10000ms)? If so, this would be an issue. DMX needs to be constantly sending packets in order to function correctly.

Try this DMX send loop; feel free to make any edits you would like:

void IRAM_ATTR dmx_loop(){
    dmx_wait_sent(dmx_a_num, portMAX_DELAY);  // Wait forever, until DMX A is done sending
    dmx_send(dmx_a_num, DMX_PACKET_SIZE);
    dmx_wait_sent(dmx_b_num, portMAX_DELAY);  // Wait forever, until DMX B is done sending
    dmx_send(dmx_b_num, DMX_PACKET_SIZE);
  }
}

This loop will block while DMX is sending. It will unblock when DMX is finished sending and then send DMX A. This way you will have a constant stream of packets being sent out. I suspect that using a separate task to send each DMX universe may be more efficient but I haven't tested this use-case.

Let me know your thoughts!