ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验

多路 PWM 原理分析以及实现试验

基本原理(参考 http͗//www͘fpga4fun͘com/PWM_D_C͘html)

PWM_(Pulse_Width_Modulation)

A PWM takes an input value of any width and creates an output that is just
one-bit wide.
PWM using a free-running counter

That’s the simplest PWM we can make.

module PWM( 
input clk, 
input [3:0] PWM_in, 
output PWM_out 
); 

reg [3:0] cnt; 
always @(posedge clk) cnt <= cnt + 1'b1;  // free-running 
counter 

assign PWM_out = (PWM_in > cnt);  // comparator 
endmodule 

We choose a 4bit PWM here so the PWM period is 16. The input
can go from 0 to 15 so the PWM output ratio goes from 0% to
15/16=93%. If you need to be able to go up to 100%, the input needs
to have an extra bit.

The code works fine, although it is a bit naive in its current form
because the input must be fixed (or change only when the counter
overflows = goes back to 0). Otherwise the output will glitch. So most
likely a bit of extra logic is required (usually in the form of a latch
capturing the input at the right time).

PWM using a loadable up-down counter

That’s a slightly more sophisticated design.

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 

First-order sigma-delta modulator

A first-order sigma-delta modulator resembles a PWM, but with a
better frequency response if you need to filter it because of its higher
frequency output content.

The simplest way to create a first-order sigma-delta modulator is
to use an hardware accumulator… every time the accumulator
overflows, output a ‘1’. Otherwise output a ‘0’. That’s very easily done
in an FPGA.

Verilog 

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 

One-bit DAC

Take one pin of an FPGA, connect a speaker and listen to an MP3?
Easy. Here, we’ll use a PC to decode an MP3, and then send the
decoded data to an FPGA that is configured as a one-bit DAC.

Audio output

We require a DAC (digital-to-analog converter) to connect the FPGA
(digital) to a speaker (analog).
The conventional approach would be to use a resistor ladder
(see here), or use a dedicated DAC IC, like the venerable DAC-08.

Since the frequency at which FPGAs run is so fast compared to the
frequencies required in the audio domain (MHZ’s compared to KHz’s),
a one-bit DAC is a better choice.

Basically, to create the analog output, we smooth out the PWM or
sigma-delta modulator output pulses using a low-pass filter. A
sigma-delta modulator is better because of its higher-frequency
output content, with which a single-order low-pass RC filter is usually
enough.

Playing an MP3

The first step is to decode the MP3. The decoded MP3 data is called
“PCM” data. To keep things simple, we send the PCM data through the
serial port of the PC to the FPGA. The maximum rate possible through
the serial port is 115200 baud (i.e. about 11.5 KBytes per second), so
the music has to be down-sampled to 11KHz 8 bits. All that is easily
done by a PC. Here’s the software used (with source code).
And for the HDL code, we simply modify the sigma-delta modulator so
that the PWM data input comes from the serial port.

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_d 
ata(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 

Now is time to connect a speaker to the FPGA. There are 3 basic ways
to do it.
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验

使用创建和封装 IP 向导创建自定义 IP

1.使用提供的 axi_lite 从属外设模板和自定义 IP 源代码来创建自定义 IP。

打开 Vivado 软件,单击 Manage IP,然后选择 New IP Location,然后在新
建 IP 位置窗口中单击 Next。
选择 Verilog 作为 Target Language,Mixed 作为 Simulator language,对于
IP 位置,请键入 D:/IP Core,然后单击 Finish(将其他设置保留为默认值,如果
提示创建目录,请单击确定)。
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验

2.运行创建和封装 IP向导 选择 Tools > Create and Package New IP…

ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
在下一个窗口中,
单击 Next
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
由于我们需要挂在到总线上,因此创建一个带
AXI 总线的用户 IP,故选择 Create a new AXI4 peripheral。 点击 Next.
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
设置
IP 的名字为 ADAU1761,版本号默认,并且记住 IP的位置 ,单击 Next.
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
设置总线形式为
Lite 总线, Lite 总线是简化的 AXI 总线消耗的资源少,当然性
能也是比完全版的 AXI 总线差一点,但是由于音频的速度并不高,因此采用 Lite 总线就够了,设置寄存器数量为 8,因为后面我们需要用到 8个寄存器。 单击
Next.
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
选择
edit IP 单击 Finish 完成
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
完成后的界面如下图所示
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验

3.用户 IP 的修改

IP创建完成后,并不能立马使用,还需要做一些修改。

  1. 打开PWM_IP_v1_0.v 文件在以下位置修改
    ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
    ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
  2. 打开PWM_IP_v1_0_S00_AXI.v 修改PWM_IP_v1_0_S00_AXI.v 的端口部分
    ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
  3. slv_reg0、slv_reg1、slv_reg2、slv_reg3、slv_reg4、slv_reg5、slv_reg6、slv_reg7 为PS 部分写入PL 的寄存器。通过这个8 个寄存器的值,我们可以控制PWM 的占空比。
    ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
  4. 下面这段代码就是PS 写PL 部分的寄存器,一共有8 个寄存器

always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 1'b0 )
begin
slv_reg0 <= 0;
slv_reg1 <= 0;
slv_reg2 <= 0;
slv_reg3 <= 0;
slv_reg4 <= 0;
slv_reg5 <= 0;
slv_reg6 <= 0;
slv_reg7 <= 0;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
3'h0:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h1:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 1
slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h2:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 2
slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h3:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 3
slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h4:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 4
slv_reg4[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h5:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 5
slv_reg5[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h6:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 6
slv_reg6[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
3'h7:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 7
slv_reg7[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
default : begin
slv_reg0 <= slv_reg0;
slv_reg1 <= slv_reg1;
slv_reg2 <= slv_reg2;
slv_reg3 <= slv_reg3;
slv_reg4 <= slv_reg4;
slv_reg5 <= slv_reg5;
slv_reg6 <= slv_reg6;
slv_reg7 <= slv_reg7;
end
endcase
end
end
end

5新建一个PWM_driver.v 文件实现8 路PWM 并行输出然后保存到PWM_IP_1.0/hdl 文件夹,并添加进来。
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验

PWM_driver.v具体内容为:
module PWM_driver(
input clk_i,
input rst_n_i,
input [31:0]pwm_reg0_i,
input [31:0]pwm_reg1_i,
input [31:0]pwm_reg2_i,
input [31:0]pwm_reg3_i,
input [31:0]pwm_reg4_i,
input [31:0]pwm_reg5_i,
input [31:0]pwm_reg6_i,
input [31:0]pwm_reg7_i,
output reg [7:0] pwm_o
);
reg[31:0]pwm_cnt0;
reg [31:0]pwm_cnt1;
reg [31:0]pwm_cnt2;
reg [31:0]pwm_cnt3;
reg [31:0]pwm_cnt4;
reg [31:0]pwm_cnt5;
reg [31:0]pwm_cnt6;
reg [31:0]pwm_cnt7;
//pwm0
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt0 <= 32'd0;
pwm_o[0] <= 1'b0;
end
else begin
if(pwm_cnt0<pwm_reg0_i)begin
pwm_cnt0 <= pwm_cnt0 +1'b1;
end
else begin
pwm_cnt0<=32'D0;
pwm_o[0]<=~pwm_o[0];
end
end
end
//pwm1
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt1 <= 32'd0;
pwm_o[1] <= 1'b0;
end
else begin
if(pwm_cnt1<pwm_reg1_i)begin
pwm_cnt1 <= pwm_cnt1 +1'b1;
end
else begin
pwm_cnt1<=32'D0;
pwm_o[1]<=~pwm_o[1];
end
end
end
//pwm2
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt2 <= 32'd0;
pwm_o[2] <= 1'b0;
end
else begin
if(pwm_cnt2<pwm_reg2_i)begin
pwm_cnt2 <= pwm_cnt2 +1'b1;
end
else begin
pwm_cnt2<=32'D0;
pwm_o[2]<=~pwm_o[2];
end
end
end
//pwm3
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt3 <= 32'd0;
pwm_o[3] <= 1'b0;
end
else begin
if(pwm_cnt3<pwm_reg3_i)begin
pwm_cnt3 <= pwm_cnt3 +1'b1;
end
else begin
pwm_cnt3<=32'D0;
pwm_o[3]<=~pwm_o[3];
end
end
end
//pwm4
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt4 <= 32'd0;
pwm_o[4] <= 1'b0;
end
else begin
if(pwm_cnt4<pwm_reg4_i)begin
pwm_cnt4 <= pwm_cnt4 +1'b1;
end
else begin
pwm_cnt4<=32'D0;
pwm_o[4]<=~pwm_o[4];
end
end
end
//pwm5
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt5 <= 32'd0;
pwm_o[5] <= 1'b0;
end
else begin
if(pwm_cnt5<pwm_reg5_i)begin
pwm_cnt5 <= pwm_cnt5 +1'b1;
end
else begin
pwm_cnt5<=32'D0;
pwm_o[5]<=~pwm_o[5];
end
end
end
//pwm5
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt6 <= 32'd0;
pwm_o[6] <= 1'b0;
end
else begin
if(pwm_cnt6<pwm_reg6_i)begin
pwm_cnt6 <= pwm_cnt6 +1'b1;
end
else begin
pwm_cnt6<=32'D0;
pwm_o[6]<=~pwm_o[6];
end
end
end
//pwm7
always @(posedge clk_i)begin
if(!rst_n_i)begin
pwm_cnt7 <= 32'd0;
pwm_o[7] <= 1'b0;
end
else begin
if(pwm_cnt7<pwm_reg7_i)begin
pwm_cnt7 <= pwm_cnt7 +1'b1;
end
else begin
pwm_cnt7<=32'D0;
pwm_o[7]<=~pwm_o[7];
end
end
end
endmodule

6.点击File–>点击 Save all files,最终如下
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
4.修改完成后还要重新打包
1 选择 tool–>Create and Package New Ip…
2. 选择 package your current project 选择 next
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
3.保持默认设置,不做任何修改,点击 Next
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
4.点击 Next 选择 Overwrite
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
5.点击 Finish,完成。
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
6.执行以下操作检查 IP是否封装完成,展开 IP XACT(1)>双击
component.xml,展开 Ports and Interface,可以看到封装 IP完成。
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
至此,创建用户
IP完成。
5添加 PWM_IP_v1_0 IP
重新建立一个新的空的工程。
Create Block Design 直接添加 zynq7 processing system。这个前面的教程内容部分已经重复很多次了,这里不重复。
1.在进程导航窗格中,单击 Project Setting选项,选择 IP,然后单击 Add Repository 按钮。 浏览窗口打开,浏览到 IP核的位置

D:/IP Core/ip_repo/PWM_IP_1.0 然后单击“ Select”,单击 Ok。
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
2.注意工具如何在目录中检测到新的 IP,点击 Apply,然后 OK。
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
3.这样做后,就可以将 PWM_IP_v1.0添加到当前项目的 IP库中,下一步是
将其添加到块设计中,并将其从一侧连接到 Zynq处理系统,并从另一侧通过使
用外部端口连接到 LED板块 。
4.由于前面的过程非常详细,很多步骤省略,搭建完成后的系统如下图所示
ZYNQ7000学习(十七)多路 PWM 原理分析以及实现试验
5.添加 管脚约束 文件,之后编译工程,导出到 SDK,这个步骤前面的教程中
也是重复过,不再过多重复,这里只给出 管脚约束 文件 PWM_pin.xdc

set_property PACKAGE_PIN T22 [get_ports {pwm_o[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[0]}]
set_property PACKAGE_PIN T21 [get_ports {pwm_o[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[1]}]
set_property PACKAGE_PIN U22 [get_ports {pwm_o[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[2]}]
set_property PACKAGE_PIN U21 [get_ports {pwm_o[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[3]}]
set_property PACKAGE_PIN V22 [get_ports {pwm_o[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[4]}]
set_property PACKAGE_PIN W22 [get_ports {pwm_o[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[5]}]
set_property PACKAGE_PIN U19 [get_ports {pwm_o[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[6]}]
set_property PACKAGE_PIN U14 [get_ports {pwm_o[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[7]}]

6.SDK 工程源码
SDK 工程部分的 C 工程新建工程也不详细讲解,前面已经重复很多次了,
这里只给出 C 代码略作分析。

#include "xparameters.h"
#include "stdio.h"
#include "xil_io.h"
#define PWM_REG0 XPAR_PWM_IP_V1_0_0_BASEADDR + 0
#define PWM_REG1 XPAR_PWM_IP_V1_0_0_BASEADDR + 4
#define PWM_REG2 XPAR_PWM_IP_V1_0_0_BASEADDR + 8
#define PWM_REG3 XPAR_PWM_IP_V1_0_0_BASEADDR + 12
#define PWM_REG4 XPAR_PWM_IP_V1_0_0_BASEADDR + 16
#define PWM_REG5 XPAR_PWM_IP_V1_0_0_BASEADDR + 20
#define PWM_REG6 XPAR_PWM_IP_V1_0_0_BASEADDR + 24
#define PWM_REG7 XPAR_PWM_IP_V1_0_0_BASEADDR + 28
int main()
{
Xil_Out32(PWM_REG0,100000000);
Xil_Out32(PWM_REG1,100000000/2);
Xil_Out32(PWM_REG2,100000000/4);
Xil_Out32(PWM_REG3,100000000/8);
Xil_Out32(PWM_REG4,100000000/10);
Xil_Out32(PWM_REG5,100000000/16);
Xil_Out32(PWM_REG6,100000000/20);
Xil_Out32(PWM_REG7,100000000/32);
return 0;
}

以上函数中
我们自定一点 PWM IP 寄存器中写 数据,实现了 100MHZ AXI4 总线的 2 分
频、 4分频、 8 分频、 10 分频、 16 分频、 20 分频、 32 分频。
测试完成,现象:
8个 灯 流水 亮起。

上一篇:使用 IntraWeb (37) - TIWApplication


下一篇:使用 IntraWeb (36) - TIWServerControllerBase