STM32 + Freertos + Audio recording + FATFS. Audio really choppy and white noisy

I’m working on STM32F405RGT6, receiving audio from ICS43432 (MSB First, 24bit on 32 bit frame).
sing Circular DMA (double buffering) to read it in. After 3 days of debugging and millions of mem corruptions and stackoverflows later I finally managed to get the pipeline to work without MemCorruptions.
Basically my implementation:
I have a UItask, that handles button presses and changes the system states. The StartWriteAudioTask() starts the DMA and creates a .wav file. Also sets the systemState to STATE_RECORDING. DMA fills the buffer and gives semaphore to my MicTask, which basically does an endian conversion and writes a wav block. Just in case I also implemented a copy of the audiobuffer, because DMA might overwrite before I can manage to write it to a wav block. Using the debugger I see that my state machine logic and start/stop works fine, file initialization works as well and even the file appears and is playable. But the timing is 2x slower and of course the overall sound is really distorted.

Here is my mic.c code:

#include “mic.h”

#include “sd.h”

#include “stm32f4xx_hal.h”

#include “freertos.h”

#include “cmsis_os.h”

#include “ui.h”

extern I2S_HandleTypeDef hi2s3;

extern osSemaphoreId RxAudioSemHandle;

extern SemaphoreHandle_t xAudioReady;

extern SemaphoreHandle_t xRecordStop;

extern SemaphoreHandle_t xAudioBlock;

#define I2S_DATA_WORD_LENGTH (24) // 24-bit MSB

#define I2S_FRAME (32) // bits per sample

#define READ_SIZE (512) // samples to read from I2S

#define BUFFER_SIZE (READ_SIZE*I2S_FRAME/16) // number of uint16_t elements expected

#define WRITE_SIZE_BYTES (BUFFER_SIZE*2) // bytes to write

#define SAMPLE_RATE (44100)

#define BITS (24)

#define CHANNELS (1)

uint16_t aud_buf[WRITE_SIZE_BYTES]; // Double buffering

static volatile uint16_t *BufPtr;

static uint16_t localBuf[BUFFER_SIZE];

/* ── File handle & byte counter ─ */

static FIL file;

static uint32_t totalBytesWritten = 0;

void convert_endianness(uint32_t *array, uint16_t Size) {

**for** (**int** i = 0; i < Size; i++) {

    array\[i\] = **\__REV**(array\[i\]);

}

}

/* ── DMA callbacks — called from ISR context ─────────────── */

void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s)

{

BufPtr = aud_buf;

osSemaphoreRelease(RxAudioSemHandle);

}

void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s)

{

// BaseType_t xHigherPriorityTaskWoken = pdFALSE;

BufPtr = &aud_buf[BUFFER_SIZE];

// xSemaphoreGiveFromISR(RxAudioSemHandle, &xHigherPriorityTaskWoken);

// portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

osSemaphoreRelease(RxAudioSemHandle);

}

void StartMicTask(void const *argument){

for(;;){

  **int** i;

  **osSemaphoreWait**(RxAudioSemHandle, osWaitForever);

  **if** (systemState != *STATE_RECORDING*){

  	**continue**;

  }

  //memcpy(localBuf, (void\*)BufPtr, WRITE_SIZE_BYTES);

  **uint32_t** validSamples = READ_SIZE / 2;

  **for** (i = 0; i < validSamples; i++) {

  	((**uint32_t**\*)localBuf)\[i\] = ((**uint32_t**\*)BufPtr)\[i \* 2 + 1\];

  }

  **convert_endianness**((**uint32_t**\*)localBuf, validSamples);

  **uint32_t** bytesToWrite = validSamples \* 3;

  **if** (**SD_WriteWavBlock**(&file, (**int32_t** \*)localBuf, validSamples, bytesToWrite) == 0)

  	totalBytesWritten += bytesToWrite;

}

}

void StartWriteAudioTask(void const *argument){

for(;;){

  **switch**(systemState){

  **case** *STATE_START_RECORDING*:

  	totalBytesWritten = 0;

  	**if** (**SD_CreateWavFile**(&file, 0, SAMPLE_RATE, BITS, CHANNELS) != 0) {

      	    **SetState**(*STATE_MAIN_MENU*);

      	    **break**;

      	}

      **HAL_I2S_Receive_DMA**(&hi2s3, aud_buf, READ_SIZE);

      **SetState**(*STATE_RECORDING*);

      **break**;

  **case** *STATE_RECORD_STOP*:

      **HAL_I2S_DMAStop**(&hi2s3);

      **osSemaphoreWait**(RxAudioSemHandle, osWaitForever);

  	**SD_FinalizeWavFile**(&file, totalBytesWritten, SAMPLE_RATE, BITS, CHANNELS);

  	totalBytesWritten = 0;

      **SetState**(*STATE_MAIN_MENU*);

      **break**;

  **default**:

      **osDelay**(50);

      **break**;

  }

}

}

and sd.c:

#include “sd.h”

#include “fatfs_platform.h”

#include <string.h>

#include <stdlib.h>

#include “main.h”

#define SUB_DIR “/REC_DIKTOFON”

/* ---------------- INTERNAL STATE ---------------- */

extern SD_HandleTypeDef hsd;

static FATFS sdFatFs;

static uint8_t sdMounted = 0;

SDStatus sdStatus = SD_STATUS_MISSING;

extern osMutexId fsMutex;

void SD_Unmount(void){

f_mount(NULL, "", 0);

sdMounted = 0;

sdStatus  = SD_STATUS_MISSING;

}

/* ---------------- INIT ---------------- */

uint8_t SD_Init(void)

{

if(BSP_PlatformIsDetected() == SD_PRESENT){

  f_mount(NULL, "", 0);

  HAL_SD_Init(&hsd);

  **if**(f_mount(&sdFatFs, "", 1) != FR_OK)

  {

  	sdMounted = 0;

  	sdStatus  = SD_STATUS_MISSING;

  	**return** 0;

  }

  sdMounted = 1;

  sdStatus  = SD_STATUS_OK;

  **return** 1;

}

return 0;

}

/* ---------------- WAV FILE FILTER ---------------- */

static uint8_t IsWavFile(const char *name)

{

**const** **char** \*dot = strrchr(name, '.');

**if**(!dot)

    **return** 0;

**return** (!strcasecmp(dot, ".wav"));

}

/* ---------------- FILE LIST ---------------- */

uint8_t SD_ListWavFiles(char list[][SD_FILENAME_LEN],

                    uint8_t maxFiles,

                    uint8_t \*count)

{

DIR dir;

FILINFO fno;

\*count = 0;

**if**(!sdMounted)

{

    sdStatus = SD_STATUS_MISSING;

    **return** 1;

}

**if**(f_opendir(&dir, SUB_DIR) != FR_OK)

{

    sdStatus = SD_STATUS_NO_FILES;

    **return** 1;

}

**while**(\*count < maxFiles)

{

    **if**(f_readdir(&dir, &fno) != FR_OK || fno.fname\[0\] == 0)

        **break**;

    **if**(fno.fattrib & AM_DIR)

        **continue**;

    **if**(IsWavFile(fno.fname))

    {

        strncpy(list\[\*count\], fno.fname, SD_FILENAME_LEN - 1);

        list\[\*count\]\[SD_FILENAME_LEN - 1\] = '\\0';

        (\*count)++;

    }

}

**if**(BSP_PlatformIsDetected() == SD_NOT_PRESENT)

{

	f_closedir(&dir);

  SD_Unmount();

  **return** 1;

}

f_closedir(&dir);

sdStatus = (\*count > 0) ? SD_STATUS_OK : SD_STATUS_NO_FILES;

**return** 0;

}

/* ---------------- WAV HEADER ---------------- */

typedef struct

{

**char**     ChunkID\[4\];

uint32_t ChunkSize;

**char**     Format\[4\];

**char**     Subchunk1ID\[4\];

uint32_t Subchunk1Size;

uint16_t AudioFormat;

uint16_t NumChannels;

uint32_t SampleRate;

uint32_t ByteRate;

uint16_t BlockAlign;

uint16_t BitsPerSample;

**char**     Subchunk2ID\[4\];

uint32_t Subchunk2Size;

} WAVHeader;

static void WAV_FillHeader(WAVHeader *hdr,

                       uint32_t dataSize,

                       uint32_t sampleRate,

                       uint16_t bits,

                       uint16_t channels)

{

memcpy(hdr->ChunkID,     "RIFF", 4);

memcpy(hdr->Format,      "WAVE", 4);

memcpy(hdr->Subchunk1ID, "fmt ", 4);

memcpy(hdr->Subchunk2ID, "data", 4);

hdr->ChunkSize     = 36 + dataSize;

hdr->Subchunk1Size = 16;

hdr->AudioFormat   = 1;

hdr->NumChannels   = channels;

hdr->SampleRate    = sampleRate;

hdr->BitsPerSample = bits;

hdr->ByteRate   = sampleRate \* channels \* (bits / 8);

hdr->BlockAlign = channels \* (bits / 8);

hdr->Subchunk2Size = dataSize;

}

/* --------------- GET FILE INDEX ------------*/

static uint8_t GetNextRecIndex(uint16_t *nextIndex)

{

DIR dir;

FILINFO fno;

int16_t maxIndex = -1;

**if** (f_opendir(&dir, SUB_DIR) != FR_OK)

    **return** 1;

**while** (1)

{

    **if** (f_readdir(&dir, &fno) != FR_OK || fno.fname\[0\] == 0)

        **break**;

    **if** (fno.fattrib & AM_DIR)

        **continue**;

    // otsi RECXX.wav

    **if** (strncasecmp(fno.fname, "REC", 3) == 0 && IsWavFile(fno.fname))

    {

    	**int** idx = 0;

  	**int** i = 3;

  	**while** (fno.fname\[i\] >= '0' && fno.fname\[i\] <= '9')

  	{

  		idx = idx \* 10 + (fno.fname\[i\] - '0');

  		i++;

  	}

        **if** (idx > maxIndex)

            maxIndex = idx;

    }

}

f_closedir(&dir);

\*nextIndex = maxIndex + 1;

**return** 0;

}

/* ---------------- WAV WRITE ---------------- */

uint8_t SD_CreateWavFile(FIL *file,

                     uint32_t dataSize,

                     uint32_t sampleRate,

                     uint16_t bits,

                     uint16_t channels){

UINT bw;

WAVHeader hdr;

osMutexWait(fsMutex, osWaitForever);

uint16_t idx;

**char** path\[64\];

**if** (GetNextRecIndex(&idx) != 0) {

    osMutexRelease(fsMutex);

    **return** 1;

}

// REC00.wav stiil

snprintf(path, **sizeof**(path),

         SUB_DIR "/REC%02u.wav", idx);

**if** (f_open(file, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK){

	osMutexRelease(fsMutex);

    **return** 1;

}

WAV_FillHeader(&hdr, dataSize, sampleRate, bits, channels);

f_write(file, &hdr, **sizeof**(hdr), &bw);

f_sync(file); // Kirjuta päis kohe kettale

osMutexRelease(fsMutex);

**return** 0;

}

uint8_t SD_WriteWavBlock(FIL *file, int32_t *data, uint32_t samples, uint32_t bytes)

{

UINT bw;

// Pack 24-bit samples from 32-bit frames, drop the lowest 8 bits

uint8_t packed\[samples \* 3\];

**for**(uint32_t i = 0; i < samples; i++){

    int32_t sample = data\[i\];  // shift out the 8 empty LSB bits

    packed\[i\*3 + 0\] = (sample)       & 0xFF;  // byte 0 LSB

    packed\[i\*3 + 1\] = (sample >> 8)  & 0xFF;  // byte 1

    packed\[i\*3 + 2\] = (sample >> 16) & 0xFF;  // byte 2 MSB

}

osMutexWait(fsMutex, osWaitForever);

FRESULT res = f_write(file, packed, samples \* 3, &bw);

osMutexRelease(fsMutex);

**return** (res == FR_OK && bw == samples \* 3) ? 0 : 1;

}

uint8_t SD_FinalizeWavFile(FIL *file, uint32_t dataSize, uint32_t sampleRate,

    uint16_t bits,

    uint16_t channels)

{

UINT bw;

osMutexWait(fsMutex, osWaitForever);

uint32_t filesize = f_size(file);

uint32_t data_len  = filesize - 44;

uint32_t total_len = filesize - 8;

f_lseek(file, 4);

f_write(file, &total_len, 4, &bw);

f_lseek(file, 40);

f_write(file, &data_len, 4, &bw);

f_close(file);

osMutexRelease(fsMutex);

**return** 0;

}

/* ---------------- HOTPLUG WATCHER ---------------- */

void StartSDWatchTask(void const *argument)

{

uint8_t lastState = BSP_PlatformIsDetected();

**if** (lastState == SD_PRESENT)

{

    **if** (SD_Init())

        sdStatus = SD_STATUS_OK;

    **else**

        sdStatus = SD_STATUS_MISSING;

}

**for**(;;)

{

    uint8_t nowState = BSP_PlatformIsDetected();

    /\* trigger ONLY on removal edge \*/

    **if**(lastState == SD_NOT_PRESENT && nowState == SD_PRESENT){

               **if** (SD_Init()){

                   sdStatus = SD_STATUS_OK;

               }

               **else**

               {

                   sdStatus = SD_STATUS_MISSING;

               }

    }

    **if**(lastState == SD_PRESENT && nowState == SD_NOT_PRESENT)

    {

    	SD_Unmount();

    	HAL_SD_DeInit(&hsd);;

    	sdStatus = SD_STATUS_MISSING;

        EventType evt = EVT_SD_REMOVED;

        xQueueSend(uiQueue, &evt, 0);

    }

    lastState = nowState;

    osDelay(50);

}

}

Would be very thankful for any help!

Just some food for thought: If the WAV file sample rate is 44.1 KHz and the bit depth is 24, then the 132300 bytes of audio is encoded in the WAV for every second of audio. It may be worth investigating if you are achieving this write throughput with FATFS.

Also, with this sample rate, 512 samples is approx. 11 milliseconds of audio. The state machine, though, has a 50 tick(?) sleep in its default state handler, which doesn’t seem to align at all with the buffer size. Without any rearchitecturing, I would try setting the buffer to a to a multiple of 441 samples (1ms of audio, but see edit) and using a mechanism like vTaskDelayUntil() to align the sleeps to millisecond increments as well.

Edit: I didn’t consider block sizes of the storage medium while writing this. So, it might have to also be a multiple of the block size. I don’t think this might be a way forward unless you have 14K of memory to throw around. The arbitrary sleep still seems suspect. This can probably be changed to use FreeRTOS Direct-to-task notifications to block the state machine task until a buffer swap is needed or the recording should be stopped, which should reduce the worst-case delay from 49 ticks (delaying for 50 ticks right before the DMA completes) to however long a context switch from ISR takes.

Thank you maitju_ut for your post. Although I worked a lot with streaming audio, I don’t have the time to go through your sources.

This forum is about FreeRTOS and its libraries, not about “other” projects. You’re not using FreeRTOS_Plus_FAT, are you?

Some things that comes to mind:

whether you are recording or playing, make sure that there are at least two alternating buffers of the same size. The longer the buffers, the less choppy the sound should be. But remind, access to an SD-card on an STM4x is pretty slow.

In case you have two buffers: when buffer-0 is writing, buffer-1 should be recording. Before buffer-1 reaches the end, buffer-0 should be passed to the recording task. This swapping of buffers is typically done in an ISR.

Why don’t you build it up step by step? Forget about the SD-card and just record sound. Don’t store it. Do some measurement about the actual timing.

And if you want your source code to be reviewed, I recommend to using one of those AI sites.

Good luck,