通过简单的计数器来实现

使用一个自由运行的计数器就可以产生一个简单的PWM波形,如下面的例子:

module PWM(
    input clk,
    input [3:0] PWM_in,
    output PWM_out
);
 
reg [3:0] cnt;
always @(posedge clk) cnt <= cnt + 1'b1;  // 计数器
 
assign PWM_out = (PWM_in > cnt);  // 比较期
endmodule

在这里我们选择了4位的PWM,所以PWM的周期为16,输入的参数可以从0-15变化,输出的PWM波形的占空比则从0%到15/16=93%变化。


通过加减计数器的方式实现

也可以通过一个可预置数的加-减计数器来实现PWM,设计稍微复杂一点:

module PWM(
    input clk,
    input [3:0] PWM_in,
    output PWM_out
);
 
reg [3:0] cnt;
reg cnt_dir;  // 0 to count up, 1 to count down
wire [3:0] cnt_next = cnt_dir ? cnt-1'b1 : cnt+1'b1;
wire cnt_end = cnt_dir ? cnt==4'b0000 : cnt==4'b1111;
 
always @(posedge clk) cnt <= cnt_end ? PWM_in : cnt_next;
always @(posedge clk) cnt_dir <= cnt_dir ^ cnt_end;
assign PWM_out = cnt_dir;
endmodule

它使用了一个可预置的加-减计数器,不需要输出比较器。输出周期有17种状态(输出从1/17=6%到16/17=94%变化).


1阶的sigma-delta调制

一个简单的一阶sigma-delta调制器代表了一个PWM,如果你使用滤波器的话它有着更好的频率响应。创建一个一阶sigma-delta调制期的最简单的方法就是使用一个硬件累加器,每次累加器溢出,输出为“1”, 否则输出'0',用FPGA非常容易实现。

module PWM(clk, PWM_in, PWM_out);
input clk;
input [7:0] PWM_in;
output PWM_out;
 
reg [8:0] PWM_accumulator;
always @(posedge clk) PWM_accumulator <= PWM_accumulator[7:0] + PWM_in;
 
assign PWM_out = PWM_accumulator[8];
endmodule

输入的值越高,计数器溢出的越快(“PWM_accumulator[8]”), 输出“1”的频率也越快。

PWM产生可调直流电压


1位的DAC

只用FPGA的一个管脚,连接一个扬声器来听MP3音乐?简单!我们可以先用PC解码一个MP3,并将解码的数据通过UART传送给FPGA,由FPGA的一根管脚做成1位的DAC来驱动扬声器播放音乐。


声音输出

我们需要一个DAC连接在FPGA和扬声器之间。常规的方式是使用一个电阻网络,或者一个专用的DAC集成电路。 PWM RC滤波器 关于利用PWM使用RC滤波器实现DAC的参数计算方式,可以参见以下的文章:Low-Pass Filter a PWM Signal into an Analog Voltage

FPGA的运行速度远高于音频(MHz相比KHz),因此一个1位的DAC是比较好的选择,最简单的就是产生一个模拟输出,通过低通滤波器对其平滑。Sigma Delta调制器更好一些只需要一阶的低通滤波器就够了。


尝试播放一下MP3音乐

第一步是解码MP3音乐文件,这个可以在PC上执行,解码后的文件为“PCM”数据,通过串口传到FPGA。串口能够传输的最大速率为115.2Kbps(大约每秒11.5KBytes),所以需要现将音乐下取样到11KHz 8bit,这些处理在PC上都是非常简单的。

module PWM(input clk, input RxD, output PWM_out);
wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver deserializer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data)); 
 
reg [7:0] RxD_data_reg;
always @(posedge clk) if(RxD_data_ready) RxD_data_reg <= RxD_data;
 
////////////////////////////////////////////////////////////////////////////
reg [8:0] PWM_accumulator;
always @(posedge clk) PWM_accumulator <= PWM_accumulator[7:0] + RxD_data_reg;
 
assign PWM_out = PWM_accumulator[8];
endmodule

现在可以将FPGA跟一个扬声器连接,有三种方法:
1. 将一个扬声器直接连接到FPGA,由于扬声器一般都是感性的,我们可以通过控制扬声器内的电流来进行播放,我们需要比较高阻的扬声器(或耳机)
2. 在FPGA和扬声器(或有放大器的扬声器)之间加一个RC滤波器,这需要比较高阻的扬声器,不会影响到RC滤波器
3. 在RC滤波器之后再加一个运算放大器,获取最好的音质。


测试

下面是基于两种PWM方式产生的PWM波形及经过低通滤波器以后得到的直流电压的波形图,供理解PWM的构成和效果进行参考。

  • 计数器方式3F(/FF)频率的PWM输出经滤波器以后的直流电压

  • 计数器方式3F(/FF)频率的PWM输出的脉冲波形

  • 计数器方式7F(/FF)频率的PWM输出经滤波器以后的直流电压

  • 计数器方式7F(/FF)频率的PWM输出的脉冲波形

  • Sigma-Delta方式的PWM用3F(/FF)频率的PWM输出经滤波器以后的直流电压

  • Sigma-Delta方式的PWM用3F(/FF)频率的PWM输出的脉冲波形

  • Sigma-Delta方式的PWM用7F(/FF)频率的PWM输出经滤波器以后的直流电压

  • Sigma-Delta方式的PWM用7F(/FF)频率的PWM的脉冲波形

  • Sigma-Delta方式的PWM用28(/FF)频率的PWM的脉冲波形

  • Sigma-Delta方式的PWM用E8(/FF)频率的PWM输出经滤波器以后的直流电压

  • Sigma-Delta方式的PWM产生的180HZ锯齿波

PWM用作DDS的DAC产生任意波形


对比R-2R和PWM方式产生的任意信号

我们用同一个正弦波表生成的数据来驱动R-2R DAC,和PWM,PWM后面跟的一阶RC滤波器截止频率设定为160KHz(R=1KΩ)、C=1000pF。

用R-2R和PWM生成的20KHz的波形对比,红色为R-2R的波形,紫色为PWM的波形,主频为96MHz

从上图可以看出,两种方式产生的波形在幅度上略有一点点的差异,主要是PWM后面的LPF在20KHz处有一点衰减。

用R-2R生成的20KHz正弦波信号的频谱,主频为96MHz

用PWM生成的20KHz正弦波信号的频谱,,主频为96MHz

从上面的两图可以看出,在DC-1MHz范围内,PWM生成的信号质量比R-2R的方式更好,杂波更少。

结论:使用FPGA的一根管脚,在其内部时钟达到100MHz左右的时候,可以完美产生20KHz以内的任意波形的信号,可非常便捷地产生音频信号。

采用FPGA和CPLD实现PWM的方法 ,这是一篇非常基础的文章,详细介绍了PWM的构成原理、构成方式、以及应用。