【【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

相关文章

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

聊聊springboot中如何自定义消息转换器

《聊聊springboot中如何自定义消息转换器》SpringBoot通过HttpMessageConverter处理HTTP数据转换,支持多种媒体类型,接下来通过本文给大家介绍springboot中... 目录核心接口springboot默认提供的转换器如何自定义消息转换器Spring Boot 中的消息

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全

Python自定义异常的全面指南(入门到实践)

《Python自定义异常的全面指南(入门到实践)》想象你正在开发一个银行系统,用户转账时余额不足,如果直接抛出ValueError,调用方很难区分是金额格式错误还是余额不足,这正是Python自定义异... 目录引言:为什么需要自定义异常一、异常基础:先搞懂python的异常体系1.1 异常是什么?1.2

Linux中的自定义协议+序列反序列化用法

《Linux中的自定义协议+序列反序列化用法》文章探讨网络程序在应用层的实现,涉及TCP协议的数据传输机制、结构化数据的序列化与反序列化方法,以及通过JSON和自定义协议构建网络计算器的思路,强调分层... 目录一,再次理解协议二,序列化和反序列化三,实现网络计算器3.1 日志文件3.2Socket.hpp

C语言自定义类型之联合和枚举解读

《C语言自定义类型之联合和枚举解读》联合体共享内存,大小由最大成员决定,遵循对齐规则;枚举类型列举可能值,提升可读性和类型安全性,两者在C语言中用于优化内存和程序效率... 目录一、联合体1.1 联合体类型的声明1.2 联合体的特点1.2.1 特点11.2.2 特点21.2.3 特点31.3 联合体的大小1

springboot自定义注解RateLimiter限流注解技术文档详解

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,... 目录什么是限流系统架构核心组件详解1. 限流注解 (@RateLimiter)2. 限流类型枚举 (

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码