打印
[STM32H7]

【STM32H745I-DISCO试用】10、播放WAV文件

[复制链接]
82|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 sujingliang 于 2025-2-9 14:57 编辑

STM32H745I-DISCO没有播放WAV文件的例程,但是STM32H743I-EVAL下有一个例程,可以照葫芦画瓢实现一下。


1、定义一些常量
#define AUDIO_FREQUENCY       SAI_AUDIO_FREQUENCY_16K
#define AUDIO_CHANNEL_NUMBER  2U
#define AUDIO_BUFFER_SIZE     256U
#define AUDIO_PCM_CHUNK_SIZE  32U

#define AUDIO_FILE_ADDRESS   0x08080000
#define AUDIO_FILE_SIZE      (180*1024)
#define PLAY_HEADER          0x2C
#define PLAY_BUFF_SIZE       4096
音频采样频率:16 kHz。
音频通道数:2(立体声)。
缓冲区大小:256字节(用于DMA传输)。
PCM数据块大小:32字节。
音频文件存储地址:0x08080000。
音频文件大小:180 KB。
音频文件头大小:44字节。
播放缓冲区大小:4 KB。


__IO int16_t        UpdatePointer = -1;
UpdatePointer变量用于管理双缓冲机制。当DMA传输完一半缓冲区时,会更新UpdatePointer,指示需要重新填充哪一半缓冲区。
ALIGN_32BYTES (uint16_t PlayBuff[PLAY_BUFF_SIZE]);
从预定义的内存地址(AUDIO_FILE_ADDRESS)加载音频数据到播放缓冲区(PlayBuff)。音频数据包括文件头(PLAY_HEADER)和实际的播放数据(PLAY_BUFF_SIZE)

2、main函数
int main(void)
{
  /* System Init, System clock, voltage scaling and L1-Cache configuration are done by CPU1 (Cortex-M7)
     in the meantime Domain D2 is put in STOP mode(Cortex-M4 in deep-sleep)
  */

        __IO uint32_t PlaybackPosition   = PLAY_BUFF_SIZE + PLAY_HEADER;
  /* Configure the MPU attributes */
  MPU_Config();

  /* Enable the CPU Cache */
  CPU_CACHE_Enable();

  /* STM32H7xx HAL library initialization:
       - Systick timer is configured by default as source of time base, but user
         can eventually implement his proper time base source (a general purpose
         timer for example or other time source), keeping in mind that Time base
         duration should be kept 1ms since PPP_TIMEOUT_VALUEs are defined and
         handled in milliseconds basis.
       - Set NVIC Group Priority to 4
       - Low Level Initialization
     */
  HAL_Init();

  /* Configure the system clock to 400 MHz */
  SystemClock_Config();

  /* When system initialization is finished, Cortex-M7 could wakeup (when needed) the Cortex-M4  by means of
     HSEM notification or by any D2 wakeup source (SEV,EXTI..)   */

  /* Configure LED1 */
  BSP_LED_Init(LED1);

  /* Configure LED2 */
  BSP_LED_Init(LED2);

  /* Initialize playback */
  Playback_Init();
  WM8994_Init_t codec_init;
  
  codec_init.Resolution   = 0;
  
  /* Fill codec_init structure */
  codec_init.Frequency    = 16000;
  codec_init.InputDevice  = WM8994_IN_NONE;
  codec_init.OutputDevice = AUDIO_OUT_DEVICE_HEADPHONE;
  
  /* Convert volume before sending to the codec */
  codec_init.Volume       = VOLUME_OUT_CONVERT(30);
  
        
        
        /* 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(Audio_Drv->Init(Audio_CompObj, &codec_init) != 0)
  {
    Error_Handler();
  }
  
  /* Start the playback */
  if(Audio_Drv->Play(Audio_CompObj) < 0)
  {
    Error_Handler();
  }
  
        
        if(HAL_OK != HAL_SAI_Transmit_DMA(&SaiOutputHandle, (uint8_t *)PlayBuff, PLAY_BUFF_SIZE))
  {
    Error_Handler();
  }

  /* Start loopback */
  while(1)
  {
                 
    /* 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;
    }
   
    /* Clean Data Cache to update the content of the SRAM */
    SCB_CleanDCache_by_Addr((uint32_t*)&PlayBuff[position], PLAY_BUFF_SIZE);
   
    /* 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();
    }
               
               
  }
}
Playback_Init():初始化音频播放系统。
WM8994_Init_t codec_init:配置音频编解码器(WM8994),设置采样率、输入/输出设备、音量等参数。
Audio_Drv->Init():初始化音频驱动,传入编解码器的配置参数。
Audio_Drv->Play():开始音频播放。
HAL_SAI_Transmit_DMA():配置SAI(串行音频接口)外设,使用DMA(直接内存访问)将音频数据从PlayBuff传输到音频编解码器。DMA可以在后台完成数据传输,减少CPU的负担。


主循环(音频播放处理)
UpdatePointer 机制:UpdatePointer变量用于管理双缓冲机制。当DMA传输完一半缓冲区时,会更新UpdatePointer,指示需要重新填充哪一半缓冲区。
缓冲区填充:主循环检查UpdatePointer,并从AUDIO_FILE_ADDRESS中加载新的音频数据到相应的缓冲区部分。
缓存维护:SCB_CleanDCache_by_Addr()函数确保更新后的音频数据写入SRAM,而不是仅仅停留在缓存中。
文件结束处理:如果音频文件播放完毕,播放位置会重置到文件开头(PLAY_HEADER)

双缓冲机制:使用两个缓冲区部分(PlayBuff的两半)实现连续音频播放,避免播放中断。当一半在播放时,另一半在填充新数据。
DMA 高效传输:DMA用于数据传输,减少CPU的负担,使其可以处理其他任务。
缓存管理:在带有缓存的系统中,正确维护缓存是确保CPU和外设之间数据一致性的关键。


3、SAI PCM Output init
/* SAI PCM Output init */
  __HAL_SAI_RESET_HANDLE_STATE(&SaiOutputHandle);

  SaiOutputHandle.Instance            = AUDIO_OUT_SAIx;
  SaiOutputHandle.Init.AudioMode      = SAI_MODEMASTER_TX;
  SaiOutputHandle.Init.Synchro        = SAI_ASYNCHRONOUS;
  SaiOutputHandle.Init.OutputDrive    = SAI_OUTPUTDRIVE_ENABLE;
  SaiOutputHandle.Init.NoDivider      = SAI_MASTERDIVIDER_ENABLE;
  SaiOutputHandle.Init.FIFOThreshold  = SAI_FIFOTHRESHOLD_1QF;
  SaiOutputHandle.Init.AudioFrequency = AUDIO_FREQUENCY;
  SaiOutputHandle.Init.Protocol       = SAI_FREE_PROTOCOL;
  SaiOutputHandle.Init.DataSize       = SAI_DATASIZE_16;
  SaiOutputHandle.Init.FirstBit       = SAI_FIRSTBIT_MSB;
  SaiOutputHandle.Init.ClockStrobing  = SAI_CLOCKSTROBING_FALLINGEDGE;

  SaiOutputHandle.FrameInit.FrameLength       = 128;
  SaiOutputHandle.FrameInit.ActiveFrameLength = 64;
  SaiOutputHandle.FrameInit.FSDefinition      = SAI_FS_CHANNEL_IDENTIFICATION;
  SaiOutputHandle.FrameInit.FSPolarity        = SAI_FS_ACTIVE_LOW;
  SaiOutputHandle.FrameInit.FSOffset          = SAI_FS_BEFOREFIRSTBIT;

  SaiOutputHandle.SlotInit.FirstBitOffset = 0;
  SaiOutputHandle.SlotInit.SlotSize       = SAI_SLOTSIZE_DATASIZE;
  SaiOutputHandle.SlotInit.SlotNumber     = 4;
  SaiOutputHandle.SlotInit.SlotActive     = (SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_2);

  /* DeInit SAI PCM input */
  HAL_SAI_DeInit(&SaiOutputHandle);

  /* Init SAI PCM input */
  if(HAL_OK != HAL_SAI_Init(&SaiOutputHandle))
  {
    Error_Handler();
  }

  /* Enable SAI to generate clock used by audio driver */
  __HAL_SAI_ENABLE(&SaiOutputHandle);
    WM8994_Probe();
  • 配置SAI外设为PCM音频输出模式。
  • 设置SAI的帧格式、时隙参数和时钟特性。
  • 初始化并启用SAI外设。
  • 探测并初始化WM8994音频编解码器。


4、WM8994_Probe

static int32_t WM8994_Probe(void)
{
  int32_t ret = BSP_ERROR_NONE;
  WM8994_IO_t              IOCtx;
  static WM8994_Object_t   WM8994Obj;
  uint32_t id;

  /* Configure the audio driver */
  IOCtx.Address     = AUDIO_I2C_ADDRESS;
  IOCtx.Init        = BSP_I2C4_Init;
  IOCtx.DeInit      = BSP_I2C4_DeInit;
  IOCtx.ReadReg     = BSP_I2C4_ReadReg16;
  IOCtx.WriteReg    = BSP_I2C4_WriteReg16;
  IOCtx.GetTick     = BSP_GetTick;

  if(WM8994_RegisterBusIO (&WM8994Obj, &IOCtx) != WM8994_OK)
  {
    ret = BSP_ERROR_BUS_FAILURE;
  }
  else
  {
    /* Reset the codec */
    if(WM8994_Reset(&WM8994Obj) != WM8994_OK)
    {
      ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    else if(WM8994_ReadID(&WM8994Obj, &id) != WM8994_OK)
    {
      ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    else if(id != WM8994_ID)
    {
      ret = BSP_ERROR_UNKNOWN_COMPONENT;
    }
    else
    {
      Audio_Drv = (AUDIO_Drv_t *) &WM8994_Driver;
      Audio_CompObj = &WM8994Obj;
    }
  }
  return ret;
}
配置WM8994编解码器的I2C通信接口。
复位WM8994编解码器。
读取WM8994的ID以验证其是否正确连接。
初始化音频驱动对象,使其可用于后续的音频操作。


5、STM32CubeProgrammer烧录WAV文件到0x08080000


程序烧录到0x08000000
WAV文件(改名为BIN文件)烧录到0x08080000
修改程序后再次烧录,避免擦除到0x08080000,否则还要重新烧录WAV文件。

6、中断处理
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  This function handles DMA2 Stream 1 for SAI1A interrupt request.
  * @param  None
  * @retval None
  */
void AUDIO_OUT_SAIx_DMAx_IRQHandler(void)
{
  HAL_DMA_IRQHandler(SaiOutputHandle.hdmatx);
}
#define AUDIO_OUT_SAIx_DMAx_IRQHandler           DMA2_Stream1_IRQHandler
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url] 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;

}

/**
  * [url=home.php?mod=space&uid=247401]@brief[/url] 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 implenetd in the user file
   */
  UpdatePointer = 0;

}
中断回调函数用于设置双缓冲标志。

7、源代码

SAI_AudioPlayback.part01.rar (10 MB)


SAI_AudioPlayback.part02.rar (9.42 MB)



   

使用特权

评论回复
沙发
幸福小强| | 2025-2-10 09:53 | 只看该作者
播放音质如何

使用特权

评论回复
板凳
sujingliang|  楼主 | 2025-2-10 10:07 | 只看该作者

播放WAV文件没有损耗,杂音。

播放MP3就不好说,刚做好MP3软解码实验,有底噪。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

100

帖子

0

粉丝