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

**URL:** <https://forums.freertos.org/t/stm32-freertos-audio-recording-fatfs-audio-really-choppy-and-white-noisy/24970>\
**Category:** Libraries\
**Tags:** debug\
**Created:** [May 4, 2026, 7:39pm UTC](https://forums.freertos.org/t/stm32-freertos-audio-recording-fatfs-audio-really-choppy-and-white-noisy/24970 "2026-05-04T19:39:43Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![maitju\_ut](https://avatars.discourse-cdn.com/v4/letter/m/b2d939/32.png) [@maitju\_ut](https://forums.freertos.org/u/maitju_ut)\
**Post date:** [May 4, 2026, 7:39pm UTC](https://forums.freertos.org/t/stm32-freertos-audio-recording-fatfs-audio-really-choppy-and-white-noisy/24970/1 "2026-05-04T19:39:44Z")

</div>

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) {
> 
> ```auto
> **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** (;;){
> 
> ```auto
> **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** (;;){
> 
> ```auto
> **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** ){
> 
> ```auto
> f_mount(NULL, "", 0);
> 
> sdMounted = 0;
> 
> sdStatus = SD_STATUS_MISSING;
> 
> ```
> 
> }
> 
> /\* ---------------- INIT ---------------- \*/
> 
> uint8\_t SD\_Init( **void** )
> 
> {
> 
> **if** (BSP\_PlatformIsDetected() == SD\_PRESENT){
> 
> ```auto
> 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)
> 
> {
> 
> ```auto
> **const**  **char** \*dot = strrchr(name, '.');
> 
> **if** (!dot)
> 
> **return** 0;
> 
> **return** (!strcasecmp(dot, ".wav"));
> 
> ```
> 
> }
> 
> /\* ---------------- FILE LIST ---------------- \*/
> 
> uint8\_t SD\_ListWavFiles( **char** list[][SD\_FILENAME\_LEN],
> 
> ```auto
> uint8_t maxFiles,
> 
> uint8_t \*count)
> 
> ```
> 
> {
> 
> ```auto
> 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)
> 
> ```
> 
> {
> 
> ```auto
> f_closedir(&dir);
> 
> SD_Unmount();
> 
> **return** 1;
> 
> ```
> 
> }
> 
> ```auto
> f_closedir(&dir);
> 
> sdStatus = (\*count > 0) ? SD_STATUS_OK : SD_STATUS_NO_FILES;
> 
> **return** 0;
> 
> ```
> 
> }
> 
> /\* ---------------- WAV HEADER ---------------- \*/
> 
> **typedef**  **struct**
> 
> {
> 
> ```auto
> **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,
> 
> ```auto
> uint32_t dataSize,
> 
> uint32_t sampleRate,
> 
> uint16_t bits,
> 
> uint16_t channels)
> 
> ```
> 
> {
> 
> ```auto
> 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)
> 
> {
> 
> ```auto
> 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,
> 
> ```auto
> 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;
> 
> ```auto
> // 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,
> 
> ```auto
> uint16_t bits,
> 
> uint16_t channels)
> 
> ```
> 
> {
> 
> ```auto
> 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)
> 
> {
> 
> ```auto
> 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!

---

<div class="post-metadata">

**Author:** ![cookpate](https://d3iykfbgfh8zhg.cloudfront.net/user_avatar/forums.freertos.org/cookpate/32/2962_2.png) [@cookpate](https://forums.freertos.org/u/cookpate)\
**Post date:** [May 4, 2026, 11:48pm UTC](https://forums.freertos.org/t/stm32-freertos-audio-recording-fatfs-audio-really-choppy-and-white-noisy/24970/2 "2026-05-04T23:48:14Z")

</div>

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()](https://freertos.org/Documentation/02-Kernel/04-API-references/02-Task-control/02-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](https://freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-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.

---

<div class="post-metadata">

**Author:** ![htibosch](https://d3iykfbgfh8zhg.cloudfront.net/user_avatar/forums.freertos.org/htibosch/32/62_2.png) [@htibosch](https://forums.freertos.org/u/htibosch)\
**Post date:** [May 5, 2026, 6:45am UTC](https://forums.freertos.org/t/stm32-freertos-audio-recording-fatfs-audio-really-choppy-and-white-noisy/24970/4 "2026-05-05T06:45:21Z")

</div>

Thank you [maitju\_ut](https://forums.freertos.org/u/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](https://github.com/FreeRTOS/Lab-Project-FreeRTOS-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,
