Urgent: problem with DmxSimple library

Status
Not open for further replies.

nlecaude

Well-known member
Hello,

I'm at a client site trying to troubleshoot a DMX problem with a box I built. I can't seem to control channels higher than 128. Anything under that works great. I checked my coded and I doubt it's that since when I print the values everything is fine. Can anyone confirm that DmxSimple can work for more than 128 channels ?

Here's my code for reference:

Code:
#include <DmxSimple.h>
#include "Timer.h"

Timer t;
char status_led = 5;

void setup() {
  pinMode(status_led, OUTPUT);
  digitalWrite(status_led, HIGH);
  
  DmxSimple.usePin(3);
  DmxSimple.maxChannel(512);

  usbMIDI.setHandleControlChange(OnControlChange);
  usbMIDI.setHandleNoteOff(OnNoteOff);
  usbMIDI.setHandleNoteOn(OnNoteOn);
  usbMIDI.setHandleVelocityChange(OnVelocityChange);
  
  Serial.begin(115200);
}

void loop() {
  
  t.update();  
  
  if (usbMIDI.read())
  {
    // Blink LED when messages are received
    t.pulseImmediate(status_led, 100, LOW);
    
    // If the message is sysex and starts with 127
    if (usbMIDI.getType() == 7 && usbMIDI.getSysExArray()[1] == 127)
    {
      char channel_a = usbMIDI.getSysExArray()[4];
      char channel_b = usbMIDI.getSysExArray()[5];
      int channel = seven_to_fourteen(channel_a, channel_b);
      
      char value_a = usbMIDI.getSysExArray()[2];
      char value_b = usbMIDI.getSysExArray()[3];
      char value = seven_to_fourteen(value_a, value_b);
      
      dmx_write(channel, value);
    }
  }
}

void OnControlChange(byte channel, byte control, byte value) {
  midi_to_dmx(channel, control, value);
}

void OnNoteOn(byte channel, byte note, byte velocity) {
  midi_to_dmx(channel, note, velocity);
}

void OnNoteOff(byte channel, byte note, byte velocity) {
  midi_to_dmx(channel, note, 0);
}

void OnVelocityChange(byte channel, byte note, byte velocity)*{
  midi_to_dmx(channel, note, velocity);
}

void midi_to_dmx(byte midi_channel, byte dmx_channel, byte value)
{  
  
  // 512 channels of DMX spread across 5 MIDI channels
  if (midi_channel >= 1 && midi_channel <= 5) 
  {
    if (midi_channel == 1)
      dmx_write(dmx_channel, byte(value / 127. * 255));
    if (midi_channel == 2)
      dmx_write(dmx_channel + 127, byte(value / 127. * 255));
    if (midi_channel == 3)
      dmx_write(dmx_channel + 254, byte(value / 127. * 255));
    if (midi_channel == 4)
      dmx_write(dmx_channel + 381, byte(value / 127. * 255));
    if (midi_channel == 5 && dmx_channel <= 4)
      dmx_write(dmx_channel + 508, byte(value / 127. * 255));
    if (midi_channel == 5 && dmx_channel > 4)
      dmx_write(512, byte(value / 127. * 255));
  }

  // Extra mapping for Roue
  if (midi_channel >= 6 && midi_channel <= 16) 
  {
    // Brightness, 50%-100%
    if (dmx_channel == 60 || dmx_channel == 7)
      dmx_write( (midi_channel-5) * 3 - 2, byte(value + 128));
    // Preset
    if (dmx_channel == 61)
      dmx_write( (midi_channel-5) * 3 - 1, byte(value / 127. * 255));
    // Speed
    if (dmx_channel == 62 || dmx_channel == 10)
      dmx_write( (midi_channel-5) * 3, byte(value / 127. * 255));
  }

}

void dmx_write(int channel, byte value) {
      DmxSimple.write(channel, value);
      
      Serial.print("Channel: ");
      Serial.println(channel, DEC);
      
      Serial.print("Value: ");
      Serial.println(value, DEC); 
      Serial.println("");
}

// Converts two 7-bit bytes into a 14-bit int
int seven_to_fourteen(char byte_a, char byte_b) {
  return byte_a & 127 | byte_b << 7;
}

Thanks !
 
Thanks Paul for the quick answer. That was the problem indeed, setting both to 512 solved the issue.
When setting it to 512 the refresh rate is very slow though :( Is that a limitation of the Teensy or the library ?
By chance my client had an artnet to dmx interface lying around that I was able to use but I'd be glad to get to the bottom of this....
 
It's mostly a limit of the library. DmxSimple uses 25% of the time to transmit (hogging the CPU to do so), and lets your sketch run the other 75%. But it's also a matter of DMX protocol, which has a fixed baud rate of 250 kbps. Sending more channels simply takes more time, even if using an approach that keeps the serial data flowing 100% of the time.

If you're using Teensy 3.1, you could edit the DmxSimple code to hog more CPU time. Or you could simple transmit data using the serial port, assuming you've connected your hardware to one of the TX pins.

For example, here's some code that sends DMX:

Code:
uint8_t buffer[513];

void xmit() {
  digitalWriteFast(12, HIGH);
  Serial1.begin(83333, SERIAL_8N1);
  Serial1.write(0);
  Serial1.flush();
  Serial1.begin(250000, SERIAL_8N2);
  digitalWriteFast(12, LOW);
  // first byte of buffer must be zero
  // to comply with DMX protocol
  Serial1.write(buffer, sizeof(buffer));
  Serial1.flush();
}

#define RED    0xFF0000
#define GREEN  0x00FF00
#define BLUE   0x0000FF
#define YELLOW 0xFFFF00
#define PINK   0xFF1088
#define ORANGE 0xE05800
#define WHITE  0xFFFFFF

void setup() {
  Serial1.begin(250000);
  pinMode(12, OUTPUT);
}

void loop() {
  colorWipe(RED);
  colorWipe(GREEN);
  colorWipe(BLUE);
  colorWipe(YELLOW);
  colorWipe(PINK);
  colorWipe(ORANGE);
  colorWipe(WHITE);
}

void colorWipe(int color)
{
  for (int i=0; i < 48; i++) {
    buffer[0] = 0;
    buffer[i*3 + 1] = color >> 16;
    buffer[i*3 + 2] = color >> 8;
    buffer[i*3 + 3] = color >> 0;
    buffer[i*3 + 1 + 144] = color >> 16;
    buffer[i*3 + 2 + 144] = color >> 8;
    buffer[i*3 + 3 + 144] = color >> 0;
    buffer[i*3 + 1 + 288] = color >> 16;
    buffer[i*3 + 2 + 288] = color >> 8;
    buffer[i*3 + 3 + 288] = color >> 0;
    buffer[25] = 0x5A;
    xmit();
    buffer[25] = 0xA5;
    xmit();
  }
}

If using the code, you might like to edit "serial1.c" inside arduino, to increase the transmit buffer to more than 514 bytes. Then xmit() will be able to send the whole thing quickly into the buffer, allowing your sketch to keep running. But this requires a little more hands-on approach, since you decide when to send, instead of putting the data into a buffer which a library automatically sends for you.

There's always lots of ways to do things....
 
But it's also a matter of DMX protocol, which has a fixed baud rate of 250 kbps

The strange thing is that the Artnet to DMX interface I ended up using seems very smooth so I don't think it's a problem related to the DMX speed.

I don't think the problem comes from my transmit code either which uses usbmidi.
If I set MaxChannels to 512 it is slow even if I send a single channel over USB letting me think it's the library indeed.

I'll try your snippet of code and see if I can optimize from there.

Should I report the library bug on github regarding the 128 channels vs 512 channels ?

Thanks Paul !
 
Sure, go ahead an open an issue on github.

I might do more with DmxSimple at some point, but my hope is to port something like the DmxSerial library at some point, or just publish something Teensy specific & optimized. I'm not sure I want to put more dev time into DmxSimple.

If you do ever get to the bottom of these performance issues and gain any insight, other than simple bandwidth, I hope you'll post a followup here. It can really help for the next time someone runs into such a problem.
 
I've found that mostly the timing is important for dmx (read: some ballasts have problems with bad timing). When i use the default library dmxsimple.cpp then on the scope the pause time between the channel packets is 2000uS. Optimally it should be reduced to the minimum, i think typically around 4uS ?
Looking at 1 datapacket (1 channel) it takes 500uS and then silence for 2000uS. In the cpp i reduced the DMXtimer to 750 and that gives me good results, about 250uS stop time between the channels. Perhaps we can reduce that even more?


Code:
#elif defined(CORE_TEENSY) && defined(__arm__)
  DMXtimer.begin(DMXinterrupt, 750);//original 2000
#endif


I tested it on a mockup system with only 3 dmx addresses but set the level of 1,2,3 + 150,152,152 + 510,511,512 to 100% (255) and every time the RGB ballast picks up correctly.

Hope that might give a good result for you too.
 
DmxSimple hogs the CPU while it's transmitting. The default setting allows other libraries and the main program to get CPU time.

On Teensy 2.0 and 8 bit AVR chips, this CPU use is a good trade-off.

For 32 bit Teensy, you can easily transmit DMX with regular serial at 250000 baud.

Code:
uint8_t buffer[513];

void xmit() {
  digitalWriteFast(12, HIGH);
  Serial1.begin(83333, SERIAL_8N1);
  Serial1.write(0);
  Serial1.flush();
  Serial1.begin(250000, SERIAL_8N2);
  digitalWriteFast(12, LOW);
  // first byte of buffer must be zero
  // to comply with DMX protocol
  Serial1.write(buffer, sizeof(buffer));
  Serial1.flush();
}
 
Absolutely correct. I used it on the Teensy 3.1 only. I can imagine that on the lower versions it's too fast.
 
If using the code, you might like to edit "serial1.c" inside arduino, to increase the transmit buffer to more than 514 bytes. Then xmit() will be able to send the whole thing quickly into the buffer, ...

The default is "#define TX_BUFFER_SIZE 40" When using this it seems to be working perfectly for the complete 512 dmx addresses. (verified with ballasts and seen on a scope) so i was wondering if this really needs to be done (increase to 513) ?
Seems like serialx_write is calling the serialx_putchar anyway, i presume that this sends it byte per byte ignoring the buffer size? Or does this hold some risks of buffer overflows?
 
AFAIK - When the TX buffer is filled the call doesn't return until the last data can safely be put into the buffer as it empties. You lose time waiting for needed transmission, but no data. If you increase the buffer then the call can fill the buffer and return as soon as the data transfers to the buffer.
 
Status
Not open for further replies.
Back
Top