STM32 WAV Player
Hardware
STM32F411CEU6 (Blackpill)
- WeAct Aliexpress
ST-Link V2
- Amazon, Aliexpress
MicroSD card breakout board+
- https://www.adafruit.com/product/254
- Get a 4-bit SDIO supported version instead
PNY 32GB MicroSD card
- https://a.co/d/beuO1eM
- format as FATFS and place a wav file named ’test.wav'
HiLetgo PCM5102 DAC Module
FT232RL
A Reliable 5V supply
I2S - PCM5102 Soldering/Wiring
- Solder the PCM5102 H1-4L as follows:
Pad Signal Level Description H1L FLT GND Normal Latency H2L DEMP GND De-emphasis control off H3L XMST 3.3V Soft un-mute H4L FMT GND I2S - Wire the PCM5102 as follows:
STM32 PCM5102 WS LCK CK BCK SD DIN GND SCK - See this link for more info on the PCM5102
CubeMX
- See my post on my Arch STM32 workflow
Clock Configuration
- Set it to 100MHz cus why not
USART2 - Debug/Printf()
- 115200,8N1
SPI2 - SD Card
- 8 bit data size
- We’ll set the prescaler in SW so it doesn’t really matter
- Pull-up MISO and MOSI lines
- Depending on your SD card module schematic you may not need to pullup the data lines
SPI2 Chip Select
- I chose PB13 for its proximity, be sure to pull up the CS as well
I2S1 - DAC/Audio
- 16 bits data on 16 bits frame
- 44 KHz
- This is to match my wav file but you can also dynamically set this
- Enable both global interrupts
- Half word data width (16 bit data)
- Circular mode, incrementing the memory address
- No need to pull up these lines but set the output to high
Middleware - FATFS
- User Defined
- Make sure the min and max sector sizes are 512
Software
user_diskio_spi.c/.h
- Copy these two files into FATFS/Target
- https://github.com/kiwih/cubeide-sd-card/blob/master/cubeide-sd-card/FATFS/Target/user_diskio_spi.c
- https://github.com/kiwih/cubeide-sd-card/blob/master/cubeide-sd-card/FATFS/Target/user_diskio_spi.h
Modify user_diskio.c to use your new files
- Line numbers may not be correct, use your judgement
user_diskio.c
38d37
+ #include "user_diskio_spi.h"
85c84,85
+ return USER_SPI_initialize(pdrv);
---
- Stat = STA_NOINIT;
- return Stat;
99c99,100
+ return USER_SPI_status(pdrv);
---
- Stat = STA_NOINIT;
- return Stat;
119c120
+ return USER_SPI_read(pdrv,buff,sector,count);
---
- return RES_OK;
141c142
+ return USER_SPI_write(pdrv,buff,sector,count);
---
- return RES_OK;
161c162,163
+ return USER_SPI_ioctl(pdrv,cmd,buff);
---
- DRESULT res = RES_ERROR;
- return res;
Setup main.h according to user_diskio_spi.c
user_diskio_spi.c
//Make sure you set #define SD_SPI_HANDLE as some hspix in main.h
//Make sure you set #define SD_CS_GPIO_Port as some GPIO port in main.h
//Make sure you set #define SD_CS_Pin as some GPIO pin in main.h
/* Private defines ------------------------------------*/
#define SD_CS_Pin GPIO_PIN_13
#define SD_CS_GPIO_Port GPIOB
/* USER CODE BEGIN Private defines */
#define SD_SPI_HANDLE hspi2
/* USER CODE END Private defines */
Add user_diskio_spi.c to the Makefile
# C sources
C_SOURCES = \
Core/Src/main.c \
...
FATFS/Target/user_diskio.c \
FATFS/Target/user_diskio_spi.c \
...
My main.c
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
/* USER CODE END Includes */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void qprint(const char *fmt, ...);
void qprint(const char *fmt, ...)
{
static char buffer[256];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
int len = strlen(buffer);
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, len, -1);
}
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// Globals /////////////////////////////////////////////////////////////////////
FATFS fatfs;
FRESULT fres;
FIL fil;
UINT bytesRead;
// For 44.1kHz stereo, one frame is 2 samples (left + right channel)
#define SAMPLES_PER_FRAME 2
#define SAMPLE_RATE 44100
#define BUFFER_TIME_MS 20 // 20ms buffer is a good compromise
// Calculate buffer size based on sample rate
#define AUDIO_BUFFER_SIZE ((SAMPLE_RATE * BUFFER_TIME_MS * SAMPLES_PER_FRAME) / 1000)
#define HALF_BUFFER_SIZE (AUDIO_BUFFER_SIZE/2)
int16_t samples[AUDIO_BUFFER_SIZE];
uint32_t fread_size = 0;
uint32_t recording_size = 0;
uint32_t played_size = 0;
volatile bool isHalfBufferDone = false;
volatile bool isFullBufferDone = false;
// Callbacks ///////////////////////////////////////////////////////////////////
void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
isHalfBufferDone = true;
}
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
isFullBufferDone = true;
played_size += AUDIO_BUFFER_SIZE;
}
// Main ////////////////////////////////////////////////////////////////////////
int main(void)
{
// Abstraction Layer and System Clock
HAL_Init();
SystemClock_Config();
// Peripheral Init
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_DMA_Init();
MX_FATFS_Init();
MX_SPI2_Init();
MX_I2S1_Init();
// Delay for SD Card
HAL_Delay(1000);
// Open the file system
fres = f_mount(&fatfs, "", 1);
if (fres != FR_OK) {
qprint("f_mount error (%i)\r\n", fres);
}
// Open the Song
fres = f_open(&fil, "test.wav", FA_READ);
// Get song header data
uint8_t header[44];
f_read(&fil, header, 44, &bytesRead);
qprint("Sample Rate: %d\r\n", *(uint32_t*)(header + 24));
qprint("Bits Per Sample: %d\r\n", *(uint16_t*)(header + 34));
qprint("Channels: %d\r\n", *(uint16_t*)(header + 22));
// Get Song Size
f_lseek(&fil, 40);
f_read(&fil, &recording_size, 4, &bytesRead);
qprint("Recording Size: %d\r\n", recording_size);
// Play the Song
f_read(&fil, samples, AUDIO_BUFFER_SIZE * sizeof(uint16_t), &bytesRead);
HAL_I2S_Transmit_DMA(&hi2s1, (uint16_t *)samples, AUDIO_BUFFER_SIZE);
while(1)
{
if (isHalfBufferDone) {
f_read(&fil, samples, HALF_BUFFER_SIZE * sizeof(uint16_t),
&bytesRead);
isHalfBufferDone = false;
}
if (isFullBufferDone) {
f_read(&fil, &samples[HALF_BUFFER_SIZE],
HALF_BUFFER_SIZE * sizeof(uint16_t), &bytesRead);
isFullBufferDone = false;
}
if (played_size >= recording_size) {
HAL_I2S_DMAStop(&hi2s1);
}
}
// Unmount the FS
f_mount(NULL, "", 0);
}
/* USER CODE END 0 */