T4.0 Hardware Serial Questions: FIFOs & Buffer Sizes, etc.

Status
Not open for further replies.

Aerokeith

Well-known member
Hi! I have some noob-ish questions about the Teensy 4.0 UARTS and the associated hardwareserial.cpp code.

HW Configuration
Multiple, identical custom PCBs using Teensy 4.0 as an MCU module, all connected with a RS-485 serial bus using a custom protocol (some similarities to DMX-512).
One board is configured as a bus master, the rest are receive-only slaves. I'm using this RS-485 transceiver (https://www.maxlinear.com/ds/sp3070e-sp3078e.pdf) which supports up to 128 nodes. Here's a photo of the board (just to make this post more visually interesting):
IMG_0622.jpg

Operational Concept
The master will transmit a small number of variable-length packets at a fixed cycle rate of 33 Hz (~30ms). The slaves will use the receive time of the first byte of the first packet in a cycle to synchronize their processing. The target data rate is 250 Kbps (40 us per byte). The master will cap the per-cycle transmit data quantity at about 380 bytes, which takes about 15ms or half of the cycle time. The slave devices will use the "sync" condition (1st byte received) to trigger their time-critical processing, and after that's done they will read and process the serial data received during that cycle. That's non-time-critical, as long as they finish before the start of the next cycle. This data is used to configure the synchronous processing in the following cycle.

Hardware Questions
I've seen conflicting information about the hardware buffers/FIFOs supported by the T4 processor. The RT1060 family reference manual seems to say that all 8 of the UARTs have only a 1 byte receive buffer, but elsewhere I've see multiple people say that two of the UARTs have a 4-byte FIFO (or 8-byte). That's somewhat important to me, as it relates to the software questions below. Which is correct?

Software Questions
I'd like to use Serial.available() to poll for the sync condition, but defer reading any of the data (other than maybe the first one or two bytes) until later in the cycle. The implies that the software receive buffer in hardwareserial.cpp must be at least 380 bytes. If I'm looking at the correct version on GitHub ( don't know where to find the version #), it looks like the receive buffer is hardcoded to 64 bytes.

I've see other posts talking about modifying hardwareserial.cpp to change the buffer sizes, but I had a hard time following the discussion and recommendations. Can someone please lay it out for me a little more clearly (assuming this is a reasonable thing to do)? It may be a little more challenging because I'm using Visual Studio Code with PlatformIO (MacOS), not the normal Arduino IDE. Also, I'm I'm not very familiar with C++ (only C) or the details of the board-specific build process.

If it's correct that the T4 UARTs have a 1-deep receive buffer, I assume that Serial.available() will return a non-zero value pretty much immediately upon receipt of the first byte. Is that correct? But...if it DID have a deeper FIFO, is it true that an interrupt wouldn't be triggered until the FIFO was full, or at some (configurable?) high-water mark? And then, is there a configurable IDLE timeout that will trigger in interrupt even if the high-water mark isn't reached? Just curious.

I'm also curious why the rx/tx buffer sizes aren't configurable except by re-compiling. This seems like a reasonable parameter for Serial.begin() or something similar. Not whining.

Sorry for being long-winded. Thanks for any help you can provide.
 
I've seen conflicting information about the hardware buffers/FIFOs supported by the T4 processor. The RT1060 family reference manual seems to say that all 8 of the UARTs have only a 1 byte receive buffer, but elsewhere I've see multiple people say that two of the UARTs have a 4-byte FIFO (or 8-byte). That's somewhat important to me, as it relates to the software questions below. Which is correct?

All the serial ports have 4 byte FIFO.


I'm also curious why the rx/tx buffer sizes aren't configurable except by re-compiling.

Partly because the API to configure them was proposed but never fully decided by Arduino. But mostly because nobody has put the time into making it runtime configurable (as still efficient).
 
Hi Paul! Thanks very much for responding so quickly. I understand that you're very busy, so hopefully some other forum members can fill in the rest of the details.

4-byte FIFO's are good as long as I can confirm when interrupts will occur as a function of FIFO fill level (or IDLE period). If Serial.available() doesn't report any received data until the 4th byte, that's fine as long as it's repeatable.

Just out of curiosity, maybe someone can point out to me how I mis-read the processor reference manual. I see no mention of FIFOs (maybe it's wrong?).

FYI, so far I really like the T4 and the strong community behind it. I just switched from the Sparkfun Pro Micro, and I plan to stick with the T4 (and successors) for a long time.
 
There's an addStorageForRead() method on the HW serial ports which looked like it was appending onto the incoming buffer, but maybe it's not implemented, or Paul probably would have mentioned it?
 
Last edited:
Just out of curiosity, maybe someone can point out to me how I mis-read the processor reference manual. I see no mention of FIFOs (maybe it's wrong?).

NXP's reference manual can be quite a challenge to read.

Here are key parts about the UART FIFOs.

page 2834:
fifo1.png

page 2856:
fifo2.png

Also see the documentation about the FIFO control registers, on pages 2874 to 2879 (in Rev 2, 12/2019 - the page numbers change in each rev)
 
Not sure if the 'protocol' to be used has a STOP state and transition like UART Serial.

I posted code some time back - enabling an interrupt on the STOP>START transition. The interrupt disables itself {so it doesn't trigger on data bits } and records the time - this was a GPS message ideally to sync time.

The message can be received/processed and the interrupt enabled before the next start time as described to be ready to the next cycle.

That would eliminate the need for polling and give relatively consistent results

Looking at sources there is code like this: { ..\hardware\teensy\avr\cores\teensy4\HardwareSerial1.cpp }
Code:
#ifndef SERIAL1_TX_BUFFER_SIZE
#define SERIAL1_TX_BUFFER_SIZE     64 // number of outgoing bytes to buffer
#endif
#ifndef SERIAL1_RX_BUFFER_SIZE
#define SERIAL1_RX_BUFFER_SIZE     64 // number of incoming bytes to buffer
#endif

So if during the build those items are 'defined' with alternate values in advance they will be used.
 
There's an addStorageForRead() method on the HW serial ports which looked like it was appending onto the incoming buffer, but maybe it's not implemented, or Paul probably would have mentioned it?
Actually this is implemented for T4 (and T4.1), but not for T3.x and LC.... Actually I had a version (https://github.com/KurtE/cores/tree/unified-serial) for these that like the T4 used one class to do this, but we never integrated it in... But I then started from that for the T4 version.
 
Thanks everyone! It sounds like there are two different methods to expand the RX buffer size:

1. #define SERIAL1_RX_BUFFER_SIZE <Size> before the #include "hardwareserial1.h"

2. Call Serial1.addStorageForRead(&buffer, bufLength)

The second method sounds more straightforward, so I'll try that first.

Mystery solved on the NXP manual: Paul quoted from Rev 2 and I was looking at Rev 1 (which doesn't say anything about the FIFOs). Both revs are on the PJRC site, but I was using an (outdated) link from Sparkfun, where I purchased my first Teensy 4.0.

So I'm happy I don't have to recompile any libraries. I think I'm good to go!
 
Thanks for the suggestion on synchronization (and on the buffer size). Sounds like the interrupt approach would work for me, but I don't need that level of timing accuracy. Also, the slaves will have nothing else left to do at the end of a cycle, so polling isn't a problem. But I'll keep it on mind in case this doesn't work out...
 
Status
Not open for further replies.
Back
Top