AudioPlaySdWav DMA timing and buffer misalignment issues

sjv

New member
Summary:

To get the example code to play a WAV file from the built-in SD card on a Teensy 3.5, I had to make two changes:

1. A short delay was needed between the __disable_irq() and SD.open() calls in AudioPlaySdWav::play(). This prevented the Teensy from getting stuck indefinitely waiting for the DMA to complete in SDHC_CardReadBlock().

2. The audio buffer in AudioPlaySdWav needed to be 4-byte aligned, otherwise the alignment check at the top of SDHC_CardReadBlock() would fail.


Detailed Description:

I wanted to try out playing a WAV file from the built-in SD card on my Teensy 3.5 so I started with the example code and made the necessary changes for DAC output as per the comments.

My only other change was to add a delay to the start of setup() to give the serial monitor time to start capturing data being transmitted by the Teensy. Here's the code I used:

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioPlaySdWav     playWav1;
AudioOutputAnalog  audioOutput;
AudioConnection    patchCord1(playWav1, 0, audioOutput, 0);
AudioConnection    patchCord2(playWav1, 1, audioOutput, 1);

// Use these with the Teensy 3.5 & 3.6 SD card
#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

void setup() {
  delay(2000);
  Serial.begin(9600);

  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(8);

  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // stop here, but print a message repetitively
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
}

void playFile(const char *filename)
{
  Serial.print("Playing file: ");
  Serial.println(filename);

  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playWav1.play(filename);

  // A brief delay for the library read WAV info
  delay(5);

  // Simply wait for the file to finish playing.
  while (playWav1.isPlaying()) {
  }
}


void loop() {
  playFile("SDTEST1.WAV");  // filenames are always uppercase 8.3 format
  delay(500);
}

I copied the the SDTEST1.WAV file to the SD card, inserted it into the Teensy, then built and ran the example. Nothing happened. After adding some Serial logging to see what was going on, I saw that the code never returned from the call to playWav1.play().

I added Serial messages to the implementation of AudioPlaySdWav::play() and in doing so, inadvertently discovered that it no longer got stuck if a Serial message was printed between the __disable_irq() and SD.open() calls. It turns out that a simple delay(1) call in this position is also enough to fix the issue.

I then worked down through the call stack to find out where the problem first occurs without the delay. It turned out to be the while loop that waits for DMA to complete in SDHC_CardReadBlock():

Code:
SdFile::open
SdFile::readDirCache
SdFile::read()
SdFile::read(uint8_t*, 1)
SdVolume::cacheRawBlock
Sd2Card::readBlock
SDHC_CardReadBlock
	while(!dmaDone);

So it appears that there is some kind of timing issue to do with how soon the DMA is issued after the __disable_irq() call. I don't know how DMA and interrupts work on this chip in anything like enough detail to be able to debug this further unfortunately.

Anyway, with the delay(1) call in place, I found that the AudioPlaySdWav::play() call returned but AudioPlaySdWav::isPlaying() was immediately returning false. Digging into the code I found that the AudioPlaySdWav class was reading a buffer full of zeros from the file. If I added code to simply read the file myself instead of trying to play it, I was able to get a hexdump of valid data. This worked even though I was using the same code to do the reading as the AudioPlaySdWav class was.

I traced the read calls to find out what was going on when AudioPlaySdWav was doing the reading. It turned out that the buffer declared in the AudioPlaySdWav class as uint8_t buffer[512] was not 4-byte aligned. This may be due to me compiling with -Os but I wouldn't have thought I'd be alone in doing that? Anyway, there's a check at the top of the SDHC_CardReadBlock() function that fails and returns -1 if the buffer is misaligned. The AudioPlaySdWav code implicitly casts this -1 value into the uint16_t buffer_length member variable and treats it as a successful read of 65535 bytes. It then tries to process the buffer and fails to find a valid header in the buffer full of zeroes.

The fix is to 4-byte align the buffer with a C++11 alignas(4):

Code:
alignas(4) uint8_t buffer[512];

With the two fixes in place, I can now run the example code and see audio waveforms generated on the DAC pin! I'm a bit surprised that playing WAV files from an SD card seems to just work for everyone else. At first I thought it might be my toolchain setup as I'm developing from Makefiles, but running the code in a simple Arduino sketch showed all the same issues.

Anyway, I hope this bug report is of some use. I'm really loving playing with Teensy and extremely grateful for PJRC and the community's contributions so I wanted to give back what little I could here.
 
Thanks for the bug fixing. This likely will solve problem with WavFilePlayer introduced by Teensyduino 1.46 and discussed in several recent threads.
https://forum.pjrc.com/threads/56239-Simple-WavFilePlayer-not-working?p=206324&viewfull=1#post206324

alternate syntax uint8_t buffer[512] __attribute__ ((aligned (4)));
and need to investigate "minimum" delay() .... delay may also be needed in play_sd_raw.cpp

delay() calls micros() which does __disable_irq()/ __enable_irq() ? cancelling benefit for disable/enable around the SD.open??
(delayMicroseconds() or spin-loop results in hang)

The alignment and delay fix WavFilePlayer hang.

EDIT: with no delay() but commenting out __disable_irq() before SD.open(), WavFilePlayer sketch works on T3.5, T3.6 and T4B2.

Observation: The 1.46 SD lib switched from SDHC polling to DMA ISR, so having a disable_irq before SD.open is probably not a good thing any more (why was disable_irq needed?). The delay(1) effectively enable_irq before SD.open(). WavFilePlayer sketch also works with alignment and just removing disable/enable_irq around the SD.open() in play_sd_wav.cpp

AudioPlaySdRaw with BUILTIN_SDCARD also hangs -- works if you remove __disable_irq() before SD.open() in libraries/Audio/play_sd_raw.cpp

Consider this March pull request: https://github.com/PaulStoffregen/Audio/pull/294

EDIT: Paul's FIX 8/11/19, get latest from https://github.com/PaulStoffregen/SD
 
Last edited:
Thanks for the bug fixing. This likely will solve problem with WavFilePlayer introduced by Teensyduino 1.46 and discussed in several recent threads.
https://forum.pjrc.com/threads/56239-Simple-WavFilePlayer-not-working?p=206324&viewfull=1#post206324

alternate syntax uint8_t buffer[512] __attribute__ ((aligned (4)));

Thanks for the confirmation, manitou. Looks like I should have searched the forums properly before assuming that it was working for everyone. I agree, __attribute__ aligned syntax is probably better for compatibility.
 
Just a quick followup to this old thread. Teensyduino 1.47 has the fix for this problem. If anyone with 1.46 is still experiencing this issue, just upgrade to 1.47.
 
Back
Top