Silly question re UARTs

Status
Not open for further replies.

Constantin

Well-known member
We've had a number of questions around the serial bus here, along with some really good suggestions re: flow control, etc that clearly were the result of lots of experience. I've learned a lot already and wanted to thank everyone for the great insights - they're already re shaping how I am approaching my project.

Given the ginormous memory the teensy 3.1 has, I wonder just how hard it would be to expand the serial buffer beyond the present maximum 255 bytes per serial bus. Could it be as simple as modifying hardwareserial.cpp to use 16bit variables vs. the 8bit variables that are defined there presently?

Or are there hardware limits and/or other reasons that a simple find and replace for the variables in question would not work?

I understand there would be an impact on the main memory... But with all the RAM that the Teensy 3.1 has, it might be a trade off that some of us would like to make. For me, being able to squirt/receive large blocks of data in the background could be quite useful.
 
Consider too, that a larger buffer won't solve a possible problem that the sustained rate from the data producer is greater than the long term average that the consumer (application in the Teensy) is providing. This is driven by how often that consumer code can run (competing for CPU time with other application things) and how much computation per packet/frame/blob it must do.
My experience is that flow control and data consumer scheduling problems are more likely at fault than the buffer size, for UART speeds on the Teensy 3.1 hot rod.
 
Spot on. For the present time, a 255 byte buffer is likely more than enough for my application. However, I was trying to think about possible applications in the future and perhaps I'm misunderstanding what/how a teensy 3.x serial buffer works?

As I understand it, the Serial buffers are a bit like the ADC - working with the CPU but independent. That is, the CPU can be doing X and in the meantime a serial message can come in. As long as the buffer does not overflow, all is well. Then, the next time the CPU checks the Serial buffer for something, that message can then be read out. Scheduling is clearly of importance here, i.e. avoiding collisions, multiple messages being sent into the buffer and the like by using a single master for the bus that prompts each slave on the bus individually for responses.

So serial communications that do not require the use of interrupts to read stuff into the serial buffer would be ideal for my current power monitoring setup - the loop executes about 1400 times a second, and it would be pretty ideal if the master could send out a message on the odd-numbered loop iterations while receiving/processing responses on the even-numbered ones. Since a interrupt from the analog front end measuring power triggers each loop iteration, it would be fantastic if the serial communications didn't interfere that process with interrupts of their own.

But it's entirely likely that my understanding of the serial hardware and its buffers is incorrect, hence my questions. Apologies in advance if I got it wrong!
 
The code supports larger buffers. Or at least it's supposed to. There's some #ifdef stuff to automatically use 8 or 16 bit index variables. Just edit the buffer size #define.

Please give the larger settings a try, and of course if you discover any problems, post a reproducible bug report.
 
Serial input on any CPU needs either polling (bad) or interrupts.
In good ARM type designs, the serial port "driver" software has a (ring) buffer, in RAM, that being software than has two indexes into the buffer: read and write.
When the hardware UART's receive FIFO buffer (typically 2-8 bytes), gets nearly full, it requests an interrupt from the CPU. A few microseconds later (see note below), the CPU saves the current state of registers and executes the interrupt handler (service routine, or ISR). The ISR (should) copy the bytes in the FIFO of the UART to the ring buffer in RAM. Then dismiss the interrupt and return to the executing program.
If the ISR finds that the ring buffer is full (i.e., the write index is (would be) equal to the read index), then the ISR has no choice but to discard the data from the UART, and set a buffer-overrun flag. This flag is sometimes not implemented or is ignored by the application.
When ISR(s) are not running, the application must read from the ring buffer to prevent overrun.
Typically, ISRs use on the order of < 1% of the CPU time.

Some common gotcha's here:
1. Some program somewhere turns off interrupts for too long, so that the FIFO in the hardware UART fills but cannot get the CPU to take the interrupt. One cannot have another I/O software or ISR loop and leave interrupts block too long. This happens only on really badly designed software or ISRs. Arduino's libraries have gotten better, but way back, they had bad code for this case. Misuse of the "noInterrupts()" function can cause this too.
2. The application might choose to always read just one byte at a time from the serial port. This causes a lot of overhead and reduces the net throughput of the data consumer (app). At lower baud rates, this not so much of an issue, as in the classic GPS "NMEA" data reader methods.
3. The producer streams bytes on the UART output, and if the reading side gets out of byte-boundary sync (start/stop bit detection), invalid data is put in the UART receive FIFO. This commonly happens if the incoming stream is uncontrolled and a constant blast, and the reading side starts up mid-stream and the UART cannot find the correct boundaries. The only cure I know of is to either tell the sender to stop for a bit (2 byte times or so), or ensure the sender always has no data flow for 2 byte times quite frequently (e.g., in between application layer "packets").

So interrupts are necessary because the FIFO in the hardware is small.
Fancier chips like the ARMs and bigboy chips can do direct memory access (DMA) for the UART (and other I/O). With DMA, data is moved by hardware from the (UART) to RAM without interrupts. But there are tradeoffs on when to interrupt the CPU, e.g., no data for x amount of time vs DMA complete for (e.g.) 1000 bytes, etc. Sometimes the 1000 byte method isn't what the app needs because of the app's data packet size and rate. So there are many scenarios.

But for UARTs with a FIFO, the interrupt rate is a fraction of the received byte rate, e.g., an 8 byte FIFO set to interrupt at 6 bytes-full, has an interrupt rate of 1/6th of the byte rate (and the bit rate is 8 times the byte rate, plus UART start/stop bytes, so a byte takes 10 bit times).
At 115200 baud and up, this is good.

Some badly designed ISR software does not enable use of FIFOs and gets an interrupt for every byte. OK at lower baud rates, but adds CPU overhead vs. the above 1/6th type reduction.
Cheap/old microprocessors have no FIFO.

BTW, there are FIFOs on the transmit side of the UART too.. so the interrupt rate is reduced for this case too, assuming the ISR uses the TX FIFO. Using DMA for transmit is easier because the ISR can know exactly how many bytes are in a buffer waiting to go.
But likely, DMA on typical low cost microprocessor UARTs isn't needed.
DMA is great for high speed transfers via SPI, e.g., for an SPI interface to an ethernet controller like the Wiz820 for the Teensy. That's because bytes/sec is really high at ethernet speeds.

I believe that the Teensy's all use the UARTs' FIFO if possible. The Teensy 3.0 had, IIRC, only one UART equipped with FIFOs and Teensy 3.1 has more (or all UARTS on T3.1 have FIFOs), and Paul's ISRs do use them properly.
 
Last edited:
Serial input on any CPU needs either polling (bad) or interrupts.
I believe that the Teensy's all use the UARTs' FIFO if possible. The Teensy 3.0 had, IIRC, only one UART equipped with FIFOs and Teensy 3.1 has more (or all UARTS on T3.1 have FIFOs), and Paul's ISRs do use them properly.

Teensy 3.0 has an 8 byte FIFO on the primary UART (Serial1, pins 0/1), 4 word FIFO on the SPI bus, and a 4 word FIFO on i2s.

Teensy 3.1 has 8 byte FIFOs on Serial1 (pins 0/1) and Serial2 (pins 9/10), a 4 word FIFO on SPI, an 8 word FIFO on I2S, and a 6 message FIFO on CAN.
 
This means that all the data in the UART's read FIFO can inserted into the read ring buffer in a single ISR to minimize interrupt overhead,

and also several bytes can be written from the shared write ring buffer to the UART's write FIFO buffer within a single ISR call

by the way: is the UART itself responsible for handling the XON/XOFF protocol?
 
Last edited:
This means that all the data in the UART's read FIFO can inserted into the read ring buffer in a single ISR to minimize interrupt overhead,

and also several bytes can be written from the shared write ring buffer to the UART's write FIFO buffer within a single ISR call

by the way: is the UART itself responsible for handling the XON/XOFF protocol?
The ISR that supports FIFOs reduces the interrupt rate quite a bit. E.g., if the receive FIFO depth is configured to 8 bytes, then the interrupt rate is 1/8th of what it would be without the FIFO. Same for TX.
On a receive interrupt (using FIFOs), the ISR reads all bytes within the FIFO and copies them to the MCU's RAM buffer for that UART. This is usually a ring buffer. If the buffer is full, the ISR usually discards the bytes that won't fit and flags an error. On TX, the ISR fills the FIFO.

XON/XOFF - I think that's an ISR function that may or may not be implemented. Or it could be pushed up to the application level. Not sure about support for XON/XOFF in the T3.

There are posts here about recent support done by a user for using CTS for hardware flow control.
 
Note, since this thread was started in 2014, it only mentions the Teensy 3.0 and 3.1. The 3.2 has exactly the same FIFO setup as the 3.1 (i.e. Serial1 and Serial2 have 8 byte FIFOs).

The LC does not have any FIFOs for the serial UARTs. Also, I don't believe you can attach interrupts to pins 0/1 (Serial1), but you can attach interrupts to 9/10 (Serial2) and 7/8 (Serial3).
 
Status
Not open for further replies.
Back
Top