Expansion to HardwareSerial?

AaronD

Active member
Corrections/improvements to the online documentation:
  1. Please add SerialX.write(buffer, size) to the online documentation. The overload exists, according to Arduino IDE's autocomplete, but the online doc doesn't mention it yet; only SerialX.write(byte). That by itself would have saved some frustration.
  2. Likewise for SerialX.readBytes().
  3. Please mention the default buffer size, above which SerialX.addMemoryFor...() should be used. Right now, it's ambiguous except for just happening to stumble onto a post in a different thread that is only tangentially related:


New addition, based on pages 2899 - 2900 of the datasheet:
Please add SerialX.configureBreak(), SerialX.sendBreak(), SerialX.sendIdle(), and SerialX.recvBreak() as new functions. (rename if something else works better) Assuming that all of these functions are non-blocking and only add to a buffer, this would allow easy synchronous timing, like:
  • C:
    void setup()
    {
        ...
        SerialX.configureBreak(...);    //translates into Table 49-2 on pages 2899 - 2900.
        ...
    }
  • C:
    //hypothetical message, showing general use
    if (trigger)
    {
        trigger = false;
        SerialX.write(buffer1, size1);      //setup
        SerialX.sendIdle();                 //give the receiving end one byte time to self-configure
        SerialX.write(buffer2, size2);      //data
        SerialX.sendBreak();                //end of message
        //continues immediately,
        //without waiting for any of it to appear on the wire
    }
  • C:
    //synchronous DMX, as opposed to free-running asynchronous that most libraries do
    if (trigger)
    {
        trigger = false;
        SerialX.sendBreak();
        SerialX.write(dmx_out, 513);
    }
    //or
    if (trigger)
    {
        trigger = false;
        SerialX.sendBreak();
        SerialX.write(dmx_out_start_code);
        SerialX.write(dmx_out, 512);
    }
  • C:
    //receive a message
    if (SerialX.recvBreak())
    {
        timeout = 0;
        fresh_data = SerialX.readBytes(dmx_in, 513);
    }
  • Etc.
 
Yes, it is related. I was going to cross-link after responding to those, but you beat me to it.
  1. Correct, as I acknowledged over there. (DMX requires a minimum 23-bit break, typically 44-bit, with no stop bits to break it up) So that's a bad example above, assuming that it controls DMX fixtures directly.
    Still want that functionality though, because my specific project has:
    • Another microcontroller between the Teensy and the fixtures, that will intercept the stream and do one last modification on it. So what comes out of the Teensy in my case doesn't have to be fully standards-compliant DMX. It can use a 9-bit break from hardware and still work just fine, while the downstream uC sends a 44-bit break per the standard.
    • Other microcontrollers that do other jobs, with the Teensy as a "logic hub" to tie them all together. Each of these communication loops is presently designed as a UART that uses a break as a frame-sync. Logically similar to DMX, but not using the entire spec.
  2. Yes it does default to idle, but I was originally thinking about the "mark-after-break" (MAB) from the DMX standard (minimum 3 bits, including the 2 stop bits if the hardware insists on sending them, maximum 1 sec) and how to ensure that without explicitly waiting for it. Likewise for anything else that could use a byte-resolution delay or "hiccup". Just stuff all of that into the one buffer that the DMA reads from, and let it run while the code does something else, like the datasheet says is possible.
 
Way-way back in time, different processor+UART that didn't support the break length we needed. The solution was to change the bit-rate dividers to a much slower bit-rate and send a single 0x00 byte. The START-bit + 8 zero's met our requirement for an extended Break. Then, change bit-rate dividers back to desired bit-rate.

One potential downside is the time wasted while waiting for STOP bit(s) to be transmitted. I know nothing about DMX, so maybe this would break (no pun intended) all sorts of DMX timing.

Also, how easy/difficult to implement using Teensy? Back then, we were dealing with 8-bit micro's and had lots of time to manipulate the hardware.
 
Way-way back in time, different processor+UART that didn't support the break length we needed. The solution was to change the bit-rate dividers to a much slower bit-rate and send a single 0x00 byte. The START-bit + 8 zero's met our requirement for an extended Break. Then, change bit-rate dividers back to desired bit-rate.

One potential downside is the time wasted while waiting for STOP bit(s) to be transmitted. I know nothing about DMX, so maybe this would break (no pun intended) all sorts of DMX timing.

Also, how easy/difficult to implement using Teensy? Back then, we were dealing with 8-bit micro's and had lots of time to manipulate the hardware.
I'd seen this already in some of the documentation, as a reason to not use the same UART for dull-duplex DMX. The baudrate hack for TX completely wrecks RX! So that was part of my motivation for a different method.

If a second bit-banged output and an off-chip AND is not desirable, then another method could be to invert the TX (but not RX) while keeping it idle, as shawn mentioned on GitHub (linked above) and I've done myself as well. (on an old AVR if I remember right) Still bit-banged, bit it's an internal configuration bit instead of a GPIO.

To avoid tying up any more resources than necessary, a buffer of 4 explicit idle characters (different from break, see the datasheet just above my code examples here) might be used to create a "completed" interrupt at the end of the break. So, invert and put 4 idle's in the buffer (44 bits at 11 bits/char for 8N2), wait for interrupt, un-invert and send the next frame (possibly with another idle up front just to guarantee MAB), wait for interrupt, invert and send 44 idle's, etc.

As far as the baud hack breaking timing, officially it doesn't. There's no real maximum time, except to get all the way around the cycle within 1 full second. Though waiting *that* long would likely cause some other, visible problems! Minimum time is 3 bits at 250k, 2 of which are already stop bits if a 44-bit break were possible.

If it were done like this (very conservative), all at 250k, then the maximum refresh rate is 43.8Hz:
  • TX-complete interrupt:
    • Invert
    • 4 idle characters (the Break itself: stay idle while inverted, using the explicit characters to create another of the same interrupt that far away)
  • TX-complete interrupt:
    • Un-invert
    • 1 idle character (MAB, required: minimum 3 bits, no hard maximum, we get 11 per idle character)
    • DMX frame (513 bytes)
    • 1 idle character (MBB, optional: minimum time here is zero)
The state of the inversion bit might even be used as the state variable: always flip it, then check it and follow the appropriate branch from there.
Just one ISR, and no timers: all of the timing comes from the UART, using idle characters in the same buffer as data, to create delays without wiggling the wire, and while keeping a constant baudrate so that async RX still works on the same port/channel.
 
Last edited:
That said though, I still want explicit functions for Idle and Break, at the very least to use according to my code examples here. Yes, that's not completely standards-compliant DMX, but for my specific project, it doesn't have to be. And I can use the exact same code to communicate with other things too.

Everything that my Teensy talks to is either a baremetal microcontroller that I write the entire code for (simple-enough chip to code everything directly with no outside libraries), or a USB host PC, or an upstream DMX controller. So if I send "bad DMX", that's fine. None of my outputs from here are actually DMX anyway. One of the uC's does produce actual DMX, based on what it receives from the Teensy, but that's "over there", not here.

And this "not quite DMX" in the code examples above, with a 9-bit break and stop-bit-only MAB, looks to be very useful in general. Continuous data with all byte values available, and guaranteed sync. I'm using COBS on USB Serial between the Teensy and the PC to satisfy that same requirement, but even that's a bit much overhead on the micros.
 
Turns out that standards-compliant DMX *is* possible with the Teensy 4.1 UART alone. Multiple BREAKs do *not* have stop bits in between, so it can produce any length with character-level resolution.

This code:
C:
#define LED_SETUP() pinMode(13, OUTPUT)
#define LED(x) digitalWrite(13, !!(x))

void setup()
{
    Serial1.begin(250000, SERIAL_8N2);
    LED_SETUP();
}

elapsedMillis timeout_LED;
elapsedMillis period1sec;

void loop()
{
    LED(timeout_LED > 500);
    if (period1sec > 1000)
    {
        period1sec = 0;
        timeout_LED = 0;

        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.write(SERIAL_IDLE_CHARACTER);
        Serial1.print("abc");
        Serial1.write(SERIAL_IDLE_CHARACTER);
        Serial1.write(SERIAL_BREAK_CHARACTER);
    }
}
produces this:
Screenshot_20260703_154230.png

after modifying HardwareSerial.{h|cpp} as attached here.

So...could the official library be modified to include this functionality? Not necessarily *exactly* like this under the hood, but I think the usage should be the same or very close.

And to detect a break on RX, using the same modified files:
C:
int rx_data = Serial1.read();
if (rx_data != -1)
{
    //got something

    if (rx_data & SERIAL_BREAK_CHARACTER)
    {
        //got a BREAK
    }
    else
    {
        //not a BREAK
        rx_data &= 0x3FF;       //do this here, because HardwareSerial doesn't anymore
    }
}
That's tested and works too, but not shown here. Look here if you really want to see it:
 

Attachments

  • HardwareSerial.h
    16.7 KB · Views: 6
  • HardwareSerial.cpp
    25.7 KB · Views: 8
Turns out that standards-compliant DMX *is* possible with the Teensy 4.1 UART alone. Multiple BREAKs do *not* have stop bits in between, so it can produce any length with character-level resolution.

Really? That's not what the chip spec led me to believe. Have you witnessed this on a scope? In any case, this is a neat discovery.
 
Back
Top