Created
July 17, 2022 01:53
-
-
Save Roman-Port/c92b8fc3cd69b93074495fb7963b7266 to your computer and use it in GitHub Desktop.
STM32F746 I2s IQ recorder to SD card
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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