Advice on program construction - DMX

SteveSFX

Well-known member
Hey all

After 2 weeks typing and 4000+ lines, I think I need to start my Teensy code all over.

I KNOW you want me to post it, but believe me... it's got so complicated, it's unfathomable. I don't want the backlash of how bad my coding is.

The Teensy4.1 is driving 5x DMX Universes with the Teensy DMX Library. That all works fine. In fact, my original code works fine at the moment, but it's going to crawl once all the data starts getting sent.

The outline. 5x DMX Universes. Each Universe has 10x possible 'channels'.
Each channel has an address, a target DMX value, and a method of getting to that target DMX value (on/off/ramped/timed etc).

All addresses for each Universe, target values, timings etc are stored in arrays of 10 values.

The main loop watches 5x input pins. If one goes high, then that Universe needs to transmit DMX data for that associated DMX Universe.

Every 100ms, if an input is ON, the main loop goes out and sees if any of the DMX needs updating (which is highly likely).
If the input then goes OFF, the main loop instantly jumps out and sees what needs turning off.

I run through a for-loop (using switch/case) for the 10x channels associated with that Universe.
This for loop checks to see what kind of output each of the 10 channels has been allocated, and then if that Universe should be updated.

If it needs updating, then it does so.

Some of these are simple 'If the input is ON, then output ###', if the input is off, then send zero. These have status flags to ensure the DMX value is only ever sent once.

The problem has start because the more advance options are getting very involved. The most complicated is the RAMP UP to a value, HOLD for a time, and RAMP back down when the input goes off.
This means tracking values after the input has been turned off, but the value isn't being turned off instantly... I need to continue updating this value at a timed speed in the loop until it reaches zero again.

Difficult to expalin and I don't expect anyone to help here.... think I need to just get it off my chest! Got number blindness.

The main problem will be speed. Updating DMX values at 100ms is slow. The light I am controlling has visible steps in its brightness as it comes on. Yet less than 100ms doesn't seem to give enough time (the timings for the ramp functions become inaccurate).

If all 5 Universes were active, and all 10 channels of each Universe are used... that is 50x DMX signals per 100ms.... probably WAY too much for the Teensy 4.1

I write all my code in one (organised) lump. Never used classes etc as I don't really understand them.

But, do they help with achieving faster code? Or is it basically a 'tidyness' thing.
I think my code is as efficent as I can make it.

This is a hobby project, so it was never going to be super efficent, but I have put some time into this.

Not exactly sure what I am asking here....
 

Attachments

  • DMX.png
    DMX.png
    283.1 KB · Views: 23
Yours Paul? #include <TeensyDMX.h>

Its a little project for a local theatre that has got out of hand!
 
The first thing I am going to do it tidy up and reduce the code..

Best way to do that I think, is to try and understand classes and objects. Am I right in thinking Classes are basically a 'template' of a function you wish to perform (like a library) and an object is a collection of the data contained within that class?

I am going to take out things like my EEPROM data saves, EEPROM reset (factory reset), SD card download, Full serial screen data dump etc and convert them to classes. This takes that huge amount of 'code' out of my current 4000+ lines.

This might make it easier for me to start tidying up.
 
Is using classes actually faster than having code in the main loop? From my Googling, it looks maybe not? (just tidier)
 
Can you not make classes in the Arduino IDE using new tabs? (opened next to your main code)

I opened new tabs and put a basic class in there, and they show up in the main codes directory, yet the main code can't find them.
Says no such file or directory, yet they are clearly there.

Update... error on the goddam classes guidance page online.
Classes will not improve my code.
 
Last edited:
Hi Steve.
Several things about your post have me interested and would love to see those 4000 or so lines of code.

Interesting bits:

For eight years I've updating hardware and software on some christmas lights (about 3000 ws2811 leds) which has roughy 8000 lines of code and incoherent comments. Your "more advanced options" are bound to match some form of one of my 60 or so lighting sequences and I would be interested to see how another amature approaches these problems.

Is that a nextion display. I use them with varying success and wonder how you use them.

DMX isn't something I've not looked at but am interested in. I'm sure your code will show the basics of how it is used.

"Classes will not improve my code"... I love classes. Purely as a way to think about a problem as groups of objects. I suspect after compilation it adds nothing speed wise. I wouldn't know where to start without classes. I start with some basic element of my program and create a class (say a Light) , put in all the variables I think i will need to represent that element ( say Red Green Blue values) and declare a heap of functions that I think I will need for that element. (turn on, turn off, fadeOn fadeOff) then I might start writing the code at a higher level using those functions without thinking about the nitty gritty of getting the lower level stuff working. The act of attemting the higher level stuff usually hase me requiring and therefore adding more functions and data structures to my lower level objects (Lights). At some point I start compiling which points me to all the lowere level functions that I didn't complete and then I one by one flesh them out.

"Its a little project for a local theatre that has got out of hand!"... With my project, which I won't continue next year, I would have loved input on programming advice, algorithym advice, hardware advice but didn't know how to tap into it.

Anyhow. My code is full of nonsense comments, unused functions, bugs and hacks to work around them and every year I spend hours to try and work out how I do things. If you were to post the code in full there will be no judgement.

Gavin.
 
The Nextion is a learning curve, but works very well if you implement it correctly. Shame they are so expensive (still).

I design all my pages in Photoshop in both 'states', and then you can achieve some pretty sweet looking (and working) screens.
I have a Photoshop default file that has all my usually used buttons (which I drew myself ages ago) and I just switch them on am move them around as required.

I don't use the Nextion library at all.

The Nextion editor is HORRIBLE. You soon learn it's faults and limits however. Gauges, progress bars etc work, but there are specific ways to make them efficient.

As for the DMX... it's pretty simple to use. My problem is I am pushing the processor too hard. 50x channels with live updated values is pretty hefty work I think.

I tinkered with classes this afternoon, but I don't think they help me. My code is segregated, and well commented.
No 'delay' statements (unless outside the look in a non-blocking area... e.g. a menu).

For reducing the code length, then I am sure classses would reduced my code considerably. But if it doesn't change the speed (or maybe even slow it down further?), then at this point it gains me nothing.

I will learn them, but not now.
 

Attachments

  • DMX-2.png
    DMX-2.png
    234.7 KB · Views: 21
The main problem will be speed. Updating DMX values at 100ms is slow. The light I am controlling has visible steps in its brightness as it comes on. Yet less than 100ms doesn't seem to give enough time (the timings for the ramp functions become inaccurate).

If all 5 Universes were active, and all 10 channels of each Universe are used... that is 50x DMX signals per 100ms.... probably WAY too much for the Teensy 4.1

I think the Teensy can handle this. The DMX runs asynchronously, and the DMX is constantly being output (it’s a continuously-transmitting protocol). In fact, it can transmit on all 8 serial ports at the same time. So really, the limiting factor is the speed at which you can process whatever your input is doing.

Sounds like a state machine would be useful for you upon “input events.” (I don’t have the time at the moment to describe those here.)

Regarding the lights having discrete steps, that might be a timing issue, but it might also be the resolution of the LEDs themselves.

Yours Paul? #include <TeensyDMX.h>

Its a little project for a local theatre that has got out of hand!

I made that library. :)
 
The trouble I have is to update (a maximum) of a possible 50 channels with incremental values.

for instance:

A for(i) loop checks all 10 channels each 100ms (of 1 Universe, and there are 5).

dmxuni_1TMP = dmxuni_1TMP + (round((dmxuni_1VAL / (1000 / dmxupdate) / dmxuni_1RUP)));

This is incrementing dmxuni_1TMP every 100ms. It is incrementing the DMX value that is going to be sent.
dmxuni_1 is obviously Universe 1. TMP is the temp value while it is being incremented.

dmxuni_1VAL is the target DMX value (1-255) that I am incrementing up to.
dmxupdate is the update timing value in ms (that can be altered in setup)
dmxuni_1RUP is the timing value in seconds that the process takes to get from 0 to the target DMX value.

After this calculation, the DMX value is sent out using

dmxTx1.set(dmxuni_1ADD, dmxuni_1TMP);

dmxuni_1ADD is obviously the required DMX address.

I have NO DOUBT this is overly complicated or could be written better, but I don't know how.

All of this works fine, but increasing the timing to less than 100ms makes the timing inaccurate. It's not the lights, its because the steps in DMX value required to reach the target in the set time are too large (because you are not updating this equation often enough).

I have written this 5 times and this stupid text thing keeps removing the formatting so the above doesn't quite make sense. These variables should have the array number after them, but this text formatter keeps removing them.

Don't worry about it!
 
Last edited:
DMX only updates at about 44Hz (22.7ms duration) (for 513-byte packets; it’s faster if the packet contains fewer slots). (For those that don’t know this fact already.)

You could try reducing the packet size for a much faster DMX update rate. (Assuming the equipment works with that.)
 
As a couple of side questions on the same project..

I have a lot of Serial.prints to the Touch screen (and a few to the debug monitor).
By default, I have got in the habit of using:

Serial7.print(F("Test")); Serial7.print(F("\xFF\xFF\xFF")); instead of Serial7.print("Test"); Serial7.print("\xFF\xFF\xFF");

I just read that adding the 'F' is slower? To be honest, it doesn't make much difference to my code speed, as most of my screen prints are done outside of speed critical loops. I started doing this because Arduino's were running out of memory, but I don't have that issue on a Teensy.
But, in the never ending search for code efficiency and all that...

For-loops. I check the status of the 10x DMX channels in a for loop every 100ms that contains a switch/case to check all 10 channels.
The For-loop runs 10 times, checking the array stored 'status' of each channel.
That 'status' is whether that channel is actually in use at that time, and if it is, then what kind of output it should be providing.
If that 'status' == 0, then just loop and try the next channel.

If it is in use (>0), then the 10 slot switch/case runs the appropriate output maths and sends the DMX value.

Is this more speed efficient than simply repeating the same command set 10 times in a row? It certainly makes for less code.

If I moved this entire process to a class, I assume that would actually slow things down slightly.

I may start registering the actual speed of the loop (micros?). Using this, I might be able to automatically adjust the update time of the DMX outputs depending on the work load of the loop.

I knew this was going to get complicated.

I should add, thank you for all the time and assistance put my way. It is greatly appreciated.
Once I get this code dragged into some kind of sense, I will post it.
 
By default, I have got in the habit of using:

Serial7.print(F("Test")); Serial7.print(F("\xFF\xFF\xFF")); instead of Serial7.print("Test"); Serial7.print("\xFF\xFF\xFF");

I just read that adding the 'F' is slower? To be honest, it doesn't make much difference to my code speed, as most of my screen prints are done outside of speed critical loops. I started doing this because Arduino's were running out of memory, but I don't have that issue on a Teensy.
But, in the never ending search for code efficiency and all that...
F() is essentially a noop on teensy.
See WString.h for its definition
 
Anyone got a pointer to a decent classes/object demo?
I tried this one and it doesn't work


Main sketch:

Code:
#include "Arduino.h”
#include "MyClass.h"
MyClass::MyClass(int pin) {
  pinMode(pin, OUTPUT);
  _pin = pin;
}
void MyClass::myFunction(int blinkRate){
digitalWrite(_pin, HIGH);
delay(blinkRate);
digitalWrite(_pin, LOW);
delay(blinkRate);
}

MyClass.h

Code:
#ifndef MyClass_h
#define MyClass_h
#include "Arduino.h"
class MyClass {
public:
  MyClass(int pin);
  void myFunction(int blinkRate);
private:
  int _pin;
};
#endif

MyClass.cpp

Code:
#include "Arduino.h”
#include "MyClass.h"
MyClass::MyClass(int pin) {
  pinMode(pin, OUTPUT);
  _pin = pin;
}
void MyClass::myFunction(int blinkRate){
digitalWrite(_pin, HIGH);
delay(blinkRate);
digitalWrite(_pin, LOW);
delay(blinkRate);
}

Throws up
C:\Users\steve\Documents\Arduino\Tutorials\Flashing_2_leds\Flashing_2_leds.ino:1:21: fatal error: MyClass.h: No such file or directory

All three live in the same folder. Can't even get the basics to work!
 
Last edited:
So #include "Arduino.h” has the wrong kind of " at the end (don't copy paste lesson there) .... and it should be #include <Arduino.h> anyway

Brilliant
 
So, clearly a lot wrong with my effort here. I thought I would try and move my serial debug routine out to a Class. I haven't sussed this out yet.

I could not see how you stop a routine placed in the .cpp files running continually.
I was going to set a flag to only run it once.

My Serialdebug.h:

Code:
#ifndef Serialdebug_h                                   
#define Serialdebug_h             
#include "Arduino.h"                                     

class Serialdebug {                                                  
 
  public:                                                             
    Serialdebug();                                                    
    void printDATA(bool runflag);                             // runflag is to be used so the report only runs once        

  private:                                                             

    byte dmxUNI;                                                        // The variables for the data being retrieved from EEPROM (to print the report)
    byte magpenboot;
    bool maglockout;
    bool magscreenpower;
    bool magfulltest;
    int tempval;
    byte testrangelowByte;                                               
    byte testrangehighByte;
    int testrange;                                                 
};
#endif


Then my Serialdebug.cpp (no actual run code in it yet):

Code:
#include <Arduino.h>
#include "Serialdebug.h"
#include <EEPROM.h>
#include <SPI.h>

Serialdebug::Serialdebug() {

  //Serial.begin(115200);                                                                           // Debug
}


void Serialdebug::printDATA(bool runflag) {

My code will run here once using the runflag as a single print flag

}


I can't post the entire DMX code... it's huge.

At the start I include my class:

Code:
#include "Serialdebug.h"
Serialdebug Serialdebugcall();

Then when I want to call it, I use:

Code:
Serialdebugcall.printDATA(0);

But I get the error

error: request for member 'printDATA' in 'Serialdebugcall', which is of non-class type 'Serialdebug()'
Serialdebugcall.printDATA(0); // Call my class to print all the data to the serial screen
^


Am I way off mark?
 
I'm merrily pasting tat on here.... but hey ho.

I have got classes working. Moved some of my code out of my main ino file.

How do I retrieve variables I gather within a class and bring them back to the main ino code?
 
How do I retrieve variables I gather within a class and bring them back to the main ino code?

What do you mean by "gather variables"? If you want to access class member variables from outside you can either make them public (not recommended) or write a 'getter' and/or a 'setter' function. The advantage of this approach is that you can prevent users of your class messing with the internals. Or you can do some transformations in the getter or....
You'll find a lot of information about c++ getters/setters online

e.g.
C++:
class Serialdebug {                                                 
 
  public:                                                             
    Serialdebug();                                                   
    void printDATA(bool runflag);                             // runflag is to be used so the report only runs once       
    
    int get getTemperature(){return tempval;}  // <<===  getter

  private:                                                             

    byte dmxUNI;                                                        // The variables for the data being retrieved from EEPROM (to print the report)
    byte magpenboot;
    bool maglockout;
    bool magscreenpower;
    bool magfulltest;
    int tempval;
    byte testrangelowByte;                                               
    byte testrangehighByte;
    int testrange;                                                 
};
 
So do I need to declare these public variables in both the main INO code and the class? (even though they are the same variable)
 
Back
Top