Tabla de Contenidos

Interfaces de audio en STM32 DISCO-L476VG

Plataforma de evaluación HW para STM32L4

DISCO-L476VG

Placa de desarrollo STM32 Discovery kit //32L476GDISCOVERY// con MCU STM32L476VG

MCU STM32L476VGT6 (encapsulado LQFP100)

Códec de audio Cirrus Logic CS43L22

Convertidor digital a analógico de audio portátil con controlador de altavoz de clase D integrado.

Configuración de Salida de Audio

Interfaz serie de audio SAI (Serial Audio Interface)

Activación y configuración de puerto I2C1

Configuración del formato de audio a transferir por el SAI

Conexión del códec al SAI

Verificar reloj I2C1 (80 MHz) y SAI1 (11.294118 MHz) enCubeMX:

Envío de muestras al SAI

En CubeMX ajustamos únicamente los parámetros del SAI1 interfaz-SAI_A, para indicar el uso de un canal de DMA en sentido desde memoria al periférico, en este caso el propio SAI, el tamaño de los datos (muestras de 16-bit) y el modo circular (reproducción continua).

El tamaño del buffer concreto se determina en SW cuando se hace la llamada a la función HAL que relaciona al SAI con DMA. Por otro lado, la técnica de doble-buffer se implementa también en SW haciendo uso de las retro-llamadas disponibles en la HAL del SAI para la detección de buffer lleno y medio lleno.

Desarrollo SW: reproductor de audio

El objetivo del tutorial es la reproducción de un archivo de audio en formato WAV que grabaremos en la memoria Flash del dispositivo incluido en la plataforma STM32L4Discovery, de acuerdo a la configuración del módulo SAI y comunicación con el códec de audio CS43L22 indicada en el apartado anterior.

Creación del proyecto con STM32CubeMX

Desde ventana principal acceder a la selección de placas STBoard e iniciar un nuevo proyecto (Start Project) para la DISCO-L476VG. En la ventana emergente para la inicialización de periféricos a su modo por defecto indicamos que Si para aprovechar parte de la configuración y pines establecidos, p.ej. del display LCD, JoyStick, LEDs.

Suprimiremos los periféricos que no vamos a utilizar para obtener una vista de sistema:

Y una vista del pinout:

Verificamos que la interfaz de depuración está establecida a Serial Wire en la categoría SYS y el temporizador base del sistema el SysTick.

Configuramos en multimedia el SAI1 interfaz SAI_A, en comunicaciones el I2C1 y los relojes según las especificaciones HW del apartado //anterior//, y dejamos las interfaces básicas de E/S incluidas para usarlas con librerías de BSP.

Generación y edición de código

Se procede a la generación de código a partir del proyecto desde CubeMX y pasamos al IDE del TrueStudio donde introducimos el código proporcionado aquí, que incluye parte del BSP de audio y diferentes modificaciones en las secciones de código de usuario del main.c

Includes

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stm32l4xx_hal.h"
#include "stm32l476g_discovery.h"
#include "stm32l476g_discovery_glass_lcd.h"
//#include "stm32l476g_discovery_audio.h"
#include "../Components/Common/audio.h"
#include "../Components/cs43l22/cs43l22.h"
/* USER CODE END Includes */

Defines

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define AUDIO_FILE_ADDRESS   0x08080000
#define AUDIO_FILE_SIZE      (180*1024)	// audio.bin (22K)
//#define AUDIO_FILE_SIZE      (448*1024)	// audio1.bin (22K)
//#define AUDIO_FILE_SIZE      (896*1024)	// audio2.bin (44K)
#define PLAY_HEADER          44
#define PLAY_BUFF_SIZE       4096
/* USER CODE END PD */

Variables

/* USER CODE BEGIN PV */
//SAI_HandleTypeDef            SaiHandle;
//DMA_HandleTypeDef            hSaiDma;
AUDIO_DrvTypeDef            *audio_drv;
uint16_t                      PlayBuff[PLAY_BUFF_SIZE];
__IO int16_t                 UpdatePointer = -1;
/* USER CODE END PV */

Código 0, 1, 2, 3

/* USER CODE BEGIN 0 */
 
/* USER CODE END 0 */
 
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	uint32_t PlaybackPosition   = PLAY_BUFF_SIZE + PLAY_HEADER;
  /* USER CODE END 1 */
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_I2C1_Init();
  MX_LCD_Init();
  MX_SAI1_Init();
  /* USER CODE BEGIN 2 */
  /* Configure LEDs */
  BSP_LED_Init(LED_GREEN);
  BSP_LED_Init(LED_RED);
 
  /* Check if the buffer has been loaded in flash (audio.bin)*/
  //if(*((uint64_t *)AUDIO_FILE_ADDRESS) != 0x017EFE2446464952 ) Error_Handler();
 
  /* Initialize playback */
  if(0 != audio_drv->Init(AUDIO_I2C_ADDRESS, OUTPUT_DEVICE_HEADPHONE, 80, AUDIO_FREQUENCY_22K))
  {
	  Error_Handler();
  }
 
  /* Initialize the data buffer */
  for(int i=0; i < PLAY_BUFF_SIZE; i+=2)
  {
    PlayBuff[i]=*((__IO uint16_t *)(AUDIO_FILE_ADDRESS + PLAY_HEADER + i));
  }
 
  /* Start the playback */
  if(0 != audio_drv->Play(AUDIO_I2C_ADDRESS, NULL, 0))
  {
    Error_Handler();
  }
  if(HAL_OK != HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t *)PlayBuff, PLAY_BUFF_SIZE))
  {
    Error_Handler();
  }
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
	  BSP_LED_Toggle(LED_GREEN);
 
	  /* Wait a callback event */
	  while(UpdatePointer==-1);
 
	  int position = UpdatePointer;
	  UpdatePointer = -1;
 
	  /* Update the first or the second part of the buffer */
	  for(int i = 0; i < PLAY_BUFF_SIZE/2; i++)
	  {
		  PlayBuff[i+position] = *(uint16_t *)(AUDIO_FILE_ADDRESS + PlaybackPosition);
		  PlaybackPosition+=2;
	  }
 
	  /* check the end of the file */
	  if((PlaybackPosition+PLAY_BUFF_SIZE/2) > AUDIO_FILE_SIZE)
	  {
		  PlaybackPosition = PLAY_HEADER;
	  }
 
	  if(UpdatePointer != -1)
	  {
		  /* Buffer update time is too long compare to the data transfer time */
		  Error_Handler();
	  }
  }
  /* USER CODE END 3 */

Código SAI1

  /* USER CODE BEGIN SAI1_Init 2 */
  /* Enable SAI to generate clock used by audio driver */
  __HAL_SAI_ENABLE(&hsai_BlockA1);
 
  /* Initialize audio driver */
  if(CS43L22_ID != cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS))
  {
	  Error_Handler();
  }
 
  audio_drv = &cs43l22_drv;
  audio_drv->Reset(AUDIO_I2C_ADDRESS);
  /* USER CODE END SAI1_Init 2 */

Código 4

/* USER CODE BEGIN 4 */
/**
  * @brief Tx Transfer completed callbacks.
  * @param  hsai : pointer to a SAI_HandleTypeDef structure that contains
  *                the configuration information for SAI module.
  * @retval None
  */
void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai)
{
  /* NOTE : This function Should not be modified, when the callback is needed,
            the HAL_SAI_TxCpltCallback could be implemented in the user file
   */
  UpdatePointer = PLAY_BUFF_SIZE/2;
}
 
/**
  * @brief Tx Transfer Half completed callbacks
  * @param  hsai : pointer to a SAI_HandleTypeDef structure that contains
  *                the configuration information for SAI module.
  * @retval None
  */
void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai)
{
  /* NOTE : This function Should not be modified, when the callback is needed,
            the HAL_SAI_TxHalfCpltCallback could be impleneted in the user file
   */
  UpdatePointer = 0;
}
/* USER CODE END 4 */

Código Debug

  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
	/* LED5 On in error case */
	BSP_LED_On(LED_RED);
	while (1)
	{
	}
  /* USER CODE END Error_Handler_Debug */

Añadir fuentes BSP

Como en otros proyectos en los que se hace uso de fuentes de BSP proporcionados en el FW, localizamos la carpeta de instalación de CubeMX:

[carpeta_STMCubeMX-version]/Repository/STM32Cube_FW_L4_V1.13.0/Drivers/BSP

Y dentro de ese directorio, encontraremos una carpeta por cada placa de evaluación y una carpeta para los componentes de las placas (acelerómetros, drivers de audio…). Debemos arrastrar y copiar (Copy files and folders) la carpeta BSP a nuestro proyecto en Eclipse, dentro de Drivers, posteriormente iremos eliminando archivos y carpetas innecesarias de otras PCBs y dispositivos hasta que queden únicamente los necesarios, en este proyecto se requiere la incorporación y configuración mínima de archivos de Drivers mostrada en las figuras siguientes:

Ejecución y pruebas de reproducción

Para realizar las pruebas se requiere previamente incorporar el archivo WAV a reproducir en la memoria Flash del dispositivo, según hemos indicado en la //sección de código de Defines//, la dirección del archivo de audio se corresponde con la macro AUDIO_FILE_ADDRESS fijada inicialmente a 0x08080000 y el tamaño del mismo con AUDIO_FILE_SIZE a 180 KB. Una forma rápida y sencilla de llevar a cabo esta tarea es mediante una herramienta como STM32CubeProgrammer, que permite visualizar las distintas secciones de memoria del dispositivo conectado, así como la edición y programación a partir de archivos binarios externos (File programming) a partir de una dirección concreta (Start Address) según la figura siguiente:

La programación en una zona de memoria requiere en la mayoría de los casos el borrado de los sectores de la Flash que va a ocupar, y dado que los archivos de audio con determinada calidad tienen un tamaño elevado, se aconseja una forma rápida de borrado completa (Full chip erase). Posteriormente se realiza la programación del archivo a partir de la dirección indicada con (Start Programming)

Una vez grabados los datos de audio en la zona prevista de la memoria Flash, desconectamos esta herramienta (Disconnect) o directamente la cerramos, para volver al IDE de TrueStudio desde donde grabaremos el programa de reproducción que los maneja según el método habitual de la perspectiva de Depuración (Debug).

Se recomienda lleva a cabo una ejecución con puntos de ruptura para comprender la técnica de doble-buffer implementada con DMA e interrupciones asociadas. También se ha dispuesto una vía de depuración adicional en ejecución con el encendido del LEDs: verde intermitente por cada llenado del buffer de 4K PlayBuff, y bloqueo en rojo ante errores.

Modificaciones propuestas

Incorporar al proyecto anterior una interfaz de reproducción más completa, con más de un sonido/archivo de audio, reproducción manual/continua, etc., haciendo uso de la pantalla LCD, JoyStick y LEDs.

Comprobar el tamaño disponible en la memoria Flash para el aprovechamiento de la misma con archivos de audio, de acuerdo a la ocupación de la aplicación.

Otra propuesta más avanzada sería la combinación con el RTC para programar y generar alarmas musicales.