【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】

FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验

通过创建和封装 IP 向导的方式来自定义 IP 核,支持将当前工程、工程中的模块或者指定文件目录封装成 IP 核,当然也可以创建一个带有 AXI4 接口的 IP 核,用于 MicroBlaze 软核处理器和可编程逻辑的数据通信。本次实验选择常用的方式,即创建一个带有 AXI 接口的 IP 核,该 IP 核通过 AXI协议实现 MicroBlaze 软核处理器和可编程逻辑的数据通信。AXI 协议是一种高性能、高带宽、低延迟的片内总线
下面展示 本次实验的系统框图
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第1张图片

Breath LED IP 核为自定义的 IP 核,McroBlaze 处理器通过 AXI 接口为LED IP 模块发送配置数据,从而来控制 LED 灯

实验任务 :
本章的实验任务是通过自定义一个 LED IP 核,来控制 LED 呈现呼吸灯的效果,并且可以通过 AXI 接口来控制呼吸灯的开关和呼吸的频率。

我们去创建自定义IP核
创建之后 会帮助我们创建一个AXI的外壳
之前我也做过类似的设计 AXI 设计 但是并没有设计的很好 这次我们细致的分析一下 该有的做法
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第2张图片

我们来看
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第3张图片

一共分为了2组
首先上面那组是 我们自定义的参数
下面一组是 系统定义的AXI接口的参数 系统也提示我们无法修改

接下来我们看下一部分用户自定义的
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第4张图片

我们 添加了上面两个 东西 一个参数 一个输出
因为这里是 主函数 main 所以 在下面例化的添加 对参数和端口的例化

接下来我们来看 主函数下的另一个函数
这个函数的出现是为了 补充 不让main看上去很臃肿
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第5张图片

这个函数的部分内容是可以修改的
我们往其中写入部分函数 使得其能够 加载入LED 呼吸灯的功能
函数同样提醒了 可以添加参数进行修改
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第6张图片

为什么要修改这里的东西 主要是 我们想通过这里 AXI 的 寄存器 的 结果 来调配呼吸灯的值
现在告诉你 slv_reg0 至 slv_reg3 是寄存器地址0 至寄存器地址 3 对应的数据,通过例化呼吸灯模块,将寄存器地址对应的数据和呼吸灯模块的控制端口相连接,即可实现对呼吸灯的控制。

【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第7张图片

观察 我们可以知道
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第8张图片

我们通过寄存器地址 0 对应的数据来控制呼吸灯的使能(sw_ctrl)
寄存器地址 1 对应数据的最高位控制呼吸灯频率的设置有效信号(set_en)
寄存器地址 1 对应数据的低 10 位控制呼吸灯频率的步长(set_freq_step)

我们会很容易的发现这个很奇怪是不是 因为我们连最重要的 breath 文件都没添加
下面补上呼吸灯的verilog 代码

module breath_led( 
 input sys_clk , //系统时钟 50MHz 
 input sys_rst_n , //系统复位,低电平有效
 input sw_ctrl , //呼吸灯开关控制信号 1:亮 0:灭
 input set_en , //设置呼吸灯频率设置使能信号
 input [9:0] set_freq_step , //设置呼吸灯频率变化步长
 output led //LED 灯
 ); 

 //parameter define 
 parameter START_FREQ_STEP = 10'd1 ; //设置频率步长初始值
 parameter CNT_2US_MAX = 7'd100 ;
 parameter CNT_2MS_MAX = 10'd1000 ; 
 parameter CNT_2S_MAX = 10'd1000 ; 

 //reg define 
 reg [6:0] cnt_2us ; 
 reg [9:0] cnt_2ms ;
 reg [9:0] cnt_2s ;
 reg inc_dec_flag ; //亮度递增/递减 0:递增 1:递减
 reg [9:0] freq_step ; //呼吸灯频率间隔步长
 reg led_t ; 

 //***************************************************** 
 //** main code 
 //***************************************************** 
 assign led = led_t & sw_ctrl; 

 //设置频率间隔,频率步长值在 1-10 之间
 always @(posedge sys_clk) begin 
 if(!sys_rst_n) 
 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'd10) 
 freq_step <= 10'd10; 
 else
 freq_step <= set_freq_step; 
 end
 end 

 //cnt_2us:计数 2us 
 always@(posedge sys_clk or negedge sys_rst_n) begin 
 if(!sys_rst_n) 
 cnt_2us <= 7'b0; 
 else if(cnt_2us == (CNT_2US_MAX - 7'b1 )) 
 cnt_2us <= 7'b0; 
 else 
 cnt_2us <= cnt_2us + 7'b1; 
 end 

 //cnt_2ms:计数 2ms 
 always@(posedge sys_clk or negedge sys_rst_n) begin 
 if(!sys_rst_n) 
 cnt_2ms <= 10'b0; 
 else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1)) 
 cnt_2ms <= 10'b0; 
 else if(cnt_2us == CNT_2US_MAX - 7'b1) 
 cnt_2ms <= cnt_2ms + 10'b1; 
 else 
 cnt_2ms <= cnt_2ms; 
 end 

 //cnt_2s:计数 2s 
 always@(posedge sys_clk or negedge sys_rst_n) begin 
 if(!sys_rst_n) 
 cnt_2s <= 10'b0; 
 else if(cnt_2s >= (CNT_2S_MAX - 10'b1) && cnt_2ms == (CNT_2MS_MAX - 10'b1)
 && cnt_2us == (CNT_2US_MAX - 7'b1)) 
 cnt_2s <= 10'b0; 
 else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1)) 
 cnt_2s <= cnt_2s + freq_step; 
 else 
 cnt_2s <= cnt_2s;
 end 

 //inc_dec_flag 为低电平,led 灯由暗变亮,inc_dec_flag 为高电平,led 灯由亮变暗
 always@(posedge sys_clk or negedge sys_rst_n) begin 
 if(!sys_rst_n) 
 inc_dec_flag <= 1'b0; 
 else if(cnt_2s >= (CNT_2S_MAX - 10'b1) && cnt_2ms ==( CNT_2MS_MAX - 10'b1) 
 && cnt_2us == (CNT_2US_MAX - 7'b1)) 
 inc_dec_flag <= ~inc_dec_flag; 
 else 
 inc_dec_flag <= inc_dec_flag; 
 end 

 //led:输出信号连接到外部的 led 灯
 always@(posedge sys_clk or negedge sys_rst_n) begin 
 if(!sys_rst_n) 
 led_t <= 1'b0; 
 else if((inc_dec_flag == 1'b1 && cnt_2ms >= cnt_2s) 
 || (inc_dec_flag == 1'b0 && cnt_2ms <= cnt_2s)) 
 led_t <= 1'b1; 
 else 
 led_t<= 1'b0; 
 end 
 
 endmodule

同样的 在我们自定义完整个IP之后 会自动连接成c语言 方便在 Vitis 软件中对 IP 核进行操作
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第9张图片

Makefile 让人想起了 Linux 的编译 这涉及到了另一个领域

接下来我们装载进入 vitis
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第10张图片

在 BSP 中包含了 我们的自己创建的 breath_led
下面介绍整个 main.c函数

#include "stdio.h"
#include "xparameters.h"
#include "xil_printf.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 //LED IP 基地址
#define LED_IP_REG0 BREATH_LED_IP_S0_AXI_SLV_REG0_OFFSET //LED IP 寄存器地址 0
#define LED_IP_REG1 BREATH_LED_IP_S0_AXI_SLV_REG1_OFFSET //LED IP 寄存器地址 1
//main 函数
int main()
{
int freq_flag; //定义频率状态,用于循环改变呼吸灯的呼吸频率
int led_state; //定义 LED 灯的状态
xil_printf("LED User IP Test!\n");
while(1){
//根据 freq_flag 的标志位,切换呼吸灯的频率
if(freq_flag == 0){
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR,LED_IP_REG1,0x800000ef);
freq_flag = 1;
}
else{
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR,LED_IP_REG1,0x8000002f);
freq_flag = 0;
}
//获取 LED 当前开关状态 1:打开 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);
xil_printf("Breath LED ON\n");
}
sleep(5);
//获取 LED 当前开关状态 1:打开 0:关闭
led_state = BREATH_LED_IP_mReadReg(LED_IP_BASEADDR,LED_IP_REG0);
//如果开关打开,关闭呼吸灯
if(led_state == 1){
BREATH_LED_IP_mWriteReg (LED_IP_BASEADDR, LED_IP_REG0, 0);
xil_printf("Breath LED OFF\n");
}
sleep(1);
}
}

我们开始分析一下
其实掌握自己的东西很开心
我们打开platform 可以看到
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第11张图片

有我们的 自定义的 IP 生成的 .c .h 内文件
我们来看主函数
我们先介绍一个非常重要的基本概念
IP的接口就是函数的输入参数
生成的.c文件 就相当于 把硬件抽象 成 一个库函数
通过库函数 来设置 输入参数
所以生成的逻辑 就是 根据你的硬件IP 的接口来的

ok 我们在做设计的时候 当合成一个IP之后 进入 C语言的层次之后 其实我们并不需要 知道 这个模块究竟是如何连接的 我们只需要知道这个模块的引脚是什么 功能是什么即可
对于这里的设计 我们需要在意的是 IP的 工作 输入输出 是什么 输入是 S_AXI 而 输出是 LED (LED 我们知道是寄存器达标的值)
那么 功能是 把 数写进 寄存器
LED的改变只是 因为寄存器变化而带来的附加效果
其实相当于把 数据存进到 寄存器
所以可想而知 .c 文件大概率也是这些功能
因为是系统来完成的 我们所能做的就是 观察 得出结论
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第12张图片

这句话 就相当于 把数据放进基地址+偏移量的位置
【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】_第13张图片

这句话 就相当于 输出的意思

ok 解析完 系统生成的.c .h 我们接下来看主函数 对我们自定义的.c的调用
很简单的 使用我们的.c .h文件

#include "stdio.h"
#include "xparameters.h"
#include "xil_printf.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 //LED IP 基地址
#define LED_IP_REG0     BREATH_LED_IP_S0_AXI_SLV_REG0_OFFSET //LED IP 寄存器地址 0
#define LED_IP_REG1     BREATH_LED_IP_S0_AXI_SLV_REG1_OFFSET //LED IP 寄存器地址 1
//main 函数
int main()
{
int freq_flag; //定义频率状态,用于循环改变呼吸灯的呼吸频率
int led_state; //定义 LED 灯的状态
xil_printf("LED User IP Test!\n");
while(1){
//根据 freq_flag 的标志位,切换呼吸灯的频率
if(freq_flag == 0){
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR,LED_IP_REG1,0x800000ef);
freq_flag = 1;
}
else{
BREATH_LED_IP_mWriteReg(LED_IP_BASEADDR,LED_IP_REG1,0x8000002f);
freq_flag = 0;
}
//获取 LED 当前开关状态 1:打开 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);
xil_printf("Breath LED ON\n");
}
sleep(5);
//获取 LED 当前开关状态 1:打开 0:关闭
led_state = BREATH_LED_IP_mReadReg(LED_IP_BASEADDR,LED_IP_REG0);
//如果开关打开,关闭呼吸灯
if(led_state == 1){
BREATH_LED_IP_mWriteReg (LED_IP_BASEADDR, LED_IP_REG0, 0);
xil_printf("Breath LED OFF\n");
}
sleep(1);
}
}

你可能感兴趣的:(FPGA学习,fpga开发,tcp/ip,网络协议)