参考:《DFZU2EG_4EV MPSoC 之嵌入式 VITIS 开发指南》——正点原子
一.自定义 IP 核-呼吸灯实验
1.简介
通过**创建和封装 IP 向导** 的方式来自定义 IP 核,将模块集成到 Vivado 中的 IP 库中。本 次实验选择常用的方式,即创建一个带有 AXI 接口的 IP 核,该 IP 核通过 AXI 协议实现 PS 和 PL 的数据通信。
<1>硬件框图
自定义一个 LED IP 核,来控制 PL LED 呈现呼吸灯的效果,并且 **PS 可以通过 AXI 接口来控制呼吸灯的开关和呼吸的频率** 。
2.硬件设计
step1:自定义IP核
<1>创建IP核
在vivado进入界面点击“Tasks”栏中的“Manage IP”。在弹出的选项中选择“New IP Location...”
设置工程路径**“IP Location”** 和 器件**“Part”(后续可重新指定):**
工程创建完成后,运行**创建和封装 IP 向导** 。点击**菜单栏的“Tools”** ,选择**“Create and Package New IP”** ,在弹出的界面中,点击“NEXT”
可选择封装 IP 或者创建一个带 AXI4 接口的 IP 核 :
接下来分别设置 IP 核名称(Name)、版本号(Version)、显示名(Display name)、描述(Description) 和路径(IP location)。
接下来对 AXI 接口进行设置:
Name(名称) :这里修改成 S0_AXI。
Interface Tpye(接口类型) :共三种接口类型可选,分别是 Lite、Full 和 Stream 。AXI4-Lite
接口是简化版 的 AXI4 接口,用于较少数据量的存储映射通信;AXI4-Full 接口是高性能存储映射接口
,用于较多数据量的存储映射通信;AXI4-Stream 用于高速数据流传输 ,非存储映射接口。本次实验只需少量数据的通信, 因此接口类型选择默认的
Lite 接口。
Interface Mode(接口模式) :接口模式有 Slave(从机)和 Master(主机) 两种模式可选,AXI
协议是主机和从机通过“握手”的方式建立连接 ,这里选择默认的 Slave 接口模式 。
Data Width(数据宽度) :数据位宽保持默认,即 32 位位宽。
Memory Size(存储器大小) : 在 AXI4-Lite 接口模式下,该选项不可设置。
Number of Registers(寄存器数量) :用于配置 PL LED 呼吸灯寄存器的数量,这里保持默认。
最后弹出封装接口的总结描述和下一步操作选项的界面。这里保持默认,即**将 IP 添加至 IP 库中** , 点击“Finish”按钮完成 IP 核的创建和封装。
在 **IP Catalog 界面** 中可以看到刚刚添加的 IP 核,位于 **User Repository** 一栏中的 AXI Peripheral 下,名称 为“breath_led_ip_v1.0”
<2>编辑IP核
右击 breath_led_ip_v1.0 IP 核, 选择“**Edit in IP Packager** ”,在弹出的界面中点击“OK”。此时会打开一个新的工程:
双击 breath_led_ip_v1_0.v文件即可开始编辑代码,来添加控制 PL LED 呼吸灯所需要的参数和端口信号:
在创建和封装 IP 核向导中,我们总共定义了 4 个寄存器,代码中的 slv_reg0 至 slv_reg3 是寄存器地址 0 至寄存器地址 3 对应的数据,通过例化呼吸灯模块,**将寄存器地址对应的数据和呼 吸灯模块(breath_led****) 的控制端口相连接**,即可实现对呼吸灯的控制。
breath_led_ip_v1_0.v文件:
breath_led_ip_v1_0_S0_AXI.v文件。 breath_led_ip_v1_0_S0_AXI 模块实现了 AXI4 协议下的读写寄存器的功能:
时我们还需要在代码的第 401 行例化 breath_led.v 文件:
// Add user logic here
breath_led #(
.START_FREQ_STEP(START_FREQ_STEP)
)
u_breath_led(
.sys_clk (S_AXI_ACLK),
.sys_rst_n (S_AXI_ARESETN),
.sw_ctrl (slv_reg0[0]),
.set_en (slv_reg1[31]),
.set_freq_step (slv_reg1[9:0]),
.led (led)
);
// User logic ends
代码中的 slv_reg0 和 slv_reg1 是寄存器地址 0 和寄存器地址 1 对应的数据 ,我们通过寄存器地址 0 对应 的数据来控制呼吸灯的使能(sw_ctrl),寄存器地址 1 对应数据的最高位控制呼吸灯频率的设置有效信号 (set_en),寄存器地址 1 对应数据的低 10 位控制呼吸灯频率的步长(set_freq_step)。
此时工程中缺失 breath_led.v 文件,breath_led.v 文件用于实现呼吸灯的功能。右击**“Design Sources”** ,选择**“Add Sources…”** ,在弹出的界面中选择“**Add or Create design source”** , 点击“NEXT”;点击“**Create File ”** 创建一个新的文件,在弹出的界面输入名称 breath_led ,路径 为../**_custom_ip/ip_repo/breath_led_ip_1.0/_ hdl**,点击“OK”按钮
在弹出的**模块定义界面中点击“OK”按钮** ,接下来在弹出的确认按钮中点击“YES”。双击 u_breath_led(breath_led.v)文件并编辑代码如下:
module breath_led(
input sys_clk , //时钟信号
input sys_rst_n , //复位信号
input sw_ctrl , //呼吸灯开关控制信号 1:亮 0:灭
input set_en , //设置呼吸灯频率设置使能信号
input [9:0] set_freq_step , //设置呼吸灯频率变化步长
output led //LED
);
//*****************************************************
//** main code
//*****************************************************
//parameter define
parameter START_FREQ_STEP = 10'd100; //设置频率步长初始值
//reg define
reg [15:0] period_cnt ; //周期计数器
reg [9:0] freq_step ; //呼吸灯频率间隔步长
reg [15:0] duty_cycle ; //设置高电平占空比的计数点
reg inc_dec_flag; //用于表示高电平占空比的计数值,是递增还是递减
//为1时表示占空比递减,为0时表示占空比递增
//wire define
wire led_t ;
//将周期信号计数值与占空比计数值进行比较,以输出驱动led的PWM信号
assign led_t = ( period_cnt <= duty_cycle ) ? 1'b1 : 1'b0 ;
assign led = led_t & sw_ctrl;
//周期信号计数器在0-50_000之间计数
always @ (posedge sys_clk) begin
if (!sys_rst_n)
period_cnt <= 16'd0;
else if(!sw_ctrl)
period_cnt <= 16'd0;
else if( period_cnt == 16'd50_000 )
period_cnt <= 16'd0;
else
period_cnt <= period_cnt + 16'd1;
end
//设置频率间隔
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'd1_000)
freq_step <= 10'd1_000;
else
freq_step <= set_freq_step;
end
end
//设定高电平占空比的计数值
always @(posedge sys_clk) begin
if (sys_rst_n == 1'b0) begin
duty_cycle <= 16'd0;
inc_dec_flag <= 1'b0;
end
else if(!sw_ctrl) begin //呼吸灯开关关闭时,信号清零
duty_cycle <= 16'd0;
inc_dec_flag <= 1'b0;
end
//每次计数完了一个周期,就调节占空比计数值
else if( period_cnt == 16'd50_000 ) begin
if( inc_dec_flag ) begin //占空比递减
if( duty_cycle == 16'd0 )
inc_dec_flag <= 1'b0;
else if(duty_cycle < freq_step)
duty_cycle <= 16'd0;
else
duty_cycle <= duty_cycle - freq_step;
end
else begin //占空比递增
if( duty_cycle >= 16'd50_000 )
inc_dec_flag <= 1'b1;
else
duty_cycle <= duty_cycle + freq_step;
end
end
else //未计数完一个周期时,占空比保持不变
duty_cycle <= duty_cycle ;
end
endmodule
模块实现了呼吸灯的功能。呼吸灯的使能由输入的端口信号 sw_ctrl 控制,呼吸灯的呼吸频率由输入的端口信号 set_en 和 set_freq_step 控制。输入的 set_freq_step 范围是 1~1000。
在左侧 Flow Navigator 导航栏中找到 SYNTHESIS,点击 该选项中的 **“Run Synthesis”** ,等待代码编译完成。
<3>IP 封装
将界面切换至 Package IP,也可以通过 **IP-XACT** 界面下的 **component.xml** 重新打开:
Identification 这一栏的选项直接保持默认,需要注意的是,我们可以点击图 6.3.31 中 **Categories** 选项下 的“+”按钮来**修改 IP 的分类** ,这里不做修改。
这里勾选“zynq”和“zynquplus”两项,表示该 IP 核支持 ZYNQ 和 ZYNQ MPSOC 器件。而 Life-cycle 表明该 IP 核当前的产品生命周期,这里选择“**Pre-Production** ”
点击 File Groups,然后点击界面上的“Merge Changes from File Groups Wizard”,此时可以在 Verilog Synthesis 一栏中查看工程中的三个模块。
点击 **Customization Parameters** ,点击界面上的“**Merge Changes from Customization Parameters Wizard”** ,此时多了 Hidden Parameters 一栏,展开这个界面,可以看到程序中自定义的参数 START_FREQ_STEP, 右击这个参数,选择“Edit Parameter…”,弹出编辑参数的界面:
在弹出的页面中勾选“Visible in Customization GUI”,将此参数显示在 GUI 参数界面中; Format 格式改为“long”; 勾选“Specify Range”来设定此参数的范围。将 Type 改为“Range of integers”,Minimum 的值改为 1, Maximum 的值改为 1000,将 Default Value 的值改为 100,点击“OK”按钮:
点击“Customization GUI”,可以在“Layout”界面拖动 Page 0 下的参数来调整参数在 GUI 显示 的位置,如下图所示:
点击**“Review and Package”** ,然后点击“**IP has been modified”** 更新总结界面,最后点击**“Re-Package IP”** ,如下图所示:
IP 核封装完成后,在 IP 核所在路径(...\custom_ip\ip_repo\breath_led_ip_1.0\drivers\breath_led_ip_v1_0\src) 目录下,Vivado 软件会自动生成.c 和.h 文件,方便在 VITIS 软件中对 IP 核进行操作。最后关闭工程IP核创建结束。
step2:创建vivado工程
创建 Vivado 工程名为 user_led,具体过程见前。点击菜单栏的“Tools”, 选择“Setting”,把自定义的IP核添加至本工程的IP库中:
点击“IP”一栏下的“Repository”,然后点击“+”来添加自定义的 IP 核。选择../custom_ip/**ip_repo/breath_led_ip_1.0** ,点击“Select”,点击“OK”按钮添加 IP 核:
step3:使用 IP Integrator 创建 Processing System
在左侧导航栏(Flow Navigator)中,单击 IP Integrator 下的 Create Block Design。然后在弹出的对话框中指定所创建的 Block Design 的名称,这里使用默认的“design_1”。在 Diagram 窗口中给设计添加 IP。点击图中加号“+”,会打开 IP 目录(IP Catalog)。在搜索栏中 键入“zynq”,找到并双击“ZYNQ Ultrascale+ MPSoC”,将 MPSOC 处理系统 IP 添加到设计中。和前面搭建嵌入式最小系统不同的是,我们保留了 pl_clk0、pl_resetn0、maxihpm0_lpd_aclk 和 M_AXI_HPM0_LPD 接口,只是添加了 UART 控制器(MIO42 和 MIO43),修改 Bank 电压和修改 DDR4 控制器**(见前)** 其它保持默认。
接下来添加 Breath LED IP 核,点击“+”图标,在搜索框中输入“led”,即可找到之前添加的“breath_led_ip_v1.0”IP,添加此IP核,可以双击 led IP 核来设置参数,可以看到我们自定义的参数(Start Freq Step)和其它四个参数:
接下来点击**“Run Connection Automation”** 来自动连线,在弹出的窗口中勾选 **All Automation** ,然后点击 OK。此时原理图中还没有 LED 的引脚,右击 breath_led_ip_0 的 led 引脚,选择“Make External”,将引出的 led_0 改为 led:
step4:生成顶层 HDL 模块
在 Sources 窗口中,选中 **Design Sources** 下的 design_1.bd,这就是我们刚刚完成的 Block Design 设 计。右键点击 design_1.bd,在弹出的菜单栏中选择**“Generate Output Products”** ,等待 Generate 完成。在 Sources 窗口中,右键点击 design_1.bd,在弹出的菜单栏中选择**“Create HDL Wrapper”** 。
step5:生成 Bitstream 文件并导出 Hardware。
在左侧 Flow Navigator 导航栏中找到 RTL ANALYSIS,点击该选项中的**“Open Elaborated Design”** 。在 ELABORATED DESIGN 界面下方找到 I/O Ports 一栏。如果没有找到则通过在菜单栏中点击 Layout, 然后在下拉列表中选择 I/O Planning。我们将在 I/O Ports 一栏对 PL 部分的接口进行管脚分配,led 分配至**BANK44** 的 **AE10** 引脚,该 BANK 的供电电压为**3.3V** ,因此 I/O Std 一列对应的电平也需要修改。
设置完成后按快捷 Ctrl+S 保存管脚约束,在弹出的对话框输入文件名“user_led”。在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的**“Generate Bitstream”** 。在连续弹出的对话框中依次点击“YES”、“OK”。然后 Vivado 工具开始依次对设计进行综合、实现、并生成 Bitstream 文件。在菜单栏中选择 File > Export > **Export hardware** 。在弹出的对话框中,勾选“**Include bitstream** ”,然后点 击“OK”按钮。
新建 vitis 文件夹,将 xsa 文件拷贝到里面。选择菜单 Tools->Launch Vitis。在弹出的界面中,指定路径到..\custom_ip\user_led\vitis 下,点击 “Launch”,打开 Vitis 软件。
3.软件设计
新建vitis工程user_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\r");
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\r");
}
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\r");
}
sleep(1);
}
}
在代码的第 8 行至第 10 行,我们对 Breath LED IP 基地址、寄存器地址 0 和寄存器地址 1 进行了宏定义。按住 Ctrl 键不放,将鼠标移动到这些参数上,单击鼠标左键,会自动跳转到定义这些参数的地方。其中 BREATH LED IP 寄存器地址 0 和寄存器地址 1 位于**breath_led_ip.h** 文件内,这个文件是系统自动为我们生成的。
程序中的 main 函数实现了每 6 秒钟(点亮 5 秒+关闭 1 秒)打开和关闭 LED 呼吸灯的开关,并切换 LED 灯呼吸频率的功能。我们通过 **BREATH_LED_IP_mReadReg()** 函数来**读取寄存器地址的数据** ,通过 **BREATH_LED_IP_mWriteReg()** 函数来**写入寄存器地址的数据** ,这两个函数同样位于 breath_led_ip.h 文件中。
在硬件设计的自定义 IP 核部分中,我们例化 breath_led 代码的时候,将寄存器 0 的数据(slv_reg0)连接至呼吸灯的开关控制信号(sw_ctrl),寄存器 1 的数据高位(slv_reg1[31])连接至呼吸灯频率设置使能信号(set_en),寄存器 1 的数据低位(slv_reg1[9:0])连接至呼吸灯频率间隔设置(set_freq_step)。因此,在 Vitis 应用程序中,可以很方便的**通过 Breath LED IP 寄存器地址 0 和寄存器地址 1 来控制 LED 呼吸灯的开关和频率** 。
值得一提的是,在 Vitis 中添加导出的硬件平台文件后,自**定义 IP 核的库函数也会导入进来** ,而这个 库函数是在自定义 IP 核时,由 Vivado 软件自动生成的:
接着“build project”,如果编译出现error,可以参考:
Vitis2021.1报错:fatal error: xparameters.h: No such file or directory-
CSDN博客
若run时出现错误:
Error while launching program: Hardaware specification file used in the launch
configuration ‘Debugger_user_led_ip-Default’ doesn’t exist at the location
…..
4.下载验证
打开 Vitis Terminal 终端,设置并连接串口。在应用工程 user_led 上右击,选择**“Run As”** ,然后选择第一项**“1 Launch on Hardware (System Debugger)”** 。
二.程序固化实验
之前都是通过 JTAG 接口将 FPGA 配置文件和应用程序下载到 MPSOC 器件中。接下来尝试把程序存储在非易失性存储器中,在上电或者复位时让程序自动运行,这个过程需要启动引导程序**(Boot Loader)** 的参与。**Boot Loader 会加载 FPGA 配置文件,以及运行在 ARM 中的软件应用。**
** 本章的实验任务是在“AXI GPIO 按键控制 LED 实验”的基础上创建 FSBL** ,实现程序上电自启动,包括从 SD
卡启动,QSPI Flash 和 eMMC 启动三种方式 。
1.*简介
MPSOC 的系统启动过程由**平台管理单元**(**PMU**)和**配置安全单元**(**CSU**)管理和执行。启动过程包括三个功能阶段:**预配置阶段、配置阶段和后配置阶段** 。
_ **预配置阶段** 由**平台管理单元** 控制。平台管理单元运行 **PMU ROM** 代码以设置系统。PMU 处理所有复位和唤醒过程。_
_**在配置阶段** ,**BootROM(CSU ROM 代码的一部分)** 解释引导头以配置系统,并在安全或非安全引导模式下将处理系统(PS)的**第一阶段引导加载程序(FSBL)代码** 加载到**片上 RAM(OCM)** 中。引导头定义了许多引导参数,包括安全模式和**执行 FSBL 的处理器 MPCore** 。在引导期间,CSU 还将 **PMU 用户固件(PMU FW)** 加载到**PMU RAM** 中,以与 PMU ROM 一起提供**平台管理服务** 。对于**基于 Xilinx 的 FSBL 和系统软件,PMU FW 必须存在于大多数系统中** 。_
_ FSBL 执行开始后,CSU ROM 代码进入**后配置阶段** ,该阶段负责系统干预响应。CSU 硬件提供持续的硬件支持,以验证文件,**通过 PCAP 配置 PL,存储和管理安全密钥,解密文件** 。_
启动流程(Boot Flow)
** **PMU对内部寄存器、存储器等进行复位,检查电压,则验证 CSU ROM 完整性并释放对 CSU 的复位。PMU
负责处理主要的预引导任务和 PS 的管理,以确保系统资源的可靠通电断电。启动 PMU
的上电复位(POR)操作,直接或间接的释放了预期上电模块的复位。在这种情况下,PMU 需要 ROM 代码来保持初始化上电顺序。即使在启动过程之后,PMU
仍在运行,并且负责处理各种系统复位。在更改系统电源状态时也会使用它(例如上电、睡眠和唤醒)。在初始化启动期间,POR 将 PMU 从复位中释放,然后执行
PMU ROM。
下面描述了 PMU 处理器在 POR 复位后,通过运行 PMU ROM 预启动代码完成的操作序列:
1、 初始化 PS SYSMON 和引导单元所需的 PLL。
2、 清除 PMU RAM 和 CSU RAM(仅外部 POR)。
3、 验证 PLL 锁。
4、 通过 PS SYSMON 单元验证 LPD、AUX 和 IO 电源范围。
5、 清除低功耗和全功耗域。
6、 如果前面的步骤没有错,PMU 将释放 CSU 复位并进入 PMU 服务模式。如果有错,将产生一个启动错误标志。
当 CSU 复位被释放,CSU 将按照下面序列运行:
1、 初始化 OCM
2、 通过读取引导模式寄存器来确定启动模式。
3、 CSU 继续在 OCM 中加载 FSBL,以供 RPU 或 APU 执行。然后,CSU 将 PMU 用户固件加载到 PMU RAM 中,以供 PMU 固件执行。
启动模式(Boot Modes)
BootROM 可以通过 Quad-SPI,SD,eMMC,USB2.0 控制器 0 或 NAND 等外部设备启动系统。
MPSOC 使用多个模式引脚来决定配置器件的类型,软件的存储位置以及其他的系统设置,这些引脚共享 PS 端的 MIO 引脚。总共有 7 个模式引脚,分别为 MIO[8:2]。其中,前四个引脚定义启动模式,第五个引脚定义是否使用 PLL,第六个和第七个引脚定义上电过程中 MIO bank0 和
bank1 的 bank 电压。如下图所示:
整个系统的启动过程
更详细的内容可参考:嵌入式Linux_Petalinux一——三.1软件栈
2.硬件设计
本次实验在“AXI GPIO 按键控制 LED 实验”的基础上进行,另存为本次实验工程,工程名为 axi_gpio_fsbl。
接下来对系统的硬件设计进行修改。在 Vivado 界面左侧选择 Open Block Design,然后在右侧的 Diagram 界面中双击 Zynq UltraScale+ MPSOC 模块修改其配置。
首先**使能 QSPI 外设** 。在左侧的导航栏中选择 I/O Configuration,在打开的右侧页面中,依次展开 Low Speed > Memory Interfaces,然后勾选 QSPI 并在后面的下拉菜单中选择 Single,QSPI Data Mode 选择 x4, QSPI IO 默认为 MIO0..5,勾选 Feedback Clk 并选择 MIO6,如下图所示:
然后**打开 SD 卡外设** 。在上一步的页面中,展开 SD 外设,勾选 SD1,在后面的菜单栏中选择 MIO46..51, Slot Type 选择 SD2.0,Data Transfer Mode 选择 4Bit,勾选**CD 用于检测 SD 卡插入** 并选择 MIO45,如下图所示:
最后**打开 eMMC 外设** 。在同样的页面中,勾选 SD0 并选择 MIO13..22,Slot Type 设置成 eMMC,Data Transfer Mode 选择 8bit,勾选 Reset 并选择 MIO23,如下图所示:
上面两幅图中具体每个外设所连接的 MIO 引脚可以通过查看开发板原理图得知,设置完成后点击“OK”。然后在 Diagram 窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示 “Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在 Source 窗口中右键点击 Block Design 设计文件“design_1.bd”,执行“Generate Output Products”。 最后在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”, 对设计进行综合、实现、并生成 Bitstream 文件。
在菜单栏中选择 File > Export > Export hardware 导出硬件,并在弹出的对话框中,勾选“Include bitstream”。
新建 vitis 文件夹,将生成的 xsa 文件放入其中。 然后在菜单栏选择 Tools > Launch Vitis,启动 Vitis 开发环境。在弹出的对话框中,将路径指定到新建 的 vitis 文件夹下,点击 Launch 启动 Vitis。
3.软件设计
新建vitis工程如上,main.c代码:
#include “stdio.h”
#include “xparameters.h”
#include “xgpiops.h”
#include “xgpio.h”
#include “xscugic.h”
#include “xil_exception.h”
#include “xil_printf.h”
#include “sleep.h”
//宏定义
#define SCUGIC_ID XPAR_SCUGIC_0_DEVICE_ID //中断控制器 ID
#define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID //PS端 GPIO器件 ID
#define AXI_GPIO_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO器件 ID
#define GPIO_INT_ID XPAR_FABRIC_GPIO_0_VEC_ID //PL端 AXI GPIO中断 ID
#define MIO_LED 38 //PS_LED1 连接到 MIO38
#define KEY_CHANNEL 1 //PL按键使用 AXI GPIO通道1
#define KEY_MASK XGPIO_IR_CH1_MASK //通道1的位定义
//函数声明
void instance_init(); //初始化器件驱动
void axi_gpio_handler(void *CallbackRef); //中断服务函数
//全局变量
XScuGic scugic_inst; //中断控制器 驱动实例
XScuGic_Config * scugic_cfg_ptr; //中断控制器 配置信息
XGpioPs gpiops_inst; //PS端 GPIO 驱动实例
XGpioPs_Config * gpiops_cfg_ptr; //PS端 GPIO 配置信息
XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例
int led_value = 1; //LED显示状态
int main()
{
printf("AXI GPIO INTERRUPT TEST!\n");
//初始化各器件驱动
instance_init();
//配置PS GPIO
XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED, 1); //设置 PS GPIO 为输出
XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED ,1); //使能 PS GPIO 输出
XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //点亮LED
//配置PL AXI GPIO
XGpio_SetDataDirection(&axi_gpio_inst, KEY_CHANNEL, 1); //设置PL AXI GPIO 通道1为输入
XGpio_InterruptEnable(&axi_gpio_inst, KEY_MASK); //使能通道1中断
XGpio_InterruptGlobalEnable(&axi_gpio_inst); //使能AXI GPIO全局中断
//设置中断优先级和触发类型(高电平触发)
XScuGic_SetPriorityTriggerType(&scugic_inst, GPIO_INT_ID, 0xA0, 0x1);
//关联中断ID和中断处理函数
XScuGic_Connect(&scugic_inst, GPIO_INT_ID, axi_gpio_handler, &axi_gpio_inst);
//使能AXI GPIO中断
XScuGic_Enable(&scugic_inst, GPIO_INT_ID);
//设置并打开中断异常处理功能
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, &scugic_inst);
Xil_ExceptionEnable();
while(1);
return 0;
}
//初始化各器件驱动
void instance_init()
{
//初始化中断控制器驱动
scugic_cfg_ptr = XScuGic_LookupConfig(SCUGIC_ID);
XScuGic_CfgInitialize(&scugic_inst, scugic_cfg_ptr, scugic_cfg_ptr->CpuBaseAddress);
//初始化PS端 GPIO驱动
gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);
XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
//初始化PL端 AXI GPIO驱动
XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_ID);
}
//PL端 AXI GPIO 中断服务(处理)函数
void axi_gpio_handler(void *CallbackRef)
{
int key_value = 1;
XGpio *GpioPtr = (XGpio *)CallbackRef;
print("Interrupt Detected!\n");
XGpio_InterruptDisable(GpioPtr, KEY_MASK); //关闭 AXI GPIO 中断使能
key_value = XGpio_DiscreteRead(GpioPtr, KEY_CHANNEL); //读取按键数据
if(key_value == 0){ //判断按键按下
led_value = ~led_value;
XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //改变LED显示状态
}
sleep(1); //延时1s 按键消抖
XGpio_InterruptClear(GpioPtr, KEY_MASK); //清除中断
XGpio_InterruptEnable(GpioPtr, KEY_MASK); //使能AXI GPIO中断
}
**保存并编译工程** 后,**创建启动镜像** 。选中应用工程,右键选择**Create Boot Image。** 接下来,在弹出的界面中添加生成 boot.bin 所需的文件,然后点击“Create Image”,如图
从上图中可以看到,软件已经给我们自动添加所需的文件。首先是 **Bootloader 启动文件** ,也就是序号 4 处的**fsbl.elf** 。其次是 **FPGA 的配置文件** design_1_wrapper.**bit** ,在上图中序号 5 处。最后是**应用程序** axi_gpio_fsbl.elf 文件,上图中序号 6 处。注意这三个文件的顺序不能错。
序号 2 处的 bif 文件是生成**BOOT 的配置文件** ,序号 3 处的 **BOOT.bin** 就是我们需要的**启动文件** ,可以烧录到 QSPI Flash 或 eMMC 中,也可以放到 SD 中来启动 ZYNQ MOPSOC。
创建完成后,在指定的路径下可以看到生成的两个文件,如下图所示:
4.下载验证
<1>从SD卡中启动程序
将 Micro SD 卡插入读卡器,然后在电脑上将其格式化为 FAT32 格式,如下图所示:
将生成的 BOOT.bin 文件拷贝到 SD 卡根目录下,开发板启动模式改为从SD卡启动即可。
<2>固化至QSPI Flash中
将程序固化到 QSPI Flash 需要使用 JTAG 下载器。首先我们将下载器与开发板上的 JTAG 接口连接,下载器另外一端与电脑连接。接下来将开发板上的启动模式开关设置为 JTAG 模式。
在 Vitis 软件的菜单栏中点击“Program Flash”,如下:
在弹出的对话框中指定前面所生成的镜像文件 BOOT.bin 以及 FSBL.elf 文件,如下图中 1 和 2 处所示。 Flash Type 选择 qspi-x4-single,并勾选 Verify after flash。点击“Program”,开始对 Flash 进行编程:
断开开发板电源,然后将开发板上的启动模式开关设置为 32bit QSPI Flash 启动即可。
<3>固化到 eMMC 中
步骤和<2>相同,在Program Flash中的 **Flash Type 选择 emmc** , 并勾选 Verify after flash,然后点击 Program,如下:
断开开发板电源,然后将开发板上的启动模式开关设置为 emmc 启动即可。
三.UART 串口中断实验
我们在使用 PS 的时候,通常会添加 UART 控制器,用于打印信息和调试代码。除此之外,PS 在和外部设备通信时,也会经常使用串口进行通信。进一步向大家 UART 控制器以及 UART 控制器利用中断进行通信的方法。
1.简介
<1>MPSOC内UART控制器
MPSOC内UART控制器支持可编程的波特率发生器、64 字节的接收 FIFO 和发送 FIFO、产生中断、RXD 和 TXD 信号的环回模式设置以及可配置的数据位长度、停止位和校验方式等。
UART 控制器的配置以及状态的获取由**控制(Control)和状态寄存器(Status Registers)** 完成。另外, UART 控制器不仅可以连接至 MIO,也可以**映射到 EMIO** ,从而使用 **PL 的端口来实现串口通信的功能** 。
当 UART 控制器连接到 MIO 时,只有 Tx(发送)和 Rx(接收)两个引脚;而当连接 EMIO 时,除 Tx 和 Rx 引脚外,可选的还有 CTS、RTS、DSR、DCD、RI、DTR 等引脚,这些引脚用于串口的流控制。
UART 控制器采用独立的接收和发送数据路径,每个路径包含一个 **64 字节的 FIFO** ,控制器对发送和接 收 FIFO 中的数据进行**串并转** 换操作。
**FIFO 的中断标志** 支持**轮询处理** 或**中断驱动处理** 两种方式。另外,控制器中还有一个模式开关,支持 RXD 和 TXD 信号的各种环回配置。UART 控制器内部框图如下图所示:
UART 控制器的寄存器通过 **APB 从机接口和 PS AXI 总线** 互联,控制器的寄存器用于对 UART 控制器 进行配置和获取状态。波特率发生器(**Baud Rate Generator** )为 UART 控制器的接收端和发送端提供位周期 时钟;中断控制器(**GIC** )为串口的收发提供了中断服务的功能。
APB 总线接口通过**向 TxFIFO 寄存器写值** ,将数据加载到 TxFIFO 存储器中。当数据加载至 TxFIFO 后, TxFIFO 的空标志变成无效的状态,直到最后一个数据从 TxFIFO 中移出,加载至传**输移位寄存器** ,TxFIFO 恢复空的标志位。同时 TxFIFO 使用 TFULL(满中断状态)用于表示当前 TxFIFO 已经写满,并且会阻止 数据继续写入。如果此时继续执行写操作,那么会触发溢出,数据不会加载到 TxFIFO 中。
RxFIFO 存储器接收来自接收移位寄存器的数据,当接收完数据后,RxFIFO 空标志信号同样变成无效 的状态,直到所有的数据通过 APB 总线发送出去。RxFIFO 的满标志状态用于表示 RxFIFO 已经写满,并且 会阻止更多的数据写入。
模式切换(**Mode Switch** )控制 RxD 和 TxD 的信号连接方式,总共分为四种模式,分别为: **正常模式(Normal Mode)、自动回音模式(Automatic Echo Mode)、本地环回模式(Local Loopback Mode)和远程环回模式(Remote Loopback Mode)** 。
如果我们只是 用串口来**打印信息** 的话,那么可以直接使用**print()或者 xil_printf()** 函数就可以了,无需在程序中对串口做配置。但是如果我们需要使用 UART 来完成某些特定功能的话,如**串口接收中断** ,那么就要了解 UART 控制器**初始化、UART 中断初始化以及 UART 常用的 API 函数** 等相关内容了。
<2>UART启动和配置
<3>收发数据
2.硬件设计
DDR4 中存放和运行程序、UART 实现串口通信。
本次实验嵌入式系统的搭建和Hello World 实验完全相同,工程名为 uart_intr_loop。 _MPSOC 开发板上的 USB UART 连接的引 脚是 MIO42 和 MIO43,因此在配置界面选择的是 UART0 MIO42..MIO43_ 。图中的 Modem signals 表示是否添加**串口的流控制功能,即调制解调器** ,如果选中的话,会额外增加一些引脚,一般不勾选。需要注意的 是,串口的流控制功能只能用于**EMIO 接口** ,MIO 接口不支持此功能。
接下来,直接导出硬件,然后新建 vitis 文件夹,将导出的 xsa 文件拷贝到里面,最后打开 Vitis 软件, 并将路径指向新建的 vitis 文件夹下。
3.软件设计
Vitis 中创建了一个名为**uart_intr_loop 的应用工程** 。展开 design_1_wrapper,找到 **platform.spr** 并双击,右面的界面中出现 **design_1_wrapper 的标签页** ,然后找到**板级支持包** 并点击,可以看到**UART 文档和导入示例:**
如果我们点击**Import Examples** ,会弹出下图所示的导入示例界面,关于**UART 有 5 个示例,** 其中 xuartps_intr_example 是串口中断的示例,可供参考。
新建main.c文件,代码如下:
#include “xparameters.h”
#include “xuartps.h”
#include “xil_printf.h”
#include “xscugic.h”
#include “stdio.h”
#define UART_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID //串口设备ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
#define UART_INT_IRQ_ID XPAR_XUARTPS_0_INTR //串口中断ID
XScuGic Intc; //中断控制器驱动程序实例
XUartPs Uart_Ps; //串口驱动程序实例
//UART初始化函数
int uart_init(XUartPs* uart_ps)
{
int status;
XUartPs_Config *uart_cfg;
uart_cfg = XUartPs_LookupConfig(UART_DEVICE_ID);
if (NULL == uart_cfg)
return XST_FAILURE;
status = XUartPs_CfgInitialize(uart_ps, uart_cfg, uart_cfg->BaseAddress);
if (status != XST_SUCCESS)
return XST_FAILURE;
//UART设备自检
status = XUartPs_SelfTest(uart_ps);
if (status != XST_SUCCESS)
return XST_FAILURE;
//设置工作模式:正常模式
XUartPs_SetOperMode(uart_ps, XUARTPS_OPER_MODE_NORMAL);
//设置波特率:115200
XUartPs_SetBaudRate(uart_ps,115200);
//设置RxFIFO的中断触发等级
XUartPs_SetFifoThreshold(uart_ps, 1);
return XST_SUCCESS;
}
//UART中断处理函数
void uart_intr_handler(void *call_back_ref)
{
XUartPs *uart_instance_ptr = (XUartPs *) call_back_ref;
u32 rec_data = 0 ;
u32 isr_status ; //中断状态标志
//读取中断ID寄存器,判断触发的是哪种中断
isr_status = XUartPs_ReadReg(uart_instance_ptr->Config.BaseAddress,
XUARTPS_IMR_OFFSET);
isr_status &= XUartPs_ReadReg(uart_instance_ptr->Config.BaseAddress,
XUARTPS_ISR_OFFSET);
//判断中断标志位RxFIFO是否触发
if (isr_status & (u32)XUARTPS_IXR_RXOVR){
rec_data = XUartPs_RecvByte(XPAR_PSU_UART_0_BASEADDR);
//清除中断标志
XUartPs_WriteReg(uart_instance_ptr->Config.BaseAddress,
XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR) ;
}
XUartPs_SendByte(XPAR_PSU_UART_0_BASEADDR,rec_data);
}
//串口中断初始化
int uart_intr_init(XScuGic *intc, XUartPs *uart_ps)
{
int status;
//初始化中断控制器
XScuGic_Config *intc_cfg;
intc_cfg = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (NULL == intc_cfg)
return XST_FAILURE;
status = XScuGic_CfgInitialize(intc, intc_cfg,
intc_cfg->CpuBaseAddress);
if (status != XST_SUCCESS)
return XST_FAILURE;
//设置并打开中断异常处理功能
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
(void *)intc);
Xil_ExceptionEnable();
//为中断设置中断处理函数
XScuGic_Connect(intc, UART_INT_IRQ_ID,
(Xil_ExceptionHandler) uart_intr_handler,(void *) uart_ps);
//设置UART的中断触发方式
XUartPs_SetInterruptMask(uart_ps, XUARTPS_IXR_RXOVR);
//使能GIC中的串口中断
XScuGic_Enable(intc, UART_INT_IRQ_ID);
return XST_SUCCESS;
}
//main函数
int main(void)
{
int status;
status = uart_init(&Uart_Ps); //串口初始化
if (status == XST_FAILURE) {
xil_printf("Uart Initial Failed\r\n");
return XST_FAILURE;
}
uart_intr_init(&Intc, &Uart_Ps); //串口中断初始化
while (1);
return status;
}
*分析:
XScuGic 和 XUartPs 为程序中定义的两个结构体。如果在 Vitis 软件中, 按住 Ctrl 键不放,将鼠标移动到 XScuGic 或者 XUartPs 上,当鼠标变成手指状时,单击鼠标左键,会自动 跳转到定义这两个结构体的地方。其中 **XScuGic 包含了中断控制器相关的参数和数据** ,而 **XUartPs 则包含 了串口相关的参数和数据** 。
在代码的第 15 行至第 40 行完成了对 UART 的初始化。其中代码的第 28 行 **XUartPs_SelfTest** 函数实现 了 UART 设备自检的功能,即使用 UART**本地环回** 的模式,并验证数据是否可以正确发送和接收。 **XUartPs_SetOperMode** 函数设置**串口的工作模式** ,这里输入的参数 XUARTPS_OPER_MODE_NORMAL 为 正常的工作模式。**XUartPs_SetBaudRate** 函数用于设置串口的通信**波特率** ,这里设置的波特率为 115200,如 果需要修改成其它波特率,可直接在此修改输入的参数即可。**XUartPs_SetFifoThreshold** 函数用于设置 RxFIFO 的中断触发等级,即**触发** **RxFIFO 中断的数据个数** ,**这里设置的值为 1(字节)** ,即每收到一个值就触发中断。注意,中断触发等级最大值不超过 63。
在代码的第 65 行至第 94 行完成了串口中断的初始化。程序首先对**中断控制器进行初始化** ,随后设置 并打开**中断异常处理** 的功能。接下来为串口中断设置**中断处理函数** ,通过 **XScuGic_Connect** 函数进行设置,这里设置的串口中断处理函数为 **uart_intr_handler** 。**XUartPs_SetInterruptMask** 函数用于设置 UART 的**中断触发方式** ,函数输入的参数为 **XUARTPS_IXR_RXOVR** ,**表示达到 RxFIFO 的触发等级** 时,开始触发中断,当然也可以设置成 RxFIFO 为满时触发中断或者为空时触发中断等。最后,通过 **XScuGic_Enable** 函数来**使能 GIC** 中的串口中断。
在代码的第 42 行至第 63 行为 **UART 中断处理函数** ,由于 RxFIFO 的触发等级设置为 1,因此每次接收到数据都会进入此中断函数。程序中首先**读取中断 ID 寄存器** ,判断触发的是**哪种中断** ,再读取**中断的状态** 。 当判断中断标志位为 **RxFIFO 触发中断** 时,通过 **XUartPs_RecvByte** 函数来**读取接收到的数据** ,并**清除对应的中断标志位** 。最后通过 **XUartPs_SendByte** 函数**发送接收到的数据** ,实现串口环回的功能。
注:首先程序会对 UART 串口进行初始化,我们知道,当使用一些打印函数的时候(如:xil_printf()),实际上调用的还是 UART 相关的 API
函数,如果在初始化的过程中, 使用打印函数,或者在打印的过程中对串口进行初始化,都会导致串口助手打印信息出错。
四.定时器中断实验
MPSOC 中 PS 部分包含许多不同类型的定时器,包括**全局定时器、TTC 定时器、系统看门狗定时器** 等。 定时器可以不受 CPU 的干预,自己独立运行,来完成计时、定时、中断以及计算来自 MIO 或 EMIO 引脚 的信号脉冲宽度等。本章我们将向大家介绍 **TTC**(**三路定时器**)以 TTC(三路定时器)**中断** 的使用方法。
1.简介
本文转自 https://blog.csdn.net/qq_32971095/article/details/136352300,如有侵权,请联系删除。