Closed MrMdR closed 1 year ago
I have a number of comments on this program, but before I get to them, can you first define what you mean by “not working”? For example, what do you expect to happen, and what is actually happening instead?
Hi! Thanks for your reply. Docs example: won't compile, My try: executes the while regardless of dmx connection. The serial monitor prints 'No DMX signal' even tho I am sending a dmx signal to my Teensy.
I had multiple tries but this one was the one that compiled and seemed to be the most logical to me. What I was expecting was a boolean response. "Yes there is a dmx connection!" "No,I can't seem to find a dmx connection".
Thanks for helping me a letting me learn! :)
The first thing I noticed is that you never called dmxRx.begin()
. That needs to happen to enable the serial port and all the behind-the-scenes logic.
The Error counts and disconnection section and subsections in the README summarize what it means to "connect" and disconnect. The best way, in my opinion, to detect "connection" is to watch for a receive timeout, as in the Flasher
example.
Basically, loop to read a packet, and when a packet hasn't been seen within some timeout, that's kind of like a "disconnect". When packets start again, that can be considered a "connect". Here's some example code (similar to what the Flasher
example does):
// Demonstrates DMX connection detection by using timeouts.
#include <TeensyDMX.h>
namespace teensydmx = ::qindesign::teensydmx;
// Timeout after which it's considered that DMX is no longer sending.
constexpr unsigned long kDMXTimeout = 1000; // 1s
// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};
// DMX packet buffer.
uint8_t buf[513];
// Keeps track of when the last frame was received.
elapsedMillis lastFrameTimer;
// Keeps track of the connection state.
bool connected = false;
// Main program setup.
void setup() {
// Initialize the serial port
Serial.begin(115200);
while (!Serial && millis() < 4000) {
// Wait for initialization to complete or a time limit
}
Serial.println("Starting...");
// Start the DMX receiver and consider ourselves as "disconnected"
// to start by setting the timer to the timeout time
dmxRx.begin();
lastFrameTimer = kDMXTimeout;
connected = false;
}
// Called when the connection state changes.
void setConnected(bool flag) {
if (flag) {
Serial.println("Connected");
} else {
Serial.println("Disconnected");
}
}
// Main program loop.
void loop() {
int read = dmxRx.readPacket(buf, 0, sizeof(buf));
if (read > 0) {
// We've seen something, anything
if (!connected) {
connected = true;
setConnected(true);
}
// Reset the timer
lastFrameTimer = 0;
// Do stuff with the data
} else {
// Nothing was read, check for a timeout
if (lastFrameTimer >= kDMXTimeout) {
if (connected) {
connected = false;
setConnected(false);
}
}
// Possibly do other things here
}
}
(Note that I haven't tested the code with any hardware; let me know how it works for you.)
Another comment I have is about your use of FastLED. FastLED.show()
should be called at approximately a constant rate. Here's some demo code:
constexpr uint32_t kFPS = 30;
elapsedMillis ledTimer;
// ...
void loop() {
// ...
if (ledTimer >= 1000/kFPS) {
FastLED.show();
ledTimer = 0;
}
// ...
}
Hope this halps and gets you started.
Hi,
Thank you for getting back to me. Your code worked. :)
Only, there was a delay of 1 - 4 seconds from when I changed the dmx values in my DMX program till it was reflected in the Adressble light. I narrowed it down it had to do with the DMX processing.
The line int read = dmxRx.readPacket(buf, 0, sizeof(buf));
made it very slow. When I changed it to 'something smaller' (I suppose) like int read = dmxRx.readPacket(drgbs, 1, 20);
, it was back to normal response times. (Where drgbs points to uint8_t drgbs[20] {0};
. At the moment I only have 20 channels but I am planning to increase this to 240ch.
I guess the flagging for (dis)connection can not be done with a large led setup.
Thanks for your addition on using FastLED.show()
. Could you point me to some documentation or explain why this is a best practice?
Below my code if anyone whats to achieve the same thing. I learn from the internet so I give something back aswell. Some context: with this you can control a multitude of 8 led bars through 5 dmx channels individually. Dimmer, Red, Green, Blue, Strobe.
//***************DMX*************//
#include <TeensyDMX.h>
namespace teensydmx = ::qindesign::teensydmx;
// Timeout after which it's considered that DMX is no longer sending.
constexpr unsigned long kDMXTimeout = 1000; // 1s
// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};
// DMX packet buffer. (not used)
uint8_t buf[513];
// Keeps track of when the last frame was received.
elapsedMillis lastFrameTimer;
// Keeps track of the connection state.
bool connected = false;
// Buffer things
uint8_t drgbs[20] {0}; //Dimmer, rgb, strobe
//***************LED*************//
#include <OctoWS2811.h>
#define USE_OCTOWS2811
#include <FastLED.h>
#define SECTION_NUM 16 //How many sections you have. Has to be divisible by 8.
#define SECTION_LENGTH 190 //How many individual leds there are in each section.
#define NUM_LEDS (SECTION_LENGTH*16)
CRGB leds[NUM_LEDS];
//FastLED.show() should be called at approximately a constant rate.
constexpr uint32_t kFPS = 30;
elapsedMillis ledTimer;
void setup() {
///*** DMX *** //
// Initialize the serial port
Serial.begin(115200);
while (!Serial && millis() < 4000) {
// Wait for initialization to complete or a time limit
}
Serial.println("Starting...");
// Start the DMX receiver and consider ourselves as "disconnected"
// to start by setting the timer to the timeout time
Serial.println("Start DMX read");
dmxRx.begin();
lastFrameTimer = kDMXTimeout;
connected = false;
//** LED **//
LEDS.addLeds<OCTOWS2811, RGB>(leds, NUM_LEDS / 8);
Serial.print("Let there be light!");
setSection(0, 10, 50, 50, 50, 0); //Section_num, dimmer_val, red, green, blue, strobe
delay(2000);
setSection(0, 0, 0, 0, 0, 0);
}
// Called when the connection state changes.
void setConnected(bool flag) {
if (flag) {
Serial.println("Connected");
} else {
Serial.println("Disconnected");
}
}
void loop() {
//int iread = dmxRx.readPacket(buf, 1, sizeof(buf));
int iread = dmxRx.readPacket(drgbs, 20);
//Serial.println(iread);
if (iread > 0) {
// We've seen something, anything
if (!connected) {
connected = true;
setConnected(true);
}
// Reset the timer
lastFrameTimer = 0;
// Do stuff with the data
} else {
// Nothing was read, check for a timeout
if (lastFrameTimer >= kDMXTimeout) {
if (connected) {
connected = false;
setConnected(false);
}
}
// Possibly do other things down here
dmxRx.readPacket(drgbs, 1, 20); //buffer, first channels id, number of channels. (Read channels 10, 11, 12 = buff, 10, 3)
setSection(0, drgbs[0], drgbs[1], drgbs[2], drgbs[3], drgbs[4]); //Section_num, dimmer_val, red, green, blue, strobe
setSection(1, drgbs[5], drgbs[6], drgbs[7], drgbs[8], drgbs[9]);
setSection(2, drgbs[10], drgbs[11], drgbs[12], drgbs[13], drgbs[14]);
setSection(3, drgbs[15], drgbs[16], drgbs[17], drgbs[18], drgbs[19]);
//more sections to be added here
}
}
void setSection(uint8_t section_num, uint8_t dmxDimmer, uint8_t dmxRed, uint8_t dmxGreen, uint8_t dmxBlue, uint8_t dmxStrobe) {
// Take into account the Dimmer value and mapp them on the collor values.
uint8_t dimmerRed = map(dmxRed, 0, 255, 0, dmxDimmer);
uint8_t dimmerGreen = map(dmxGreen, 0, 255, 0, dmxDimmer);
uint8_t dimmerBlue = map(dmxBlue, 0, 255, 0, dmxDimmer);
Serial.printf("Section: %d | Dimmer: %d RGB: %d %d %d Strobe: %d \n", section_num, dmxDimmer, dimmerRed, dimmerGreen, dimmerBlue, dmxStrobe);
//Normal, open
if (dmxStrobe < 10) {
for (int i = (section_num * SECTION_LENGTH); i < (((section_num + 1 ) * SECTION_LENGTH) - 1); i++) {
leds[i].setRGB(dimmerRed, dimmerGreen, dimmerBlue);
}
}
//Strobe effect
if (dmxStrobe > 10) {
int strobeIntervalMs = map(dmxStrobe, 10, 255, 10, 250); //min in, max in, min uit, max uit
if ((millis() / strobeIntervalMs) % 2) {
for (int i = (section_num * SECTION_LENGTH); i < (((section_num + 1 ) * SECTION_LENGTH) - 1); i++) {
leds[i].setRGB(0, 0, 0);
}
} else {
for (int i = (section_num * SECTION_LENGTH); i < (((section_num + 1 ) * SECTION_LENGTH) - 1); i++) {
leds[i].setRGB(dimmerRed, dimmerGreen, dimmerBlue);
}
}
}
if (ledTimer >= 1000 / kFPS) {
FastLED.show();
ledTimer = 0;
}
}
Making your program slower with a larger readPacket()
size really surprises me. I'm using the std::copy_n()
function under the covers and that shouldn't take very long. Can you describe exactly the effect you're seeing? Are you sure the sluggishness is caused by passing a larger size for that function? Note that if you start reading a packet at position zero, the first byte will be the packet's start code. Might this indexing be related? i.e.buf[0]
will be the start code, but when you call readPacket(drgbs, 1, 20)
, drgbs[0]
will be slot 1.
Note: you should comment out the unused buf
if you're not using it because it will save about 513 bytes of space. Not that it matters for a small program, but it might affect a large program where saving half a kilobyte might be important.
To answer your FastLED.show()
question: I see you're using WS2811-based LEDs. This means that, while powered, you don't need to continually send pixel data; once it latches, the values should stay there. This is unlike DMX, which needs to continuously send. However, FastLED has time-based dithering that gets updated every time FastLED.show()
(or FastLED.delay()
) gets called. Calling it regularly will make the dithering better.
A follow-up question: If you call FastLED.show()
at some regular rate, do you still see the sluggishness with readPacket(buf, 0, 513)
vs. readPacket(buf, 1, 20)
?
Update: I'm skeptical that calling readPacket()
with larger values is that much slower than calling it with smaller values (even though I'm open to the possibility).
Hi! Below is my description of what I see happening. But first, it might be handy to know more about my setup.
Setup On my (win11) laptop I use Dot2 as lighting software to control DMX fixtures. Since Dot2 outputs only Art-Net (and I have DMX fixtures), I use Art-Net-to-DMX (from Freesyler) and an internal loopback adapter to send data via USB towards an Enttec DMXUSB pro. The setup software I'm using for a long time has never failed me. From that Enttec interface the DMX travels to my Teensy via a MAX485 breakout. Teensy uses OctroWS2811 to dive 8x GS8208 ledstrips (similar to WS2815). No other DMX fixtures are connected but will be in the future.
What I observe
When I uncomment int read = dmxRx.readPacket(buf, 0, sizeof(buf));
' (and comment the line below) I notice the response time is getting lower. For example; when I change the dimmer value in Dot2 (the dmx software); the led strip response is a few sec later. Especially with a fading effect, you see clear steps in brightness. When I open the serial monitor I can monitor the RGB values teensy sends to the ledstip (fasted). Although these values are updated constantly the value changes a few sec later when I change them in Dot2.
I changed 'readPacket(buf, 0, 513)
' to ' readPacket(buf, 1, 513)
' > same result
I change 'readPacket(buf, 1, 20)
' to 'readPacket(buf, 0, 20)
' > DMX channels are read wrong
" If you call FastLED.show()
at some regular rate" > The way I implemented 'FastLED.show()
' is at a regular rate right? In that case: Yes I still see the low response time.
readPacket()
First, I'd like to clarify how readPacket()
works, given your comment about 0, 20
being read wrong: The first argument is the buffer into which to store values, the second argument is the starting slot, and the third argument is the count. It will read up to, but maybe less than, the count, which is why you need to look at the return value to see how many have actually been read. To interpret all the examples:
readPacket(buf, 0, 20)
will read slots 0 through up to 19 inclusive, with slot 0 being the start code.1, 513
will always return zero because there are only a total of 513 slots (0-512), and these arguments tell the system to read slots 1-513; slot 513 doesn't exist.0, 513
will read slot 0 up to 512.1, 20
will read slot 1 up to 20.It's always wise to check how much has actually been received, otherwise there might be garbage sent to the connected hardware (in this case, the LEDs). If you control the DMX contents, then this situation is unlikely, but it's still good practice.
You're not calling FastLED.show()
regularly; the way you have it is that it's dependent on the DMX input, which might or might not be variable. You're also only updating the lights when there isn't DMX data. That might partially explain the slowness. That combined with only updating the LEDs non-regularly.
You also need to update setSection()
regularly because the strobing won't work correctly otherwise. I'd completely decouple the LED updates (that includes show()
and changing LED RGB values) from the DMX input. Here's what I believe should be done:
setSection()
and show()
.Here's some off-the-cuff demo code. Some notes:
#define NUM_LEDS (SECTION_LENGTH*16)
to #define NUM_LEDS (SECTION_LENGTH*SECTION_COUNT)
. Was this correct?SECTION_NUM
to SECTION_COUNT
.constexpr
values instead of defines.#include <OctoWS2811.h>
#define USE_OCTOWS2811
#include <FastLED.h>
#include <TeensyDMX.h>
//***************DMX*************//
namespace teensydmx = ::qindesign::teensydmx;
// Timeout after which it's considered that DMX is no longer sending.
constexpr unsigned long kDMXTimeout = 1000; // 1s
// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};
// DMX packet buffer.
uint8_t dmxBuf[513];
// Keeps track of when the last frame was received.
elapsedMillis lastFrameTimer;
// Keeps track of the DMX connection state.
bool dmxConnected = false;
//***************LEDs*************//
constexpr int kSectionCount = 16; // How many sections we have; must be divisible by 8
constexpr int kSectionLen = 190; // How many individual LEDs there are in each section
constexpr int kNumLEDs = kSectionLen * kSectionCount;
CRGB leds[kNumLEDs];
//FastLED.show() and any LED updates should be called at approximately a constant rate.
constexpr uint32_t kFPS = 60;
elapsedMillis ledTimer;
// Current information about one section.
struct SectionInfo {
CRGB rgb;
uint32_t framesPerStrobe = 0; // NOTE: Can't go faster than the chosen LED update rate
uint32_t strobeCounter = 0;
bool strobeState = false;
};
SectionInfo sections[kSectionCount];
// Main program setup.
void setup() {
// Initialize the serial port
Serial.begin(115200);
while (!Serial && millis() < 4000) {
// Wait for Serial connection or a time limit
}
Serial.println("Starting...");
// Set up the LEDs
LEDS.addLeds<OCTOWS2811, RGB>(leds, kNumLEDs/8); // Divide by 8 because it's an OctoWS2811
// Show the LEDs for a bit
Serial.println("Let there be light!");
setSectionInfo(0, 10, 50, 50, 50, 0); // Section, dimmer, red, green, blue, strobe
updateSectionLEDs();
FastLED.show();
delay(2000);
setSectionInfo(0, 0, 0, 0, 0, 0);
updateSectionLEDs();
FastLED.show();
// Start the DMX receiver and consider ourselves as "disconnected"
// to start by setting the timer to the timeout time
Serial.println("Starting DMX receiver");
dmxRx.begin();
lastFrameTimer = kDMXTimeout;
dmxConnected = false;
}
// Called when the DMX connection state changes.
// NOTE: This program doesn't really use connectedness other than to
// print the current state; it could probably be removed
void setDMXConnected(bool flag) {
if (flag) {
Serial.println("DMX connected");
} else {
Serial.println("DMX disconnected");
}
}
// Main program loop.
void loop() {
// Doing these things separately decouples DMX rate from LED rate
processDMX();
updateLEDs();
}
// Processes incoming DMX and stores values.
void processDMX() {
int read = dmxRx.readPacket(dmxBuf, 0, sizeof(dmxBuf));
if (read > 0) {
// We've seen something, anything
if (!dmxConnected) {
dmxConnected = true;
setDMXConnected(true);
}
// Reset the timer
lastFrameTimer = 0;
// Do stuff with the data
if (dmxBuf[0] == 0) { // Ignore non-zero start codes
int i = 1;
while (i + 5 <= read) {
// NOTE: setSection() rejects out-of-range sections
setSectionInfo((i - 1)/5, // Section
dmxBuf[i], // Dimmer
dmxBuf[i + 1], dmxBuf[i + 2], dmxBuf[i + 3], // RGB
dmxBuf[i + 4]); // Strobe
i += 5;
}
}
} else {
// Nothing was read, check for a timeout
if (lastFrameTimer >= kDMXTimeout) {
if (dmxConnected) {
dmxConnected = false;
setDMXConnected(false);
}
}
}
}
// Update the LEDs. This includes section strobing.
void updateLEDs() {
// LED and section update
if (ledTimer >= 1000 / kFPS) {
updateSectionLEDs();
FastLED.show();
ledTimer = 0;
}
}
// Sets information for one section.
void setSectionInfo(int section,
uint8_t dimmer,
uint8_t red, uint8_t green, uint8_t blue,
uint8_t strobe) {
if (section < 0 || kSectionCount <= section) {
return;
}
// Serial.printf("Section: %d | Dimmer: %u RGB: %u %u %u Strobe: %u \n",
// section, dimmer, red, green, blue, strobe);
// NOTE: It's okay to rewrite the values because they don't get used again
red = map(red, 0, 255, 0, dimmer);
green = map(green, 0, 255, 0, dimmer);
blue = map(blue, 0, 255, 0, dimmer);
SectionInfo &si = sections[section]; // NOTE: Use a reference and not a copy
si.rgb.setRGB(red, green, blue);
if (strobe < 10) {
si.framesPerStrobe = 0;
} else {
si.framesPerStrobe = map(strobe, 10, 255, 1, kFPS/4); // 1 frame to 1/4 second
// NOTE: There's some math you could do here to avoid strange
// flickering when the strobe rate changes
}
}
// Updates all the LEDs in a section from the section infos. This
// needs to be called regularly so strobing works. It is assumed that
// this is called at a frame rate of kFPS.
void updateSectionLEDs() {
// Q: Why did the loop end used to be minus one?
int ledIndex = 0;
for (int i = 0; i < kSectionCount; i++) {
SectionInfo &si = sections[i]; // Use a reference and not a copy
CRGB rgb;
// To strobe or not to strobe
if (si.framesPerStrobe == 0) {
rgb = si.rgb;
} else {
if (si.strobeCounter++ >= si.framesPerStrobe) {
si.strobeCounter = 0;
si.strobeState = !si.strobeState;
}
rgb = si.strobeState ? si.rgb : CRGB::Black;
}
for (int j = 0; j < kSectionLen; j++) {
leds[ledIndex++] = rgb;
}
}
}
There's likely to be problems with this code because it's off-the-cuff, but it's an outline of how I would approach this. Basically, the LED update and refresh is not coupled with the DMX data input. There's also another improvement that could be done to avoid flickering when the strobe rate changes.
It's also possible to have a Teensy 4.1 receive Art-Net and convert directly to the LEDs without any intervening DMX hardware. (I do this kind of thing all the time.) See also the https://github.com/ssilverman/QNEthernet library for Ethernet support (for Teensy 4.1 only — I'm not sure which Teensy model you're using).
Hope this helps.
@MrMdR Planning to close this. Did my example help?
Hi! I will get back to you in the upcoming days. Tx
Hello Silverman, First: Sorry for the late reply. Secondly: you are amazing! Thank you so much for helping out. Not only by giving me a working example code but also by adding notes in the code so I can better understand what you did and learn. I love that :). Not all is yet clear to me but I will get there I think!
At the moment I tested with a few strips and it all seems to work like a charm. Even with strobing I don't see any glitching happening. I will do some more testing in the upcoming 2 months. (Then the installation will be displayed during a voluntary art festival).
Thanks for the tip on the Teensy4.1 (I use a 3.2 atm) with ARTnet. Since it's my first time building a more professional setup I tough I stick to what I know (DMX). For the future, I'm planning to expand the setup and swap to Artnet. I will for sure look at your other lib! Since you say "I do this kind of thing all the time" It makes me curious about what you do! But that is for another threat ;).
Thanks again for your time @ssilverman!
Thanks for the kind words. Can we consider this question closed?
Feel free to reach out to me privately (eg. email or something) if you want to chat more about what I do. :)
Closing for now. Please re-open if this is still an issue.
Hi,
I try to detect whether my DMXreceiver receives a dmx signal. I wanted to use the
connected()
function. However, I don't seem to get it working. I think the docs are not written for an Intermediate level programmer so I'm a bit stuck.I would like to keep looping a function when no DMX signal is detected. The example code in the docs gives an error. I try to put something together myself but also had no luck so far. What would be the correct implementation of connection detection for my use case?
Docs example:
My try:
Full code