Can't get Teensy LC to receive DMX

Status
Not open for further replies.

GardG

Member
I'm trying to get a Teensy LC to receive DMX using the TeensyDMX library (and testing with TeensyDmx as well), but I'm stuck. I could've sword I've gotten it to work before, but now I just for the life of me can't get it to receive.

I'm using a Maxim 3.3V transceiver. I'm getting the LC to send DMX, but not receive it. (I've wired DE/_RE to a Teensy pin).

I know the hardware is OK; I've checked the output of the transceiver on the scope, and it seems fine. I have even connected the transceiver TTL output to an Arduino Leonardo, which happily accepts the signal as valid DMX using a different library.

In my initial testing the LC would appear to freeze (serial output would stop abruptly) when connected to a valid DMX source. After some messing around with updating Teensyduino and the libraries, it seems that it doesn't freeze anymore, but it fails to decode the DMX signal.

Both the libraries are advertised as compatible with the LC, so I'm a bit at loss as to why this isn't working. Any clues?
 
Hi, Gard! I’m going to have a look at and re-test Teensy LC support for TeensyDMX.

Could you tell me more about your system? For example:
  • How many channels are in each DMX frame?
  • What is the transmission rate?
  • Can you describe the electrical setup, for example, what kind of connectors and terminations are there, what kind of cabling, and how far is the transmitter from the Teensy LC?
  • For one of the minimal example programs included with TeensyDMX, do you still see the issue, or is it only when you incorporate DMX into a larger program?
  • Can you provide code for the smallest program that can reproduce this issue?

Thanks!
 
Hi Shawn, thanks a lot for your reply!

At the moment I'm using an ELC Checker DMX tester as the transmitter. I have also tried with a Madrix Plexus Artnet node, with the same results. In the final installation, we'll probably be using an MA Lighting 8-port node. I initially used the "max" transmission settings on the Checker (out of habit), but changed to "normal" and "safe" with the same result. The timings are:

Normal:
Rate: 33 ms
Break: 200µs
MAB: 20µs
Ch2ch: 0µs

Safe:
Rate: 40 ms
Break: 500µs
MAB: 100µs
Ch2ch: 10µs

Max:
Rate: 22.6ms
Break: 88µs
MAB: 8µs
Ch2ch: 0µs

In all cases, all 512 channels are being sent.


The electrical setup consists of the Teensy LC and a MAX3483 on a prototyping board. The DMX signal is connected via screw terminals very close to the transceiver. There is a .1µF decoupling cap at the MAX' power pins. The wires from the terminals to the transceiver are short (10mm approx) and of equal length for the true and the inverted signal. For bench testing I'm using hook-up wire (not twisted pair, not shielded) to connect to the transmitter (3 wires, A/B/GND), and I'm not terminating the line. The wires to the trasmitter are approx 10 cm long. The output from the MAX looks fine, and an Arduino with a different DMX library accepts the TTL output from the MAX.

The issue is there with the included example programs as well. Currently I'm using a very stripped-down version of the included receiver test:

Code:
#include <cstring>
#include <TeensyDMX.h>

namespace teensydmx = ::qindesign::teensydmx;

// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};

// The last value on the channel, for knowing when to print a change
// (Example 1).
uint8_t lastValue = 0;


void setup() {
  // Serial initialization, for printing things
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for initialization to complete or a time limit
  }
  Serial.println("Starting BasicReceive.");

  // Turn on the LED, for indicating activity
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWriteFast(LED_BUILTIN, HIGH);


  pinMode(2, OUTPUT);
  digitalWriteFast(2, HIGH);

  // Start the receiver
  dmxRx.begin();

  // Print the first values
  lastValue = dmxRx.get(1);
  Serial.printf("Channel 1: %d\n", lastValue);

}

void loop() {


  Serial.println(dmxRx.get(1));
  delay(50);
  
}


At the moment it just prints zeroes, regardless of what DMX values I transmit. Yesterday, it would stop transmitting altogether when a DMX source was connected – that does not seem to be happening now.

Curiously, using a transmitter example sketch works perfectly …
 
That's the MAX' DE/_RE pin, and setting it high … enables the line driver and disables the receiver. Yeah. That's a bit embarassing.

I changed it back to LOW, as it should be, and I'm now back at the behaviour where the serial output abruptly stops when a DMX source is connected.

FWIW, to ensure that numptiness like this is not causing furither issues, I connected the MAX TTL out straigt to the RX pin of a Leonardo and made it control its built-in LED over DMX, so the transceiver is working and is now, again, in RX mode …
 
Last edited:
!!!!!!!

It does!!!

I tried a random selection of older versions with varying degrees of luck earlier, but not 3.1.1 apparently.

Ace – I think this should get me through for this project. I don't think missing out on the changes between 3.2.0 and 3.1.1 will be an issue for now. Thanks a lot for the help :D
 
Thank you for helping narrow that down. If v3.2.0 is the culprit then clearly I’ve introduced a problem. I’ll dive in.
 
So the application for this is to control some SK6812 strips and a Xenon strobe (trigger triac on pin 13) – I'm almost there (but with a bit of of code cleanup remaining!), using a mash-up of the included DMX receiver and flasher examples, just one last hurdle besides cleaning it all up – it's reacting a bit slowly. The LED strip seems to refresh approx once a second, and there is a bit of delay in the strobe – and it's not maintaining a steady flash rate, it for 500-1000ms every few seconds.

I'll look at this tomorrow (it's getting late here), but are there any glaringly obvious mistakes in this code that would slow things down?



Code:
#include <cstring>

#include <TeensyDMX.h>
#include <Adafruit_NeoPixel.h>

#define PIX_PIN     17
#define PIX_COUNT   120
#define PIX_BRIGHT  255

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Receiver dmxRx{Serial1};

Adafruit_NeoPixel strip(PIX_COUNT, PIX_PIN, NEO_GRBW + NEO_KHZ800);

// Buffer in which to store packet data
uint8_t packetBuf[120]{0};
// The last values received
uint8_t rgb[120]{0};

constexpr int kChannel = 122; // strobe channel
constexpr unsigned long kDMXTimeout = 1000;  // 1s
constexpr uint8_t kLEDPin = LED_BUILTIN;

// Constants for flashing rate
constexpr int32_t kPeriodMax = 500;
constexpr int32_t kPeriodMin = 50;
uint8_t buf[1]; // Buffer used for reading the DMX data.
uint8_t lastValue; // The last value received on kChannel.
elapsedMillis lastFrameTimer; // Keeps track of when the last frame was received.

// Flashing state
int32_t period = kPeriodMax;
int64_t phi = 0;

void setup() {
  
  pinMode(2, OUTPUT);
  digitalWriteFast(2, LOW);
 
  strip.begin();
  strip.show();
  strip.setBrightness(PIX_BRIGHT);

  pinMode(kLEDPin, OUTPUT);
  digitalWriteFast(kLEDPin, LOW); 

  // Start the receiver
  dmxRx.begin();
  lastFrameTimer = kDMXTimeout;

}

void loop() {
 
  // A return of -1 means no data, and a value < 120 means that there was data,
  // but the received packet wasn't large enough to contain all chs
  int read = dmxRx.readPacket(packetBuf, 1, 120);
  if (read == 120) {
    if (memcmp(packetBuf, rgb, 120) != 0) {
      memcpy(rgb, packetBuf, 120);

      for(int i = 0; i < 120; i=i+4){
        for(int p = 0; p < 4; p++){
          strip.setPixelColor(i+p, strip.Color(rgb[i], rgb[i+1], rgb[i+2], rgb[i+3]));
        }
      }
      strip.show();      
    }
  }
  
  int readS = dmxRx.readPacket(buf, kChannel, 1);
  int64_t t = millis();
  if (readS > 0) {
    // We've read everything we want to
    lastValue = buf[0];
    lastFrameTimer = 0;
  }


  if (lastFrameTimer <= kDMXTimeout) {
    if (lastValue > 0 && lastValue < 7) {
      digitalWriteFast(kLEDPin, LOW);
    } else if (lastValue > 8 && lastValue < 14) {
      digitalWriteFast(kLEDPin, HIGH);
    } else if (lastValue > 15) {
        
        // Use a wave equation to make the speed-ups and slow-downs smoother using
        // the offset, phi
        int32_t newPeriod = map(lastValue, 0, 255, kPeriodMax, kPeriodMin);
        if (newPeriod != period) {
          phi = (t*(period - newPeriod) + newPeriod*phi)/period;
          period = newPeriod;
        }

        int32_t v = (t - phi)%period;
        if (v < period/2) {
          digitalWriteFast(kLEDPin, HIGH);
        } else {
          digitalWriteFast(kLEDPin, LOW);
        }
        
    
    } else {
      digitalWriteFast(kLEDPin, LOW);
    }
  }
}


FWIW, I've tried commenting out the LED strip parts (including the initialisation of the strip), but the strobe is still slow. And the LED strip was reacting slowly before added in the strobe part.

I'll take a closer look at this tomorrow, and possibly start a new thread, just thought I'd ask before signing off for the night, just in case someone can easily see where the slowness comes from.

(I'm well aware the code is a bit of a mess!)
 
A few notes on your code:
  1. To confirm, you're making every 4 pixels the same?
  2. Move the `strip.Color` call outside the inner loop because it doesn't depend on `p`.
  3. Call `readPacket` just once. For the first check, do `read >= 120` and for the second, you can dispense with `readS` and just do `read == 122` instead of `readS > 0`. Of course, you'd need the value of `packetBuf[121]` instead of `buf[0]`, and to increase the size of `packetBuf` to 122 instead of 120. This will avoid one internal lock by calling `readPacket` once instead of twice.
  4. Your strobe code will turn the light off when the value is 0, 7, 8, 14, or 15.
  5. For the strobe, do you want to map the values 15-255 to a period of 500-50ms, or do you want to map 0-255 to 500-50ms? Your current code does the second.
  6. I'm curious how long the strip-related code takes. Maybe collect some statistics around that block and print every second or so or something? Eg. measure the time before and after that block.
  7. DMX is only transmitted at up to around 44Hz, so that should rate limit how fast the strip is written to, but maybe try adding some code that doesn't allow strip update faster than some rate.
  8. I'm curious what a small delay at the end of the loop would do, eg. 20ms. And then alternately comment out the strip code and the strobe code, to see the effect. (For mathematical posterity, this value should divide "most" of the possible period values for the strobe since the strobe timer values are being polled in a loop and not hooked up to a timer.)
Those are just some initial core notes.

(And don't forget to try the new release; see the previous post, in case you're reading this and miss it.)
 
Last edited:
Code:
Thanks a lot for the feedback!

- Every 4 pixels should be the same – I'm using 144 LED/m strip for the sake of output power, and grouping them together in bunches of 4 to save DMX channels.
- The strobe trigger triac is connected to pin 13 through a high pass filter that makes it trigger on a rising edge. It's intended to work as follows:

DMX 0-7: off
DMX 8-14: single flash
DMX 15-255: flash rate slow to fast

I tried installing 4.0.0A, but it doesn't seem to work – haven't checked very closely, but the test code from my earlier post seems to only return zeroes. It does not seem to freeze, though.

I made some changes to the code above, but nothing seems to have changed. Here are the current versions:


Strobe only, no pixels:
Code:
#include <cstring>

#include <TeensyDMX.h>

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Receiver dmxRx{Serial1};

// Buffer in which to store packet data
uint8_t packetBuf[122]{0};

constexpr int kChannel = 122; // strobe channel
constexpr unsigned long kDMXTimeout = 1000;  // 1s
constexpr uint8_t kLEDPin = LED_BUILTIN;

// Constants for flashing rate
constexpr int32_t kPeriodMax = 500;
constexpr int32_t kPeriodMin = 50;
uint8_t buf[1]; // Buffer used for reading the DMX data.
uint8_t lastValue; // The last value received on kChannel.
elapsedMillis lastFrameTimer; // Keeps track of when the last frame was received.

// Flashing state
int32_t period = kPeriodMax;
int64_t phi = 0;

void setup() {
  
  pinMode(2, OUTPUT);
  digitalWriteFast(2, LOW);

  pinMode(kLEDPin, OUTPUT);
  digitalWriteFast(kLEDPin, LOW); 

  // Start the receiver
  dmxRx.begin();
  lastFrameTimer = kDMXTimeout;
}

void loop() {
  // return of -1 means no data
  int read = dmxRx.readPacket(packetBuf, 1, 122);
  
  int64_t t = millis();
  if (read == 122) {
    lastValue = packetBuf[121];
    lastFrameTimer = 0;
  }

  if (lastFrameTimer <= kDMXTimeout) {
    if (lastValue > 0 && lastValue < 8) {
      digitalWriteFast(kLEDPin, LOW);
    } else if (lastValue > 8 && lastValue < 15) {
      digitalWriteFast(kLEDPin, HIGH);
    } else if (lastValue > 15) {
        
        // Use a wave equation to make the speed-ups and slow-downs smoother
        int32_t newPeriod = map(lastValue, 15, 255, kPeriodMax, kPeriodMin);
        if (newPeriod != period) {
          phi = (t*(period - newPeriod) + newPeriod*phi)/period;
          period = newPeriod;
        }

        int32_t v = (t - phi)%period;
        if (v < period/2) {
          digitalWriteFast(kLEDPin, HIGH);
        } else {
          digitalWriteFast(kLEDPin, LOW);
        }
        
    } else {
      digitalWriteFast(kLEDPin, LOW);
    }
  }
}


Pixels only, no strobe:
Code:
#include <cstring>

#include <TeensyDMX.h>
#include <Adafruit_NeoPixel.h>

#define PIX_PIN     17
#define PIX_COUNT   120
#define PIX_BRIGHT  40

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Receiver dmxRx{Serial1};

Adafruit_NeoPixel strip(PIX_COUNT, PIX_PIN, NEO_GRBW + NEO_KHZ800);

// Buffer in which to store packet data
uint8_t packetBuf[122]{0};
// The last values received
uint8_t rgb[122]{0};

void setup() {
  pinMode(2, OUTPUT);
  digitalWriteFast(2, LOW);

  strip.begin();
  strip.show();
  strip.setBrightness(PIX_BRIGHT);

  // Start the receiver
  dmxRx.begin();
}

void loop() {
 
  // return of -1 means no data
  int read = dmxRx.readPacket(packetBuf, 1, 122);


  // check if enough data for all LEDs was received
  if (read >= 120) {
    if (memcmp(packetBuf, rgb, 120) != 0) {
      memcpy(rgb, packetBuf, 120);
      // group LEDs in 4's
      for(int i = 0; i < 199; i=i+4){
        for(int p = 0; p < 4; p++){
          strip.setPixelColor(i+p, rgb[i], rgb[i+1], rgb[i+2], rgb[i+3]);
        }
      }
    }

  strip.show(); // refresh strip only if enough data was received
  }
}


Full version:
Code:
#include <cstring>

#include <TeensyDMX.h>
#include <Adafruit_NeoPixel.h>

#define PIX_PIN     17
#define PIX_COUNT   120
#define PIX_BRIGHT  40

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Receiver dmxRx{Serial1};

Adafruit_NeoPixel strip(PIX_COUNT, PIX_PIN, NEO_GRBW + NEO_KHZ800);

// Buffer in which to store packet data
uint8_t packetBuf[122]{0};
// The last values received
uint8_t rgb[122]{0};

constexpr int kChannel = 122; // strobe channel
constexpr unsigned long kDMXTimeout = 1000;  // 1s
constexpr uint8_t kLEDPin = LED_BUILTIN;

// Constants for flashing rate
constexpr int32_t kPeriodMax = 500;
constexpr int32_t kPeriodMin = 50;
uint8_t buf[1]; // Buffer used for reading the DMX data.
uint8_t lastValue; // The last value received on kChannel.
elapsedMillis lastFrameTimer; // Keeps track of when the last frame was received.

// Flashing state
int32_t period = kPeriodMax;
int64_t phi = 0;

void setup() {

  Serial.begin(9600);
  
  pinMode(2, OUTPUT);
  digitalWriteFast(2, LOW);

  
  strip.begin();
  strip.show();
  strip.setBrightness(PIX_BRIGHT);
  

  pinMode(kLEDPin, OUTPUT);
  digitalWriteFast(kLEDPin, LOW); 

  // Start the receiver
  dmxRx.begin();
  lastFrameTimer = kDMXTimeout;

}

void loop() {
 
  // return of -1 means no data
  int read = dmxRx.readPacket(packetBuf, 1, 122);


  // check if enough data for all LEDs was received
  if (read >= 120) {
    if (memcmp(packetBuf, rgb, 120) != 0) {
      memcpy(rgb, packetBuf, 120);
      // group LEDs in 4's
      for(int i = 0; i < 199; i=i+4){
        for(int p = 0; p < 4; p++){
          strip.setPixelColor(i+p, rgb[i], rgb[i+1], rgb[i+2], rgb[i+3]);
        }
      }
    }

  strip.show(); // refresh strip only if enough data was received
  }

  
  int64_t t = millis();
  if (read == 122) {
    lastValue = packetBuf[121];
    lastFrameTimer = 0;
  }


  if (lastFrameTimer <= kDMXTimeout) {
    if (lastValue > 0 && lastValue < 8) {
      digitalWriteFast(kLEDPin, LOW);
    } else if (lastValue > 8 && lastValue < 15) {
      digitalWriteFast(kLEDPin, HIGH);
    } else if (lastValue > 15) {
        
        // Use a wave equation to make the speed-ups and slow-downs smoother
        int32_t newPeriod = map(lastValue, 15, 255, kPeriodMax, kPeriodMin);
        if (newPeriod != period) {
          phi = (t*(period - newPeriod) + newPeriod*phi)/period;
          period = newPeriod;
        }

        int32_t v = (t - phi)%period;
        if (v < period/2) {
          digitalWriteFast(kLEDPin, HIGH);
        } else {
          digitalWriteFast(kLEDPin, LOW);
        }
        
    
    } else {
      digitalWriteFast(kLEDPin, LOW);
    }
  }
}
 
OK, that was another rather embarrasing mistake from my side. I had moved my test sending over to a lighting console, and had made some mistakes in the network setup there, so the signal only reached the DMX node at 5 Hz. I fixed that, and now it's super smooth. No wonder it felt a bit more responsive with the Checker than the console. I really need to get some proper sleep some time soon.

Well, seems it works! With 3.1.1 at least – 4.0.0 still doesn't seem to do much.

Thanks a lot for the help!
 
I think I found the problem. See the new v4.0.0-alpha.1 release for a fix.

I'm using bit 9 (the R8 bit of UARTx_C3) to validate that the first stop bit is high, and if it's low, I accumulate one framing error. On the Teensy LC, this bit always seems to return 0, even for the 8N2 mode (which uses bit 9 as the first stop bit). This check was changed to always return true for Teensy LC.

Also, some side notes:
  1. Your code still sets the LED to LOW if the value is 0, 8, or 15. 8 does not do "single flash" and 15 does not do a slow rate.
  2. For clarity, may I suggest renaming `kChannel` and `lastChannel`?
  3. The `buf` variable can be deleted.
 
Last edited:
For posterity, I’ll post the reason why R8 is zero on a Teensy LC. It’s because there’s a 2-stop-but mode and the check is not necessary, as for Teensy 3.2 and below. Same for Teensy 3.5 and 3.6. The latest fixes for all the Teensy boards are in v3.2.2. Release v4.0.0-alpha.1 has the fix for Teensy LC but not for Teensy 3.5 or 3.6.
 
Status
Not open for further replies.
Back
Top