cancel
Showing results for 
Search instead for 
Did you mean: 

Final Demo of how to play Audio files from External Memory using STM32G0 Part2

A.ALUR_SWAMY
Associate

How to play Audio files from a W25Qxx SPI External Memory using STM32G0

Welcome back with the part 2 of our article series that explains how to play Audio files from a W25Qxx SPI External Memory using STM32G0

Here we will continue with the required firmware implementation to play the Audio files from external memory

Now, on the audio file, you can use the __attribute__((section(".myAudioFiles"))) to place it in the newly created section in the external memory. The reason we add the audio file array with the attribute is to create a binary with both the program, and the audio file the audio file binary will have different address based on the linker script settings and this will be used by the STM32CubeProgrammer external loader to program the audio file array portion in the external memory and the code in the MCU's memory. As the C vector file created can be edited, it is suggested to create a define for the size and add this define in the “adpcm.h”, thus adding these modifications:
1379.png
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
1380.png
 

#define ADPCMD_PI 6622 
#define NUMBER_OF_AUDIO_FILES 1

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Exported types ------------------------------------------------------------*/
typedef union
{
   uint8_t		uBytes[2];
   uint16_t		uShort;
}  tTwoByte;

typedef struct
{
   uint32_t		AudioFiles[NUMBER_OF_AUDIO_FILES];
   uint32_t		AudioSize[NUMBER_OF_AUDIO_FILES];
}  AudioElement;

/* Exported constants --------------------------------------------------------*/
extern const unsigned char Pure_Inspiration[ADPCMD_PI];
/* Exported macro ------------------------------------------------------------*/
#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

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, we use this: #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’ll need to:

  1. Include the adpcm.h
  2. In the main function, set the PWM starting value
  3. Enable the TIM3 periodic interrupt
  4. The main loop will consist of 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 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 */


In the interrupt service routine, more specifically in the TIM3 periodic interrupt, we must execute these actions as well:

  1. Include the adpcm.h header
  2. Read the audio file from the external memory
  3. Check if the audio file to be played has changed or not
  4. Clear the interrupt flag
  5. Decode the audio file
  6. Repeat the same position of the audio file 8 times
  7. Output the decoded value using the PWM or DAC
  8. Once the 8 repetitions are made, move to the next decoded position
  9. If the audio file has been fully played, stop the TIM3 interrupt to stop the audio output

Translating the steps above into actual code please locate it in the stm32g0xx_it.c we have it as shown below, please track the code position by verifying the /* USER CODE BEGIN*/ and /* USER CODE END */

/* USER CODE BEGIN Includes */
#include "adpcm.h"
#ifdef USE_DAC
#include "dac.h"
#endif
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
tTwoByte newSample;
extern AudioElement AudioFile;
extern uint8_t AudioFileToPlay;
/* USER CODE END PV */

/**
  * @brief This function handles TIM3 global interrupt.
  */

void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
	uint8_t  adpcmSample;
    static uint8_t  one=1;
	static uint16_t pcmSample;
	static uint8_t nibble = 1;
	static uint8_t repetition = 0;
	static uint16_t sample_position = 0;
    extern uint32_t __myAudioFiles_START;
	extern uint32_t __myAudioFiles_END;
    uint32_t sizeofAudio= (uint32_t)(&__myAudioFiles_END)-  (uint32_t (&__myAudioFiles_START);
    static uint8_t RawAudio[sizeofAudio];
	uint32_t address=0x00000000;
	static uint8_t PrevAudioFileToPlay = 0xFF;

    // To make sure we read the Audio file just once 
	if(one==1)
	{
	readData(address, RawAudio, sizeofAudio);
	one=0;
	}

	if(PrevAudioFileToPlay != AudioFileToPlay)
	{
		PrevAudioFileToPlay = AudioFileToPlay;
		nibble = 1;
		repetition = 0;
		sample_position = 0;
		
	}

	if (LL_TIM_IsActiveFlag_UPDATE(TIM3))
	{
		LL_TIM_ClearFlag_UPDATE(TIM3);

		if ((repetition==0) & (sample_position < sizeofAudio))
     	{  
#ifndef USE_STM8
		repetition = 7;
#endif

		if (nibble)
		{   // first 4 bits of the ADPCM byte decoded
			adpcmSample = (uint8_t)(RawAudio[sample_position] >>4);
		}
		else
		{   // last 4 bits of the ADPCM byte decoded
			adpcmSample = (uint8_t)(RawAudio[sample_position] & 0x0F);
			sample_position++ ;
		}

			nibble = (uint8_t)(!nibble);			
			pcmSample = ADPCM_Decode(adpcmSample);

	    	// update sample
			newSample.uShort = (uint16_t)32768 + pcmSample;
			TIM3->CCR2 = newSample.uBytes[0]; //LSB
			TIM3->CCR1 = newSample.uBytes[1]; //MSB
#ifdef USE_DAC
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, (newSample.uShort)>>4);
#endif
		}
		else if (sample_position < sizeofAudio)
		{  // repetition 7 times of the PWM period before new sample
			repetition--;

		    // reload Timer with the actual sample value
			newSample.uShort = (uint16_t)32768 + pcmSample;
			TIM3->CCR2 = newSample.uBytes[0]; //LSB
			TIM3->CCR1 = newSample.uBytes[1]; //MSB

#ifdef USE_DAC
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, (newSample.uShort)>>4);
#endif
		}
		else
		{   // end of the audio clip
			/* Disable the TIM3 Interrupt */
			NVIC_DisableIRQ(TIM3_IRQn);
			// stop the timer
			LL_TIM_DisableCounter(TIM3);
		}

}
	return ;
  /* USER CODE END TIM3_IRQn 0 */
}

Conclusion:
This is a full-fledged tutorial which combines all the parts of several earlier tutorials with just a few additional steps such as configuring SPI2, TIM3 interrupt handler, linker file editing and combining the SPI Flash driver files to read the audio files from external flash. Now we have enhanced the storage with an external memory and have created our own audio player on STM32.

Version history
Last update:
‎2022-07-01 10:06 AM
Updated by: