====== Interfaces de audio en STM32 DISCO-L476VG ====== ====== Plataforma de evaluación HW para STM32L4 ====== ===== DISCO-L476VG ===== Placa de desarrollo STM32 Discovery kit [[https://www.st.com/en/evaluation-tools/32l476gdiscovery.html|//32L476GDISCOVERY//]] con MCU **STM32L476VG** * Depurador/programador en PCB ST-LINK/V2-1 con conector SWD * Alimentación (4x opciones): * ST-LINK/V2-1 * Conector USB FS * Externa 5 V * Batería CR2032 * 2x LEDs de usuario: LD4 (rojo), LD5 (verde) * 2x botón pulsador: USER y RESET * 1x puerto USB OTG FS con conector micro-AB, capacidad de re-enumeración USB y 3x interfaces USB: * VCP (Virtual Com port) * Almacenamiento masivo (USB Disk drive) para programación por arrastre * Depuración (Debug port) * 1x Joystick de 4x direcciones y selección central * **SAI Audio DAC, estéreo, con conector Jack de salida** * **Micrófono digital MEMS** * Acelerómetro y magnetómetro MEMS * Giróscopo MEMS * 128-Mbit memoria Quad-SPI Flash * Amperímetro de corriente del MCU con 4 rangos y auto calibración {{:silab6:image1.png?566x425}} ==== MCU STM32L476VGT6 (encapsulado LQFP100) ==== * ARM®32-bit Cortex®-M4 CPU con FPU y acelerador ART * 80 MHz frecuencia máxima de CPU * VDD de 1.71V a 3.6 V * 1 MB Flash * 128 KB SRAM * 114x GPIOs con capacidad de interrupción externa * 3x 12-bit ADCs con 16 canales * 2x 12-bit DAC * 3x USART * 2x UART * 1x LPUART * 2x **SAI** * 3x **I2C** * 3x **SPI** * 1x Quad SPI * 7x temporizadores (Timers) de propósito general, 2x básicos y 2x avanzados * 2x temporizadores de bajo consumo (low-power) * 2x temporizadores tipo Watchdog * 1x CAN * USB OTG FS * SDMMC * SWPMI * LCD COM x SEG * RTC * TRNG (generador de aleatorios por HW) * 21x sensores capacitivos * 2x comparadores analógicos * 2x amplificadores operacionales ==== Micrófono MEMS MP34DT01 ==== * Micrófono MEMS digital (ST-MP34DT01) con salida PDM para aplicaciones de audio con pequeño tamaño y coste, alta calidad de sonido e inmunidad al ruido (SNR=61dB; -26dBFS; AOP=120 dBSPL). * Opera a una frecuencia de reloj desde 1 MHz a 3,25 MHz. > {{:silab6:image2.png?700}} * Esquema funcional: > {{:silab6:image3.png?500}} * Conexión a MCUs Arm® Cortex® mediante interfaces serie SPI / I2S, SAI y DFSDM. > {{:silab6:image4.png?600}} * Adquisición mono/estéreo en **PDM** (modulación por densidad de impulsos) a transformar en datos estándar para audio, p.ej. **PCM** (modulación por impulsos codificados). > {{:silab6:image5.png?700}} * Esquemático de conexiones en STM32L4Discovery: > {{:silab6:image6.png?700}} ==== Interfaz DFSDM ==== Un **DFSDM** (**Filtro digital para moduladores Sigma-Delta**) es periférico nuevo incorporado a la familia STM32, que actúa como un ADC estándar con velocidad/resolución escalable (hasta 24-bit), con un interfaz (front-end) **analógico externo** separado/aislado de la parte digital (1/2 hilos). La parte digital realiza el procesamiento digital de señal a partir de los datos externos, p.ej. para procesar la señal de audio a partir de micrófonos digitales MEMS con salida de datos PDM, que puede ser conectados directamente al DFSDM. También pueden procesar datos internos en paralelo (16-bit) transferidos por CPU/DMA desde memoria. {{:silab6:image7.png?300}} {{:silab6:image8.png?500}} * Diagrama de bloques: > {{:silab6:image9.png?800}} * **Transceptores**: * Entradas de datos externas (hasta 8 canales) en **Serie** y modo rápido (20 MHz): * Codificación SPI (2 hilos: reloj y datos) o Manchester (1 hilo, coste mínimo). * Generación de reloj: derivado de sistema o PLL de audio (divisor). * Entradas de datos internas (hasta 8 canales) **Paralela** (16-bit): * Registro de datos de entrada de 16-bit (escritura por CPU/DMA) > {{:silab6:image10.png?400}}{{:silab6:image11.png?400}} * **Filtros**: * Etapa de filtrado digital sobre un flujo (stream) de datos de entrada mediante media fija (averaging), o media móvil (SincX) por sobremuestreo * Sinc1, Sinc2, Sinc3, Sinc4, Sinc5 y FastSinc con tasa de sobremuestreo (FOSR) hasta 1024 y resolución de hasta 31-bit * Integrador con tasa de sobremuestreo (IOSR) de hasta 1024 y número de muestras (N) de hasta 256 {{:silab6:image12.png?300}}{{:silab6:image13.png?300}}{{:silab6:image14.png?300}} * Ventajas para **aplicaciones**: * Soporte para varios suministradores de moduladores SD (ST, TI, Analog Devices,…) * Selección de velocidad vs. resolución por configuración del filtro * Post-procesado de datos internos (resultados de SAR ADC, …) * Funciones adicionales: perro guardián (WD), detector de corto circuito, detector de extremos, corrección de offset. * Rendimiento muy alto para DSP en HW y reducción de consumo respecto ADC convencional: * Reloj DFSDM máximo = reloj sistema (máx. 80 MHz) * Reloj entrada serie (tasa de datos de entrada): * SPI: Reloj DFSDM / **4** (máx. 20 MHz) * Manchester: Reloj DFSDM / **6** (máx. 10 MHz) * Frecuencia de reloj de salida: Reloj DFSDM / **4** (máx. 20 MHz) * Tasa de datos de salida = Tasa de datos de entrada / (FOSR *IOSR) * FOSR = 1-1024 * IOSR = 1-256 > {{:silab6:image15.png?300}}{{:silab6:image16.png?300}} > \\ > {{:silab6:image17.png?300}}{{:silab6:image18.png?300}} * Selección del **tipo de conversión**: * Conversiones **regulares**: * Selección de //un solo canal// (de entre 8 canales) * Iniciadas por SW (no disparo HW) * Posibilidad de modo continuo * Posibilidad de interrupción por conversión inyectada (flag) > {{:silab6:image19.png?500}} * Conversiones **inyectadas**: * Grupo de canales: más de un canal de entrada * Modo //Scan// (disparo): todo el grupo de canales es convertido * Modo //Single// (disparo): sólo un canal es convertido y el siguiente es seleccionado * Iniciadas por disparo SW o HW (temporizadores o pines externos) * No hay posibilidad de modo continuo (emulado por disparo de temporizador) * Adecuado para micrófonos MEMS y medidores STPMS2 > {{:silab6:image20.png?600}} ===== Configuración de Entrada de Audio ===== > {{:silab6:image21.png?450}} * Una de las principales aplicaciones de los DFSDM es la de soporte de entrada de audio a partir de micrófonos con salida PDM, debido principalmente a la considerable reducción de **consumo de energía** y mínima utilización/intervención del MCU al realizarse el **procesamiento** directamente en HW. * El **conexionado** también se simplifica: * Sólo 2 hilos para una entrada estéreo, comunes a ambos micrófonos L/R, compartiendo señales de reloj y datos. * La **separación de canales** se hace internamente: diferentes flancos para datos L/R. * La **tasa de datos de salida** y la **resolución** está en función de las configuraciones de los filtros. * A modo de ejemplo, acorde a la figura, podrían realizarse conversiones regulares en ambos canales en modo continuo, con transferencia directa a memoria vía DMA, y una segunda transferencia DMA envía los datos de audio a una interfaz I2S (asociada a un códec de audio externo). * La siguiente sección de [[#desarrollo-sw-grabadorreproductor-de-audio|//desarrollo SW//]] muestra los pasos para crear, con la herramienta STM32CubeMX, el código de inicialización de una aplicación que adquiere datos PDM del micrófono digital MP34DT01, incluido en la PCB STM32L4Discovery, en modo mono, y los convierte a datos PCM utilizando el HW de DFSDM. Se requiere: * Configuración de pines GPIO * Configuración de canal/es DFSDM * Configuración del orden del filtro Sinc, de cada canal, acorde a la frecuencia de muestreo, p.ej. > {{:silab6:image22.png?500}} * Configuración del modo de operación del filtro/s * Configuración del reloj de salida (divisor): > Divisor = Frecuencia_reloj_DFSDM / (Frecuencia_muestreo × Factor_diezmado) * Configuración de DMA del DFSDM * Configuración de reloj fuente del DFSDM, p.ej. > {{:silab6:image23.png?500}} ====== Desarrollo SW: grabador/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 ===== En este caso se parte del proyecto de CubeMX creado en el tutorial anterior haciendo una **copia** del mismo, para ello abriremos el proyecto **origen** (p.ej. SIlab5.ioc) desde la ventana principal, y se selecciona la //opción de menú// (File→Save Project As ..) o la combinación de teclas (Ctrl-A). En la ventana emergente subimos un nivel respecto la carpeta original y creamos una **nueva carpeta** con el nuevo nombre del proyecto **destino** (p.ej. SIlab6), nos movemos a la nueva carpeta creada desde la misma ventana y procedemos a guardar, en este punto se crea el nuevo archivo de proyecto (p.ej. SIlab6.ioc) dentro de la nueva carpeta creada con el mismo nombre. {{:silab6:image24.png?400}} Puede comprobarse en la pestaña de **Project Manager** que la ubicación es correcta, e incluso puede ya generarse el código inicial a partir de la configuración origen. {{:silab6:image25.png?700}} La configuración copiada incluye la selección de la placa **DISCO-L476VG** así como la configuración y pines establecidos en el tutorial anterior: salida de audio (I2C, SAI, DMA), display LCD, JoyStick, LEDs. Revisamos en el nuevo proyecto que la **interfaz de depuración** está establecida a //Serial Wire// en la categoría **SYS** y el temporizador base del sistema el //SysTick//, heredado del proyecto origen en **multimedia** debe aparecer el **SAI1 interfaz** **SAI_A**, en comunicaciones el **I2C1**, los **relojes**, y las //interfaces básicas de E/S// que conservamos para usarlas con librerías de **BSP**. Siguiendo los pasos indicados en el apartado [[#configuracion_de_entrada|//anterior//]] sobre la configuración propuesta: ==== Configuración de pines ==== {{:silab6:image26.png?600}} ==== Configuración del canal DFSDM y orden del filtro Sinc ==== {{:silab6:image27.png?500}} ==== Configuración del filtro Sinc ==== {{:silab6:image28.png?500}} ==== Configuración de DMA del DFSDM ==== {{:silab6:image29.png?600}} {{:silab6:image30.png?600}} ==== Configuración del reloj de salida (divisor) ==== El reloj del micrófono MP34DT01 viene generado por el DFSDM, en base al reloj de audio (11.294 MHz) dividido por 4 en este caso (2.8235 MHz), dentro de los márgenes del MEMS mic (1-3.25 MHz). {{:silab6:image31.png?600}} ==== Configuración de relojes general y audio ==== {{:silab6:image32.png?600}}{{:silab6:image33.png?250}} ==== Tabla de interrupciones NVIC ==== Para evitar problemas en servicios de interrupción como el derivado del uso de retardos HAL_Delay() a partir del SysTick, se recomienda poner éste con mayor prioridad respecto el resto de ISR usadas en la aplicación. {{:silab6:image34.png?550}} ===== 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 ==== /* 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 */ ==== Macro ==== /* USER CODE BEGIN PM */ #define SaturaLH(N, L, H) (((N)<(L))?(L):(((N)>(H))?(H):(N))) /* USER CODE END PM */ ==== Variables ==== /* USER CODE BEGIN PV */ AUDIO_DrvTypeDef *audio_drv; int32_t RecBuff[2048]; int16_t PlayBuff[4096]; uint32_t DmaRecHalfBuffCplt = 0; uint32_t DmaRecBuffCplt = 0; uint32_t PlaybackStarted = 0; /* USER CODE END PV */ ==== Código 1 ==== /* USER CODE BEGIN 1 */ uint32_t i; /* USER CODE END 1 */ ==== Código 2, 3 ==== /* USER CODE BEGIN 2 */ /* Configure LEDs */ BSP_LED_Init(LED_GREEN); BSP_LED_Init(LED_RED); /* Initialize playback */ if(0 != audio_drv->Init(AUDIO_I2C_ADDRESS, OUTPUT_DEVICE_HEADPHONE, 90, AUDIO_FREQUENCY_44K)) { Error_Handler(); } /* Start DFSDM conversions */ if(HAL_OK != HAL_DFSDM_FilterRegularStart_DMA(&hdfsdm1_filter0, RecBuff, 2048)) { Error_Handler(); } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ BSP_LED_Toggle(LED_GREEN); /* USER CODE BEGIN 3 */ if(DmaRecHalfBuffCplt == 1) { /* Store values on Play buff */ for(i = 0; i < 1024; i++) { PlayBuff[2*i] = SaturaLH((RecBuff[i]>> 8), -32768, 32767); PlayBuff[(2*i)+1] = PlayBuff[2*i]; } if(PlaybackStarted == 0) { if(0 != audio_drv->Play(AUDIO_I2C_ADDRESS, (uint16_t *) &PlayBuff[0], 4096)) { Error_Handler(); } if(HAL_OK != HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t *) &PlayBuff[0], 4096)) { Error_Handler(); } PlaybackStarted = 1; } DmaRecHalfBuffCplt = 0; } if(DmaRecBuffCplt == 1) { /* Store values on Play buff */ for(i = 1024; i < 2048; i++) { PlayBuff[2*i] = SaturaLH((RecBuff[i]>> 8), -32768, 32767); PlayBuff[(2*i)+1] = PlayBuff[2*i]; } DmaRecBuffCplt = 0; } } /* 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 Half regular conversion complete callback. * @param hdfsdm_filter : DFSDM filter handle. * @retval None */ void HAL_DFSDM_FilterRegConvHalfCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter) { DmaRecHalfBuffCplt = 1; } /** * @brief Regular conversion complete callback. * @note In interrupt mode, user has to read conversion value in this function using HAL_DFSDM_FilterGetRegularValue. * @param hdfsdm_filter : DFSDM filter handle. * @retval None */ void HAL_DFSDM_FilterRegConvCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter) { DmaRecBuffCplt = 1; } /* 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 */ while (1) { /* Toggle LED4 with a period of one second */ BSP_LED_Toggle(LED_RED); HAL_Delay(1000); } /* 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: {{:silab6:image35.png?400}}{{:silab6:image36.png?600}} ===== Ejecución y pruebas de reproducción ===== Para realizar las pruebas no se requiere ningún archivo de audio, se han definido un buffer de reproducción y otro de grabación de forma circular en modo de doble-buffer, cuando se llena la mitad del buffer de grabación se rellena la primera mitad del buffer de reproducción, cuando se llena totalmente el buffer de grabación se pone la segunda parte del buffer de grabación en la segunda mitad del buffer de reproducción. Se recomienda lleva a cabo una ejecución con puntos de ruptura para comprender la técnica de dobles-buffers entrelazadas de grabación/reproducción 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 parpadeo lento en //rojo// ante errores. ===== Modificaciones propuestas ===== * Incorporar al proyecto anterior una **interfaz de grabación/reproducción** asistida por una interfaz, haciendo uso de la pantalla LCD, JoyStick y LEDs. * Generar un **archivo** de audio grabado en **memoria Flash** y/o en memoria **USB externa**, añadiendo la correspondiente cabecera WAV, para extraerlo y comprobar en PC. * Otra propuesta más **avanzada** sería detección de comandos vocales para gobernar algún actuador, donde pueden emplearse librerías recientes basadas en **redes neuronales** con modelos pre-entrenados, como [[https://github.com/ARM-software/ML-KWS-for-MCU|https://github.com/ARM-software/ML-KWS-for-MCU]]