ESP8266 软件实现 Delta-sigma(ΔΣ)调制器 并通过I2S接口输出PDM信号

2019/02/24 22:01
阅读数 3.7K

  本文提出一种完全由“软件定义”的Delta-sigma调制器,用于取代硬件生成PDM(Pulse Density Modulation信号,从而降低成本。

  该方案在低成本音频应用中具有一定价值:通过调研我们发现:在一些方案中,音频DAC的成本竟然与主控芯片成本相当,而加入PA电路后成本则更高。这些应用并不要求极高的音频性能,在少量硬件辅助的情况下,软件定义的数模转换器完全可以胜任过去由纯硬件完成的工作。

  本文针对该问题给出的方案如下:1、利用软件实现delta-sigma调制器;2、利用普通的I2S接口输出PDM信号;3、通过简单的RC低通滤波器还原出模拟音频信号。这种方法在保证原始音质和较低的CPU运算负载的前提下,取消了DAC芯片并简化了后级PA电路,进一步优化了成本。

  该方案在espressif公司的ESP8266平台上测试通过。

 

一、一阶Delta-sigma(ΔΣ)调制器的黑盒模型

  Delta-sigma(ΔΣ)调制器是Delta-sigma模数转换器的核心部件,如下所示为其最简的一阶形式的具体实现。

  为了便于说明问题,我们先暂时将其视作黑盒。于是,从A/D的角度看,它将输入的模拟量调制为1bit脉冲流,从而实现模拟到数字的转换;而从D/A的角度看,调制输出的脉冲经过低通滤波后即可还原回模拟信号,这是本方案的基础所在。

 

一、一阶Delta-sigma(ΔΣ)调制器的实现原理

  为了更加清楚地描述整个调制器的工作过程,我们在具体实现的基础上,从Z域模型的角度去研究:

 

  首先,因为转换精度是有限的,这就不可避免地引入量化噪声e(Z):

  e(Z) = y(Z) - x(Z)

  如果能将量化噪声减少到一定范围,则y(Z)能代表x(Z),从而实现调制。

  Delta-sigma使用了过采样(即以远高于Nyquist频率的采样率R*Fs/2进行采样),过采样后原本均匀分布在0 ~ Fs/2频带内的量化噪声被分散到了0 ~ R*Fs/2的频带上,因此减小了基带内的量化噪声。

  但有了过采样还远远不够,还有必要进行噪声整形。由于积分器的存在,调制器的传输函数为:

  y(Z) = x(Z) + (1-Z-1)e(Z)

  输出对量化噪声e(Z)呈高通形式,这就实现了量化噪声的整形。

  通过上述方法,量化噪声基本趋向了高频段并远离基带,通过一个低通滤波器即可基本滤除这些多余的噪声。

 

  从另一种角度也可以解释调制器的工作原理。对整个调制过程做总体分析,我们看到积分器的输入为:

  x(Z) - X'(Z)

  即系统对量化误差进行了积分,其结果是:调制输出积分后的波形不断向着输入波形逼近,这也许就是Delta-sigma的原理所在。

 

  为了更加形象地了解delta-sigma调制器的工作状态,我们回到上面所提到的具体实现。输入初相位0的正弦信号,在一个周期内各个节点的时域波形如下:

 

  观察其中1-Bit DAC的波形,在模拟输入为Vref+的时间附近, 调制输出大部分为1(图中未画出,参考“1-Bit DAC”波形);而在模拟输入为Vref-的时间附近,调制输出大部分为0。同时还可以看到,在模拟输入为0的时间附近,调制输出不断在0,1间振荡,这正是PDM(脉冲密度调制)这个名字的由来。

 

二、提高SNR

  SNR(信噪比)是评估转换器性能的重要指标。

  对于L阶的delta-sigma调制器,其理想信噪比有如下定量计算公式:

 

  SNR(dB) = 6.02N + 10lg(2L + 1) + 10(2L + 1)lg R - 10L

  其中N为量化位数,R为过采样比。

  

  一阶的Delta-sigma调制器SNR尚达不到音频应用的水平,因此需要通过改进设计提高SNR。

  从上式可以看出,若要提高信噪比,可以增加调制器的阶数L、量化位数N、或者过采样比R。

  由于我们使用的I2S接口支持的时钟频率有限,通过提高时钟频率来减少量化噪声不是一个很有效的办法,因此考虑采用更高的量化位数来降低对采样率的要求,但受制于运算性能,难以做到很高的量化精度。

  综上可知,增加调制器阶数是最有效的方法。当然阶数必须合理设置,因为提高阶数后,系统的稳定性也降低了,同时也增加了CPU的运算负载。

 

三、二阶Delta-sigma调制器

  基于上面的考虑,我们采用二阶调制器。一个典型的二阶调制器如下图所示:

  当然,也可以采用高于2阶的delta-sigma调制器,但难以像上面这样通过简单地增加积分器实现它们。系统的复杂度增加了。

 

四、软件实现二阶Delta-sigma调制器

  现在我们要以“软件定义”的方式去实现上文所描述的二阶调制器。

  注意到锁存器(latch)在电路中的作用:连续的信号流在时钟的作用下离散化了,因此硬件的每个时钟周期对应到软件中即执行一趟处理函数,更新状态并将本次结果缓存起来。换句话说,硬件的每个时钟周期都对应于软件执行一遍处理函数。因为软件不是按调制频率运行的(比调制频率快得多),是非实时的,需要引入缓存来确保硬件能以正确的时钟频率还原调制结果——I2S按调制时钟的节拍依次输出缓存内容,从而还原结果。

  这里采用C语言完成DSP。

1 int32_t w;                     /* Data Flow path */
2 static int i1v = 0, i2v = 0;   /* Integrator 1 and 2 */
3 static int latch_reg = 0;      /* Latch */

  对每个调制周期的处理过程如下。对输入电平的采样帧w迭代n次可得出任意采样率的调制输出。

 1 #define HW (32767)
 2 
 3 [Input: w]
 4 
 5 if (latch_reg > 0) w -= HW; else w += HW; /* Difference 1 */
 6 w += i1v; i1v = w; /* Integrator 1 */
 7 if (latch_reg > 0) w -= HW; else w += HW; /* Difference 2 */
 8 w += i2v; i2v = w; /* Integrator 2 */
 9 latch_reg = w;   /* Latch */
10 b = (w > 0);     /* Comparator */
11 
12 [Output to bitstream: b]

 

  现在假设我们对每一采样迭代32次,则调制器的采样频率可通过如下方法计算:

    Fs = Fs(Input) * 32,其中Fs(Input)为输入数字信号的原始采样率。

  例如:对于PCM音频数据,若原始采样率为48kHz,则该delta-sigma调制器的时钟频率可达:

    Fs = 48kHz * 32 = 1.536MHz

  则要求I2S总线的BCLK时钟频率至少达到:

    Fmin = Fs = 1.536MHz

 

五、ESP8266的I2S输出端口

  由于I2S接口具有串行数据连续传输而不断流的特性,因此I2S接口可用于输出delta-sigma调制器的PDM脉冲流。除I2S接口外,其它具有连续串行输出特性的接口也可以承担输出比特流的工作,本文仅以I2S接口为例说明。

  因为I2S最初的设计目的并不是输出PDM脉冲流,具体实现需要一些变通:

 

  首先将I2S的传输格式配置为16bit x 2chs(双声道),则每个传送周期可发送32个比特位。

  Delta-sigma调制输出的PDM脉冲流是连续的,需要按每32个比特一组的分法切分为若干字节,然后“伪装”成4字节的PCM音频采样数据,通过DMA传输到I2S模块,而I2S模块再将伪装的PCM采样串行化输出,最终我们便可以从芯片的SDAT端口得到完整的脉冲流信号。

  写入I2S模块的数据会预先压入FIFO缓冲器(硬件为我们隐藏了细节),在I2S时钟的驱动下,I2S会以慢于写的速率从FIFO取出数据,从而可以保证输出脉冲流的连续性。

  以上便是对于I2S端口输出PDM脉冲流的分析。

 

  接下来说明完整的代码实现。函数samp_to_delta_sigma(s)实现了将每个电平采样s调制为32bit脉冲流:

 1 int32_t
 2 samp_to_delta_sigma(short s)
 3 {
 4   int w;
 5   static int i1v = 0, i2v = 0;
 6   static int latch_reg = 0;
 7 
 8   int i;
 9   int32_t val=0;
10   
11   for (i=0; i<32; i++)
12    {
13       val<<=1;
14       w=s;
15       if (latch_reg > 0) w -= HW; else w += HW; /* Difference 1 */
16       w += i1v; i1v = w; /* Integrator 1 */
17       if (latch_reg > 0) w -= HW; else w += HW; /* Difference 2 */
18       w += i2v; i2v = w; /* Integrator 2 */
19       latch_reg = w;   /* Latch */
20       if (w > 0) val|=1; /* comparator */
21     }
22   return val;
23 }

 

  有了脉冲流,接下来考虑如何将其输出到硬件上。本文采用espressif提供的I2S驱动库,它提供了三个函数:I2sInit()用于初始化I2S接口,I2sSetRate()用于设置采样率,i2sPushSample用于向缓冲区中压入数据。

 

  如下为一个调用调制器的实例,核心代码只有4行,该实例调制采样率为48kHz的PCM音频信号并输出PDM.

  其中:size = 音频采样帧数,s是一块连续内存,存储所有音频采样(short类型)。

1 I2sInit();
2 I2sSetRate(48000, 0);
3 
4 for(i=0; i < size; i++)
5   i2sPushSample( samp_to_delta_sigma(s[i]) );

   对于每个输入信号采样s:通过调用i2sPushSample( samp_to_delta_sigma(s) )即可实现调制输出。

 

六、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的决定因素非常复杂,需要取平均值T0,将T0代入上式即可得出近似的Tfree时间。

 

七、后级电路

  1、可通过简单的一阶RC低通滤波器,直接从I2S接口得出模拟信号,从而省去成本较高的DAC芯片。

 

  2、在要求功率较小的情况下(例如驱动小型扬声器或者耳机),完全可以对输出脉冲流进行简单缓冲后,接入扬声器发声。

 

  3、加入简单的功率输出和滤波电路,即可实现Class-D数字功放,如下图所示。

 

  最后,将该方案扩展到非音频的应用是可行的,但只能在对信号的带宽,精度等的要求都不严格的场合使用。

 

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部