2026-05-04 2:25 PM
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 int16_t localBuf[BUFFER_SIZE];
/* ── File handle & byte counter ─ */
static FIL file;
static uint32_t totalBytesWritten = 0;
/* ── 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(;;){
osSemaphoreWait(RxAudioSemHandle, osWaitForever);
if (systemState != STATE_RECORDING){
continue;
}
uint32_t validSamples = READ_SIZE / 2; // 256 samples
for (int i = 0; i < validSamples; i++) {
// 1. Capture the 32-bit frame from BufPtr (Pattern: 0, 0, Data, Data)
// We grab the second 32-bit word (index i*2 + 1)
int32_t rawValue = ((int32_t*)BufPtr)[i * 2 + 1];
int32_t clean = ((rawValue & 0xFFFF) << 8) | (rawValue >> 24);
// 4. Store back into localBuf (compacting it)
// We cast localBuf to int32_t* to store the cleaned 32-bit words
((int32_t*)localBuf)[i] = clean;
}
// 5. Write only the cleaned samples (256 samples * 3 bytes/sample = 768 bytes)
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, BUFFER_SIZE);
SetState(STATE_RECORDING);
break;
case STATE_RECORD_STOP:
osSemaphoreWait(RxAudioSemHandle, osWaitForever);
HAL_I2S_DMAStop(&hi2s3);
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);
}
}
2026-05-05 1:36 AM
Hello @maitj and welcome to the St community,
Hard to tell what the problem is. That need more debug from you even you already did it..
It could be a problem related to the performance, is the MCU running at its maximum system clock speed?
It could be also an interrupt priority related issue. The interrupts level are not well chosen.
Use an oscilloscope and toggle GPIOs at critical sections and see what happens versus what you are expecting.