How to add Idle and Break to HardwareSerial?

AaronD

Active member
This seems to have stalled:
and the datasheet says on pages 2899 - 2900 (functional description) and 2920 - 2932 (registers) that it's possible:

So in the interest of getting my project past this hurdle, how can I add for myself in a way that works with everything else:
  • SerialX.configureBreak(...) sets or clears the BRK13 bit in the STAT register. All other bits in Table 49-2 on pages 2899 - 2900, also control other functions that should not be cobbered. Probably not needed at all, really. My receivers will work either way.
  • SerialX.sendBreak() works just like SerialX.write(...), except that it puts a break character in the buffer. Normal zero, but with the stop bits intentionally incorrect. Used to get a receiver's attention, or to separate packets, while continuing to fill the buffer with data to be sent after the break, without the code waiting for the break to actually happen.
  • SerialX.sendIdle() works just like SerialX.write(...), except that it puts an idle character in the buffer. No activity on TX for one character time. Used to create a delay, while continuing to fill the buffer with more data to be sent after the delay, without the code waiting for the delay to be over. More for completeness, really. My receivers won't need it, and it comes for free with the proposed solution below.
  • SerialX.recvBreak() is non-blocking. Returns true if a break character has been received since the previous call, otherwise false.
sendBreak and sendIdle should also keep the transmitter active, if used with SerialX.transmitterEnable(pin).

I've looked at HardwareSerial.{h|cpp}, and seen that all single-character writes funnel into size_t HardwareSerialIMXRT::write9bit(uint32_t c), which is conveniently next to void HardwareSerialIMXRT::IRQHandler().
int HardwareSerialIMXRT::read(void) is just before both.

Ignoring the complex logic and only following uint32_t c from write9bit through the buffers and to what looks to be the actual TX register, suggests that I can simply change the buffers from uint8_t to uint16_t (enable 9-bit support at the top of HardwareSerial.h, even though I'm only using 8-bit characters), and adjust the funnel to pass all 16 bits, with no other changes to the library, and then use the write function as usual except with the special-character flag in the upper bits per page 2932.

Likewise to receive a break, expand the RX buffers from 8-bit to 16-bit (already done with 9-bit support) and remove the & 0x3ff from both hardware reads (one each in read and IRQHandler), and then check the same flag in the upper bits that the otherwise-normal read function returns. Once checked, mask off the data myself.

Is that true?

And what prevents all of that from getting wiped out with the next Arduino update?
 
Last edited:
I missed another 10-bit mask in the peek function, probably because I don't plan to use it. But I removed that too, mostly for completeness.

Anyway, after modifying HardwareSerial.{h|cpp} as attached here, 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.print("ab");
        Serial1.write(SERIAL_IDLE_CHARACTER);
        Serial1.print("cd");
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.print("ef");
        Serial1.write(SERIAL_BREAK_CHARACTER);
    }
}
produces this:
Screenshot_20260703_144910.png


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


bool state_LED;

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

    LED_SETUP();
    state_LED = true;
}

elapsedMillis period1sec;

void loop()
{
    LED(state_LED);

    if (period1sec > 1000)
    {
        period1sec = 0;

        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.print("ab");
        Serial1.write(SERIAL_IDLE_CHARACTER);
        Serial1.print("cd");
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.write(SERIAL_BREAK_CHARACTER);
        Serial1.print("ef");
        Serial1.write(SERIAL_BREAK_CHARACTER);
    }

    int rx_data = Serial1.read();
    if (rx_data != -1)
    {
        if (rx_data & SERIAL_BREAK_CHARACTER)
        {
            state_LED = !state_LED;
        }
    }
}
produces this:
Screenshot_20260703_151949.png

Same timing as the previous screenshot, but I had to take the TX wire off to put the jumper on.

---

Also encouraging in the context of DMX (see my other recent threads), is to see a double break that does NOT have stop bits in between! So the Teensy 4.1 hardware CAN produce a super-long break! Yay!

Still unsure about the library getting an update and wiping that out. I guess I can keep a copy elsewhere for reference, and re-edit when that happens.
Better, I think, would be to incorporate this (or functional equivalent) into the official library, which is what the linked thread at the top of this one is for.
But at least this project can move on now.
 

Attachments

  • HardwareSerial.h
    16.7 KB · Views: 3
  • HardwareSerial.cpp
    25.7 KB · Views: 5
Last edited:
Back
Top