前一节的按键的代码,是在主程序中不停的轮回去扫描IO的状态来达到按键检测的目的,对于小型的系统来说没什么问题,但是对于大型复杂的系统程序来说,如果main的主循环中要处理很多事务,对于IO的检测或者说对于外部事件的触发就不能及时的进行响应了,并且按键循环本身也占用了系统资源,这个时候外部中断的重要性就凸显出来了。
外部中断可以让MCU实时地处理外部事件,当外部事件发生时,系统的中断机制将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理;中断处理完毕后.又返回被中断的程序处,继续执行下去。
本章我们将通过GPIO 中断实验来初步认识ZYNQ 的中断系统。
- 此章节内容适用于Lemon ZYNQ主板,如是其他板子请看对应板子目录
- 本文在 vivado2018.3版本上演示
一、硬件介绍
本实验仍然采用按键点灯的方式进行外部中断的演示,让按键去触发外部中断的功能,并且在中断函数中增加改变灯状态的命令。
先看原理图,由原理图可以看出板子上接了四个按键,并且默认是通过下拉电阻下拉到GND的,也就是当按键没有按下的时候 按键KEY 的信号脚是0V ,当按键按下后,该信号脚被拉高到3.3V 。
为了演示按键的功能,这里再在工程中引入LED指示灯,指示灯之前章节的例子里测试过,将LED的驱动脚拉高,指示灯亮,拉低指示灯熄灭。
为了方便代码的演示,这里仅用两个按键,和两个LED进行测试。
二、工程创建
工程创建的过程可以参考试验一中的内容,这里不再详细描述了。基于Lemon ZYNQ的PS实验一 GPIO之用EMIO方式点亮LED(完整图文)(芯片型号选XC7Z020CLG400-1)
三、Vivado 中的设置
1)在BLOCK DESIGN 中搜索并添加一个ZYNQ模块
2)在ZYNQ 设置界面,设置 DDR MT41K256M16RE-125的型号 和位宽16bit
3)因为Lemon ZYNQ主板的PS时钟是50M的晶振输入的,所以这里需要把默认的PS输入时钟33.33M改成50M
4)添加4个EMIO ,其中两个用来驱动LED灯,另两个用来读取按键KEY的信息
5)对GPIO make external,并且连接AXI的时钟(AXI默认开启的,这里用不到AXI,也可以去设置里关闭AXI功能,就不需要连接了)
6)点选Create HDL Wrapper
7)之后创建和添加约束文件:
set_property PACKAGE_PIN P14 [get_ports {GPIO_0_0_tri_io[3]}]
set_property PACKAGE_PIN R14 [get_ports {GPIO_0_0_tri_io[2]}]
set_property PACKAGE_PIN D20 [get_ports {GPIO_0_0_tri_io[1]}]
set_property PACKAGE_PIN D19 [get_ports {GPIO_0_0_tri_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[0]}]
8) 之后对程序进行正常的编译综合,并且导出 Export Hardware (勾选 include bitstream)供SDK加载
四、PS部分程序设计:
在SDK环境下建立一个名为GPIO_INTERRUPT空的工程,并且添加main.c 添加如下代码:
#include "xparameters.h" #include "xgpiops.h" #include "xscugic.h" #include "xil_exception.h" #include "xplatform_info.h" #include <xil_printf.h> #define LED2 57 #define LED1 56 #define KEY2 55 #define KEY1 54 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID XGpioPs Gpio; #define GPIO_BANK XGPIOPS_BANK0 /* Bank 0 of the GPIO Device */ #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID #define GPIO_INTERRUPT_ID XPAR_XGPIOPS_0_INTR static XScuGic Intc; /* The Instance of the Interrupt Controller Driver */ static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status) { XGpioPs *Gpio_cb = (XGpioPs *)CallBackRef; if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY1)){ XGpioPs_WritePin(&Gpio, LED1, 1); XGpioPs_WritePin(&Gpio, LED2, 0); XGpioPs_IntrClearPin(Gpio_cb, KEY1); } else if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY2)){ XGpioPs_WritePin(&Gpio, LED1, 0); XGpioPs_WritePin(&Gpio, LED2, 1); XGpioPs_IntrClearPin(Gpio_cb, KEY2); } } void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId){ XScuGic_Config *IntcConfig; Xil_ExceptionInit(); IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); XScuGic_CfgInitialize(GicInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr); XScuGic_Connect(GicInstancePtr, GpioIntrId, (Xil_ExceptionHandler)IntrHandler, (void *)Gpio); XScuGic_Enable(GicInstancePtr, GpioIntrId); XGpioPs_SetIntrTypePin(Gpio, KEY1, XGPIOPS_IRQ_TYPE_EDGE_FALLING); XGpioPs_SetIntrTypePin(Gpio, KEY2, XGPIOPS_IRQ_TYPE_EDGE_FALLING); XGpioPs_IntrEnablePin(Gpio, KEY1); XGpioPs_IntrEnablePin(Gpio, KEY2); Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); } void Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, LED1, 1); XGpioPs_SetOutputEnablePin(&Gpio, LED1, 1); XGpioPs_WritePin(&Gpio, LED1, 1); XGpioPs_SetDirectionPin(&Gpio, LED2, 1); XGpioPs_SetOutputEnablePin(&Gpio, LED2, 1); XGpioPs_WritePin(&Gpio, LED2, 1); XGpioPs_SetDirectionPin(&Gpio, KEY1, 0); XGpioPs_SetDirectionPin(&Gpio, KEY2, 0); SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID); } int main(void) { Gpio_Init(); while(1){ } return 0; }
程序很简单, Gpio_Init是对4个GPIO进行初始化, 因为EMIO 是从54的管脚号开始的,所以根据原先VIVADO 的管脚约束来看,两个按键应该对应54和55,两个LED灯对应56和57
中断初始化:SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID)是对中断进行初始化函数,在这个函数中这里我们分别对KEY1和KEY2的IO口进行中断的初始化。因为我们硬件上按键IO口没按下时默认电平是低电平的,按下后IO口被拉高,所以这里需要设置成上升沿中断,即XGPIOPS_IRQ_TYPE_EDGE_RISING 。如果硬件是下降沿中断的,则用XGPIOPS_IRQ_TYPE_EDGE_FALLING。
IntrHandler 是中断服务函数,在中断事件产生后对中断进行响应,一般我们中断后需要的动作代码都放在这个函数里面。 (因为我们同时增加了两个外部中断,所以中断回调函数进来第一件事是区分哪个GPIO产生了中断,所以这里用XGpioPs_IntrGetStatusPin命令去读取CallBackRef的值,来判断哪个按键产生了中断,并根据结果点亮不同的LED灯)
static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status) { XGpioPs *Gpio_cb = (XGpioPs *)CallBackRef; if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY1)){ XGpioPs_WritePin(&Gpio, LED1, 1); XGpioPs_WritePin(&Gpio, LED2, 0); XGpioPs_IntrClearPin(Gpio_cb, KEY1); } else if (XGpioPs_IntrGetStatusPin(Gpio_cb, KEY2)){ XGpioPs_WritePin(&Gpio, LED1, 0); XGpioPs_WritePin(&Gpio, LED2, 1); XGpioPs_IntrClearPin(Gpio_cb, KEY2); } }
对于主函数main函数来说,我们只需要完成GPIO初始化(里面包括中断初始化),之后就直接不作任何操作
int main(void) { Gpio_Init(); while(1){ } return 0; }
五、下载与验证
1)新创建的工程最好先对Run as 进行配置:右键工程,并点选Run As -> Run Configurations
2) 在弹出的窗口中对Reset entire system 和 Program FPGA两个选项进行勾选操作,这样就不会出现下载程序debug的时候概率性不工作的问题了。(这样操作后系统会自动对FPGA进行配置,不需要按之前工程手动对FPGA进行编程了)
PS如果没有出现下图对话框,可以直接双击左侧的applicationo (System Debugger)。
3)设置好之后,选择Run As
-> Launch on Hardware (System Debugger)
进行调试。
下载成功后会发现两个按键(BTN0,BTN1)任意一个被按下对应的LED灯灯会被点亮,证明我们的外部中断已成功运行(因为主程序中并不负责点灯,只有在中断中才负责改变灯的状态)。
中断比较简单,但是对应系统性的程序设计来说又必不可少。
- 本文的完整工程下载:04_PS_GPIO_INTERRUPT
- VIVADO的版本:2018.3
- 工程创建目录: E:\Lemon_ZYNQ\SDK\04_PS_GPIO_INTERRUPT