ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

时间:2023-03-09 23:12:39
ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

一、前言

  在实时性要求较高的场合中,CPU软件执行的方式显然不能满足需求,这时需要硬件逻辑实现部分功能。要想使自定义IP核被CPU访问,就必须带有总线接口。ZYNQ采用AXI BUS实现PS和PL之间的数据交互。本文以PWM为例设计了自定义AXI总线IP,来演示如何灵活运用ARM+FPGA的架构。

功能定义:在上一篇ZYNQ入门实例博文讲解的系统中添加自定义IP核,其输出驱动LED等实现呼吸灯效果。并且软件通过配置寄存器方式对其进行使能、打开/关闭配置以及选择占空比变化步长。另外,可以按键操作完成占空比变化步长的增减。

平台:米联客 MIZ702N (ZYNQ-7020)

软件:VIVADO+SDK 2017

注:自定义IP逻辑设计采用明德扬至简设计法

二、PWM IP设计

  PWM无非就是通过控制周期脉冲信号的占空比,也就是改变高电平在一段固定周期内的持续时间来达到控制目的。脉冲周期需要一个计数器来定时,占空比由低变高和由高变低两种模式同样需要一个计数器来指示,因此这里使用两个嵌套的计数器cnt_cyc和cnt_mode。cnt_mode的加一条件除了要等待cnt_cyc计数完成,还要考虑占空比的变化。

  我们可以使用下降沿位置表示占空比,位置越靠近周期值占空比越高。在模式0中下降沿位置按照步长增大直至大于等于周期值,模式1中下降沿位置则按照步长递减直到小于步长。使用两个信号up_stage和down_stage分别指示模式0和模式1。至于步长值,在配置有效时被更新,否则使用默认值。模块最终的输出信号在周期计数器小于下降沿位置为1,反之为零。设计完毕,上代码:

 `timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/03/01 18:14:44
// Design Name:
// Module Name: pwm
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////// module pwm(
input clk,//频率100MHz 10ns
input rst_n,
input sw_en,//输出使能
input sw_set_en,//步长设定使能
input [-:] sw_freq_step,//步长数值
output reg led
); parameter FREQ_STEP = 'd100; parameter CNT_CYC_MAX = ; function integer clogb2 (input integer bit_depth);
begin
for(clogb2=;bit_depth>;clogb2=clogb2+)
bit_depth = bit_depth >> ;
end
endfunction localparam CNT_CYC_WIDTH = clogb2(CNT_CYC_MAX-); reg [CNT_CYC_WIDTH-:] cnt_cyc=;
wire add_cnt_cyc,end_cnt_cyc;
reg [-:] cnt_mode=;
wire add_cnt_mode,end_cnt_mode;
wire up_stage,down_stage;
reg [CNT_CYC_WIDTH+-:] neg_loc=;
reg [-:] freq_step=FREQ_STEP; //周期计数器 计数50ms=50*1000ns = 50000_0ns
always@(posedge clk)begin
if(~rst_n)begin
cnt_cyc <= ;
end
else if(add_cnt_cyc)begin
if(end_cnt_cyc)
cnt_cyc <= ;
else
cnt_cyc <= cnt_cyc + 'b1;
end
end assign add_cnt_cyc = sw_en == ;
assign end_cnt_cyc = add_cnt_cyc && cnt_cyc == CNT_CYC_MAX- ; //模式计数器 0-占空比递增 1-占空比递减
always@(posedge clk)begin
if(~rst_n)begin
cnt_mode <= ;
end
else if(add_cnt_mode)begin
if(end_cnt_mode)
cnt_mode <= ;
else
cnt_mode <= cnt_mode + 'b1;
end
end assign add_cnt_mode = end_cnt_cyc && ((up_stage && neg_loc >= CNT_CYC_MAX) || (down_stage && neg_loc == ));
assign end_cnt_mode = add_cnt_mode && cnt_mode == - ; //变化步长设定
always@(posedge clk)begin
if(~rst_n)begin
freq_step <= FREQ_STEP;
end
else if(sw_set_en)begin
if(sw_freq_step >= && sw_freq_step < )
freq_step <= sw_freq_step;
else
freq_step <= FREQ_STEP;
end
end //脉冲下降沿对应周期计数器数值
always@(posedge clk)begin
if(~rst_n)begin
neg_loc <= ;
end
else if(end_cnt_cyc)begin
if(up_stage )begin//占空比递增阶段
if(neg_loc < CNT_CYC_MAX)
neg_loc <= neg_loc + freq_step;
end
else if(down_stage )begin//占空比递减阶段
if(neg_loc < freq_step)
neg_loc <= ;
else
neg_loc <= neg_loc - freq_step;
end
end end assign up_stage = add_cnt_cyc && cnt_mode == ;
assign down_stage = add_cnt_cyc && cnt_mode == ; //输出
always@(posedge clk)begin
if(~rst_n)begin
led <= 'b0;//高电平点亮
end
else if(add_cnt_cyc && cnt_cyc < neg_loc)begin
led <= 'b1;
end
else
led <= 'b0;
end endmodule

pwm.v

  VIVADO综合、布局布线比较慢,且软硬件级联调试费时费力,所以仿真是极其重要的。编写一个简单的testbench,定义update_freq_step task更新步长。这里使用System Verilog语法有一定的好处。首先单驱动信号可以统一定义为logic变量类型,其次等待时长能指定单位。

 `timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/03/01 20:49:25
// Design Name:
// Module Name: testbench
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
////////////////////////////////////////////////////////////////////////////////// module testbench(); logic clk,rst_n;
logic sw_en,sw_set_en;
logic [-:]sw_freq_step;
logic led; parameter CYC = ,
RST_TIM = ; defparam dut.CNT_CYC_MAX = ; pwm#(.FREQ_STEP())
dut(
.clk (clk) ,//频率100MHz 10ns
.rst_n (rst_n) ,
.sw_en (sw_en) ,//输出使能
.sw_set_en (sw_set_en) ,//步长设定使能
.sw_freq_step (sw_freq_step) ,//步长数值
.led (led)
); initial begin
clk = ;
forever begin
#(CYC/2.0);
clk=~clk;
end
end initial begin
rst_n = ;
#;
rst_n = ;
#(RST_TIM*CYC) rst_n = ;
end initial begin
sw_en = ;
sw_set_en = ;
sw_freq_step = 'd10;
#;
#(RST_TIM*CYC);
#(CYC*);
sw_en = ; #600us;
update_freq_step();
#600us;
$stop; end task update_freq_step([-:] freq_step);
sw_set_en = ;
sw_freq_step = freq_step;
#(*CYC);
sw_set_en = ;
endtask endmodule

testbench.sv

  设计较简单,直接使用VIVADO仿真器观察波形即可:

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  可以看到输出信号led的占空比在不断起伏变化,当更新freq_step为50后变化更为减慢。

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  配置前相邻两个neg_loc数值差与更新后分别是100和50。以上证明逻辑功能无误。

三、硬件系统搭建

  设计完PWM功能模块还没有完,需要再包一层总线Wrapper才能被CPU访问。创建AXI总线IP

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  在封装器中编辑。

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  最终IP结构如图:

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  具体操作不过多讲述,直接以代码呈现:

 `timescale  ns /  ps

     module pwm_led_ip_v1_0 #
(
// Users to add parameters here
parameter FREQ_STEP = 'd100,
// User parameters ends
// Do not modify the parameters beyond this line // Parameters of Axi Slave Bus Interface S00_AXI
parameter integer C_S00_AXI_DATA_WIDTH = ,
parameter integer C_S00_AXI_ADDR_WIDTH =
)
(
// Users to add ports here
output led,
// User ports ends
// Do not modify the ports beyond this line // Ports of Axi Slave Bus Interface S00_AXI
input wire s00_axi_aclk,
input wire s00_axi_aresetn,
input wire [C_S00_AXI_ADDR_WIDTH- : ] s00_axi_awaddr,
input wire [ : ] s00_axi_awprot,
input wire s00_axi_awvalid,
output wire s00_axi_awready,
input wire [C_S00_AXI_DATA_WIDTH- : ] s00_axi_wdata,
input wire [(C_S00_AXI_DATA_WIDTH/)- : ] s00_axi_wstrb,
input wire s00_axi_wvalid,
output wire s00_axi_wready,
output wire [ : ] s00_axi_bresp,
output wire s00_axi_bvalid,
input wire s00_axi_bready,
input wire [C_S00_AXI_ADDR_WIDTH- : ] s00_axi_araddr,
input wire [ : ] s00_axi_arprot,
input wire s00_axi_arvalid,
output wire s00_axi_arready,
output wire [C_S00_AXI_DATA_WIDTH- : ] s00_axi_rdata,
output wire [ : ] s00_axi_rresp,
output wire s00_axi_rvalid,
input wire s00_axi_rready
);
// Instantiation of Axi Bus Interface S00_AXI
pwd_led_ip_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH),
.FREQ_STEP(FREQ_STEP)
) pwd_led_ip_v1_0_S00_AXI_inst (
.S_AXI_ACLK(s00_axi_aclk),
.S_AXI_ARESETN(s00_axi_aresetn),
.S_AXI_AWADDR(s00_axi_awaddr),
.S_AXI_AWPROT(s00_axi_awprot),
.S_AXI_AWVALID(s00_axi_awvalid),
.S_AXI_AWREADY(s00_axi_awready),
.S_AXI_WDATA(s00_axi_wdata),
.S_AXI_WSTRB(s00_axi_wstrb),
.S_AXI_WVALID(s00_axi_wvalid),
.S_AXI_WREADY(s00_axi_wready),
.S_AXI_BRESP(s00_axi_bresp),
.S_AXI_BVALID(s00_axi_bvalid),
.S_AXI_BREADY(s00_axi_bready),
.S_AXI_ARADDR(s00_axi_araddr),
.S_AXI_ARPROT(s00_axi_arprot),
.S_AXI_ARVALID(s00_axi_arvalid),
.S_AXI_ARREADY(s00_axi_arready),
.S_AXI_RDATA(s00_axi_rdata),
.S_AXI_RRESP(s00_axi_rresp),
.S_AXI_RVALID(s00_axi_rvalid),
.S_AXI_RREADY(s00_axi_rready), .led(led)
); // Add user logic here // User logic ends endmodule

pwm_led_ip_v1_0.v

 `timescale  ns /  ps

     module pwm_led_ip_v1_0_S00_AXI #
(
// Users to add parameters here
parameter FREQ_STEP = 'd100,
// User parameters ends
// Do not modify the parameters beyond this line // Width of S_AXI data bus
parameter integer C_S_AXI_DATA_WIDTH = ,
// Width of S_AXI address bus
parameter integer C_S_AXI_ADDR_WIDTH =
)
(
// Users to add ports here
output led,
// User ports ends
// Do not modify the ports beyond this line // Global Clock Signal
input wire S_AXI_ACLK,
// Global Reset Signal. This Signal is Active LOW
input wire S_AXI_ARESETN,
// Write address (issued by master, acceped by Slave)
input wire [C_S_AXI_ADDR_WIDTH- : ] S_AXI_AWADDR,
// Write channel Protection type. This signal indicates the
// privilege and security level of the transaction, and whether
// the transaction is a data access or an instruction access.
input wire [ : ] S_AXI_AWPROT,
// Write address valid. This signal indicates that the master signaling
// valid write address and control information.
input wire S_AXI_AWVALID,
// Write address ready. This signal indicates that the slave is ready
// to accept an address and associated control signals.
output wire S_AXI_AWREADY,
// Write data (issued by master, acceped by Slave)
input wire [C_S_AXI_DATA_WIDTH- : ] S_AXI_WDATA,
// Write strobes. This signal indicates which byte lanes hold
// valid data. There is one write strobe bit for each eight
// bits of the write data bus.
input wire [(C_S_AXI_DATA_WIDTH/)- : ] S_AXI_WSTRB,
// Write valid. This signal indicates that valid write
// data and strobes are available.
input wire S_AXI_WVALID,
// Write ready. This signal indicates that the slave
// can accept the write data.
output wire S_AXI_WREADY,
// Write response. This signal indicates the status
// of the write transaction.
output wire [ : ] S_AXI_BRESP,
// Write response valid. This signal indicates that the channel
// is signaling a valid write response.
output wire S_AXI_BVALID,
// Response ready. This signal indicates that the master
// can accept a write response.
input wire S_AXI_BREADY,
// Read address (issued by master, acceped by Slave)
input wire [C_S_AXI_ADDR_WIDTH- : ] S_AXI_ARADDR,
// Protection type. This signal indicates the privilege
// and security level of the transaction, and whether the
// transaction is a data access or an instruction access.
input wire [ : ] S_AXI_ARPROT,
// Read address valid. This signal indicates that the channel
// is signaling valid read address and control information.
input wire S_AXI_ARVALID,
// Read address ready. This signal indicates that the slave is
// ready to accept an address and associated control signals.
output wire S_AXI_ARREADY,
// Read data (issued by slave)
output wire [C_S_AXI_DATA_WIDTH- : ] S_AXI_RDATA,
// Read response. This signal indicates the status of the
// read transfer.
output wire [ : ] S_AXI_RRESP,
// Read valid. This signal indicates that the channel is
// signaling the required read data.
output wire S_AXI_RVALID,
// Read ready. This signal indicates that the master can
// accept the read data and response information.
input wire S_AXI_RREADY
); // AXI4LITE signals
reg [C_S_AXI_ADDR_WIDTH- : ] axi_awaddr;
reg axi_awready;
reg axi_wready;
reg [ : ] axi_bresp;
reg axi_bvalid;
reg [C_S_AXI_ADDR_WIDTH- : ] axi_araddr;
reg axi_arready;
reg [C_S_AXI_DATA_WIDTH- : ] axi_rdata;
reg [ : ] axi_rresp;
reg axi_rvalid; // Example-specific design signals
// local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH
// ADDR_LSB is used for addressing 32/64 bit registers/memories
// ADDR_LSB = 2 for 32 bits (n downto 2)
// ADDR_LSB = 3 for 64 bits (n downto 3)
localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/) + ;
localparam integer OPT_MEM_ADDR_BITS = ;
//----------------------------------------------
//-- Signals for user logic register space example
//------------------------------------------------
//-- Number of Slave Registers 4
reg [C_S_AXI_DATA_WIDTH-:] slv_reg0;
reg [C_S_AXI_DATA_WIDTH-:] slv_reg1;
reg [C_S_AXI_DATA_WIDTH-:] slv_reg2;
reg [C_S_AXI_DATA_WIDTH-:] slv_reg3;
wire slv_reg_rden;
wire slv_reg_wren;
reg [C_S_AXI_DATA_WIDTH-:] reg_data_out;
integer byte_index;
reg aw_en; // I/O Connections assignments assign S_AXI_AWREADY = axi_awready;
assign S_AXI_WREADY = axi_wready;
assign S_AXI_BRESP = axi_bresp;
assign S_AXI_BVALID = axi_bvalid;
assign S_AXI_ARREADY = axi_arready;
assign S_AXI_RDATA = axi_rdata;
assign S_AXI_RRESP = axi_rresp;
assign S_AXI_RVALID = axi_rvalid;
// Implement axi_awready generation
// axi_awready is asserted for one S_AXI_ACLK clock cycle when both
// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is
// de-asserted when reset is low. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_awready <= 'b0;
aw_en <= 'b1;
end
else
begin
if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
begin
// slave is ready to accept write address when
// there is a valid write address and write data
// on the write address and data bus. This design
// expects no outstanding transactions.
axi_awready <= 'b1;
aw_en <= 'b0;
end
else if (S_AXI_BREADY && axi_bvalid)
begin
aw_en <= 'b1;
axi_awready <= 'b0;
end
else
begin
axi_awready <= 'b0;
end
end
end // Implement axi_awaddr latching
// This process is used to latch the address when both
// S_AXI_AWVALID and S_AXI_WVALID are valid. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_awaddr <= ;
end
else
begin
if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)
begin
// Write Address latching
axi_awaddr <= S_AXI_AWADDR;
end
end
end // Implement axi_wready generation
// axi_wready is asserted for one S_AXI_ACLK clock cycle when both
// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is
// de-asserted when reset is low. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_wready <= 'b0;
end
else
begin
if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )
begin
// slave is ready to accept write data when
// there is a valid write address and write data
// on the write address and data bus. This design
// expects no outstanding transactions.
axi_wready <= 'b1;
end
else
begin
axi_wready <= 'b0;
end
end
end // Implement memory mapped register select and write logic generation
// The write data is accepted and written to memory mapped registers when
// axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to
// select byte enables of slave registers while writing.
// These registers are cleared when reset (active low) is applied.
// Slave register write enable is asserted when valid address and data are available
// and the slave is ready to accept the write address and write data.
assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID; always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
slv_reg0 <= ;
slv_reg1 <= ;
slv_reg2 <= ;
slv_reg3 <= ;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
'h0:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
'h1:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 1
slv_reg1[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
'h2:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 2
slv_reg2[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
'h3:
for ( byte_index = ; byte_index <= (C_S_AXI_DATA_WIDTH/)-; byte_index = byte_index+ )
if ( S_AXI_WSTRB[byte_index] == ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 3
slv_reg3[(byte_index*) +: ] <= S_AXI_WDATA[(byte_index*) +: ];
end
default : begin
slv_reg0 <= slv_reg0;
slv_reg1 <= slv_reg1;
slv_reg2 <= slv_reg2;
slv_reg3 <= slv_reg3;
end
endcase
end
end
end // Implement write response logic generation
// The write response and response valid signals are asserted by the slave
// when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted.
// This marks the acceptance of address and indicates the status of
// write transaction. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_bvalid <= ;
axi_bresp <= 'b0;
end
else
begin
if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)
begin
// indicates a valid write response is available
axi_bvalid <= 'b1;
axi_bresp <= 'b0; // 'OKAY' response
end // work error responses in future
else
begin
if (S_AXI_BREADY && axi_bvalid)
//check if bready is asserted while bvalid is high)
//(there is a possibility that bready is always asserted high)
begin
axi_bvalid <= 'b0;
end
end
end
end // Implement axi_arready generation
// axi_arready is asserted for one S_AXI_ACLK clock cycle when
// S_AXI_ARVALID is asserted. axi_awready is
// de-asserted when reset (active low) is asserted.
// The read address is also latched when S_AXI_ARVALID is
// asserted. axi_araddr is reset to zero on reset assertion. always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_arready <= 'b0;
axi_araddr <= 'b0;
end
else
begin
if (~axi_arready && S_AXI_ARVALID)
begin
// indicates that the slave has acceped the valid read address
axi_arready <= 'b1;
// Read address latching
axi_araddr <= S_AXI_ARADDR;
end
else
begin
axi_arready <= 'b0;
end
end
end // Implement axi_arvalid generation
// axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both
// S_AXI_ARVALID and axi_arready are asserted. The slave registers
// data are available on the axi_rdata bus at this instance. The
// assertion of axi_rvalid marks the validity of read data on the
// bus and axi_rresp indicates the status of read transaction.axi_rvalid
// is deasserted on reset (active low). axi_rresp and axi_rdata are
// cleared to zero on reset (active low).
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_rvalid <= ;
axi_rresp <= ;
end
else
begin
if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)
begin
// Valid read data is available at the read data bus
axi_rvalid <= 'b1;
axi_rresp <= 'b0; // 'OKAY' response
end
else if (axi_rvalid && S_AXI_RREADY)
begin
// Read data is accepted by the master
axi_rvalid <= 'b0;
end
end
end // Implement memory mapped register select and read logic generation
// Slave register read enable is asserted when valid address is available
// and the slave is ready to accept the read address.
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
always @(*)
begin
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
'h0 : reg_data_out <= slv_reg0;
'h1 : reg_data_out <= slv_reg1;
'h2 : reg_data_out <= slv_reg2;
'h3 : reg_data_out <= slv_reg3;
default : reg_data_out <= ;
endcase
end // Output register or memory read data
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 )
begin
axi_rdata <= ;
end
else
begin
// When there is a valid read address (S_AXI_ARVALID) with
// acceptance of read address by the slave (axi_arready),
// output the read dada
if (slv_reg_rden)
begin
axi_rdata <= reg_data_out; // register read data
end
end
end // Add user logic here
pwm#(.FREQ_STEP(FREQ_STEP))
u_pwm(
.clk (S_AXI_ACLK),
.rst_n (S_AXI_ARESETN),
.sw_en (slv_reg0[]),
.sw_set_en (slv_reg1[]),
.sw_freq_step (slv_reg2[-:]),
.led (led)
);
// User logic ends endmodule

pwm_led_ip_v1_0_S00_AXI.v

  最后重新封装

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  接下来搭建硬件IP子系统。

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  和之前相比只是添加了pwm_led_ip_0,并连接在AXI Interconnect的另一个Master接口上。使用SystemILA抓取总线信号以备后续观察。还是同样的操作流程:生成输出文件,生成HDL Wrapper,添加管脚约束文件,综合,实现,生成比特流并导出硬件,启动SDK软件环境。

四、软件编程与调试

  其实CPU控制自定义IP的方式就是读写数据,写就是对指针赋值,读就是返回指针所指向地址中的数据,分别使用Xil_Out32()和Xil_In32()实现。创建pwm_led_ip.h文件,进行地址宏定义并编写配置函数。为了更好地实现软件库的封装和扩展,创建environment.h文件来include不同的库以及宏定义、全局变量定义。

  软件代码如下:

 /*
* main.c
*
* Created on: 2020年2月22日
* Author: s
*/ #include "environment.h" void GpioHandler(void *CallbackRef);
int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr
,u32 IntrId); int main()
{
int Status;
u8 i=;
u32 sys_led_out=0x1;
u32 data_r;
freq_step_value = ; Status = gpiops_initialize(&GpioPs,GPIOPS_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
} Status = gpio_initialize(&Gpio,GPIO_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
} /*
* Set the direction for the pin to be output and
* Enable the Output enable for the LED Pin.
*/
gpiops_setOutput(&GpioPs,MIO_OUT_PIN_INDEX); for(i=;i<LOOP_NUM;i++){
gpiops_setOutput(&GpioPs,EMIO_OUT_PIN_BASE_INDEX+i);
} gpio_setDirect(&Gpio, ,GPIO_CHANNEL1); Status = setupIntSystem(&Intc,&Gpio,INTC_GPIO_INTERRUPT_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
} Status = pwm_led_setFreqStep(freq_step_value);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
} printf("Initialization finish.\n"); while(){ for(i=;i<LOOP_NUM;i++){
if(int_flag == )
{
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x1);
usleep(*);
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x0);
}
else
{
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM--i, 0x1);
usleep(*);
gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM--i, 0x0);
}
} gpiops_outputValue(&GpioPs, MIO_OUT_PIN_INDEX, sys_led_out);
sys_led_out = sys_led_out == 0x0 ? 0x1 : 0x0;
}
return ;
} int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr
,u32 IntrId)
{
int Result;
/*
* Initialize the interrupt controller driver so that it is ready to
* use.
*/ Result = gic_initialize(&Intc,INTC_DEVICE_ID);
if (Result != XST_SUCCESS) {
return XST_FAILURE;
} XScuGic_SetPriorityTriggerType(IntcInstancePtr, IntrId,
0xA0, 0x3); /*
* Connect the interrupt handler that will be called when an
* interrupt occurs for the device.
*/
Result = XScuGic_Connect(IntcInstancePtr, IntrId,
(Xil_ExceptionHandler)GpioHandler, gpioInstancePtr);
if (Result != XST_SUCCESS) {
return Result;
} /* Enable the interrupt for the GPIO device.*/
XScuGic_Enable(IntcInstancePtr, IntrId); /*
* Enable the GPIO channel interrupts so that push button can be
* detected and enable interrupts for the GPIO device
*/
XGpio_InterruptEnable(gpioInstancePtr,GPIO_CHANNEL1);
XGpio_InterruptGlobalEnable(gpioInstancePtr); /*
* Initialize the exception table and register the interrupt
* controller handler with the exception table
*/
exception_enable(&Intc); IntrFlag = ; return XST_SUCCESS;
} void GpioHandler(void *CallbackRef)
{
XGpio *GpioPtr = (XGpio *)CallbackRef;
u32 gpio_inputValue; /* Clear the Interrupt */
XGpio_InterruptClear(GpioPtr, GPIO_CHANNEL1);
printf("Input interrupt routine.\n"); //IntrFlag = 1;
gpio_inputValue = gpio_readValue(GpioPtr, );
switch(gpio_inputValue)
{
case :
//printf("button up\n");
freq_step_value+=;
pwm_led_setFreqStep(freq_step_value);
break;
case :
printf("button center\n");
break;
case :
//printf("button left\n");
int_flag = ;
break;
case :
//printf("button right\n");
int_flag = ;
break;
case :
//print("button down\n");
freq_step_value-=;
pwm_led_setFreqStep(freq_step_value);
break;
} }

main.c

 /*
* environment.h
*
* Created on: 2020年3月2日
* Author: s
*/ #ifndef SRC_ENVIRONMENT_H_
#define SRC_ENVIRONMENT_H_ #include "xparameters.h"
#include <xil_printf.h>
#include "sleep.h"
#include "xstatus.h" #include "gpiops.h"
#include "gpio.h"
#include "pwm_led_ip.h"
#include "gic.h" XGpioPs GpioPs; /* The driver instance for GPIO Device. */
XGpio Gpio;
XScuGic Intc; /* The Instance of the Interrupt Controller Driver */ #define printf xil_printf /* Smalller foot-print printf */
#define LOOP_NUM 4 u32 MIO_OUT_PIN_INDEX =; /* LED button */
u32 EMIO_OUT_PIN_BASE_INDEX = ;
volatile u32 IntrFlag; /* Interrupt Handler Flag */ #endif /* SRC_ENVIRONMENT_H_ */

environment.h

 /*
* pwm_led_ip.h
*
* Created on: 2020年3月2日
* Author: s
*/ #ifndef SRC_PWM_LED_IP_H_
#define SRC_PWM_LED_IP_H_ #define PWM_LED_IP_S00_AXI_SLV_REG0_OFFSET 0
#define PWM_LED_IP_S00_AXI_SLV_REG1_OFFSET 4
#define PWM_LED_IP_S00_AXI_SLV_REG2_OFFSET 8
#define PWM_LED_IP_S00_AXI_SLV_REG3_OFFSET 12 #define PWM_LED_IP_BASEADDR XPAR_PWM_LED_IP_0_S00_AXI_BASEADDR
#define FREQ_STEP_SET_VALUE 30 #define PWM_LED_IP_REG_EN (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG0_OFFSET)
#define PWM_LED_IP_REG_SET_EN (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG1_OFFSET)
#define PWM_LED_IP_REG_FREQ_STEP (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG2_OFFSET)
#define PWM_LED_IP_REG_RESERVED (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG3_OFFSET) volatile u32 freq_step_value; int pwm_led_setFreqStep(u32 value)
{ u32 data_r;
Xil_Out32(PWM_LED_IP_REG_EN,0x01);
Xil_Out32(PWM_LED_IP_REG_SET_EN,0x01);
Xil_Out32(PWM_LED_IP_REG_FREQ_STEP,value);
data_r = Xil_In32(PWM_LED_IP_REG_FREQ_STEP);
Xil_Out32(PWM_LED_IP_REG_SET_EN,0x00);
if(data_r == value)
return XST_SUCCESS;
else
return XST_FAILURE; } #endif /* SRC_PWM_LED_IP_H_ */

pwm_led_ip.h

  其他文件与上一篇ZYNQ入门实例博文相同。Run程序后多次按下按键,从串口terminal可以看出系统初始化成功,进入按键中断回调函数。开发板上呼吸灯频率也随着按键按下在变化。

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  最后打开VIVADO硬件管理器,观察AXI总线波形:ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  按下步长值增加按键后,会有四次写数据操作,正好对应pwm_led_setFreqStep function中的四次Xil_Out32调用。每次写后一个时钟周期写响应通道BVALID拉高一个时钟周期证明写正确。

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  再来观察用于确认写入无误的读操作对应总线波形:

ZYNQ自定义AXI总线IP应用——PWM实现呼吸灯效果

  读取数据为40,与写入一致。到此功能定义、设计规划、硬件逻辑设计仿真、IP封装、子系统搭建、软件设计、板级调试的流程全部走完。