文章目录
- 自定义IP核——呼吸灯实验
- 固化程序
自定义IP核——呼吸灯实验
Xilinx官方提供了很多IP核,在Vivado的IP Catalog中可以查看这些IP核,在构建自己复杂的系统时,只使用Xilinx官方的免费IP核一般满足不了设计的要求,因此很多时候需要用户自定义IP核。自定义IP核有很多好处,比如可以定制系统设计和复用,可以在IP核中加入license有偿提供给别人使用,简化系统设计等。用ZYNQ系统设计IP核,最常用的就是使用AXI总线将PS同PL部分的IP核连接起来。
本实验将通过自定义一个LED IP核来控制PL LED呈现呼吸灯的效果,并且PS端可以通过AXI接口来控制呼吸灯的开关和呼吸的频率。
在创建工程之前先创建一个自定义IP核的存放位置。
选择创建AXI4接口。
可以自定义一个IP核的名称。
这里保持默认设置即可。
信息汇总界面,点击Finish即可完成创建。
创建完成后就可以在IP库中看到自定义的AXI接口了。
在该条目上右键选择Edit in IP Packager,选择默认,点击OK后就打开了下图所示的新界面。
添加一个呼吸灯源文件,位置就存放在自定义IP核的目录下。
在新建的源文件中添加下面的Verilog代码,其作用是实现呼吸灯。
`timescale 1ns / 1ps
module breath_led(
input sys_clk, //时钟信号
input sys_rst, //复位信号
input sw_ctrl, //呼吸灯开关控制信号
input set_en, //设置呼吸灯频率设置使能信号
input [9:0] set_freq_step, //设置呼吸灯频率变化步长
output led
);
parameter START_FREQ_STEP = 10'd100; //设置频率步长初始值
reg [15:0] period_cnt; //周期计数器
reg [9:0] freq_step; //呼吸灯频率间隔步长
reg [15:0] duty_cycle; //设置高电平占空比的计数点
reg inc_dec_flag; //用于表示高电平占空比的计数值,1-递减,0-递增
wire led_t;
assign led_t = (period_cnt <= duty_cycle)?1'b1:1'b0;
//assign led = led_t & sw_ctrl; //LED是高电平点亮
assign led = ~(led_t & sw_ctrl); //LED是低电平点亮
always@(posedge sys_clk) begin
if(!sys_rst || !sw_ctrl || period_cnt == 16'd50000) //周期信号计数器在0-50000之间
period_cnt <= 16'd0;
else
period_cnt <= period_cnt + 16'd1;
end
always@(posedge sys_clk) begin //设置频率间隔,在1-1000之间
if(!sys_rst)
freq_step <= START_FREQ_STEP;
else if(set_en) begin
if(set_freq_step == 0)
freq_step <= 10'd1;
else if(set_freq_step > 10'd1000)
freq_step <= 10'd1000;
else
freq_step <= set_freq_step;
end
end
always@(posedge sys_clk) begin //设定高电平占空比的计数值
if(sys_rst == 1'b0 || !sw_ctrl) begin
duty_cycle <= 16'd0;
inc_dec_flag <= 1'b0;
end
else if(period_cnt == 16'd50000) begin //计数满一个周期就调节占空比
if(inc_dec_flag) begin //占空比递减
if(duty_cycle == 16'd0)
inc_dec_flag <= 1'b0; //减至0开始递增
else if(duty_cycle < freq_step)
duty_cycle <= 16'd0;
else
duty_cycle <= duty_cycle - freq_step;
end
else begin
if(duty_cycle >= 16'd50000)
inc_dec_flag <= 1'b1; //加到最大开始递减
else
duty_cycle <= duty_cycle + freq_step;
end
end
else
duty_cycle <= duty_cycle; //计数不满一个周期时,占空比保持不变
end
endmodule
在顶层和第二层的.v文件中都添加下面的内容。
在顶层文件中例化AXI总线接口处添加下面的代码。
在第二层.v文件的endmodule之前添加下面的例化代码。
// Add user logic here
breath_led #(
.START_FREQ_STEP(START_FREQ_STEP)
)
u_breath_led(
.sys_clk(S_AXI_ACLK),
.sys_rst(S_AXI_ARESETN),
.sw_ctrl(slv_reg0[0]), //reg0的最低位是开关
.set_en(slv_reg1[31]), //reg1的最高位是设置频率步长使能
.set_freq_step(slv_reg1[9:0]), //reg1的低10位是设置频率步长
.led(led)
);
代码添加完成之后跑一下综合,没有问题就可以进行后续操作了。
打开IP核封装界面。
在兼容性这一项中添加ZYNQ芯片族。
对自定义的参数进行设置,使其可见。
重新封装自定义IP,然后关闭该工程。
接下来创建一个呼吸灯的工程,步骤和之前的类似。
工程创建完毕之后,将自定义的IP核添加进来。
添加成功后就可以在IP库中看到自定义的IP核了。
随后点击Create Block Design进入IP核配置界面。
添加ZYNQ IP核,添加UART口并配置DDR,添加自定义的呼吸灯IP核,双击后可以修改其开始频率步长。
两个IP核创建成功之后让其自动连接,同时在led口上右击选择Make External添加端口。
验证框图没有问题后就可以生成输出产品和HDL封装,给led端口分配管脚,完成后就可以生成比特流文件了。导出硬件,然后启动SDK进行代码编程。
在xparameters.h文件中找到自定义IP核的相关定义如下。
#define XPAR_BREATH_LED_IP_0_DEVICE_ID 0
#define XPAR_BREATH_LED_IP_0_S0_AXI_BASEADDR 0x43C00000
#define XPAR_BREATH_LED_IP_0_S0_AXI_HIGHADDR 0x43C0FFFF
在BSP目录下生成了自定义IP核相关的C代码,在编写程序的时候可以参考。
最终SDK端编写的C代码如下。
#include "stdio.h"
#include "xparameters.h"
#include "breath_led_ip.h"
#include "xil_io.h"
#include "sleep.h"
#define LED_IP_BASEADDR XPAR_BREATH_LED_IP_0_S0_AXI_BASEADDR
#define LED_IP_REG0 BREATH_LED_IP_S0_AXI_SLV_REG0_OFFSET
#define LED_IP_REG1 BREATH_LED_IP_S0_AXI_SLV_REG1_OFFSET
int main()
{
printf("breath_led test!\n");
int set_freq_flag = 0;
int led_state;
while(1)
{
if(set_freq_flag == 0)
{
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, LED_IP_REG1, 0x800000ff); //最高位要使能,设置频率步长为255
set_freq_flag = 1;
}
else
{
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, LED_IP_REG1, 0x8000001f); //设置频率步长为31
set_freq_flag = 0;
}
led_state = BREATH_LED_IP_mReadReg(LED_IP_BASEADDR, LED_IP_REG0);
if(led_state == 0)
{
//打开呼吸灯
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, LED_IP_REG0, 1);
printf("Breath led on!\n");
}
sleep(5);
led_state = BREATH_LED_IP_mReadReg(LED_IP_BASEADDR, LED_IP_REG0);
if(led_state == 1)
{
//关闭呼吸灯![请添加图片描述](https://img-blog.csdnimg.cn/direct/00281d89d6de4ad5a24be9e407bfeaf7.gif)
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR, LED_IP_REG0, 0);
printf("Breath led off!\n");
}
sleep(1);
}
return 0;
}
通过给相应的寄存器写值设置频率步长或者打开关闭呼吸灯。最终开发板上的验证效果如下动图所示。
在SDK终端窗口中也交替打印了程序中设置的语句,如下图所示。
如果自定义的IP核需要修改Verilog代码,需要在框图上右击选择Edit in IP Packager,然后找到相应的位置修改,完成后需要在框图界面重新加载,将代码中所做的更新同步到框图中,然后重新生成产品和比特流文件,导出到硬件,然后启动SDK编程。
固化程序
固化程序的例子以呼吸灯实验为例,拷贝该项目,然后打开拷贝的项目进行操作。为了让开发板支持从SD卡启动,也支持从Flash启动,需要先对IP核进行相关配置。
SD卡在原理图中的连接如下图所示。
对照原理图勾选Flash和SD卡外设。
依照原理图,Bank0设置为3.3V,Bank1设置为1.8V。
配置完成后生成输出产品,然后生成比特流文件,导出到硬件,启动SDK,将源文件添加进去,在bsp文件目录上右击进行板级支持包设置,勾选赛灵斯文件系统,点击OK。
创建一个新工程,命名为FSBL(First Stage Bootloader,第一阶段引导加载程序),如下图所示。
点击下一步,选择ZYNQ模板工程。
FSBL文件目录下包含了很多文件,这里保持默认即可,然后选中工程文件名,点击菜单栏Xilinx,点击Create Boot Image,生成文件BOOT.bin,该文件是由FSBL、比特流、elf三个文件合成的。
在工程中的bootimage文件夹中就生成了BOOT.bin文件,将其拷贝到SD卡中。
断开开发板的JTAG线,UART线也断开,只给开发板连接电源线即可,将SD卡插入到开发板的卡槽中,然后启动开发板,开发板上电后,LED就已经开始以呼吸灯的状态工作了,开发板上运行结果和上面一致,这就说明程序固化成功了。
如果要固化到Flash中,需要给开发板连接JTAG线并上电,然后点击菜单栏Xilinx—>Program Flash,按照下图进行配置。
如果在烧写时碰到下面的错误,可能是你开发板的模式设置不正确,调整开发板上的跳帽到JTAG模式。
中断打印下面的语句就下载成功了!
然后关闭电源,将跳帽改到QSPI启动,但是这个时候又出现问题了,程序并没有像预想的那样在开发板上执行。
划重点,这里我鼓捣了半天才整明白!
FSBL文件夹下main.c一改动就会自动编译生成FSBL.elf文件,而这个文件是生成BOOT.bin的文件之一,与此同时,这个文件也是烧写Flash时需要用到的文件。把程序烧写进Flash时需要使用JTAG接口,因此需要在FSBL文件夹下的main.c文件中的第386行添加下面的代码。
BootModeRegister = JTAG_MODE;
添加完成后保存,就会自动生成FSBL.elf文件。
然后使用把这个FSBL.elf文件和前面生成的BOOT.bin通过JTAG接口烧写到Flash中,然后断电,改变跳帽位置,就可以从Flash启动了。这里重点要强调的是,生成BOOT.bin文件时,用的FSBL.elf文件不需要添加那一行代码,而通过JTAG接口烧写到Flash时的FSBL.elf文件则必须要添加那一行代码。
烧写成功之后,改变跳帽到QSPI,开发板只连接电源线,上电之后的结果如下动图所示。
这就说明开发板从Flash启动成功了!
参考视频:
正点原子手把手教你学ZYNQ之嵌入式开发