teensy 4.1 Display and SD Card issues

I think there was a similar thread a while ago.
Basically, I'm having an issue where streaming samples from the internal Teensy 4.1 SD card reader causes audio stuttering or other glitches as soon as there are updates on my SPI display.

So far I've been able to minimize it by only updating the areas of the display that actually need to change. But as my project grows, I'm getting a bit concerned because I'll have to update quite a few different areas on the display. Right now I'm basically just streaming a single 700 KB sample, and it already seems to be close to its limit.

Does anyone have any suggestions on how to deal with this? I'm using a 64 GB Samsung Pro SD card. I heard a 32 GB card might perform better, but I'm not sure if that would actually solve the problem. Is there anything else I can do?
 
I asked Google AI and it answered..

The core reason your audio stutters is SPI bus contention and latency spikes caused by sharing a single SPI bus or allowing the display updates to block the Teensy 4.1 processor while the audio library expects immediate SD card data.
Because the Teensy Audio library relies on strict timing (every 2.9 milliseconds) to fill its buffers, any delay from a slow display transaction causes an audio dropout. [1, 2]
Here is how you can completely resolve this issue:

1. Move the SD Card to the Native SDIO Bus
The absolute best solution for Teensy 4.1 is to stop using SPI for the SD card entirely. The Teensy 4.1 has a dedicated, built-in 4-bit SDIO card slot. [1, 2, 3, 4]
  • The Fix: In your Arduino code, change your initialization from SD.begin(chipSelectPin) to use SD.begin(BUILTIN_SDCARD).
  • Why it works: This bypasses the SPI bus completely, utilizes a hardware-level 4-bit parallel data bus, and features automatic Direct Memory Access (DMA). Your display and SD card will no longer fight for the same lines. [1, 2, 3]

2. Separate Hardware SPI Buses
If you are using an external SD card wrapper instead of the built-in slot, do not share SPI (pins 11, 12, 13). The Teensy 4.1 features multiple independent hardware SPI ports. [1, 2]
  • SPI (Default): Pins 11 (MOSI), 12 (MISO), 13 (SCK). Use this exclusively for your display.
  • SPI1: Pins 26 (MOSI), 1 (MISO), 27 (SCK).
  • SPI2: Pins 35 (MOSI), 34 (MISO), 36 (SCK).
  • The Fix: Wire your SD card adapter to SPI1 or SPI2 and initialize your SD library using the corresponding port (e.g., SD.begin(chipSelect, SPI1) if your library supports it). [1, 2, 3, 4, 5]

3. Implement Transaction-Safe Code
If you absolutely must share the exact same SPI pins for both devices, you must ensure that your display code does not lock the bus while the audio library interrupts try to read from the SD card.
  • Ensure you are using SPI.beginTransaction(SPISettings(...)) and SPI.endTransaction() around your manual SPI writes.
  • Ensure you use optimized display libraries like ILI9341_t3n or ST7789_t3 which are specifically written for Teensy and are fully transaction-aware and interrupt-safe. [1, 2]

4. Increase Audio Block Memory
Giving the audio library a larger buffer allows it to survive brief periods where the display is holding up the processor.
  • The Fix: Increase the allocation in your setup() function.
  • Change AudioMemory(8); to something much larger, like AudioMemory(24); or AudioMemory(32);.

Summary Action Plan
  1. Change your code to use BUILTIN_SDCARD.
  2. Move your micro SD card into the Teensy 4.1's onboard metal spring slot.
  3. Keep your display wired to Pins 11, 12, and 13. [1, 2, 3]


 
Yup, AI fails again...
  1. As noted by @jmarsh, not applicable since the SDIO slot is already in use
  2. ...so this isn't applicable either
  3. Not stated by OP, but almost certainly already true
  4. Entirely irrelevant, SD card streaming is not affected by audio block allocation
It would be useful to know which display is in use, though the stock Teensyduino display and SPI libraries all currently suffer from the same long-standing issues:
  • the AudioPlaySdWav object claims it's using the SPI bus, even when it's actually using the SDIO slot
  • the SPI library thus disables the audio updates while display updates are taking place
  • many display methods can take a lot longer than the 2.9ms audio update period
Depending on the display in use, various options are available. Possibly the simplest is at https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD-for-PR (discussion thread here), which takes a bit of getting used to but allows you to set up your own buffers of a size suitable to accommodate long display updates, unexpected SD card latency etc. If you're using an ST77xx display, I've done some updates for that at https://github.com/h4yn0nnym0u5e/ST7735_t3/tree/dev/big-screen-t4-muxedCS (discussion thread here); unfortunately this approach, although it just about works with AudioPlaySdWav, does also require changes to the Teensyduino cores to make DMA pre-emption work properly.

AudioPlaySdWav is fundamentally horrible as currently implemented, as it reads the SD card within interrupts and has a ridiculously small buffer. It just barely works for streaming a single file - anything more ambitious is liable to fail without warning for no discernable reason.
 
One day someone should probably try to figure out why DMA_SDIO mode is so horribly slow... I feel like it would help a lot with audio tasks.
 
Haha yep, asking AI actually led me to cut a PCB trace so I could move the CS pin 10 that my display was using to another pin, because apparently the internal SD card was using that SPI pin. After doing some digging though, I also found out that the SD card slot is actually using SDIO.

Sorry, I should have mentioned that I'm using an MSP2807 display with the ILI9341_t3n driver.
I tried the display driver DMA mode as well, but it actually made things worse.
Changing the audio buffer didn't make any difference for me either.
I might have done something wrong, though, because I heavily rely on AI for programming.
😅



Thanks everyone though.
😃
I'll check out the links. It looks pretty helpful.
 
I know AI info is suspect when I see it quoting stuff I wrote ;~)

The thread that @h4yn0nnym0u5e linked to has lots of good info buried in it about this issue.

Rapidly drawing larger filled rectangles like for audio amplitude bars, tended to be one of the worst case situations. If you are doing something like that, you might try reducing the total filled area.

In my case, I was using the 3.5" ST7796 display and I could also patch the issue by increasing the SPI bus speed to the display up to 80MHz so that writes were fast enough that they didn't conflict with the audio stream, but the ILI9341 can't handle that kind of speed.
 
Yes, anything you can do to reduce the time taken for a single display change will help. Assuming a SPI bus speed of 16MHz, keeping operations to fewer than 1000 pixels will make them take less than 1ms, very roughly. To draw a large amplitude bar of say 20x200=4,000 pixels, draw it in four operations. The overall time to draw will increase very slightly, because in one of the gaps the audio update will delay the start of the next operation. Some operations are hard to split at sketch level, though, e.g. drawing very large characters. That’s why I went into the library internals, and found about 19 time-consuming operations that needed fixing…

DMA / async updates are worse, because the ILI9341 library can only do whole-screen updates, which hog the SPI bus for ages.

I haven’t updated the ILI9341 library (or any others) in the same way as the ST77xx one, though doing so would be relatively straightforward. I’ll probably do a pull request when the Teensyduino 1.63 campaign gets under way, and if that gets adopted I’ll consider doing the others, though I’d prefer to do the enhanced ones that are available, rather than the older ones currently bundled in Teensyduino.
 
Perhaps it is possible to first load the data from the SD card into PSRAM Chip on the Teensy 4.1 and then play it back :unsure:
 
That works, as long as you have PSRAM fitted (it wasn’t mentioned…), and as long as you don’t want to play more than 95 seconds of mono audio (do your own maths for stereo, or multiple simultaneous files), and you can tolerate the time taken to re-load when changing samples (best case about 0.4s for a complete re-load, at 20MB/s).

PSRAM is very useful, especially for buffering multiple streams from SD, or pre-buffering to get low-latency triggered streaming.
 
Haha funny, that's literally what I was about to ask. I already have the PSRAM sitting in front of me, but I haven’t soldered it yet. Probably my next step.
I was basically just doing some research on whether it makes more sense to use two PSRAM chips (16 MB total) versus one PSRAM and one flash chip. From what I’ve read on the forum so far, it seems like everything above a 16 MB flash chip can get quite tricky as well.

I was thinking about having a small static sample library that I could store on the flash chip, but I’m not sure how much of an advantage that would actually give me compared to using the SD card and loading samples into PSRAM on the fly. And even then, I would probably need at least a 32 or 64 MB flash chip for it to really be worthwhile.

So maybe 16 MB PSRAM and loading data from the SD card in a ring-buffer style might be my best bet so far?
 
Note that recently a 16MB PSRAM chip has become available, and the latest Teensyduino supports it, so 32MB is possible. Not sure how widely available they are.

I’ve not used Flash much. AFAIK it’s mostly supported by LittleFS, and is OK if you pick one on the supported list. It may get “tricky” if you try to run it close to capacity, but what filesystem doesn’t?

At the risk of continuing to blow my own trumpet, my buffered SD extension to the Audio library does attempt to give you a sensible compromise, by implementing pre-loading of the first part of a sample into memory (RAM or PSRAM). Playback can thus start immediately without the usual filesystem delays - it can take a few milliseconds just to find a file, plus more to load the first few sectors. As long as the preloaded audio is big enough, playback then continues by streaming from the rest of the file. A lot of the heavy lifting is thus done for you - there’s a demo of an 88-note piano with 3 velocity layers, pre-loading the first 170ms of every sample into PSRAM.

Another option is the TeensyVariablePlayback library - not mine, though I did do some work on it. Useful if you want to change playback speed to do pitch shifting, but only supports rudimentary pre-load by starting playback at zero speed, then triggering by changing the speed to non-zero at the right time.
 
Oh, this is really amazing news with the 16MB PSRAM.
I got a bit further with the SD card streaming. Using PSRAM helped quite a bit to make it more stable, but as soon as I go a bit crazier with the step sequencer and notes, I start getting issues. I think loading all the samples into PSRAM seems to be the most stable solution so far, and also the least painful. With 32MB I would actually have plenty of room. :)
 
Last edited:
If you want to push boundaries, keep in mind that you can increase the QSPI bus speed. Default is set for 105MHz. The 8MB PSRAM can usually be run up to 166MHz or so. The 16MB PSRAM will give you more space, but top out at around 120MHz.

If you are international, Mouser has the 16MB parts in stock.
 
Not that pushing it should really be necessary. 105MHz is about 52MB/s, but 16 stereo channels at 44.1kHz is only 2.8MB/s. Which is why streaming from SD card can work just fine, provided you’re careful how you go about it. Which I am…
 
I tried the display driver DMA mode as well, but it actually made things worse.
Changing the audio buffer didn't make any difference for me either.
It may depend on if you are updating things all over the screen. If for example you are only updating sections of the display,
I believe the code is in place, that if you do an updateScreenAsync with a clip rectangle set, it will only update that section of the screen.

Note: the title of this thread is slightly misleading. I have not had any problems on the Teensy 4.1 with SD cards... But then I don't typically
use Audio... 😉
 
I believe the code is in place, that if you do an updateScreenAsync with a clip rectangle set, it will only update that section of the screen.
Not for the ILI9341_t3n library, as far as I can tell. All the code and comments I can find suggest only full-screen updates are implemented.

As I found out when updating the ST77xx library, an asynchronous change to a clip rectangle is actually quite hard to achieve. The general case for a tall, narrow clip which is greater than 32 pixels wide (i.e. more than the SPI FIFO depth) requires either an interrupt per line or a super-long chain of DMA TCDs. In the end I decided to impose a requirement for an intermediate buffer …
Note: the title of this thread is slightly misleading.
I’m not sure how? You even participated in the thread where we had a long discussion about how and why display updates and SD audio interfere. Not related to the ILI9341, but essentially true for the vast majority of display and audio libraries; out-of-the-box Teensyduino just doesn’t handle things properly.

I’m hoping that addressing these perennial issues will form part of the Teensyduino 1.63 campaign - time will tell.
 
Not for the ILI9341_t3n library, as far as I can tell. All the code and comments I can find suggest only full-screen updates are implemented.
I was sure that the branch that has the clipping of the Async I had merged it in, but does not appear to be:

It did not have the continuous updates support. It also also I believe interrupts on every several lines, as to skip over the words that are
not output...

I will try to rebase that branch with the current stuff or cherry pick it and see if everything still builds.
 
Back
Top