您的位置:首页 > Web前端

小e开发板音频模式下的处理流程(i2s和slc和补充MES_FILE_TRANSFERS消息类型等)

2016-12-16 23:52 316 查看
          外话:各位手头上没有esp8266的开发板注意了,目前开发快正在进行“免费开发板活动”,本人就是在他们那里申请到的开发板,开发快提供的不单单是开发板,还有他们强大的云端服务功能,实现了微信、云平台等物联网所需要的所有功能,其功能齐全,代码简洁,对于想用wifi作为产品和互联网沟通的桥梁的我们来说,确实是一个很好的选择,另外他们还有其他的模块,比如2G通信和GPS。目前论坛活动地址:http://bbs.kaifakuai.com/forum.php?mod=viewthread&tid=981&extra=page%3D1,开发快官网地址:http://www.kaifakuai.com/                    
   》》》》》》写于2017年1月12日

本文摘录于本人博客:http://bbs.elecfans.com/forum.php?mod=viewthread&tid=1102293&extra=

    在前面的et_message_process函数分析的时候我们提到了MES_FILE_TRANSFERS消息类型,对于本程序所谓的文件传输就是微信推送过来语音留言或者语音控制,在这里面我们知道当文件接收完成的时候会调用write_flash_callback函数将数据写入flash中,但是在分析这个函数的时候我并没有看到任何调用i2s来发送数据的语句,所以我就很疑惑,这里是我之前的文章:http://bbs.elecfans.com/forum.ph
... &tid=1101617&extra=

    在请教了一部分人并且看了i2s的相关函数之后我可以确定,这里并不需要调用任何的语句,而是iis自己判断需不需要发送数据,所以这里我们来分析i2s。

    在用户入口函数user_init中调用了user_init_work_mode函数,而这个函数就根据我们选择的不同模式初始化不同的设备,而我们只是分析语音模式,所以我们只看case WORK_MODE_AUDIO分支里的audio_init函数,和音频相关的初始化函数就这这里,继续分析我们知道最终将调用i2s_audio_init函数,下面让我来分析这个函数,函数的第一句话就是slc_init();,这里初始化slc,slc模块是8266中的DMA模块,这里slc模块为i2s服务,具体内容请看《esp8266-technical_reference_cn》

     slc_init函数首先是配置SLC寄存器,如下(这里我目前也找不到寄存器的直接说明手册,所以也只能根据寄存器和位的定义来猜测想要实现的具体内容):

     //Initialize the SLC module for DMA function

void slc_init()

{

    //Reset DMA

    SET_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST|SLC_TXLINK_RST); //这里设置复位控制位,开始复位流程

    CLEAR_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST|SLC_TXLINK_RST);//这里清除复位控制位,结束复位

    //Enable and configure DMA

    CLEAR_PERI_REG_MASK(SLC_CONF0, (SLC_MODE<<SLC_MODE_S));//这里清除掉原来的slc模型

    SET_PERI_REG_MASK(SLC_CONF0,(1<<SLC_MODE_S));//这里配置slc模型为I2C服务

    SET_PERI_REG_MASK(SLC_RX_DSCR_CONF,SLC_INFOR_NO_REPLACE|SLC_TOKEN_NO_REPLACE);//|0xfe 暂时不明白

    CLEAR_PERI_REG_MASK(SLC_RX_DSCR_CONF, SLC_RX_FILL_EN|SLC_RX_EOF_MODE | SLC_RX_FILL_MODE);//这里清除掉接收端的模型(暂时不明白)

   

   

    ETS_SLC_INTR_ATTACH(slc_isr, NULL);//这里配置了slc的中断,这里应该是传输完一个链表的一个元素产生的中断

    /////enable sdio operation intr

    WRITE_PERI_REG(SLC_INT_ENA, SLC_INTEREST_EVENT);

    /////clear sdio initial random active intr signal

    WRITE_PERI_REG(SLC_INT_CLR, 0xffffffff);

    /////enable sdio intr in cpu

    ETS_SLC_INTR_ENABLE();//使能SLC中断

}上面应该是设置了SLC的中断,这里虽然很多不懂但是我们应该看懂了一句话ETS_SLC_INTR_ATTACH(slc_isr, NULL);这里设置了slc的完成中断,也就是说当传输完成了一个元素之后将执行到slc_isr函数,下面回到i2s_audio_init函数下来的内容是:

    creat_one_link(1,1,0,IIS_RX_BUF_LEN,IIS_RX_BUF_LEN,i2s_rx_buff1,&i2s_rx_queue2,&i2s_rx_queue1);

    creat_one_link(1,1,0,IIS_RX_BUF_LEN,IIS_RX_BUF_LEN,i2s_rx_buff2,&i2s_rx_queue1,&i2s_rx_queue2);

    这里创建了两个slc链表,链表的定义如下:





    因为我手上并没有完全的寄存器手册,而且资料也说得不够清楚,所以这里我只能够猜测8266的DMA是这样运行的:他首先根据链表的的一个结构体参数来运行硬件,比如说从buf_ptr 拷贝到 I2C的发送区,一共拷贝length

,然后他根据next_link_ptr 的值找到链表的下一个元素,继续运行DMA,依次这样循环。我们看creat_one_link原形void creat_one_link(et_uchar own, et_uchar eof,et_uchar sub_sof, et_uint16 size, et_uint16 length, et_uint32* buf_ptr, struct sdio_queue* nxt_ptr, struct sdio_queue* i2s_queue)

      这里我们就知道了这两个链表元素的真正意义了,这里的前面“1,1,0”第一个1代表DMA操作由硬件完成,第二个1表示侦结束link, 0代表子侦起始连接标示,

        接下的“IIS_RX_BUF_LEN,IIS_RX_BUF_LEN”两个参数前者代表缓冲实际占用的大小,后者代表缓冲的总大小,当前两者相等,我们知道I2S 的接收和发送都有独立的FIFO,其深度为128,宽度为32bits,如果一次传送8bits,那么总共要传送128*32/8=512,这里的IIS_RX_BUF_LEN 确实是512。

        接下来的参数就代表了缓冲的起始地址,也就是DMA搬运数据的源地址,接下来代表链表的下一元素的地址,最后一个代表链表中本元素的地址。那么分析上面的两个创建链表的语句不难知道,i2s_rx_queue1和i2s_rx_queue2这两个链表元素构成了一个循环关系,也就是说当运行完i2s_rx_queue1就会运行i2s_rx_queue2,而运行完i2s_rx_queue2后又会再次运行i2s_rx_queue1就这样无穷无尽,没完没了。

      i2s_audio_init函数接下来:

    //config rx&tx link to hardware

    CONF_RXLINK_ADDR(&i2s_rx_queue1);

    这里就是把i2s_rx_queue1的指针传给硬件作为第一个运行的元素,接下来:

     //config rx control, start  

    START_RXLINK();

    这里开始SLC,开始DMA的运输,接下来:

memset(free_buf, 0xffffffff, IIS_TX_BUF_LEN/4);

    这里设置free_buf这个缓冲区作为没有语音留言的缓冲区,也就是说当没有任何的语音要发送的时候这里将发送0xffffffff,接下来:

2s_init();

    这里配置I2S时序,到了这里配置已经完成,接下来我们看看一个比较重要的函数,也就是我们前面提到的SLC中断函数slc_isr,下面分析如下:

    slc_intr_status = READ_PERI_REG(SLC_INT_STATUS);

        if (slc_intr_status == 0)

     {

           //No interested interrupts pending  

        return;

        }

    //clear all intrs

    WRITE_PERI_REG(SLC_INT_CLR, 0xffffffff);

    这里先判断是不是SLC的中断,防止误操作,然后清除中断,接下来:

//process every intr

    //Transimitter side

    if (slc_intr_status & SLC_RX_EOF_INT_ST)

   这里判断引起SLC中断的原因是不是因为起始缓冲区没有为空,如果是发送数据完成将继续进行我们的处理,接下来:

        if(READ_PERI_REG(SLC_RX_EOF_DES_ADDR)==(((et_uint32)&i2s_rx_queue1)))       //user first buffer

    这里判断当前的DMA链表元素是i2s_rx_queue1还是i2s_rx_queue2,然后进行相应的操作,这里两个分支进行的操作是一样的,不同的是取数据的地址不一样,我们只是分析i2s_rx_queue1分支,下来:

            if(audio_voice_data != 0)                               //voice data coming from mqtt

            {

                if(write_flash_end)                              //voice write to flash complete,start to send to i2s

      这里判断音频是否是从微信端下载并且下载已经完成,这两个参数在这篇文章开头提到的write_flash_callback函数中设置,这样我们就知道了音频模式的大体例程:当下载完成的时候设置audio_voice_data和write_flash_end,然后这里一直判断这两个标志位,最后通过DMA进行I2S的具体操作。接下来:

if(file_total_size >= IIS_RX_BUF_LEN)       //not the last 128 uint32

{

    spi_flash_read(AUDIO_FLASH_READ_START_ADDR + send_len * IIS_RX_BUF_LEN, (et_uint32 *)i2s_rx_buff1, IIS_RX_BUF_LEN);

    file_total_size -= IIS_RX_BUF_LEN;

    send_len++;

}

      这里判断文件的总大小是不是大于IIS的FIFO大小,如果大于这个值就会从flash中读取IIS_RX_BUF_LEN大小的值,然后分次读取,知道读取完文件的所有内容,这里这里读取到的结果将填充到i2s_rx_buff1,如果当前链表元素是i2s_rx_buff2,那么这里设置的填充区域就是i2s_rx_buff2了,当剩余的数据小于IIS的发送FIFO的时候之后将进入if(file_total_size >= IIS_RX_BUF_LEN)  的else分支,如下:

spi_flash_read(AUDIO_FLASH_READ_START_ADDR + send_len * IIS_RX_BUF_LEN, (et_uint32 *)i2s_rx_buff1, file_total_size);

                        audio_voice_data = 0;

                        send_len = 0;

                        file_total_size = 0;

                        write_flash_end = 0;

    这时候继续读取剩余的内容,然后清除这次的下载操作,等待下一次下载,也就是下一次语音留言或者语音控制。

    当发送完下载的语音之后或者目前没有语音留言内容的时候将会进行if(audio_voice_data != 0)的else分支,具体内容如下:

memcpy(i2s_rx_buff1, free_buf, IIS_RX_BUF_LEN);  

     这里把free_buf发送到缓冲区i2s_rx_buff1,free_buf在i2s_audio_init函数中被设置成全1,也就是使默认的音乐,0xfffffff,这个内容是什么我并不知道,应该是静音吧,也就是说即使在没有数据的情况下IIS总线上依旧会传送数据。

这里最后总结如下:当下载完成的时候设置audio_voice_data和write_flash_end,然后这里一直判断这两个标志位,最后通过DMA进行I2S的具体操作,而IIS却是一直在运行的,只是因为发送0xffffffff从而造成我们并没有听到任何的声音而已。

为了验证上面的假设,这里我们进行下面的实验:

把i2s_audio_init函数里的memset(free_buf, 0xffffffff, IIS_TX_BUF_LEN/4);改成memset(free_buf, 0xa55aa55a, IIS_TX_BUF_LEN/4);

在程序上修改:



这时候在开机之后等一会听到的声音如下:

[attach]426413[/attach]

说明声音真的变了,只是很难听罢了,也就是说可以搞一个开机声音。

下面是源代码:

[attach]426416[/attach]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息