1.说明这一期继续上次的话题,还是播放.因为上次播放为了说明原理,使用了非常原始的软件结构,即使用定时器定时来更新DAC输出.虽然说明了音频播放的本质,但此种方法在实践中很少被用到.原因相信善于思考的读者早就猜到了.那就是对CPU的资源消耗很严重. 举上次的8K采样率为例子, CPU需要每125 us更新一次Sample. 这对于跑几十几百MHz的处理器来说不算什么. 但是通常嵌入式系统使用的20MHz左右的主频率,假使中断+更新操作100个指令周期完成(更新的数据源一般来自外接存储器,已经很保守估计). 那么此操作所占用的CPU资源: Acpu = 100*0.05 / 125 =4% 如果这个还算能接受的话, 那么加多一个通道,就是增加一倍. 采样率增多为16KHz则又是一倍.就算这个占用率可以接受,这还只是原始音频数据播放,而实践中经常会使用某种编解码算法以减轻存储与传输的带宽压力. 即使使用更快的处理器可以负担得起这种浪费,从能耗的角度来看也不倾向于使用这种结构. 话说回来, 嵌入式系统的特点就是没有特定的规则, 如果简单的方法能实现设计目的, 也不能说绝对否定这种方法. 本系列**的目的就是发扬探索精神, 将各种方法都来试验一把,品味其中优劣, 学习诸种原理. 以下介绍几种应用了其他技术的播放实验. 2.实践之一(DAC+DMA)2.1 双缓冲播放解决上文所叙的CPU资源占用问题简单直接的方法就是DMA传输. 当然DMA也不是每个处理器都有. 这里只是实验一下子有DMA的处理器如何将DMA利用起来.事实上几乎所有专门处理音频的处理器(DSP,或者音频ASIC)都利用DMA来传输音频数据. 还是接上回的实验, 直接使用上次所叙的DAC输出的硬件结构,接耳机,接音箱,接放大板都可以.
将软件改成这样的结构:
这个实验看起来简单,其实内容有点多.最主要的是Buffer管理. 下面简单讲讲这个buffer的管理过程.假设Buffer大小为BUF_SIZE,那么第一次需要从资源处填充BUF_SIZE的内容到这个Buffer.开始DMA,这里注意这两个回调函数:voidHAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac){ UpdatePointer = PLAY_BUFF_SIZE/2;} voidHAL_DACEx_ConvHalfCpltCallbackCh2(DAC_HandleTypeDef* hdac){ UpdatePointer = 0;}分别是DMA传输完成与DMA传输完一半的回调函数.需要用户实现,如果用户不实现,将使用默认的HAL自带的回调函数:__weak voidHAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac){ /* Preventunused argument(s) compilation warning */ UNUSED(hdac); /* NOTE :This function Should not be modified, when the callback is needed, the HAL_DACEx_ConvCpltCallbackCh2 could be implemented in the user file */}__weak void HAL_DACEx_ConvHalfCpltCallbackCh2(DAC_HandleTypeDef*hdac){ /* Preventunused argument(s) compilation warning */ UNUSED(hdac); /* NOTE :This function Should not be modified, when the callback is needed, the HAL_DACEx_ConvHalfCpltCallbackCh2 could be implemented in the userfile */}看到前面那个__weak关键字没有,有__weak修饰表明这函数可以被重载,或者覆盖,或者隐藏. 这里也不知道该用什么术语,对C++或者其他面向对象语言有了解的同学应该一下子就能明白, 这里不节外生枝以后有时间再展开来说.首先初始化buffer的时候,将buffer填充了第一次要播放的内容.
|