在用户入口函数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链表,链表的定义如下:

MES系统流程功能框图_小e1开发板试用体验

MES系统流程功能框图_i2s和slc_02


    因为我手上并没有完全的寄存器手册,而且资料也说得不够清楚,所以这里我只能够猜测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从而造成我们并没有听到任何的声音而已。 为了验证上面的假设,这里我们进行下面的实验: