EBAZ4205 第二十二个工程基于PS端的 外部中断测试

前一节的按键的代码,是在主程序中不停的轮回去扫描IO的状态来达到按键检测的目的,对于小型的系统来说没什么问题,但是对于大型复杂的系统程序来说,如果main的主循环中要处理很多事务,对于IO的检测或者说对于外部事件的触发就不能及时的进行响应了,并且按键循环本身也占用了系统资源,这个时候外部中断的重要性就凸显出来了。

外部中断可以让MCU实时地处理外部事件,当外部事件发生时,系统的中断机制将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理;中断处理完毕后.又返回被中断的程序处,继续执行下去。

本文在 vivado2018.3版本上 演示, 其他版本请自行研究

硬件介绍

本实验仍然采用按键点灯的方式进行 外部中断的演示,让按键去触发外部中断的功能,并且在中断函数中增加改变灯状态的命令。

先看原理图,由原理图可以看出板子上接了五个按键,并且默认是通过上拉电阻上拉到3.3V的,也就是当按键没有按下的时候 按键KEY 的信号脚是3.3V ,当按键按下后,该信号脚被拉低到GND也就是0V 同样也能看出 两个按键分别连接到了 FPGA芯片的 T19 P19 U20 U19 和V20上

为了按键的功能,这里再引入指示灯,转接板上一共有3个指示灯如下图所示,将LED的驱动脚拉高,指示灯亮,拉低指示灯熄灭,指示灯分别接在主芯片的 H18 K17和E19脚

实际本次试验只用到了KEY1和KEY3两个按键,灯也只用到了LED1和LED2两个

工程创建

工程几乎和上一个实验完全一致,仅简单介绍。

完整的工程创建图文教程前面已经详细图文描写了,可以参考前面的工程,这里仅仅增加关键步骤。 详细过程可以参考Tiny ZYNQ板 工程七 用ZYNQ的PS点亮连接到PL端的LED灯EMIO 方式

器件名称选择 xc7z010clg400-1

在BLOCK DESIGN 中添加一个ZYNQ模块

DDR的选择 MT41K128M16JT 并选择位宽为16bit

添加4个EMIO ,其中两个用来驱动LED灯,另两个用来读取按键KEY的信息

对GPIO make external,并且连接AXI的时钟(AXI默认开启的,这里用不到AXI,也可以去设置里关闭AXI功能,就不需要连接了)

之后 点选Create HDL Wrapper

之后创建和添加约束文件:

可以在图文界面里设置管脚定义

也可以在约束文件里添加管脚定义的描述

set_property PACKAGE_PIN H18 [get_ports {GPIO_0_0_tri_io[3]}]
set_property PACKAGE_PIN K17 [get_ports {GPIO_0_0_tri_io[2]}]
set_property PACKAGE_PIN T19 [get_ports {GPIO_0_0_tri_io[1]}]
set_property PACKAGE_PIN U20 [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]}]

之后对程序进行正常的编译综合,并且导出 Export Hardware (勾选 include bitstream)供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都增加了中断初始化,这两两个按键中任何一个按键被按下 都会调用中断XGpioPs_SetIntrTypePin(Gpio, KEY1, XGPIOPS_IRQ_TYPE_EDGE_FALLING);代表对KEY1设置下降沿中断。

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;
}

我们将程序下载进板子进行DEBUG,发现KEY1和KEY3两个按键任意一个被按下对应的灯被点亮,证明我们的外部中断已成功运行(因为主程序中并不负责点灯,只有在中断中才负责改变灯的状态)

中断比较简单,但是对应系统性的程序设计来说又必不可少。

以下是完整代码(仅供参考):

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注