ESP8266 软件实现 Delta-sigma(ΔΣ)调制器 并通过I2S接口输出编码流
一、关于Delta-sigma(ΔΣ)调制器
Delta-sigma(ΔΣ)调制器是Delta-sigma转换器的核心部件。如下所示为一个简单的一阶Delta-sigma调制器,该调制器产生一个1bit比特流。将该比特流脉冲输入低通滤波器,从滤波器输出端口可以获得比特流信号的平均电平,该电平代表了调制器的输入电平。
一阶Delta-sigma调制器的实例时序如下:
在该实例中,时钟频率(此处等于采样率)为输入信号的64倍。根据采样定理,转换器需要至少2倍于有效信号最高频率的采样率,而Delta-sigma转换器则需要比传统转换器高得多的采样频率来产生足够数量的比特流脉冲,相同条件下提高调制器的时钟频率,则可以获得更高的精度。
二、降低量化噪声
可以看出,比特流的平均电平(经过低通滤波后的电平)只会在某个精度范围内接近于原始电平,而永远不会完全相同,输出信号永远叠加着量化噪声。
一种降低量化噪音的方法是:增加时钟频率(过采样)。例如,假设现有一个带宽为20kHz的音频信号,一个典型的PCM采样率为48kHz,而典型的Delta-sigma转换器的时钟频率高达 64 x 48kHz = 3.072 MHz,即相当于64倍的过采样。
另一种更好的解决方案是:采用高阶的Delta-sigma调制器。一般来说,一阶调制器在高频段产生较多噪声功率,如果输入信号电平接近动态范围,则这种效应最为显著。采样高阶调制器可获得较一阶调制器更低的噪声。
三、二阶Delta-sigma调制器
典型的二阶delta-sigma调制器如下图所示:
该类型的调制器产生的比特流更加接近于理想的比例脉冲信号,因此可带来如下优势:增大输入信号带宽、降低采样频率、增加转换精度(抑制量化噪声)。
理论上高于2阶的delta-sigma调制器是可行的,但无法通过简单地增加差分积分单元实现,其根本原因是由2个以上积分器产生的相位误差将使降低系统的稳定性。
成熟的商业的音频ADC往往采用5阶甚至以上的delta-sigma调制器,使用64倍的过采样,其架构与本文所述有所不同,但基本原理是相同的。
四、软件实现二阶Delta-sigma调制器
通过上文对二阶delta-sigma调制器的描述,我们可以容易得出该调制器的数字实现。这里采用C语言实现DSP(digital signal process)。
int32_t w; /* Data Flow path */ , i2v = ; /* Integrator 1 and 2 */ ; /* Latch */
对每个比特位的处理过程如下。对输入电平w迭代n次可得出任意采样率的调制输出。
#define HW (32767) [Input: w] ) w -= HW; else w += HW; /* Difference 1 */ w += i1v; i1v = w; /* Integrator 1 */ ) w -= HW; else w += HW; /* Difference 2 */ w += i2v; i2v = w; /* Integrator 2 */ latch_reg = w; /* Latch */ b = (w > ); /* Comparator */ [Output to bitstream: b]
五、ESP8266的I2S输出端口
由于I2S接口具有串行数据连续传输而不断流的特性,因此在提高时钟频率的情况下,I2S接口可用于输出delta-sigma调制器的比特流。除I2S接口外,其它具有连续串行输出特性的接口同样可以承担输出比特流的工作。
ESP8266芯片提供了I2S PHY及接口,可配置工作于较高的时钟频率,SER并串转换器(串行移位器)可将并行字节通过端口串行地发送出去。对于比特流输出的实现而言,我们只关心I2S的数据输出端口SDAT,至于LRCLK和BCLK信号则完全抛弃。将I2S传输格式配置为每声道16bit全数据(双声道),则每个I2S传送周期(传输一次所有声道的PCM采样)可发送32个比特位。
这里先将delta-sigma调制输出的比特流的每32位伪装成4字节的PCM音频采样数据,通过DMA传输到I2S总线,而I2S模块中的ser模块再将伪装的PCM采样数据串行化输出,最终从SDAT端口得到完整的比特流信号。
调制得出的比特流可预先压入FIFO缓冲器,在I2S时钟的驱动下,以慢于软件调制速率的节拍从FIFO取出数据,这样就保证了输出比特流的连续性。
函数samp_to_delta_sigma()实现了将输入电平s调制为32bit比特流。简单计算调制器的采样率如下:
Fs = Fs(Input) * 32,其中Fs(Input)为输入数字信号的采样率。
对于PCM音频数据,若原始采样率为48kHz,则该delta-sigma调制器的采样率可达:
Fs = 48kHz * 32 = 1.536MHz
则要求I2S总线的BCLK时钟频率至少达到:
Fmin = 1.536MHz。
int32_t samp_to_delta_sigma(short s) { int w; , i2v = ; ; int i; ; ; i<; i++) { val<<=; w=s; ) w -= HW; else w += HW; /* Difference 1 */ w += i1v; i1v = w; /* Integrator 1 */ ) w -= HW; else w += HW; /* Difference 2 */ w += i2v; i2v = w; /* Integrator 2 */ latch_reg = w; /* Latch */ ) val|=; /* comparator */ } return val; }
从上面简单计算可以看出,当调制器迭代次数与I2S传输周期的有效位数相同时,将I2S驱动请求的采样率配置为输入信号的原始采样率即可确保时钟正确。
本文采用espressif NONOS SDK提供的I2S驱动库。首先调用I2sInit()初始化I2S接口,再调用I2sSetRate()设置采样率。对于每个输入信号采样s,通过调用i2sPushSample( samp_to_delta_sigma(s) )即可实现delta-sigma调制和比特流输出。
如下为一个实例,该例程调制采样率为48kHZ的PCM音频信号并输出:
I2sInit(); I2sSetRate(, ); ; i < size; i++) i2sPushSample( samp_to_delta_sigma(s[i]) );
六、CPU负载评估
仍以原始采样率为48kHz的PCM音频数据为例,从第五节已经得出,FIFO输出侧(I2S端口)最小带宽为 W1 = 1.536 * 10^(6) / 1024^2 ≈ 1.5 (MBits/s)。对于FIFO输入侧(软件调制器),为便于计算,先设CPU执行一次samp_to_delta_sigma()函数的平均用时为T0 (s),则软件调制的输出带宽为 W2 = 32 / T0 / 1024^2 (MBits/s)。
若系统能稳定工作,则要求I2S端口实际带宽Wf与W1、W2满足如下关系(正常情况下W1 = Wf,以确保正确还原比特流信号的时钟频率):
W1 <= Wf < W2
一旦条件满足,CPU将得到空闲时间:
Tfree = 1 / [(W2 - Wf) * 1024^2] (s)
这是因为系统运行中,除起始状态外FIFO将永远保持非空状态。Tfree可以代表用于运行其它任务的空闲时间长度(包括任务调度切换的时间)。若其它操作占用超过Tfree的运行时间,则可能导致FIFO输入侧带宽不足,从而造成FIFO下溢出,使得输出中断。
T0的决定因素非常复杂,需要考虑生成的汇编指令、整个CPU的运行状态(如时钟主频,cache状态等)和输入的数字信号,因此只能得出平均值T0,将T0代入上式即可得出近似的Tfree时间。
七、应用价值
在音频应用领域,该方法可通过简单的一阶RC滤波电路,直接从I2S接口得出模拟音频信号,从而省去成本较高的DAC芯片。除此之外,加入简单的功率输出和滤波电路,即可实现Class-D数字功放,如下图所示。
ESP8266 软件实现 Delta-sigma(ΔΣ)调制器 并通过I2S接口输出编码流的更多相关文章
- C#软件设计——小话设计模式原则之:接口隔离原则ISP
前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...
- ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- sigma网格中水平压力梯度误差及其修正
1.水平梯度误差产生 sigma坐标系下,笛卡尔坐标内水平梯度项对应形式为 \[\begin{equation} \left. \frac{\partial }{\partial x} \right| ...
- 水平梯度在sigma坐标对应形式
sigma 坐标变换 一般 \(\sigma\) 坐标转换方程为 \[\sigma = \frac{z-\eta}{D} = \frac{z-\eta}{H+\eta} \] 转换后水深 z 范围由原 ...
- C#软件设计——小话设计模式原则之:依赖倒置原则DIP
前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...
- C#软件设计——小话设计模式原则之:单一职责原则SRP
前言:上篇C#软件设计——小话设计模式原则之:依赖倒置原则DIP简单介绍了下依赖倒置的由来以及使用,中间插了两篇WebApi的文章,这篇还是回归正题,继续来写写设计模式另一个重要的原则:单一职责原则. ...
- C#软件设计——小话设计模式原则之:开闭原则OCP
前言:这篇继续来看看开闭原则.废话少说,直接入正题. 软件设计原则系列文章索引 C#软件设计——小话设计模式原则之:依赖倒置原则DIP C#软件设计——小话设计模式原则之:单一职责原则SRP C#软件 ...
- Google首席软件工程师Joshua Bloch谈如何设计一款优秀的API【附PPT】
编者按]随着近来软件规模的日益庞大,API编程接口的设计变的越来越重要.良好的接口设计可以降低系统各部分之间的相互依赖,提高组成单元的内聚性,降低组成单元间的耦合度,从而提高系统的维护性和稳定性. J ...
随机推荐
- Mybatis中 Integer 值为0时,默认为空字符串的解决办法。
需求是查询级别为0的用户 User对象里的level字段的值为0,查询时居然没有查到为level为0的用户. <select id="selectSelective" par ...
- Java程序的第一次作业
- mybatis关于ORM的使用以及设计(二)[DaoInterface 转换 Mapper代理对象]
第一节中,分析了Mybatis的ORM框架的初始化,这篇来分析SQL执行过程中,对象->SQL是如何转换的 其中包含两种映射思想 ①DAO接口->Mapper实例 ②执行DAO的方法时,参 ...
- django rest framework serializer中获取request中user方法
views.py serializer = self.get_serializer(data=request.data, context={'request': request}) seriali ...
- 比sort()性能更好的原生js代码实现数组从小到大排序
nums = [1,2,4,1,34,6,-1,2] for(let i = nums.length - 1; i > 0; i--) { let maxIdx = i; for(let j = ...
- Python-Thread(通俗易懂)
此类表示在单独的控制线程中运行的活动,有两种方法可以指定该活动,一是将可调用对象传递给构造函数,二是通过覆盖子类中的run()方法. 如果你对线程不太理解,我们可以打个比方,把线程数看作车辆数,我们来 ...
- HTTP 请求头中的 Remote_Addr,X-Forwarded-For,X-Real-IP
REMOTE_ADDR 表示发出请求的远程主机的 IP 地址,remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间 ...
- LeetCode 206. Reverse Linked List倒置链表 C++
Reverse a singly linked list. Example: Input: 1->2->3->4->5->NULL Output: 5->4-> ...
- loadrunner 上传下载
转http://blog.163.com/yings_9371/blog/static/66196922010711115545137/ (1)LoadRunner上传文件 web_submit_da ...
- JS时间戳转时间
function timestampToTime(timestamp) { S = timestamp, T = new Date(1E3 * S), Format = function(Q){ret ...