How to play audio files using STM32?
Welcome back to the second part of our tutorial.
Assuming you have done the previous steps in the part 1 , the files are now created and you will need to manually copy the adpcm.c and adpcm.h from the STSW-STM32022 pack into your STM32CubeIDE project: Core/Src and Core/Inc folders. The result should look like this:
As you might have noticed, in the image above there is also a source folder called “AudioFile”, do not worry, as the next steps will allow us to create it, but a small process for converting a given audio file (WAV format) into a C vector that can be used by our STM32 is needed. So, speaking of the process, this is how ST presented the approach it on the AN4453 that we are using as a reference:
As mentioned in the prerequisites, we need to install a tool called SOX (click here to download it) this tool can convert WAV into IMA using a given output sampling frequency. As you may recall, our target for audio output sampling frequency is 15625Hz.
Once installed, the SOX tool needs to be called using the CMD.exe, so this is a quick example of how to call it–you can refer to the application note if you need more details:
Change the directory to be in the installation folder of the SOX tool, in this case it was in C:\Program files (x86)\sox-14-4-2. The call is quite simple. Call the sox.exe and pass the arguments: *.wav input file, resample frequency (-r 15625), output file name *.ima, gain.
In this example, the audio file used is called “pure_insperation.wav” and it is in this path” C:\Users\MYUSERNAME\Desktop\AudioADPCM\AudioFile”, thus the CLI command looks like this:
We need another tool called HxD (click here to download it) that can convert IMA into C–it is a GUI interface and it is fairly simple to use. Once installed, go to File->Open: select the *.ima file:Use File->Export to C. Specify the path you want the file to be placed and done–now we have the C file. With the C file, we can create the source folder and add it inside. By right clicking in the STM32CubeIDE’s project, we can create a source folder by:Note: if you do not use the “Source Folder”, the STM32CubeIDE will not recognize its content as something to build and your application will failJust as a checkpoint, your workspace structure should look like this:If you have made it this far, congratulations, you have made all the foundation steps needed for the STM32 to play your audio file, all that is left to be implemented is the transfer from the C vector that contains the audio file to the peripheral, either PWM or DAC. To make a more realistic implementation, this article maps the internal FLASH with a section just for the audio file(s) as this would relate to an enhanced application with external memory being used as the storage location.The main.c and stm32g0xx_it.c have functions responsible to load the pointer of the given audio and its size into a structure–this happens in the function LoadAudioFiles() located in the main.c file and we will have the TIM3 interrupt handler, which resides in the stm32g0xx_it.c, to fetch the decoded content from the pointer and output it over the PWM or DAC.To prepare the FLASH memory section that we need to edit and add this section into the linker file. The linker file is the STM32G071RBTX_FLASH.ld:Once opened, add the few lines below, this will create a section called “myAudioFiles” in the FLASH memory, located 20KB (0x5000) after the initial code:As mentioned, this could be replaced by your external memory position, mapped on the linker file. Now, on the audio file, you can use the __attribute__((section(".myAudioFiles"))) to place it in the newly created section. As the C vector file created will be edited, it is suggested to create a define for the size and add this define in the “adpcm.h”, thus adding these modifications:In the adpcm.h, these small modifications were made to create the defines used previously and to create a structure that will allow multiple files to be loaded.Note that the #define ADPCMD_PI 6622 should reflect your actual vector size and you can customize the name. As this implementation only deals with a single audio file, the #define NUMBER_OF_AUDIO_FILES 1 Based on the automatically generated code, we need to add a few extra lines to make our application run. Starting with the main.c, you will need to:
Include the adpcm.h
Create an AudioElement variable
Create the LoadAudioFile function, this will require an AudioElement type variable
In the main function, set the PWM starting values or enable the DAC
Enable the TIM3 periodic interrupt
The main loop consists in a small button check and it could increment the audio file to be played if more than 1 were present.
Translating the steps above into actual code please locate it in the main.c we have it as shown below, please track the code position verifying the /* USER CODE BEGIN*/ and /* USER CODE END */
/* USER CODE BEGIN Includes */
#include "adpcm.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PD */
#define DEFAULT_STARTUP_VAL (0x80)
/* USER CODE END PD */
/* USER CODE BEGIN PV */
void LoadAudioFiles(void);
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
AudioElement AudioFile;
uint8_t AudioFileToPlay = 0;
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
LoadAudioFiles();
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
#ifdef USE_DAC
HAL_DAC_Start(&hdac1, DAC_CHANNEL_1);
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0x7FF);
#endif
// capture/compare registers (CC1 PWM duty 50%)
TIM3->CCR1 = DEFAULT_STARTUP_VAL;
TIM3->CCR2 = DEFAULT_STARTUP_VAL;
LL_TIM_EnableIT_UPDATE(TIM3);
TIM3->CCER |= TIM_CCER_CC2E | TIM_CCER_CC1E;
LL_TIM_EnableCounter(TIM3);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(!HAL_GPIO_ReadPin(BT1_GPIO_Port,BT1_Pin))
{
while(!HAL_GPIO_ReadPin(BT1_GPIO_Port,BT1_Pin));
if(AudioFileToPlay>=1)
{
AudioFileToPlay = 0;
}else
{
AudioFileToPlay++;
}
/* Disable the TIM3 Interrupt */
NVIC_EnableIRQ(TIM3_IRQn);
// stop the timer
LL_TIM_EnableCounter(TIM3);
}
HAL_Delay(100);
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
void LoadAudioFiles(void)
{
AudioFile.AudioFiles[0] = (uint32_t)&Pure_Inspiration;
AudioFile.AudioSize[0] = NELEMS(Pure_Inspiration);
}
/* USER CODE END 4 */
As you can see, the LoadAudioFiles function implementation is quite simple, as it will just assign the C vector address and size into a structure. Please check the Part 3 (last part) of this tutorial, where the interrupt implementation and decode of the audio file is handled
See you soon!Part 1Part 2Part 3