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

2023-12-03 17:28

本文主要是介绍【【FPGA 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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

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

我们去创建自定义IP核
创建之后 会帮助我们创建一个AXI的外壳
之前我也做过类似的设计 AXI 设计 但是并没有设计的很好 这次我们细致的分析一下 该有的做法
在这里插入图片描述

我们来看
在这里插入图片描述

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

接下来我们看下一部分用户自定义的
在这里插入图片描述

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

接下来我们来看 主函数下的另一个函数
这个函数的出现是为了 补充 不让main看上去很臃肿
在这里插入图片描述

这个函数的部分内容是可以修改的
我们往其中写入部分函数 使得其能够 加载入LED 呼吸灯的功能
函数同样提醒了 可以添加参数进行修改
在这里插入图片描述

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

在这里插入图片描述

观察 我们可以知道
在这里插入图片描述

我们通过寄存器地址 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; elsefreq_step <= set_freq_step; endend //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 核进行操作
在这里插入图片描述

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

接下来我们装载进入 vitis
在这里插入图片描述

在 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 可以看到
在这里插入图片描述

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

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

这句话 就相当于 把数据放进基地址+偏移量的位置
在这里插入图片描述

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

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 之 MicroBlaze 自定义IP核 之 呼吸灯实验】】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/450245

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

2024.9.8 TCP/IP协议学习笔记

1.所谓的层就是数据交换的深度,电脑点对点就是单层,物理层,加上集线器还是物理层,加上交换机就变成链路层了,有地址表,路由器就到了第三层网络层,每个端口都有一个mac地址 2.A 给 C 发数据包,怎么知道是否要通过路由器转发呢?答案:子网 3.将源 IP 与目的 IP 分别同这个子网掩码进行与运算****,相等则是在一个子网,不相等就是在不同子网 4.A 如何知道,哪个设备是路由器?答案:在 A

HNU-2023电路与电子学-实验3

写在前面: 一、实验目的 1.了解简易模型机的内部结构和工作原理。 2.分析模型机的功能,设计 8 重 3-1 多路复用器。 3.分析模型机的功能,设计 8 重 2-1 多路复用器。 4.分析模型机的工作原理,设计模型机控制信号产生逻辑。 二、实验内容 1.用 VERILOG 语言设计模型机的 8 重 3-1 多路复用器; 2.用 VERILOG 语言设计模型机的 8 重 2-1 多

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在