首页 > 评测 > 嵌入式系统与音频处理
[导读]Everyboard Can Sing

21ic打算携手资(tu)深(ding)直男癌晚期工程师zhanzr21,来给大家讲一讲嵌入式系统与音频处理的故事。

关于zhanzr21

曾经混迹于两岸三地,摸爬滚打在前端后端,搞过学术上过班。现在创业中,欢迎各种撩

点击链接加入群【嵌入式音频信号处理】:https://jq.qq.com/?_wv=1027&k=45wk8Ks

嵌入式音频专用资料代码分享:https://pan.baidu.com/s/1dFh5pWd

C2.jpg

前言

此章仅为最基本的播放原理介绍, 还有不少关於播放的内容由于篇幅与活动配套安排等原因没有包含在此, 所以在标题上加 播放篇之一, 后面的章节中也会有对这个话题的继续讨论.

1.基本的播放原理与硬件组成

从原理上讲,数字系统播放声音就是把声音的样本数据转换成人耳能感觉到的变化.上一章的文章中我们已经体验过声音的数字形式.那么将这种数据如何转换为最终能感觉到的变化呢.理论上的功能Block如下:

音频播放的理论Block.jpg

图 音频播放的理论Block

这里依次说明.

1. 控制系统,负责控制生成数据或者读取数据(可能需要解压或其他操作),并将其交给下一级的DAC,一般来讲这个指的是MCU/DSP/CPU/FPGA这样的器件.

2. 数据即是音频数据,可以是控制系统即时生成的也可以是读取其它系统处理好的数据.一般而言,除了实验性质或者创作性质的系统,数据一般是其它系统生成存放在存储器中的数据.

3. DAC负责把数字转换成模拟.这个DAC可以是内部DAC也可以是外部DAC.甚至可能是个假的DAC(数字口+低通滤波器).

4. 功率级将DAC的输出进行功率放大,功率级视乎后面的输出设备需求来决定其性质.可以是管子(FET,BJT,电子管),也可能是集成电路. 现代音频系统中集成电路放大器居多.如果后一级的负载足够小, 这一级可以省掉, 后面的实验会进一步详细解释.

5. 输出设备一般而言就是扬声器(音箱或者耳机中的扬声器).

2.实际硬件原理分析 (硬件DAC直接输出)

这里给出我搭建的几个音频播放的硬件例子以助理解.

1.系统之一:Nucleo板子+有源音箱

首先来看看DAC+音箱形式.

DAC+有源音响.jpg

图 DAC+有源音箱形式

左边是一个ST的Nucleo开发板子,具体型号是STM32F722ZE. 但是绝大多数的类似板子都能替代. 音频信号通过芯片内部的DAC输出到音箱的输入级. 音箱须为有源音箱(带电源的就是有源音箱, 本文例子使用的就是网上买的几十块钱包邮的USB音箱, 如果是无源音箱, 需要另外增加功率分大级. 根据作者的经验, 你能找到的音箱绝大多数是有源音箱).

我们看看连接的细节:

earjack_detail.jpg

图 耳机接口细节

这是个耳机接口座子+杜邦线改装出来的连接器, 左右两根线是左右声道信号, 当中那个是共地. 本文所讨论的音频系统除非另外说明都为单声道, 故此只会使用左右两通道中的之一加下面那根地线.

大多数的MCU自带DAC为两个或以上的通道, 足够完成简单的立体声输出了. 为讨论简便, 先只用单声道做实验, 所以只会用到一个DAC输出. 这是Nucleo板上的连接, 一个DAC输出(PA5,DAC通道2), 一个地.

DAC连接细节.jpg

图 DAC连接细节

2.系统之二: Nucleo板子+耳机

DAC+耳机.png

图 DAC+耳机形式

按照数据手册,ST的DAC是无法直接驱动耳机的.

f7_dac_load.png

图 F722数据手册中关于DAC的负载参数

这里发扬(不怕烧坏板子烧坏耳机的)探索精神, 使用STM32F722的DAC直接驱动32 Ohm耳机, 经试验是可以进行播放的. 当然当做产品长时间运行这样是有点吊儿郎当的, 此处只是做实验. 如果你没有心理准备或者接受不了这种冒险行为, 请勿模仿!

3.系统之三: Nucleo板子+功放板+扬声器

这个系统应该是属于本章目前为止分析的最正统, 最教科书式的系统构造了.

DAC+功放+扬声器.jpg

图 DAC+功放板子+扬声器

DAC输出信号到功放板,功放板驱动扬声器. 其实这个系统跟第一个有音箱的系统在架构上是一模一样的. 只是把音箱拆开了开膛破肚, 把功放板与扬声器拿出来, 大家更容易看明白信号的走向流程. 图片中的功放板子是用的Adafruit做的一个板子, 作者自己打的板子将在后文中介绍.

D类音频功放扬声器.jpg

图 D类音频功放板子

这里补一句, 一般而言音频功放分为AB类和D类. 当然还有许多其他类以及变种, 但是用得最多的就是AB类和D类. 两者的差别用一句话概括:

AB类效果好,D类效率高.

具体关于两种放大器的分析与仿真作者打算使用自制的板子再开一篇来讲. 这里先跳过去.

至于扬声器,这里是作者从旧的笔记本上拆的,我想大家应该都能在家中找到这样的旧的扬声器吧. 扬声器上标的+-跟马达/无源蜂鸣器/灯丝灯抛一样, 只是个接线参考, 两极从电路原理上讲是对称的.

speaker_3.jpg

图 旧笔记本上拆的扬声器

以上三个系统都是本人为了说明音频播放原理而搭建的简化型的系统. 这些系统便于说明原理, 也能够用作大家自己动手的参考. 事实上实际的音频系统中很少直接使用处理器的ADC/DAC来播放音频, 而经常使用专用的Codecs来处理音频. 这些Codecs都是专门为音频应用优化过的ADC/DAC, 有的还加入了运放电路,DSP处理模块等等. 在成本与性能上都大大优于处理器自带ADC/DAC. 本文在介绍基本原理时使用这些通用ADC/DAC, 在此之后将回到使用专用Codecs的更加主流的道路上来. 如果看过第一篇的读者, 应该会记得曾经介绍过的F769-Discovery板子上的音频系统,那就是使用的专用Codecs: WM8994. 作者也会做一些测试板子来配合文中的内容.

4.播放实验之一(DAC+定时器播放生成音频)

硬件介绍差不多了, 现在开始写代码了. 读者如果要试验本章配套代码, 需要搭建的硬件就是上面所述的三种之一. 从软件角度来说, 这三种硬件中控制器所需要做的工作就是将数据从DAC中发出去就行了. 具体而言, 就是将数据以目标采样率的速度把数据发到DAC上, 就是这么简单. 为求最迅捷的展示原理, 这里使用的音频参数为:

 

8bit PCM * 单通道 * 8K采样率

 

这个参数今天看来很寒酸,可是退回一二十年, 这样参数的硬件可都是高端人士才用的起的数字音频系统哦.作为嵌入式系统,实现这种参数也是能接受的.

程序的结构很容易构思:

simplest_audio_playback_prog_archi.png

图 最简单的音频播放程序结构图

以STM32F722ZE板子为例,主系统跑216MHz, 使用定时器6作8KHz的更新中断, 那么Period设定为

 

P = (216000000/2)/8000 = 13500

 

这里顺便碎碎念一下子采样率与主频率的关系. 当使用定时器6的时, 采样率8K/16K/32K/48K/96K/144K都能计算得到整数的更新Period, 也就是都能得到无更新误差的输出精度.但是使用11.025K/22.05K/44.1K这样的采样率时得不到整数的更新Period, 也就是播放这种采样率的音频的时, 需要重新配置时钟源才能得到无更新误差的回放精度. 在音频系统中经常会有这样的问题, 切换采样率需要重新配置时钟. 一旦主频时钟定了,某些采样率是怎么也配置不了无更新误差的. 在使用I2S接口这样的外部Codecs时候原理也跟这个类似. 关于这个采样率定时误差后面的章节会再次详叙.

在定时器ISR中做个标记,表示125us的节点到了,主函数该更新输出了:

/* USER CODE BEGIN TIM6_DAC_IRQn 1 */

g_Tim6_Flag = true;

/* USER CODE END TIM6_DAC_IRQn 1 */

main文件中定义如下几个宏用于生成播放数据:

#ifndef M_PI

#define M_PI 3.14159265358979323846

#endif

#define TEST_SAMPLE_RATE 8000

#define AUDIO_HZ 200

#define AUDIO_CYCLE (TEST_SAMPLE_RATE / AUDIO_HZ)

#define PULSE_HZ 4

#define PULSE_CYCLE (TEST_SAMPLE_RATE / PULSE_HZ)

主函数中的主循环:

while (1)

{

if(true == g_Tim6_Flag)

{

//Play Next sample

//This demo play pulsed sine wave, if you want to play other shape of audio, please read the last article of this series,

//and modified the next line of data feeding

if(0==((time_index/PULSE_CYCLE)%2))

{

tmpSample = INT8_MAX * ((sin(M_PI*2*(time_index%AUDIO_CYCLE)/AUDIO_CYCLE)) + 1);

//Note: this macro is deliberately defined without parentness for simplication

#warning This gain is very important if you test the playback with an earphone, \

too little you will not here audible sound, too loud your ears are in risk.\

I suggest you start from a little value and adjust up.

#define TEST_GAIN 1/7

tmpSample = (uint8_t)(tmpSample * TEST_GAIN );

#undef TEST_GAIN

}

else

{

tmpSample = 0;

}

HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_8B_R, tmpSample);

time_index ++;

HAL_DAC_Start(&hdac, DAC_CHANNEL_2);

HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);

/* check the end of the file */

//No end for generated data, if you need to stop at some point, uncomment next block

//       if(SOME_POINT == time_index)

//       {

//          while(1);

//        }

           g_Tim6_Flag = false;

         }

}

完整代码请参考共享文件夹. 这里播放的是2Hz脉冲(跳变频率为4Hz)的200Hz正弦波. 听到应该是 嘟-嘟-嘟 这种效果. 关于如何生成该种波形, 请参考上一篇的内容. 还说一点就是增益设置, 如果是音箱或者功放板子那倒无所谓, 如果用耳机收听建议先将增益调小一点试验(本文实验中设置的1/7), 觉得声音小了, 慢慢往上调整以保护你的耳朵.

 

dac_pulse_2hz_oscope.jpg

 

图 DAC实际输出波形

5.播放实验之二(DAC+定时器播放raw音频)

上一章与上一节所讲的播放这种计算出来的数据大致有两种用途:

· 可以测试我们的系统是否工作,频率响应等等参数的测量都需要生成周期性的信号.

· 有艺术家这样创作音乐.

除此之外, 大多数情况下音频系统还是应用在播放其他人已经录制/处理好的声音. 这里播放一个作者处理好的8K/8bit的一段raw音频为例作实验. 至于如何处理这样的文件下节马上介绍. 这里先使用本文附带资源中的文件作实验.

共享文件夹\ Chapter2\code\resource\ 8k8bit_mono_raw_sample.bin

这个文件大小为: 125548, hex表示是0x1EA6C, 实验使用的STM32F722ZE的Flash大小是0x80000, 这里将此文件烧录在Flash的末端以0x08060000开始的位置.

首先打开STM32 ST-LINK Utility.exe,连接板子:

 

st_ut_openfile.png

 

图STM32 ST-LINK Utility.exe打开bin文件

点烧录,注意选地址: 0x08060000.

st_ut_flash_conf_addr_prog.png

图 选地址烧录

点Start开始烧录.

再修改程序,上 一节的程序为更新DAC前计算采样值. 将其改为从Flash取数据即可:

关键代码:

#define RAW_FILE_ADDRESS 0x08060000

#define RAW_FILE_SIZE 125548

uint8_t* PlaybackPosition = (uint8_t*)RAW_FILE_ADDRESS;

....

//Play Next sample

HAL_DAC_SetValue(&hdac, DAC_CHANNEL_2, DAC_ALIGN_8B_R, *((uint8_t *)(PlaybackPosition)));

PlaybackPosition ++;

完整代码请参考共享文件夹工程.

编译之后下载就可以听音乐了. 歌曲是老版本TVB拍的聊斋的主题曲<<隔世情>>的片段,也不知道读者是否喜欢这种风格. 如果想播放你们想听的歌曲, 请继续阅读下一节就能自己处理了.这里解释一下子程序与音乐在Flash中的关系图:

flash_space_audio_prog.png

图 音频数据与程序在Flash中的分布图(非比例)

一瞬间有一种冯诺伊曼架构的感觉, 不过这是开玩笑的, 实际上M7是哈佛结构.

6. 将任意音频文件处理成8K/8bit的raw数据

任意找一个你喜欢的音频文件,比如xyz.mp3.用Audacity打开,注意首次Audacity打开mp3的话需要另外安装插件. 这个通过软件提示应该很容易完成. 这里篇幅原因不介绍这个了.如果不想装插件包, 请找个ogg格式或其他软件自身支持的格式也是一样操作.

mp3_raw_1.png

图 打开你要转的文件

一般而言电脑上的音乐文件一般为双通道的立体声歌曲,这里为了后面简单处理,将其合并为单通道.(立体声的处理以后会讲到.)

mp3_raw_2.png

图 立体声至单声道

重采样,44.1KHz->8KHz,再将工程采样率改成8KHz:

mp3_raw_3.png

 

图 重采样

截取一段音频,导出,因为只分配了128K的空间给音频数据,所以最多只能截取:

128K/(8K*8/8) = 16秒

的片段.(以后我们会讲压缩,以及外部存储的.)

mp3_raw_4.png

图 选取并导出

注意选择格式,最好把采样率与位宽反映在文件名中,以便以后使用.

mp3_raw_5.png

图 选取格式

至此就可以烧录播放了, Enjoy!

如果与上次生成的音频数据长度不同的话,注意代码中文件长度相应的定义也需要改一下子,否则播放的后段会有以前的残余数据.

此篇至此为止,恳请读者多多反馈指教.

后记

这篇介绍了最基本的音频播放原理, 关于播放这个话题还有很多内容没有覆盖, 比如Arduino开发板子播放音频, PWM+LPF播放原理仿真, I2S接口信号介绍, SAI简介,作者自制的音频播放板子也没有登场等等等. 暂时计划将多余的内容分成附录形式在以后的章节顺带发. 当然还得听编辑与读者的意见.

代码与资源共享地址: https://pan.baidu.com/s/1dFh5pWd

本文系21ic原创,未经许可禁止转载!

网友评论

立 即 购 买 查看产品细节
更多相似方案