Xilinx zynq嵌入式设计,vitis使用之中断设计。例程来源——正点原子zynq7020。
内容:PS 端的用户按键 PS_KEY1 通过中断控制核心板上 LED2 的亮灭

#include <xparameters.h>
#include <xgpiops.h>
#include <xscugic.h>
#include <xil_exception.h>
#include <xplatform_info.h>
#include <xil_printf.h>
#include <sleep.h>

#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

#define KEY 11
#define LED 0

static void intr_handler(void *callback_ref);
int setup_interrupt_system(XScuGic *gic_ins_ptr, XGpioPs *gpio, u16 GpioIntrId);

XGpioPs gpio;
XScuGic intc;
u32 key_press;
u32 key_val;

int main(void){
	int status;
	XGpioPs_Config *ConfigPtr;

	xil_printf("Gpio interrupt test \r\n");

	ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
	if (ConfigPtr == NULL){
		return XST_FAILURE;
	}
	XGpioPs_CfgInitialize(&gpio, ConfigPtr, ConfigPtr -> BaseAddr);
	XGpioPs_SetDirectionPin(&gpio, KEY, 0);
	XGpioPs_SetDirectionPin(&gpio, LED, 1);
	XGpioPs_SetOutputEnablePin(&gpio, LED, 1);
	XGpioPs_WritePin(&gpio, LED, 0x0);

	status = setup_interrupt_system(&intc, &gpio, GPIO_INTERRUPT_ID);
	if (status != XST_SUCCESS){
		xil_printf("Setup interrupt system failed\r\n");
		return XST_FAILURE;
	}

	while (1) {
		if(key_press){
			usleep(20000);
			if (XGpioPs_ReadPin(&gpio, KEY) == 0) {
				key_val = ~key_val;
				XGpioPs_WritePin(&gpio, LED, key_val);
			}
			key_press = FALSE;
			XGpioPs_IntrClearPin(&gpio, KEY);
			XGpioPs_IntrEnablePin(&gpio, KEY);
		}
	}
	return XST_SUCCESS;
}

static void intr_handler(void *callback_ref){
	XGpioPs *gpio = (XGpioPs *) callback_ref;
	if (XGpioPs_IntrGetStatusPin(gpio, KEY)){
		key_press = TRUE;
		XGpioPs_IntrDisablePin(gpio, KEY);
	}

}

int setup_interrupt_system(XScuGic *gic_ins_ptr, XGpioPs *gpio, u16 GpioIntrId)
 {
	 int status;
	 XScuGic_Config *IntcConfig;
	
	 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	 if (NULL == IntcConfig) {
		 return XST_FAILURE;
	 }
	
	 status = XScuGic_CfgInitialize(gic_ins_ptr, IntcConfig, IntcConfig->CpuBaseAddress);
	 if (status != XST_SUCCESS) {
		 return XST_FAILURE;
	 }
	
	 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, gic_ins_ptr);
	 Xil_ExceptionEnable();
	
	 status = XScuGic_Connect(gic_ins_ptr, GpioIntrId, (Xil_ExceptionHandler) intr_handler, (void *) gpio);
	 if (status != XST_SUCCESS) {
		 return status;
	 }
	
	 XScuGic_Enable(gic_ins_ptr, GpioIntrId);
	
	 XGpioPs_SetIntrTypePin(gpio, KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
	
	 XGpioPs_IntrEnablePin(gpio, KEY);

	 return XST_SUCCESS;
}

为了方便之后学习,现在将学习过程总结一下。
首先,在vitis原生编辑界面进行编程体验非常不友好,需要alt+?才能启用提示,考虑使用visual studio进行编程。参考博客ZYNQ 笔记(一):开发流程,这篇博客最后使用vscode进行编程,因为没有我这边没有配置成功所以转而使用vs。
基本步骤一样,在这里插入图片描述
右键使用vs打开vitis文件夹,进入之后无需过多配置,系统自动识别编译环境,并将Xilinx库导入,因此可直接进行代码编写而不会报错。编写完成保存在vitis里再次打开项目进行编译即可。

main代码分析

#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

以上代码段定义系统参量,方便后续修改。

#define KEY 11
#define LED 0

以上代码块定义链接的MIO引脚。

XGpioPs gpio;
XScuGic intc;
u32 key_press;
u32 key_val;

以上代码定义了几个类型,这几个类型并不是c标准库中的,而是属于Xilinx官方定义的标准库,其中u32u16可以在xil_types.h库中找到,XGpioPs可以在xgpiops.h库中找到,用于控制gpio,XScuGic可以在xscugic.h库中找到,用于进行中断设计。
下面分析main函数体。定义int类型status用来保存信息识别状态,之后是
XGpioPs_Config *ConfigPtr;
XGpioPs_Config属于xgpiops.h库中定义的结构体,结构属于c/c++概念,用于存储一些相关的数据项。这里用于存储设备信息。

typedef struct {
	u16 DeviceId;		/**< Unique ID of device */
	u32 BaseAddr;		/**< Register base address */
} XGpioPs_Config;

*ConfigPtr用来指向设备信息位置。

xil_printf("Gpio interrupt test \r\n");

上面的代码用来打印输出,其中xil_printf隶属于xil_printf.h库,同样是Xilinx官方定义库。在Xilinx编程中同样可以采用printf函数输出,但由于在FPGA中几乎不会涉及浮点数处理,因此用printf函数处理输出时会将额外的处理过程导入内存,挤占内存资源。xil_printf剔除了浮点数的操作,精简了代码,节约了内存。

ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);

以上XGpioPs_LookupConfig用来查询设备配置,索引来源是设备ID,返回值是XGpioPs_Config指针。如果没有找到则返回NULL

XGpioPs_CfgInitialize(&gpio, ConfigPtr, ConfigPtr -> BaseAddr);

以上XGpioPs_CfgInitialize用来初始化设备。其定义如下:

s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, const XGpioPs_Config *ConfigPtr, u32 EffectiveAddr)

其中XGpioPs用来指向被初始化的设备,本例中使用&gpio用于指向设备(gpio),InstancePtr 是指向XGpioPs实例的指针,本例中就是上面刚刚返回的ConfigPtrEffectiveAddr指向设备在虚拟内存中的基地址(base address in the virtual memory address space),本例中通过访问XGpioPs_Config结构体中的BaseAddr成员来获得地址。
以上代码便完成了对设备状态的基本配置。

XGpioPs_SetDirectionPin(&gpio, KEY, 0);
XGpioPs_SetDirectionPin(&gpio, LED, 1);
XGpioPs_SetOutputEnablePin(&gpio, LED, 1);
XGpioPs_WritePin(&gpio, LED, 0x0);

以上代码用以设置管脚方向和使能。XGpioPs_SetDirectionPin的定义为:

void XGpioPs_SetDirectionPin(const XGpioPs *InstancePtr, u32 Pin, u32 Direction)

*InstancePtr 是指向XGpioPs实例的指针,在这里是&gpio,用于指向当前设备;u32类型的Pin用来指示要写入数据的引脚号,在Zynq中有效值为0-117,本例中使用先前定义的KEY,指代11号管脚;Direction 是要为指定引脚设置的方向。有效值为0表示输入方向,1表示输出方向,我的理解是这里KEY被用作外界的输入,LED作为程序的输出。
XGpioPs_SetOutputEnablePin需要紧接着使用,这是XGpioPs_SetDirectionPin的“配套服务”。它的定义为:

void XGpioPs_SetOutputEnablePin(const XGpioPs *InstancePtr, u32 Pin, u32 OpEnable)

定义前两项和XGpioPs_SetDirectionPin一致;OpEnable 指定是否应启用指定引脚的输出使能,有效值为0表示禁用输出使能,1表示启用输出使能。这里只对LED进行了设置,因为我们只需对LED进行控制,而不需要对KEY进行控制(或者说KEY是我们需要人为去控制的,而不是通过程序操控,因此在这里无需在该程序块中对KEY进行设置),设置为1即表示Enabling Output Enable(输出使能开启),通俗的解释就是“我们现在可以对它进行操作了”。
GpioPs_WritePin用以写入数据,其定义为:

void XGpioPs_WritePin(const XGpioPs *InstancePtr, u32 Pin, u32 Data)

Data 是要写入到指定引脚的数据(01),本例中是对LED写入0x0,即最开始时LED灯是灭的。
下面,开始对中断进行设置:

status = setup_interrupt_system(&intc, &gpio, GPIO_INTERRUPT_ID);
if (status != XST_SUCCESS){
	xil_printf("Setup interrupt system failed\r\n");
	return XST_FAILURE;
}

setup_interrupt_system是自定义的中断函数,参考了官方示例中的配置方法,在本例中其定义为:
int setup_interrupt_system(XScuGic *gic_ins_ptr, XGpioPs *gpio, u16 GpioIntrId)
官方函数只是与其名称不同:
int GpioIntrExample(XScuGic *Intc, XGpioPs *Gpio, u16 DeviceId, u16 GpioIntrId)
其中,XScuGic *Intc指向配置中断的驱动指针,本例中是&intc,接下来两个参数和上面的函数用法基本一致;GpioIntrIdxparameters.h中的XPAR_<GIC>_<GPIO_Instance>_VEC_ID值,本例中并未用到。我们先把设置的中断函数理清楚:

中断函数分析

int setup_interrupt_system(XScuGic *gic_ins_ptr, XGpioPs *gpio, u16 GpioIntrId)
 {
	 int status;
	 XScuGic_Config *IntcConfig;
	
	//识别中断状态
	 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	 if (NULL == IntcConfig) {
		 return XST_FAILURE;
	 }
	 status = XScuGic_CfgInitialize(gic_ins_ptr, IntcConfig, IntcConfig->CpuBaseAddress);
	 if (status != XST_SUCCESS) {
		 return XST_FAILURE;
	 }
	
	//配置异常
	 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, gic_ins_ptr);
	 Xil_ExceptionEnable();
	
	 status = XScuGic_Connect(gic_ins_ptr, GpioIntrId, (Xil_ExceptionHandler) intr_handler, (void *) gpio);
	 if (status != XST_SUCCESS) {
		 return status;
	 }
	
	//配置中断
	 XScuGic_Enable(gic_ins_ptr, GpioIntrId);
	 XGpioPs_SetIntrTypePin(gpio, KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
	 XGpioPs_IntrEnablePin(gpio, KEY);

	 return XST_SUCCESS;
}

setup_interrupt_system函数中,同样需要首先识别设备并存储设备信息,这里用到XGpioPs_Config *ConfigPtr;,和上面main函数里的XGpioPs_Config *ConfigPtr;用法是很像的,区别在于这里是在中断库xscugic.h中进行调用。之后XScuGic_LookupConfigXScuGic_CfgInitialize的配置更是神似,我们只需照着前面main函数的设置复制就好。接着我们再看一下Xil_ExceptionRegisterHandlerXil_ExceptionEnable,这是Xilinx公司专门设计的用于异常处理的函数,Xil_ExceptionRegisterHandler的定义为:

void Xil_ExceptionRegisterHandler(u32 Exception_id, Xil_ExceptionHandler Handler, void *Data)

其中exception_id包含异常源的ID,应在0XIL_EXCEPTION_ID_LAST的范围内;Handler是该异常的处理程序;Data是对将传递给处理程序的数据的引用。Xil_ExceptionEnable()是异常使能函数,启用IRQ异常,没有参数和返回值。
对于本例而言,这两个函数放在这里就表示,首先将中断异常信息进行“注册”,看是哪种异常,找到对应处理措施,之后开启这种措施来进行应对。另外,在通常的设置中,
XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler总是不变的,因此可以直接照搬。
下面,就是将中断异常源和中断异常处理程序联系起来,当中断发生时会调用设置的中断处理程序:

status = XScuGic_Connect(gic_ins_ptr, GpioIntrId, (Xil_ExceptionHandler) intr_handler, (void *) gpio);
if (status != XST_SUCCESS) {
 return status;
}

XScuGic_Connect函数的定义为:

s32  XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id, Xil_InterruptHandler Handler, void *CallBackRef)

InstancePtr是指向XScuGic实例的指针,本例中是gic_ins_ptr,即我们希望定义的中断识别的设备状态。Int_Id包含中断源的ID,应在0XSCUGIC_MAX_NUM_INTR_INPUTS - 1的范围内;Handler是该中断的处理程序;CallBackRef是回调引用,通常是连接驱动程序的实例指针。

XScuGic_Enable(gic_ins_ptr, GpioIntrId);

上面的代表示为指定的中断提供中断源。

XGpioPs_SetIntrTypePin(gpio, KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);

上面表示设置单个GPIO引脚的IRQ类型,在本例中就表示我们对KEY设置的中断识别类型是怎样的,而从该函数的原始定义可以看到,
#define XGPIOPS_IRQ_TYPE_EDGE_FALLING 0x01U /**< Interrupt Falling edge */
即我们对KEY识别下降沿中断。

XGpioPs_IntrEnablePin(gpio, KEY);

上面的代码启动中断。
我们可以将这个中断配置总结为:
识别设备中断状态→注册并使能异常处理手段信息→配置中断并使能

继续回到主程序

下面这段代码

while (1) {
	if(key_press){
		usleep(20000);
		if (XGpioPs_ReadPin(&gpio, KEY) == 0) {
			key_val = ~key_val;
			XGpioPs_WritePin(&gpio, LED, key_val);
		}
		key_press = FALSE;
		XGpioPs_IntrClearPin(&gpio, KEY);
		XGpioPs_IntrEnablePin(&gpio, KEY);
	}
}
return XST_SUCCESS;

就没有什么特别难以理解的地方了,顺便提一下XGpioPs_SetIntrTypePinXGpioPs_IntrClearPinXGpioPs_IntrEnablePin,因为长得比较像,我们不如放在一起对比一下

void XGpioPs_SetIntrTypePin(const XGpioPs *InstancePtr, u32 Pin, u8 IrqType) //设置单个GPIO引脚的IRQ类型
void XGpioPs_IntrClearPin(const XGpioPs *InstancePtr, u32 Pin) //清除指定的待处理中断
void XGpioPs_IntrEnablePin(const XGpioPs *InstancePtr, u32 Pin)  //启用指定引脚中断

基本上感觉就是一套设计的基本组件,都得包含。

用户层中断回调

static void intr_handler(void *callback_ref){
	XGpioPs *gpio = (XGpioPs *) callback_ref;
	if (XGpioPs_IntrGetStatusPin(gpio, KEY)){
		key_press = TRUE;
		XGpioPs_IntrDisablePin(gpio, KEY);
	}
}

至于上面这段代码,用来检查是否所有的开关都已被按下以停止中断处理并退出示例。

作为中断处理的入门阶段,目前对于很多代码还有不太了解的地方,仍然需要不停的学习和总结。
希望未来再回头看这些笔记,那时的自己已经入门了吧。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐