Close

Success with the SD Card!

A project log for Spy Thing

A tracking device with GPS, a digital microphone, LoRa transceiver, and an STM32 microcontroller.

ville-tiukuvaaraVille Tiukuvaara 12/21/2017 at 03:090 Comments

After quite a bit of frustration, I have finally gotten writes to the SD card working! As I mentioned in my previous log, I am using the 4-bit interface available on SD cards, rather than the simpler SPI interface used in most hobby projects. This allows more data throughput - I have it running at 48 MHz after initialization which must be done at 400 kHz or less. It turned out that my problem was leaving the signals floating. After pulling high (internally with the microcontroller) I was successful. This answer on Stack Exchange has more about this.

Saving Audio

With that issue resolved, I thought it would be neat to capture audio from the MEMS microphone and save it to the SD card. I took the USB audio streaming demo from STMicroelectronics that I mentioned in my previous post and modified it so that it saved data from the audio stream to the SD card. I found that SD writes were at first taking too long, resulting in horrible sounding audio because many samples were being dropped. I did two things to solve this:

  1. Increase the buffer length for SD writes. SD writes occur in multiples of the card sector size (usually 512 bytes) so writing less (or writing without alignment to the 512-byte segments) results in overhead.
  2. Use double buffering. I used two buffers, so that SD card writes can occur using one buffer while audio is still being recorded to the second buffer.

Here is a snippet of code that demonstrates the double buffering, starting with some global variables:

wave_sample_t PCM_buffer[AUDIO_PCM_BUFFER_LENGTH];
wave_sample_t SD_buffers[2][AUDIO_SD_BUFFER_LENGTH];
bool audio_ready = false;
uint32_t SD_buffer_pos = 0;
uint32_t SD_buffer_num = 0;

The double buffering is done with SD_buffers[0] and SD_buffers[1] (and wave_sample_t is a 16-bit type for PCM). And a snippet from the application:

uint32_t start = HAL_GetTick();
uint32_t total_samples = 0, current_samples = 0;

while(HAL_GetTick() < start + millis)
{
    if(audio_ready)
    {
        current_samples = SD_buffer_pos;
        SD_buffer_pos = 0;
        total_samples += current_samples;

        // Switch to the other buffer while writing out this buffer
        SD_buffer_num = (SD_buffer_num + 1)%2;
        audio_ready = false;

        if(logger_wav_append(file, current_samples, SD_buffers[SD_buffer_num]) != LOGGER_OK)
        {
            BSP_AUDIO_IN_Stop();
            return AUDIO_ERROR;
        }
    }
}

BSP_AUDIO_IN_Stop();
if(logger_wav_write_header(file, AUDIO_SAMPLING_FREQUENCY, 1, total_samples) != LOGGER_OK) return AUDIO_ERROR;

The line with modulus operation switches the buffer. This loop waits for audio_ready, which is set by the interrupt handler that fires when audio is ready for processing. It writes takes the audio and copies it to SD_buffers[n].

void BSP_AUDIO_IN_TransferComplete_CallBack(void)
{
    if(audio_ready) return;

    if(SD_buffer_pos + AUDIO_PCM_BUFFER_LENGTH < AUDIO_SD_BUFFER_LENGTH)
    {
        memcpy(SD_buffers[SD_buffer_num] + SD_buffer_pos, PCM_buffer, AUDIO_PCM_BUFFER_LENGTH*sizeof(wave_sample_t));
        SD_buffer_pos += AUDIO_PCM_BUFFER_LENGTH;
    }

    if(SD_buffer_pos + AUDIO_PCM_BUFFER_LENGTH >= AUDIO_SD_BUFFER_LENGTH) audio_ready = true;
}

Thus when the SD_buffer[n] is full the main application code knows to write to the SD card since audio_ready is set to true.

WAVE File Format

To actually save the data to the SD card, I decided to use the WAVE file format. This format can be used as a container for the raw uncompressed PCM data, so no processing needs to be done on the microcontroller. I am currently recording at 32 kHz with 16-bit samples, which sounds very decent. Other than the PCM data, the WAVE file has a 44-byte header that is described here

Discussions