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

          外话:各位手头上没有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]


2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值