ZYNQ MIO中断配置实现与中断响应过程

中断结构

在进行中断相关的程序编写之前,首先需要了解zynq的中断框图。
这部分内容建议直接看xilinx官方手册ug585的第7章,里面有非常详细的介绍。xilinx文档可以直接用DocNav查看,会很方便。
中断框图
从整体框图中可以看到,中断的来源主要分为三个部分,分别是软件中断私有外设中断共享外设中断。这里的顺序也是它中断ID编号的顺序,从前往后依次增大,总共有81个中断ID。

  • 软件中断ID 0~15
  • 私有外设中断ID 27 ~ 31,编号 16 ~ 26的ID是保留的
  • 共享外设中断ID 32 ~ 91

这些中断源送进通用中断控制器(GIC)后再由GIC分配给两个CPU。手册中还有很多关于这三类中断的详细介绍。
有一个很好的地方是,共享外设中断虽然说是两个CPU共享的,但是只有其中一个CPU会进行处理,而不需要额外的加互斥锁之类的操作。
比较让我在意的是这里的共享外设中断除了那几个来自PL的中断可以自定义中断的触发类型,而其他的中断都是固定的触发类型。然而我们要用的GPIO中断可能会有不同的应用场景,这要是不能配置触发类型就很奇怪。后来发现这部分是在GPIO相关的寄存器中进行配置的。
6个ICD ICFR寄存器用来配置不同中断的触发类型,其中SGI和PPI中断的触发类型是固定的,所以是不能改的,寄存器只读。SPI的中断有两种配置选项,高电平触发或者是上升沿触发。
几个ICD IPTR寄存器用来设置中断目标,除了PPI的中断目标是固定的,SGI和SPI的中断目标都是可以配置的。

MIO中断实现

基本配置

Vivado的Block Design部分基本不需要什么配置,只要有GPIO就行。硬件上按键引脚上拉,按键按下后变成低电平,设计上采用下降沿触发。实验后发现应该是硬件上自带有消抖的功能,在软件中没有另外进行按键消抖处理也能正常使用。

#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_NUMBER 50
#define LED_NUMBER 0

这个案例中,中断相关的头文件要包含"xgpiops.h"和"xscugic.h",主要对GPIO和GIC进行配置。宏定义的设备ID用来后面查找设备,这里用到的GPIO的引脚比较少,可以直接对单个引脚进行配置,而不需要对整个GPIO Bank进行配置。
GPIObankj
有些GPIO的函数需要用到Bank号,但只用引脚编号(0-117),就可以不需要用到Bank号,直接对单个IO进行配置。

	static XGpioPs Gpio; /* The Instance of the GPIO Driver */
	static XScuGic Intc; /* The Instance of the Interrupt Controller Driver */

GPIO和GIC的实例是全局唯一的,所以声明为全局变量。

	XGpioPs_Config *ConfigPtr;
	int Status;
	ConfigPtr = XGpioPs_LookupConfig(DeviceId);
	if (ConfigPtr == NULL) {
		return XST_FAILURE;
	}
	XGpioPs_CfgInitialize(Gpio, ConfigPtr, ConfigPtr->BaseAddr);
	Status = XGpioPs_SelfTest(Gpio);
	if( Status != XST_SUCCESS ){
		return XST_FAILURE;
	}
	XScuGic_Config *IntcConfig;
	int Status;
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	if (NULL == IntcConfig) {
		return XST_FAILURE;
	}
	Status = XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
					IntcConfig->CpuBaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

GPIO和GIC的初始化是类似的,都是先根据设备ID查找设备,再调用初始化函数。

	XGpioPs_SetDirectionPin(Gpio, KEY_NUMBER, 0);// input
	
	XGpioPs_SetDirectionPin(Gpio, LED_NUMBER, 1);// output
	XGpioPs_SetOutputEnablePin(Gpio, LED_NUMBER, 1);
	XGpioPs_WritePin(Gpio, LED_NUMBER, 1);// led off

GPIO口的基本配置,输入输出方向,输出使能,设置输出引脚的初始状态。关于GPIO中断的配置在SetupInterruptSystem函数中实现,在GPIO初始化函数中调用该函数。

	Status = SetupInterruptSystem(Intc, Gpio, GPIO_INTERRUPT_ID);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

中断相关配置

除了对GIC进行初始化,接着主要就是回调函数的绑定。

	Xil_ExceptionInit();

中断处理函数都是由这个“异常”来统一管理的,异常不仅限于IRQ中断,还有系统复位、未定义的中断、FIQ等等。这里的Xil_ExceptionInit函数在函数定义的注释里解释了这个没有任何作用,只是为了兼容。

	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr);

这个函数用来在异常处理向量表汇总注册GIC的中断处理函数。异常总共只有七中,这个异常处理向量表实际上是一个结构体数组,数组中的每个元素是一个结构体,包含异常处理函数的函数指针和这个函数的自变量。
在异常处理向量表中注册

	typedef void (*Xil_ExceptionHandler)(void *data);

这个函数指针的类型已经有定义,和我以前所遇到的中断服务函数不同,这里的中断服务函数可以由自变量。这个自变量一般就是这个GIC的实例的指针。
XScuGic_InterruptHandler这个函数也不是我们自己定义的,而是GIC的驱动自带的,那么它完成了什么工作呢?
它实际上一个管理所有中断处理函数的一个函数,它会根据中断编号,比如GPIO的中断编号是52,根据52它会在HandlerTable中找到相应的中断处理函数进行执行。这里的这个HandlerTable和上面的异常处理向量表类似,也是一个结构体数组,将中断处理函数和自变量一同存储,而这个自变量命名为CallBackRef,一般也就是某个实例的指针。
为什么经常会看到函数把实例的指针作为参数进行传递?这类似面向对象的思想,实例本身是一个结构体,其中有关于这个实例的所有相关的内容。上面的HandlerTable也可以在GIC的实例中找到,而把实例作为一个参数传给中断处理函数,也使得中断处理函数能更加灵活地对外设进行修改。

	Status = XScuGic_Connect(GicInstancePtr, GpioIntrId,
				(Xil_ExceptionHandler)XGpioPs_IntrHandler,
				(void *)Gpio);
	if (Status != XST_SUCCESS) {
		return Status;
	}

在HandlerTable中并没有明确相应的中断处理函数,所以我们还要用这个XScuGic_Connect把GPIO的中断处理函数放到HandlerTable中。XGpioPs_IntrHandler是一个GPIO的通用的中断处理函数,它有一个自变量是GPIO的实例,XScuGic_Connect把它和GIC的实例中的HandlerTable进行绑定。

	XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler);

然而XGpioPs_IntrHandler也还是GPIO驱动自带的中断服务函数,我们还需要定义一个实际的中断处理函数,用XGpioPs_SetCallbackHandler把XGpioPs_IntrHandler和实际的中断处理函数进行绑定。

	typedef void (*XGpioPs_Handler) (void *CallBackRef, u32 Bank, u32 Status);

为了实现更加灵活的功能,GPIO对实际的中断服务函数的定义需要按照XGpioPs_Handler这个函数指针的形式来,其中除了CallBackRef,另外对Bank进行了区分,是的GPIO中断在产生时,可以根据不同的bank定义不同的功能。

static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status)
{
	XGpioPs *Gpio = (XGpioPs *)CallBackRef;
	u32 DataRead;
	DataRead = XGpioPs_ReadPin(Gpio, LED_NUMBER);
	if (DataRead != 0) {
		XGpioPs_WritePin(Gpio, LED_NUMBER, 0);
	}
	else {
		XGpioPs_WritePin(Gpio, LED_NUMBER, 1);
	}
}

以上是我定义的实际的中断服务函数,实现的是LED在按键后切换状态的功能。因为只有一处GPIO中断,所以没用到这里形参中的Status,如果有多个MIO中断来源,就需要根据Status和Bank进行判断具体是哪个IO口发生了中断。
总结
实际的配置过程没有顺序要求,但一定要有这三个步骤;GPIO的中断响应过程基本上就是上面右图所示。
最后还有一些中断类型和使能的配置,这些都比较简单就不细说了。

	/* Enable falling edge interrupts for the key pin. */
	XGpioPs_SetIntrTypePin(Gpio, KEY_NUMBER, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
	/* Enable the GPIO interrupts of key pin. */
	XGpioPs_IntrEnablePin(Gpio, KEY_NUMBER);
	/* Enable the interrupt for the GPIO device. */
	XScuGic_Enable(GicInstancePtr, GpioIntrId);
	/* Enable interrupts in the Processor. */
	Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);

在设计过程中有时候文件很大找不到函数,可以打开Windows菜单下的Open perspective… 选第一个C/C++,就会有一个Outline的窗口,可以很方便地查看有哪些函数。
open perspective
outline

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小裘HUST

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

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

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

打赏作者

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

抵扣说明:

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

余额充值