STM32 WAV Player

Hardware

STM32F411CEU6 (Blackpill)

  • WeAct Aliexpress

ST-Link V2

  • Amazon, Aliexpress

MicroSD card breakout board+

PNY 32GB MicroSD card

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 USART_config

SPI2 - SD Card

  • 8 bit data size
  • We’ll set the prescaler in SW so it doesn’t really matter SPI2
  • Pull-up MISO and MOSI lines
  • Depending on your SD card module schematic you may not need to pullup the data lines SPIpins

SPI2 Chip Select

  • I chose PB13 for its proximity, be sure to pull up the CS as well SPICS

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 I2S
  • Enable both global interrupts I2Snvic
  • Half word data width (16 bit data)
  • Circular mode, incrementing the memory address I2Sdma
  • No need to pull up these lines but set the output to high I2Sgpio

Middleware - FATFS

  • User Defined
  • Make sure the min and max sector sizes are 512 FATFS

Software

user_diskio_spi.c/.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 */