Skip to content

Instantly share code, notes, and snippets.

@Roman-Port
Created July 17, 2022 01:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Roman-Port/c92b8fc3cd69b93074495fb7963b7266 to your computer and use it in GitHub Desktop.
Save Roman-Port/c92b8fc3cd69b93074495fb7963b7266 to your computer and use it in GitHub Desktop.
STM32F746 I2s IQ recorder to SD card
#include "core.h"
#include "main.h"
#include "fatfs.h"
#define RECORDER_STATUS_DISABLED 0 // Recorder is off.
#define RECORDER_STATUS_RECORDING 1 // Recording normally.
#define RECORDER_BUFFER_STATUS_AVAILABLE 0 // Buffer is available for writing
#define RECORDER_BUFFER_STATUS_PENDING 1 // Waiting to write to disk
#define BUFFER_SIZE 8192
#define BUFFER_COUNT 8
typedef struct {
uint8_t status;
uint16_t data_m[BUFFER_SIZE];
uint16_t data_s[BUFFER_SIZE];
} recorder_buffer_t;
static FATFS fs;
static FIL file;
static recorder_buffer_t recorder_buffers[BUFFER_COUNT];
static uint32_t disk_buffer[BUFFER_SIZE];
static uint8_t recorder_status = RECORDER_STATUS_DISABLED;
static uint32_t dropped_buffers = 0;
static uint32_t written_buffers = 0;
static uint8_t wav_file_header[44] = {
0x52, 0x49, 0x46, 0x46, 0xFF, 0xFF, 0xFF, 0xFF, 0x57, 0x41, 0x56, 0x45,
0x66, 0x6D, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, /* <- channels */ 0x00,
0x2A, 0xEB, 0x09, 0x00, 0x10, 0xB1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00,
0x64, 0x61, 0x74, 0x61, 0xFF, 0xFF, 0xFF, 0xFF
};
// Following should only be touched from DMA callbacks
static uint8_t current_buffer = 0xFF;
static uint8_t next_buffer_ready = 0xFF; // Set halfway through block to indicate if this block is complete or not after the end
static uint8_t next_buffer_index = 0xFF; // Set halfway through the block BY THE MASTER. 0xFF indicates that it is unset, and is invalid if read by the slave
// Determines the next DMA buffer to use and updates the state accordingly. Returns the bank index to use next.
static inline void recorder_determine_next_dma_buffer() {
//Peek at the next buffer and see if it's ready or not
int after = (current_buffer + 1) % BUFFER_COUNT;
if (recorder_buffers[after].status == RECORDER_BUFFER_STATUS_AVAILABLE) {
//Ideal state. We'll be good to queue up the next block
next_buffer_ready = 1;
next_buffer_index = after;
} else {
//FUCK!! We didn't complete the write in time! We'll have to overwrite the data we just got in the current buffer while the file catches up
next_buffer_ready = 0;
next_buffer_index = current_buffer;
}
}
/* DMA CALLBACKS */
// First or second half of circular buffer is complete
static void recorder_dma_master_completed(DMA_HandleTypeDef *hdma) {
//Change status of the block
if (next_buffer_ready) {
// We are not overriding ourselves (as we would if a buffer had to be dropped) so mark the active buffer as complete and advance
recorder_buffers[current_buffer].status = RECORDER_BUFFER_STATUS_PENDING;
current_buffer = next_buffer_index;
} else {
//Count the buffer as dropped
dropped_buffers++;
}
}
// First half of circular buffer is half full
static void recorder_dma_master_half_completed_m0(DMA_HandleTypeDef *hdma) {
//Determine other DMA buffer
recorder_determine_next_dma_buffer();
//Swap other DMA buffer
hsai_BlockA1.hdmarx->Instance->M1AR = (uint32_t)recorder_buffers[next_buffer_index].data_m;
hsai_BlockB1.hdmarx->Instance->M1AR = (uint32_t)recorder_buffers[next_buffer_index].data_s;
}
// Second half of circular buffer is half full
static void recorder_dma_master_half_completed_m1(DMA_HandleTypeDef *hdma) {
//Determine other DMA buffer
recorder_determine_next_dma_buffer();
//Swap other DMA buffer
hsai_BlockA1.hdmarx->Instance->M0AR = (uint32_t)recorder_buffers[next_buffer_index].data_m;
hsai_BlockB1.hdmarx->Instance->M0AR = (uint32_t)recorder_buffers[next_buffer_index].data_s;
}
// Error in DMA. Set a breakpoint on abort.
static void recorder_dma_error(DMA_HandleTypeDef *hdma) {
abort();
}
/* CONFIGURATION */
static void recorder_configure_sai(SAI_HandleTypeDef* hsai) {
__HAL_SAI_ENABLE_IT(hsai, SAI_IT_OVRUDR | SAI_IT_AFSDET | SAI_IT_LFSDET);
hsai->Instance->CR1 |= SAI_xCR1_DMAEN;
hsai->Instance->CR2 |= SAI_xCR2_FFLUSH;
}
// Does initial DMA configuration for the master, like changing the mode and setting up callbacks
static void recorder_configure_dma_master(SAI_HandleTypeDef *hsai) {
//Configure DMA callbacks
hsai->hdmarx->XferCpltCallback = recorder_dma_master_completed;
hsai->hdmarx->XferM1CpltCallback = recorder_dma_master_completed;
hsai->hdmarx->XferHalfCpltCallback = recorder_dma_master_half_completed_m0;
hsai->hdmarx->XferM1HalfCpltCallback = recorder_dma_master_half_completed_m1;
hsai->hdmarx->XferErrorCallback = recorder_dma_error;
hsai->hdmarx->XferAbortCallback = NULL;
//Configure DMA settings
hsai->hdmarx->Instance->NDTR = BUFFER_SIZE;
hsai->hdmarx->Instance->PAR = (uint32_t)&hsai->Instance->DR;
hsai->hdmarx->Instance->CR |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME | DMA_SxCR_DBM | DMA_IT_HT;
hsai->hdmarx->Instance->FCR |= DMA_IT_FE;
//Make sure the current target is reset to the first buffer
hsai->hdmarx->Instance->CR &= ~DMA_SxCR_CT;
//Set the next buffers
hsai->hdmarx->Instance->M0AR = (uint32_t)recorder_buffers[current_buffer].data_m;
hsai->hdmarx->Instance->M1AR = (uint32_t)recorder_buffers[(current_buffer + 1) % BUFFER_COUNT].data_m;
//Enable DMA
__HAL_DMA_ENABLE(hsai->hdmarx);
}
// Does initial DMA configuration for the slave, like changing the mode and setting up callbacks
static void recorder_configure_dma_slave(SAI_HandleTypeDef *hsai) {
//Configure DMA callbacks
hsai->hdmarx->XferCpltCallback = NULL;
hsai->hdmarx->XferM1CpltCallback = NULL;
hsai->hdmarx->XferHalfCpltCallback = NULL;
hsai->hdmarx->XferM1HalfCpltCallback = NULL;
hsai->hdmarx->XferErrorCallback = recorder_dma_error;
hsai->hdmarx->XferAbortCallback = NULL;
//Configure DMA settings
hsai->hdmarx->Instance->NDTR = BUFFER_SIZE;
hsai->hdmarx->Instance->PAR = (uint32_t)&hsai->Instance->DR;
hsai->hdmarx->Instance->CR |= DMA_IT_TE | DMA_IT_DME | DMA_SxCR_DBM;
hsai->hdmarx->Instance->FCR |= DMA_IT_FE;
//Make sure the current target is reset to the first buffer
hsai->hdmarx->Instance->CR &= ~DMA_SxCR_CT;
//Set the next buffers
hsai->hdmarx->Instance->M0AR = (uint32_t)recorder_buffers[current_buffer].data_s;
hsai->hdmarx->Instance->M1AR = (uint32_t)recorder_buffers[(current_buffer + 1) % BUFFER_COUNT].data_s;
//Enable DMA
__HAL_DMA_ENABLE(hsai->hdmarx);
}
static void interleave_channels(uint16_t* output, const uint16_t* inA, const uint16_t* inB) {
for (int i = 0; i < BUFFER_SIZE; i++) {
*(output++) = *(inA++);
*(output++) = *(inB++);
}
}
// The main loop to run while recording
static void recorder_main_loop() {
int target = 0;
UINT bw;
// Temporary; Just read 512 buffers for now
while (written_buffers < 512) {
//Wait for signal to begin write
while (recorder_buffers[target].status == RECORDER_BUFFER_STATUS_AVAILABLE);
//Interleave the two channels...SLOOOOW
interleave_channels((uint16_t*)disk_buffer, recorder_buffers[target].data_m, recorder_buffers[target].data_s);
//Write the buffer to disk
HAL_GPIO_WritePin(GPIOB, LD3_Pin, GPIO_PIN_SET);
if (f_write(&file, disk_buffer, sizeof(disk_buffer), &bw) != FR_OK)
abort();
HAL_GPIO_WritePin(GPIOB, LD3_Pin, GPIO_PIN_RESET);
//Update state
written_buffers++;
recorder_buffers[target].status = RECORDER_BUFFER_STATUS_AVAILABLE;
//Advance to next buffer
target = (target + 1) % BUFFER_COUNT;
}
}
void app_main() {
//Mount the filesystem
if (f_mount(&fs, SDPath, 1) != FR_OK)
abort();
//Open test file
if (f_open(&file, "0:/test.wav", FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
abort();
//Write WAV file header
UINT bw;
if (f_write(&file, wav_file_header, sizeof(wav_file_header), &bw) != FR_OK)
abort();
//Reset state
dropped_buffers = 0;
written_buffers = 0;
current_buffer = 0;
recorder_status = RECORDER_STATUS_RECORDING;
next_buffer_ready = 0xFF;
next_buffer_index = 0xFF;
//Reset buffer states
for (int i = 0; i < BUFFER_COUNT; i++)
recorder_buffers[i].status = RECORDER_BUFFER_STATUS_AVAILABLE;
//Configure SAIs
recorder_configure_sai(&hsai_BlockB1);
recorder_configure_sai(&hsai_BlockA1);
//Configure DMA
recorder_configure_dma_slave(&hsai_BlockB1);
recorder_configure_dma_master(&hsai_BlockA1);
//Enable SAI peripherals. We enable the slave B block first, since it won't do anything til the master A block is enabled
__HAL_SAI_ENABLE(&hsai_BlockB1);
__HAL_SAI_ENABLE(&hsai_BlockA1);
//Enter main loop
recorder_main_loop();
//Abort DMA and stop SAI
HAL_DMA_Abort(hsai_BlockA1.hdmarx);
HAL_DMA_Abort(hsai_BlockB1.hdmarx);
__HAL_SAI_DISABLE(&hsai_BlockA1);
__HAL_SAI_DISABLE(&hsai_BlockB1);
//Close file
f_close(&file);
//Finally, change state
recorder_status = RECORDER_STATUS_DISABLED;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment