• 农村土地权确权将给农民带来什么? 2020-02-20
  • 陕西首批“00后”高考结束轻松走出考场 6月24日起填报志愿 2020-02-17
  • В Пекине закрылась первая сессия ВСНП 13-го созыва 2020-02-15
  • 自私的小萌们眼看着自己的队友挨踹也不出来帮一把? 2020-02-15
  • 村教扶智,幕天在行动:“千城万区助村教”行动走进北京、泉州等全国各地城市 2020-02-14
  • 光明网招聘两名行政助理 2020-02-14
  • 美国派往越南的第五纵队也不少。而越南却没有经过反修防修锻炼的人民。希望越南能闯过难关,不让美国第五纵队得逞。 2020-02-07
  • 高温“烤验”,品读这些自带凉意的避暑诗词 2020-02-03
  • 玉带串明珠 蛟龙贴地飞 2020-02-02
  • 啥叫“阴阳合同”?崔永元和范冰冰给中国人上了堂税法普及课 2020-02-02
  • 我看“支付宝回收垃圾”这件事不错,应该支持。[微笑][微笑] 首先是提高的回收效率,其次便于集中处理旧物品,防止污染有利。 2020-01-28
  • 西川:把自己活成一件作品西川作品 2020-01-15
  • 税费“红包”助推高质量发展 2020-01-15
  • 年终奖PK大赛来袭 网友:心疼得抱住自己 2020-01-05
  • 中国何以成为数字大国 2020-01-01
  • 首页 > 评测 > 嵌入式音频列表页

    三肖中特长期免费:第三篇-嵌入式系统音频基本实践-播放声音之二

    • [导读]
    • Everyboard Can Sing

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

    关于zhanzr21

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

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

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

    本期活动地址:bbs.21ic.com/icview-1713672-1-1.html

    TIM截图20170428101100.jpg

    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输出的硬件结构,接耳机,接音箱,接放大板都可以.

    f722_nucleo_dac_earphone1.jpg

    将软件改成这样的结构:

    3.jpg

     

    这个实验看起来简单,其实内容有点多.最主要的是Buffer管理. 下面简单讲讲这个buffer的管理过程.

    假设Buffer大小为BUF_SIZE,那么第一次需要从资源处填充BUF_SIZE的内容到这个Buffer.开始DMA,这里注意这两个回调函数:

    void HAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac)

    {

    UpdatePointer = PLAY_BUFF_SIZE/2;

    }

    void HAL_DACEx_ConvHalfCpltCallbackCh2(DAC_HandleTypeDef* hdac)

    {

    UpdatePointer = 0;

    }

    分别是DMA传输完成与DMA传输完一半的回调函数.需要用户实现,如果用户不实现,将使用默认的HAL自带的回调函数:

    __weak void HAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac)

    {

    /* Prevent unused 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)

    {

    /* Prevent unused 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 user file

    */

    }

    看到前面那个__weak关键字没有,有__weak修饰表明这函数可以被重载,或者覆盖,或者隐藏. 这里也不知道该用什么术语,对C++或者其他面向对象语言有了解的同学应该一下子就能明白, 这里不节外生枝以后有时间再展开来说.

    首先初始化buffer的时候,将buffer填充了第一次要播放的内容.

    4.jpg

    在T1时刻开始播放(就是DMA传输), 这时候主循环可以处理其他事务,只是定期检查一下子上面所说的Flag即可.如果BUF_SIZE设定的足够大,检查Flag的期限可以很长.

    T2时刻发生了传输完成一半的事件,因此半传输回调函数被调用.通过检查Flag主循环知道了之后取数据填充在Buffer的前一半也就是绿色的那一半.

    过一阵子在T3时刻又发生了传输完成事件,全传输回调函数被调用. 通过检查Flag主循环知道之后取数据填充在Buffer的后一半也就是红色的那一半.

    如此循环下去播放每个Buffer期间软件只需要干预两次(比如把BUF_SIZE设定为可以装播放0.5秒的数据,则软件干预的间隔为250ms,如果没有很多其他任务,CPU完全可以在此期间Sleep,当然只有CPU能Sleep,其他外设还得干活.).

    这种更新数据的方式称之为双缓冲方式.不仅仅是音频应用,很多类似的场合都能用上.

    那么说DMA传输数据到DAC,采样率怎样控制呢? 答案是将DAC设为定时器更新触发.再将定时器的重装频率设置为想要的采样率即可.STM32系列的处理器中定时器6与定时器7是专为此功能设计的(这两个定时器当然也能当通用的定时器来用).

    2.2 wav格式

    这一期的实验烧写文件还是与上一集一样子.只是这次我直接烧写wav文件而不是原始的raw文件.所以这里简单的介绍一下子wave格式以理解实验代码.wave格式以后还要详细一点介绍.

    简单的说wav文件就是一种容器格式RIFF的实例化.RIFF文件可以装各种数据,只是为人们熟知的就是wav文件了.我们这个实验中从文件头跳过44个字节就是原始音频数据了.

    WAV文件 = WAV头 + 原始音频

    这里给个简单的wav头的结构:

    struct zWavHdr

    {

    FOURCC RiffHdr; //"RIFF"

    uint32_t ChunkSize; //file size - 8

    FOURCC WavHdr;//"WAVE"

    FOURCC FmtHdr; //"fmt "

    uint32_t HdrLen; //16, length of above

    uint16_t DataType; //1->PCM

    uint16_t ChanNo; //1 Channel

    uint32_t SampleRate;//8000 Hz

    uint32_t SamplePerSec; //8000 sample per second

    uint16_t BytePerSample;//Bytes per sample

    uint16_t BitsPerSample;//Bits per sample

    FOURCC dataHdr;//"data"

    uint32_t RawSize;//data size from this point

    };

    记得某大公司某年的笔试题目给出类似这样的一个结构, 对应试者进行提问. 所以有志于以后进大公司的同学们也可以将此结构体当做一个练习,理解一下子这样设计结构体的思路以后兴许用得上. 当然我这里给出的只是我自己进行简单应用的代码, 命名注释什么没怎么细究规范. 真正感兴趣的同学请自行找该公司官方发布的wav头实现代码进行学习. 不那么关心的同学可以今后看本连载了解, 因为以后还会讲到这个wav文件的.

    3.实践之二(PWM+LPF)

    上次写了文章后, 有人就说还有好多处理器没DAC呢,怎么办.

    别担心,这里介绍一种所有处理器都能用的播放音频的方式.就是PWM+LPF的方式.这里LPF指的是低通滤波器.

    5.jpg

    事实上不只是PWM,PDM也能实现.就算没有PWM,PDM这样的外设,自己计算指令周期从而通过IO口拉高拉低也能实现这功能.

    关于滤波器,最简单的一阶RC滤波器就行.根据实验结果,不要滤波器直接接耳机或者8欧姆的扬声器也可以.此种情况是因为PWM到扬声器的传输路径上的分布电容电阻与扬声器本身的分布参数构成了滤波器.但是不是每个扬声器都能构成合适的滤波器.这种直接连接方式一来对IO口负载过大, 多少有点风险(比DAC直接驱动耳机的风险大,因为PWM引脚输出全高的瞬间对外输出全部加在外部负载上, 不像DAC很少有满幅度输出的情况), 二来不利于说明原理, 所以这里还是不跳过LPF这个环节.

    现在回到PWM+LPF这种典型方式.这种方式的原理其实就是用PWM+LPF做了一个伪DAC而已.关键在于滤波器的设计,将20KHz以上的频率分量滤去.那么每个周期的占空比就和输出的能量成线性比例关系了.

    为了做这个实验,作者专门做了个滤波器板子.有两种滤波器实现,分别是有源一阶与无源二阶RC两种类型(其中无源的后面还是加了一级跟随以便于驱动耳机与扬声器).

    电路图如下:

    6.jpg

    上图是有源的.

    7.jpg

    上图是无源的.

    这两个滤波器经过实验,都可以用.下面一种效果稍为好一点.可能跟我使用的运放有点关系.因为正好手边没有Rail-to-Rail的运放随手拿了个运放焊接上去了.另外上述各种参数都需要在实际使用中进行调整,标注的都是我计算出来的值.购买器件也没有买高精度的,因为都属于模拟范畴的这里不多讲.

    这是LPF板子的实物图:

     

    8.jpg

     

    lpf2.jpg

    资源文件还是用之前实验用剩下的8KHz,8bit的样本. 这些都在本章的共享文件夹中可以找到.

    为求多样化,新鲜感, 后面的实验尽量用不同的板子来做. 这一节的实验使用Arduino Uno板子来实现. 因为ATmega328的Flash大小为32KB, 所以在资源中截取3秒,也就是24KB的数据出来.

    9.jpg

    bin文件到C数组有很多种方法和现成工具,作者这里图个快捷,使用HexEdit的一个菜单复制出来.

    10.jpg

    重要代码如下:(完全代码在共享文件夹中找)

    #include "resource.h"

    #define speakerPin 11

    #define SAMPLE_RATE 8000

    uint32_t sample_idx = 0;

    bool update_flag = true;

    // the setup function runs once when you press reset or power the board

    void setup() {

    pinMode(2, OUTPUT);

    pinMode(speakerPin, OUTPUT);

    // 这里要修改PWM引脚的频率,因为Arduino的默认PWM输出使用了64分频,导致要使用的IO口最多只能输出几百Hz的PWM出来.详情请参阅完整代码

    // 让定时器1每125us中断一次,这里上节讲过原理

    }

    ISR(TIMER1_COMPA_vect) {

    sample_idx ++;

    //翻转IO口以测试实际的更新率

    }

    void loop() {

    if (update_flag)

    {

    if (sample_idx == RSC_SIZE)

    {

    //循环

    }

    //设定输出

    update_flag = false;

    }

    }

    代码跟上一篇文章的定时器+DAC的例子基本一样子,只是将DAC换成了PWM+LPF.下图是实际的工作图.

    11.jpg

    实验证明将信号转接至音箱效果更好, 这是因为小音箱内部也LPF的原因.

    4.实践之三(I2S输出,以CS4344为例)

    4.1 I2S信号连接

    这个实验讲述的应该属于最主流的音频播放方式了. 就是通过I2S接口将数据传输给外部DAC. 使用外部音频DAC的动机大致有两条:

    1.如之前的文章所述,就音频角度来讲,外部音频DAC的性能一般情况下都比内部DAC要强.ST与其类似处理器的DAC是被设计用来做测量控制类的应用的.F4,F7这一类的中高端片子所带的DAC也就只有12bit解析度. 而只要1美元或者之下的价格就能采购到16bit双通道的I2S接口音频DAC, 还自带耳放. 比较过两种DAC性能的读者应该有体会.

    2.通过I2S或者类似的接口将数据以数字形式发送给外部音频DAC, 使得布线简化.可以将对干扰敏感的音频部分隔离开来. 包括对尺寸敏感的手机等移动设备也有时采用外部音频DAC. 这点还能成为宣传的噱头.比如至少有两个国产手机都曾使用外部音频DAC作为HIFI手机宣传的卖点.

    感兴趣的读者可以浏览本人拍的视频,对比两种DAC的性能:

    //v.youku.com/v_show/id_XMjcwNDUyMDA1Mg==.html

    首先看看实验图片:

    12.jpg

    本节实验是用一块本人自制的GD32F105RCT6的开发板子做演示. GD32的片子与ST的同后缀基本兼容, 除了某些细节比如HSE启动等等. 相关改动详情可以参阅共享文件夹中的代码.

    由于篇幅问题,此处也不对此板子进行过多阐述.只是介绍一下I2S部分的信号接口, 其余部分将在今后的文章中介绍.

    13.jpg

    这四根脚连接到外部I2S的DAC即可.

    14.jpg

    四根线定义分别如下:

    MCK:主时钟 = Fsample * SampleDepth * ChannelNum * 某个系数,此系数跟当前采样率相关.

    CK: Bit时钟 = Fsample * SampleDepth * ChannelNum

    SD: 数据线,相当I2C中的SDA

    WS: 左右时钟 = Fsample

    至于软件流程则跟本篇文章第一个实验的流程一样, 只是把DAC换成了I2S了.

    关于I2S信号接口问题,以后还会讲到.

    4.2 下载资源到Flash中

    这一节的实验通过插件将音频数据下载到Flash中去. 在共享文件夹中找到"F105_AudioT2_25Q128.FLM"这个文件, 放在你安装的Keil的目录:

    15.jpg

    将资源文件定位于0xC0000000开始的虚拟位置,

    #ifndef __SPI_AUDIO_INFO_H__

    #define __SPI_AUDIO_INFO_H__

    #include

    #define E_FLASH_START_ADD 0xC0000000

    //16bit 2ch 8K

    #define AUDIO_SEC_1 0x00000000

    #define SEC_1_SIZE 128000

    //16bit 2ch 8K

    #define AUDIO_SEC_2 (AUDIO_SEC_1+SEC_1_SIZE)

    #define SEC_2_SIZE 485032

    //8bit 1ch 8K

    #define AUDIO_SEC_3 (AUDIO_SEC_2+SEC_2_SIZE)

    #define SEC_3_SIZE 125548

    #endif

    ...

    #include "spi_audio_info.h"

    static

    const uint8_t raw_audio_16bit2ch8k_2[] __attribute__((at(E_FLASH_START_ADD + AUDIO_SEC_2))) = {

    0x1F, 0x00, 0x1F, 0xFB, 0x54, 0xFF, 0xEA, 0xF9, 0x92, 0x00, 0xA7, 0xFB, 0x55, 0x00, 0x38, 0xFB,

    ....

    };

    选择Flash类型:

    16.jpg

    点下载就可以将音频数据下载到版上的Flash芯片中. 当然这个Flash插件是为此F105开发板定制的. 以后会介绍如何写这个插件.

    更好的方法是通过USB与文件系统来下载音频数据. 或者下次做个能插TF卡的实验板子.

    5.总结与后记

    此篇补充了一些常用的播放音频的方式与实验.因为篇幅原因, 有些问题简化带过了. 期望今后讲到具体情况时候再加以铺陈展开. 读者如果有疑问或者建议也可在文章后面留言. 暂时打住,下期见!

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

    网友评论

  • 农村土地权确权将给农民带来什么? 2020-02-20
  • 陕西首批“00后”高考结束轻松走出考场 6月24日起填报志愿 2020-02-17
  • В Пекине закрылась первая сессия ВСНП 13-го созыва 2020-02-15
  • 自私的小萌们眼看着自己的队友挨踹也不出来帮一把? 2020-02-15
  • 村教扶智,幕天在行动:“千城万区助村教”行动走进北京、泉州等全国各地城市 2020-02-14
  • 光明网招聘两名行政助理 2020-02-14
  • 美国派往越南的第五纵队也不少。而越南却没有经过反修防修锻炼的人民。希望越南能闯过难关,不让美国第五纵队得逞。 2020-02-07
  • 高温“烤验”,品读这些自带凉意的避暑诗词 2020-02-03
  • 玉带串明珠 蛟龙贴地飞 2020-02-02
  • 啥叫“阴阳合同”?崔永元和范冰冰给中国人上了堂税法普及课 2020-02-02
  • 我看“支付宝回收垃圾”这件事不错,应该支持。[微笑][微笑] 首先是提高的回收效率,其次便于集中处理旧物品,防止污染有利。 2020-01-28
  • 西川:把自己活成一件作品西川作品 2020-01-15
  • 税费“红包”助推高质量发展 2020-01-15
  • 年终奖PK大赛来袭 网友:心疼得抱住自己 2020-01-05
  • 中国何以成为数字大国 2020-01-01
  • 网上购彩票官网百度 欢乐捕鱼4破解版 2017六合图库大全 大神娱乐棋牌下载网址 秒速飞艇开奖规律 足球指数什么意思 7m足球指数网 斗牛牛捕鱼游戏送金币 `捕鱼达人 快乐10分网上购买