本帖最后由 正点原子 于 2020-10-16 16:41 编辑
1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:
6)关注正点原子公众号,获取最新资料
第四章按键中断实验
中断是一种当满足要求的突发事件发生时通知处理器进行处理的信号。中断可以由硬件处理单元和外部设备产生,也可以由软件本身产生。对硬件来说,中断信号是一个由某个处理单元产生的异步信号,用来引起处理器的注意。对软件来说,中断还是一种异步事件,用来通知处理器需要改变代码的执行,不过轮询所产生的中断的过程是同步的。
本章我们将学习AXI GPIO接口中断信号的使用,包括以下几个部分:
44.1 简介
4.2实验任务
4.3硬件设计
4.4程序设计
4.5下载验证
4.1简介
在实验之前我们需要了解一下AXI INTC这个IP核,AXI INTC的全称是AXI Interrupt Controller(AXI中断控制器),AXI中断控制器(INTC)核能将来自外围设备的多个中断输入集中到单个中断输出,再将中断传输给系统处理器。通过用于AMBA(高级微控制器总线体系结构高级可扩展接口)协议的AXI规范的从属接口访问用于检查,启用和确认中断的寄存器。AXI INTC(中断控制器)具有以下特点:
AXI INTC IP核可以通过AXI4-Lite接口访问,最高支持32个中断,中断控制器之间可以级联产生其他的中断信号并且支持快速中断模式。
中断控制器的每个输入或输出都可以配置为电平或边缘敏感。中断信号间的优先级由向量的位决定,最低有效位(LSB,在本例中为位0)具有最高优先级。
中断控制器中包含中断使能寄存器,能够有选择地使能单个中断输入。该IP也可以配置软件中断功能,还能中断嵌套。
图 4.1.1是AXI INTC IP核的顶层框图。AXI INTC IP核主要功能就是接收外部输入的中断信号,检测到中断后,输出一个中断请求信号,而且处理系统可以通过AXI接口对AXI INTC IP核进行配置。下面我们将简单介绍AXI INTC IP核的三个主要模块。
图 4.1.1 AXI INTC IP 核顶层框图
Regs Block(Registers Block即寄存器模块):此模块包含控制寄存器和状态寄存器。它们都通过AXI4-Lite从接口访问,每个寄存器均为四字节。
Int Det(Interrupt Detection即中断检测):此模块用于检测中断输入。它可以配置每个中断输入为电平或边缘触发。
Irq Gen(Interrupt Generation即中断生成):此模块具有以下功能:(1)从中断控制器生成最终输出中断。(2)中断灵敏度由配置参数决定。(3)检查控制寄存器(MER和IER)中用于中断生成的启用条件。(4)在确认后重置中断。(5)在IVR(中断向量)寄存器中写入活动中断的向量地址,并为挂起的中断启用IPR寄存器。
4.2实验任务
本章的实验任务是通过AXI GPIO检测按键状态产生中断信号,中断控制器检测到中断后,给处理器发送中断请求,处理器通过接收到的中断控制LED灯的亮灭。
4.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
图 4.3.1 系统框图
系统框图中,按键KEY作为AXI GPIO的输入,LED作为AXI GPIO的输出。当AXI GPIO检测到按键状态发生变化时,AXI GPIO就会产生一个中断信号传入中断控制器(AXI Interrupt Controller),中断控制器生成中断输出信号,传入MicroBlaze处理器,MicroBlaze处理器通过接收到的中断信号控制LED。中断控制器通过AXI Interconnect与MicroBlaze互联,MicroBlaze可以通过AXI接口对中断控制器进行配置。
我们打开《Hello World》实验中的Vivado工程“hello_world”,打开后在菜单栏中依次点击“File->Project->Save As…”,如图 4.3.2所示:
图 4.3.2 选择另存为
在弹出的对话框中输入新的工程名,在Project name一栏输入工程名“key_interrupt”,工程位置保持默认即可,点击“OK”。如图 4.3.3所示:
图 4.3.3 工程另存为
此时我们就在hello_world工程的基础上得到了一个新的工程key_interrupt,这样就省去了重新搭建硬件的过程。接下来我们将在《Hello World》实验中硬件设计的基础上搭建本次实验的硬件平台。
根据实验任务,我们是要通过按键产生中断信号控制LED,之前的《Hello World》实验我们已经添加了AXI Uartlite IP核,在本次实验中我们还需添加一个连接按键的AXI GPIO IP核和一个连接LED的AXI GPIO IP核,还要添加一个AXI INTC(中断控制器)IP核。打开Block Design添加以下两个AXI GPIO IP核,其中“axi_gpio_0”设置位宽为1(一个按键),并打开中断使能,如图 4.3.4所示;“axi_gpio_1”位宽设置为4(四个LED),但并不打开中断功能,如图 4.3.5所示:
图 4.3.4 axi_gpio_0设置
图 4.3.5 axi_gpio_1设置
接着点击继续“+”按钮,在弹出的搜索框输入“intc”,双击“AXI Interrupt Cotroller”添加该IP,如图 4.3.6所示:
图 4.3.6 添加AXI INTC(中断控制器) IP核
添加IP后我们双击该IP,进入配置页面,在本次实验中我们不需要做任何配置,直接点击“OK”,图 4.3.7是AXI GPIO IP核的Basic页面示图:
图 4.3.7 设置AXI GPIO IP核
在这里我们简单介绍一下各个配置选项的功能:
Number of Peripheral Interrupts(Auto):外围中断设备数量。此选项允许选择外围中断输入的数量。在IP Integrator中,此值由连接中断信号的数量自动确定。
Enable Fast Interrupt Logic:使能快速中断逻辑。此选项使AXI INTC能够在快速中断模式下工作。在这种模式下,AXI INTC使用interrupt_address信号提供中断向量地址,处理器通过processor_ack信号确认中断。当选择单信号中断输出时,不可用快速中断模式。
Peripheral Interrupts Type:外部设备中断类型,该选项用于设置输入的中断信号。
Interrupts type - Edge or Level:中断类型-边缘或电平。此选项用于将输入中断设置为边缘或电平类型。0表示电平类型,1表示边缘类型。
Level type - High or Low:电平类型-高电平或低电平。0表示低电平,1表示高电平。
Edge type - Rising or Falling:边缘类型-上升沿或下降沿。0表示下降沿,1表示上升沿。
需要注意的是Interrupts type - Edge or Level、Level type - High or Low和Edge type - Rising or Falling的值通常由连接的中断信号自动确定,但在必要时也可以手动设置。因此本实验中我们没有对AXI GPIO IP核进行配置。
Processor Interrupt Type and Connection:处理器中断类型和连接。该目录用于设置中断控制器的输出信号。在该目录可以设置中断类型为电平或边缘类型,以及触发方式。Interrupt Output Connection(中断输出连接)选项用于设置中断控制器输出的中断信号是单信号还是总线类型的信号。
我们将AXI Interrupt Controller的输出信号“interrupt”接口与MicroBlaze的“INTERRUPT”接口相连,将新添加的AXI GPIO的“ip2intc_irpt”接口与AXI Interrupt Controller的intr[0:0]接口相连。如图 4.3.8所示:
图 4.3.8 连接中断
然后点击空白处上方按钮“Run Connection Automation”,在弹出的界面选中所有信号,点击“OK”。如图 4.3.9所示:
图 4.3.9 信号全选
连线完成后我们在Diagram页面的菜单栏选择“Regenerate Layout”重新布局,如图 4.3.10所示:
图 4.3.10 重新生成布局
最后将“gpio_rtl_0”接口名称改为“key”,“gpio_rtl_1”接口名称改为“led”,最终的硬件设计连线图,如图 4.3.11所示:
图 4.3.11 整体系统架构连接图
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl+S”保存设计。此时我们双击AXI Interrupt Controller IP核打开配置页面我们可以看到,AXI Interrupt Controller IP核的中断输入已经设置为了高电平敏感,如图 4.3.12所示,这是因为AXI GPIO产生的中断信号是一个高电平信号。
图 4.3.12 输入中断自动设置
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
添加约束文件:打开system_wrapper.xdc文件删除之前的约束文件,添加如下管脚约束:
- create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
- set_property PACKAGE_PIN R4 [get_ports sys_clk]
- set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
- set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
- set_property PACKAGE_PIN U2 [get_ports sys_rst_n]
- set_property IOSTANDARD LVCMOS33 [get_ports UART_rxd]
- set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
- set_property PACKAGE_PIN U5 [get_ports UART_rxd]
- set_property PACKAGE_PIN T6 [get_ports UART_txd]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[3]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[2]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[1]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[0]}]
- set_property IOSTANDARD LVCMOS33 [get_ports {key_tri_io[0]}]
- set_property PACKAGE_PIN Y2 [get_ports {led_tri_io[3]}]
- set_property PACKAGE_PIN V2 [get_ports {led_tri_io[2]}]
- set_property PACKAGE_PIN R3 [get_ports {led_tri_io[1]}]
- set_property PACKAGE_PIN R2 [get_ports {led_tri_io[0]}]
- set_property PACKAGE_PIN T1 [get_ports {key_tri_io[0]}]
复制代码
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成Bitstream之后,在菜单栏中依次点击“File->Export->Export hardware”导出硬件,并在弹出的对话框中,勾选“Include bitstream”,在导出路径最后添加“/vitis”。然后在菜单栏依次点击“Tools->Launch Vitis”,点击“Browse…”选择工程“key_interrupt”下vitis文件夹作为软件设计的工作空间,点击“Lanch”,启动Vitis软件。启动Vitis如图 4.3.13所示:
图 4.3.13 设置工作空间
4.4软件设计
在将硬件导出至Vitis,并打开Vitis开发环境后,创建应用工程的步骤都是一样的,这里不再赘述,新创建的空白应用工程命名为“key_interrupt”。
首先我们在key_interrupt/src目录上右键,依次点击“New->Source File”,将源文件命名为“main.c”。
我们在新建的main.c文件中输入以下代码:
- 1 #include "xparameters.h"
- 2 #include "xintc.h"
- 3 #include "xgpio.h"
- 4 #include "sleep.h"
- 5
- 6 #define KEY_DEV_ID XPAR_AXI_GPIO_0_DEVICE_ID //按键 AXI GPIO ID
- 7 #define LED_DEV_ID XPAR_AXI_GPIO_1_DEVICE_ID //LED AXI GPIO ID
- 8 #define INTC_DEVICE_ID XPAR_INTC_0_DEVICE_ID //中断控制器ID
- 9 #define EXCEPTION_ID XIL_EXCEPTION_ID_INT //中断异常ID
- 10 #define AXI_GPIO_INTR_ID XPAR_INTC_0_GPIO_0_VEC_ID //AXI GPIO中断ID
- 11
- 12 static XIntc Intc; //中断控制器实例
- 13 static XGpio KEY_Gpio; //GPIO中断实例 按键
- 14 static XGpio LED_Gpio; //GPIO实例
- 15
- 16 int led_value; //LED值
- 17 int key_value; //按键值
- 18 int Intr_times = 0; //有效中断计数
- 19 int key_intr_flag = 0; //中断标志
- 20
- 21 void GpioHandler(void *CallbackRef);
- 22 int GpioSetupIntrSystem(XIntc *IntcInstancePtr, XGpio *InstancePtr,
- 23 u16 IntrId, u16 IntrMask);
- 24
- 25 int main(){
- 26 xil_printf("AXI GPIO INTERRUPT TEST!\r\n");
- 27 //AXI_GPIO器件初始化
- 28 XGpio_Initialize(&KEY_Gpio, KEY_DEV_ID);
- 29 XGpio_Initialize(&LED_Gpio, LED_DEV_ID);
- 30 //为指定的GPIO信道设置所有独立信号的输入/输出方向
- 31 XGpio_SetDataDirection(&LED_Gpio, 1, 0);
- 32 //设置LED初始值
- 33 XGpio_DiscreteWrite(&LED_Gpio, 1, 0x0f);
- 34 XGpio_SetDataDirection(&KEY_Gpio, 1, 1);
- 35 //初始化中断控制器
- 36 XIntc_Initialize(&Intc, INTC_DEVICE_ID);
- 37 //关联中断ID和中断服务函数
- 38 XIntc_Connect(&Intc, AXI_GPIO_INTR_ID,(Xil_ExceptionHandler)GpioHandler,&KEY_Gpio );
- 39 //使能中断
- 40 XGpio_InterruptEnable(&KEY_Gpio, 1);
- 41 //使能全局中断
- 42 XGpio_InterruptGlobalEnable(&KEY_Gpio);
- 43 //在中断控制器上启用中断向量
- 44 XIntc_Enable(&Intc,AXI_GPIO_INTR_ID);
- 45 //启动中断控制器
- 46 XIntc_Start(&Intc, XIN_REAL_MODE);
- 47 //设置并打开中断异常处理
- 48 Xil_ExceptionInit();
- 49 Xil_ExceptionRegisterHandler(EXCEPTION_ID,
- 50 (Xil_ExceptionHandler)XIntc_InterruptHandler,&Intc);
- 51 Xil_ExceptionEnable();
- 52
- 53 while(1){
- 54 if(key_intr_flag){ //检测中断标志信号有效
- 55 key_value = XGpio_DiscreteRead(&KEY_Gpio, 1); //读取按键值
- 56 if(key_value == 0){ //检测按键是否按下
- 57 if(Intr_times == 0) //根据有效中断数点亮LED
- 58 led_value = 0x01;
- 59 else if(Intr_times == 1)
- 60 led_value = 0x02;
- 61 else if(Intr_times == 2)
- 62 led_value = 0x04;
- 63 else
- 64 led_value = 0x08;
- 65 //按键按下后点亮对应LED灯
- 66 XGpio_DiscreteWrite(&LED_Gpio, 1, led_value);
- 67 xil_printf("i = %d\r\n",Intr_times); //打印当前的Intr_times
- 68 Intr_times = (Intr_times + 1)%4; //将计数值约束在0到3之间
- 69 //延迟1秒
- 70 sleep(1);
- 71 }
- 72 key_intr_flag = 0; //中断标志清零
- 73 }
- 74 }
- 75 return 0;
- 76 }
- 77
- 78 void GpioHandler(void *CallbackRef){
- 79 XGpio *GpioPtr = (XGpio *)CallbackRef;
- 80 key_intr_flag = 1; //接收到中断,标志信号拉高
- 81 XGpio_InterruptDisable(GpioPtr, 1); //关闭中断
- 82 XGpio_InterruptClear(GpioPtr, 1); //清除中断
- 83 XGpio_InterruptEnable(GpioPtr, 1); //使能中断
- 84 }
复制代码
在这里我们来了解一下这段代码。程序1到4行是四个头文件,在接下来的代码中我们将会调用这四个头文件中的函数进行代码设计;代码第6到10行我们定义了一系列ID,包括按键和LED对应的AXI GPIO的ID,中断控制器ID,中断源ID以及中断异常ID。中断源就是指连接按键的AXI GPIO输出到中断控制器的中断信号。中断异常ID(XIL_EXCEPTION_ID_INT)是一个适配所有处理器的宏定义,但在不同处理器中它对应的的值也不同,我们将其宏定义为EXCEPTION_ID如代码第9行所示。
在主函数部分可以分为器件初始化、设置输入输出、设置中断系统和中断异常处理四部分。
代码28和29行分别对按键和LED连接的AXI GPIO进行了初始化。
代码第31行我们设置LED为输出,代码第34行我们设置按键KEY为输入,在第33行给LED赋初值为0x0f,对应到开发板上就是四个LED都点亮。
代码第36至46行是设置中断系统部分。首先代码第36行初始化中断控制器,代码第38行XIntc_Connect函数将中断服务函数(GpioHandler)和中断源(AXI_GPIO_INTR_ID)关联起来。中断服务函数GpioHandler是需要我们自己编写的,用于响应和处理AXI GPIO中断的函数。接着使能AXI GPIO通道中断和AXI GPIO全局中断,只有全局中断使能后XGpio_InterruptEnable启用的中断才会被传递。代码第44行使能中断控制器,将中断控制器与中断源连接。代码第46行开启中断控制器。
代码第48到51行为中断异常处理。首先初始化处理器异常处理,如代码第48行。然后关联中断异常处理函数和中断异常ID,代码第49、50行,MicroBlaze支持多种异常情况,每种异常也都有自己的ID标识,其中XIL_EXCEPTION_ID_INT用于标识中断请求(IRQ)异常,在代码第9行将其宏定义为EXCEPTION_ID,其对应的值为0。代码第51行就是使能处理器的异常处理。
代码第78到83行是中断服务函数部分。当系统接收到中断信号时就将中断标志信号key_intr_flag拉高,然后用XGpio_InterruptDisable函数关闭中断,再调用XGpio_InterruptClear(GpioPtr, 1)函数函数清除AXI GPIO通道1的中断状态寄存器,如程序第82行所示。最后再使能AXI GPIO中断以等待下一次中断触发。
在while语句部分,当检测到中断标志信号key_intr_flag拉高后,读取按键对应通道的值(key_value),当key_value为0时说明按键按下,此时中断计数Intr_times加1;代码57到64行是根据Intr_times的值给led_value的赋值,由于开发板上只有四个LED,所以我们将Intr_times的范围约束在0到3之间,如代码65行所示。当检测到按键按下时,就点亮LED灯并打印Intr_times的值,延迟1秒进行消抖,最后将中断标志信号key_intr_flag清零,等待下次中断到来。
右键“key_interrupt_system”,点击“Build Project”编译工程,生成elf文件。
4.5下载验证
首先我们将下载器与达芬奇开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
图 4.5.1达芬奇开发板连接图
在Vitis软件下方的Vitis Terminal窗口中点击右上角的加号连接串口,并在弹出的窗口中按照之前的工程对对串口进行设置,连接好串口,下载程序(与之前工程操作相同,不再赘述)。
软件程序下载完成后,在下方的Vitis Terminal中可以看到应用程序打印的信息,开发板4个LED灯全亮。如图 4.5.2所示:
图 4.5.2 串口终端中打印的信息
每按下按键KEY0,可以看到Terminal打印的Intr_times的值,如图 4.5.3所示:
图 4.5.3 打印Intr_times值
每按下KEY0,可以看到开发板上的LED灯依次被点亮。说明我们通过按键中断控制LED的实验下载验证成功。实验结果如下图所示:
图 4.5.4 LED灯依次被点亮
|