Minimal repro (Teensy 3.5, jumper pin 8 → pin 9) -> Please check attached main.cpp file
Summary
In cores/teensy3/serial1.c … serial6.c, the ring buffer index variables are sized at compile time based on the static buffer size:
However, addMemoryForRead() / addMemoryForWrite() only change rx_buffer_total_size_ at runtime — the index type stays whatever the compile-time size selected. With the default sizes (RX 64, TX 40), the indices are uint8_t, so any added memory that pushes the total past 256 makes the stored indices wrap at 256 while the wrap-around comparison
Effect on FIFO UARTs (UART0/UART1, e.g. Serial1/Serial2 on Teensy 3.5/3.6): actual data corruption.
The RX ISR drains the FIFO in a burst loop, keeping the head in a 32-bit local and storing it once at the end:
When a burst crosses index 255, the incoming bytes are written at indices 256, 257, … (into the extended storage), but the stored head wraps to a small value. The reader then consumes stale bytes from rx_buffer[1..k] — data received ~256 bytes earlier — while the real bytes are never read. The result is a silent, in-place replacement of 1–7 bytes (bounded by the FIFO depth) with old stream data, roughly once every 256 received bytes. Because the replacements are copies of earlier traffic, they usually look like plausible data and are very hard to attribute to the driver. The TX drain loop in the ISR has the same pattern, so addMemoryForWrite() past a total of 256 corrupts transmit data the same way.
Effect on non-FIFO UARTs: silent capacity loss.The per-byte ISR stores the truncated index on every byte, so reader and writer alias consistently — no corruption, but the ring effectively has only 256 slots: memory added beyond a total of 256 is never used, and available() returns wrong counts (rx_buffer_total_size_ + head - tail computed with the small indices).
Forum and Teensy Cores PRs / Issues search
Before writing this post, I searched this forum and also briefly looked through the pull requests and issues related to Teensy cores on GitHub, but I didn’t find anything very conclusive about this problem. I found an old comment by Paul that seems to be related, but I believe the problem persists in the most recent versions of Teensyduino.
https://forum.pjrc.com/index.php?threads/serial-interrupt-patching.77244/#post-361135
Suggested fix
Never use uint8_t for the indices, since addMemoryForRead()/addMemoryForWrite() can grow the total at runtime beyond the compile-time size. Mirror the Teensy 4 core:
plus a clamp in the add-memory functions so the runtime total can never exceed what the index type can address:
Same change in all six serialN.c files, RX and TX. RAM cost is 2 extra bytes per index (≤8 bytes per port), which seems a fair price for making the add-memory API safe at any size.
Summary
In cores/teensy3/serial1.c … serial6.c, the ring buffer index variables are sized at compile time based on the static buffer size:
C++:
#if SERIAL2_RX_BUFFER_SIZE > 65535
static volatile uint32_t rx_buffer_head = 0;
...
#elif SERIAL2_RX_BUFFER_SIZE > 255
static volatile uint16_t rx_buffer_head = 0;
...
#else
static volatile uint8_t rx_buffer_head = 0; // default sizes (64/40) land here
static volatile uint8_t rx_buffer_tail = 0;
#endif
(if (++i >= rx_buffer_total_size_) i = 0;) uses the full total. The stored value is silently truncated.Effect on FIFO UARTs (UART0/UART1, e.g. Serial1/Serial2 on Teensy 3.5/3.6): actual data corruption.
The RX ISR drains the FIFO in a burst loop, keeping the head in a 32-bit local and storing it once at the end:
C++:
head = rx_buffer_head; // uint32_t local <- uint8_t (0..255)
do {
n = UART1_D;
newhead = head + 1;
if (newhead >= rx_buffer_total_size_) newhead = 0; // wraps at 64+added, e.g. 8256
if (newhead != tail) { head = newhead; /* store byte at head */ }
} while (--avail > 0);
rx_buffer_head = head; // TRUNCATED to uint8_t (e.g. 259 -> 3)
Effect on non-FIFO UARTs: silent capacity loss.The per-byte ISR stores the truncated index on every byte, so reader and writer alias consistently — no corruption, but the ring effectively has only 256 slots: memory added beyond a total of 256 is never used, and available() returns wrong counts (rx_buffer_total_size_ + head - tail computed with the small indices).
Forum and Teensy Cores PRs / Issues search
Before writing this post, I searched this forum and also briefly looked through the pull requests and issues related to Teensy cores on GitHub, but I didn’t find anything very conclusive about this problem. I found an old comment by Paul that seems to be related, but I believe the problem persists in the most recent versions of Teensyduino.
https://forum.pjrc.com/index.php?threads/serial-interrupt-patching.77244/#post-361135
Looks like the old Teensy 3.x hardware serial code is using only uint8_t for the buffer index.
![]()
cores/teensy3/serial1.c at a23cade55c7530cce43c641ec3ddb8b7f83148a8 · PaulStoffregen/cores
Teensy Core Libraries for Arduino. Contribute to PaulStoffregen/cores development by creating an account on GitHub.github.com
Before we supported addMemoryForRead() the only way was to edit the code. On Teensy 3.x, you'll still need to edit that core library code to gain support for more that 255 chars in the buffer.
Suggested fix
Never use uint8_t for the indices, since addMemoryForRead()/addMemoryForWrite() can grow the total at runtime beyond the compile-time size. Mirror the Teensy 4 core:
C++:
#if SERIAL2_RX_BUFFER_SIZE > 65535
static volatile uint32_t rx_buffer_head = 0;
static volatile uint32_t rx_buffer_tail = 0;
#else
static volatile uint16_t rx_buffer_head = 0; // was uint8_t for sizes <= 255
static volatile uint16_t rx_buffer_tail = 0;
#endif
C++:
void serial2_add_memory_for_read(void *buffer, size_t length)
{
#if SERIAL2_RX_BUFFER_SIZE <= 65535
if (length > 65536 - SERIAL2_RX_BUFFER_SIZE) length = 65536 - SERIAL2_RX_BUFFER_SIZE;
#endif
...
}