Vitis开发二——FPGA学习笔记<?>

参考:《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_handlerXUartPs_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.简介

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

switch_swq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值