对ZYNQ设备GPIO中断函数的详解 (一)

对ZYNQ设备GPIO中断函数的详解 (一)

简言:同别的文章一样,各大厂商,正点原子,米联客,威视瑞等等,都对GPIO中断有了很实际应用的讲解。但是却没有找到类似与AXI时序那样,再进一步深入的讲解。也许是因为这个没有那么重要。但是对我来说,我时长认为,真正有价值的,不在于广度而在于深度,不在于复杂,而在于细节。同时我还是相信,别希望有很多人会这样。于是便有意借此机会,研究GPIO中断函数相关。本文是讲解PL发送中断,PS端接收的情况。当然,情形对应的是最简单的按键触发LED亮灭。

/*
1 GPIO初始化
*/
//1.1查找GPIO设备
XGpioPs_LookupConfig();
//1.2初始化GPIO设备
	XGpioPs_CfgInitialize();
//1.3将中断对应的GPIO设置为输入
XGpioPs_SetDirectionPin(Gpio, Input_Pin, 0x0);

/*
	2 GPIO 中断初始化
*/
//2.0.兼容性
Xil_ExceptionInit();
//2.1.查找GIC设备
XScuGic_LookupConfig();
//2.2.初始化GIC设备
XScuGic_CfgInitialize();
//2.3.将中断处理handler与处理器硬件中断处理逻辑相关联
Xil_ExceptionRegisterHandler();
//2.4.连接设备驱动程序处理程序,该设备驱动程序处理程序在设备中断
//发生时将被调用,上面定义的处理程序对设备执行特定的中断处理。
XScuGic_Connect();
//2.5.使能bank 0中所有引脚的下降沿中断
XGpioPs_SetIntrType();
//2.6.设置gpio中断的处理程序。其中IntrHandler为中断处理函数
XGpioPs_SetCallbackHandler();
//2.7.启用GPIO设备的中断。
XScuGic_Enable();
//2.8.在处理器中启用中断。
Xil_ExceptionEnableMask();

/*
	3.对于输出GPIO,要使能输出,并设定输出的值
*/
//3.1使能输出
XGpioPs_SetOutputEnablePin();
//设定输出的值
XGpioPs_WritePin();

  上面是官方历程“xgpiops_intr_example.c”工程文件中提炼出的整个PL端GPIO初始化,中断初始化所需要调用的函数,以及流程。下面我们依次进行说明。

1. GPIO初始化相关函数

1.1 函数XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId)

1.1.1XGpioPs_Config结构

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

  同时为了方便,我同时说,DeviceId是唯一的设备ID为2个字节,u32BaseAddr是设备的基地址为4个字节。即该结构体一共是6个字节。

1.1.2 XPAR_XGPIOPS_NUM_INSTANCES

#define XPAR_XGPIOPS_NUM_INSTANCES 1 这个是该参量的宏定义

1.1.3 XGpioPs_ConfigTable变量

XGpioPs_ConfigTable变量是1.1.1中所述XGpioPs_Config类型的。

XGpioPs_Config XGpioPs_ConfigTable[XPAR_XGPIOPS_NUM_INSTANCES] =
{
	{
		XPAR_PS7_GPIO_0_DEVICE_ID,
		XPAR_PS7_GPIO_0_BASEADDR
	}
};

其中XPAR_XGPIOPS_NUM_INSTANCES是在1.1.2节中定义,值为1。

其中我们XGpioPs_Config结构体中

DeviceId被初始化为XPAR_PS7_GPIO_0_DEVICE_ID,
BaseAddr被初始化为XPAR_PS7_GPIO_0_BASEADDR

1.1.4 XPAR_PS7_GPIO_0_DEVICE_ID

#define XPAR_PS7_GPIO_0_DEVICE_ID 0

  默认是第一个ID 0,这个在对于其他设备来说,也是这样的。

1.1.5 XPAR_PS7_GPIO_0_BASEADDR

#define XPAR_PS7_GPIO_0_BASEADDR 0xE000A000

  这个对应的就是手册上GPIO的基地址。(摘自手册UG585)

在这里插入图片描述

1.1.6 函数整体观

XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId)
{
	XGpioPs_Config *CfgPtr = NULL;//声明一个XGpioPs_Config结构体,见1.1.1
	u32 Index;//声明一个索引号,用于下面的for循环控制条件

	for (Index = 0U; Index < (u32)XPAR_XGPIOPS_NUM_INSTANCES; Index++) {
		//接着是一个for循环,for循环从0开始,每次加一
		//停止条件是Index

  FAQ:这个时候我是有疑问的,就是为什么调用这个函数的时候,使用的DeviceId = 0。我自然的想法是,我使用哪个GPIO管脚,DeviceId就应该等于多少呀?我的答案是这样的,GPIO控制器初始化,之后包括其他设备也是一样的,初始化只需要给入第一个设备的地址就可以了。

1.2 s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, XGpioPs_Config *ConfigPtr,u32 EffectiveAddr)函数

1.2.1 首先s32

  typedef int32_t s32;

1.2.2 XGpioPs结构

typedef struct {
	XGpioPs_Config GpioConfig;	/**< Device configuration */
	u32 IsReady;			/**< Device is initialized and ready */
	XGpioPs_Handler Handler;	/**< Status handlers for all banks */
	void *CallBackRef; 		/**< Callback ref for bank handlers */
	u32 Platform;			/**< Platform data */
	u32 MaxPinNum;			/**< Max pins in the GPIO device */
	u8 MaxBanks;			/**< Max banks in a GPIO device */
} XGpioPs;

  结构如上图:宏观上来看,可以指导这个结构体应该是描述GPIO这个设备一个很重要的结构体。

1.2.2.1 XGpioPs_Config GpioConfig

  这个已经在1.1.3中说明了,他占6个字节。

1.2.2.2 u32 IsReady

  4个字节,用来指明设备初始化情况。

1.2.2.3 XGpioPs_Handler Handler

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

  很有意思的遇到了这个栋栋,我截图一下,以便大家看的更清晰。

对ZYNQ设备GPIO中断函数的详解 (一)_第1张图片

  我们把这个东西叫做函数指针,就是指向一个函数的指针。大家看我下面这个图就明白他是怎么作用的了。

#include 
void test_func_ptr_1();
void test_func_ptr_2(int num) ;
int test_func_ptr_3(int num) ;
// int_handler可以随便取名字,你也可以改成 typedef void (*lalala) ();
//返回值和形参要对应相同 
typedef void (*int_handler_1) ();
typedef void (*int_handler_2) (int num);
typedef int  (*int_handler_3) (int num);
int main()
{
int tmp_num;
printf("hello world!\n");
int_handler_1 func_test_1 = &test_func_ptr_1;
(* func_test_1)();

int_handler_2 func_test_2 = &test_func_ptr_2;
(* func_test_2)(1);

int_handler_3 func_test_3 = &test_func_ptr_3;
tmp_num = (* func_test_3)(2);
printf("the return num = %d\n",tmp_num);
return 0;
} 

void test_func_ptr_1()
{
	printf("this is test_func_ptr_1()\n");
}
void test_func_ptr_2(int num) 
{
	printf("this is test_func_ptr_2() ,the num = %d\n",num);
}
int test_func_ptr_3(int num) 
{
	printf("this is test_func_ptr_3() ,the num = %d\n",num);
	return 0;
}

结果:

对ZYNQ设备GPIO中断函数的详解 (一)_第2张图片

  在了解了函数指针的用法之后,这个也就很自然的明白了,他就是定义了一个函数指针XGpioPs_Handler变量Handler。

1.2.2.4 void *CallBackRef

  这个是用来存储回掉函数指针的。

1.2.2.5 u32 Platform

  平台相关信息,4个字节

1.2.2.6 u32 MaxPinNum

  最大pins个数,4个字节。这个计算方法后面会介绍。

.2.2.7 u8 MaxBanks

  最大Bank数,一个字节。

1.2.3 函数解释

1.2.3.1 StubHandler

对ZYNQ设备GPIO中断函数的详解 (一)_第3张图片

  他只是初始化的时候用于占位的。

1.2.3.2 Xil_AssertVoidAlways()

对ZYNQ设备GPIO中断函数的详解 (一)_第4张图片

  这个函数会报告当前的文件目录和当前的行号,并反回XIL_ASSERT_OCCURRED,成功状态。(没有关系,知道他是提示作用就可以了。)

1.2.3.3 函数整体框架解释

s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, XGpioPs_Config *ConfigPtr,u32 EffectiveAddr)
{
	s32 Status = XST_SUCCESS;//初始化status变量,它用于指示各个函数执行情况
	u8 i;//用于后面控制for循环
	//下面是用来打印报警信息,这个函数不在深入喽。
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(ConfigPtr != NULL);
	Xil_AssertNonvoid(EffectiveAddr != (u32)0);
	//初始化
	InstancePtr->IsReady = 0U;//初始化未完成
	//我们传入的EffectiveAddr的值未baseaddress = 0xE000A000
	InstancePtr->GpioConfig.BaseAddr = EffectiveAddr;
	//DeviceId 的值为0,上面解释过,只需要传入第一个设备的地址就可以了
	InstancePtr->GpioConfig.DeviceId = ConfigPtr->DeviceId;
	//见1.2.3.1
	InstancePtr->Handler = StubHandler;
	//获得平台相关信息
	InstancePtr->Platform = XGetPlatform_Info();
	if (InstancePtr->Platform == XPLAT_ZYNQ_ULTRA_MP) {
		InstancePtr->MaxPinNum = (u32)174;
		InstancePtr->MaxBanks = (u8)6;
	} else {
		/*
		 *	Max pins in the GPIO device
		 *	0 - 31,  Bank 0
		 *	32 - 53, Bank 1
		 *	54 - 85, Bank 2
		 *	86 - 117, Bank 3
		 */
		InstancePtr->MaxPinNum = (u32)118; //mio=54 EMIO=64 总共118个
		InstancePtr->MaxBanks = (u8)4;
	}
	//这个函数看他的参数就可以知道,他是操作XGPIOPS_REG_MASK_OFFSET寄存器,
	//将该寄存器的值设置为0xFFFFFFFF,其目的是屏蔽掉中断
	//这个函数不在具体进入讲解了,但是需要注意的一点是,原则上来讲,这个函数
	//可以操作任何一个设备的任何寄存器,只需要给正确的地址和偏移就可以了。
	for (i=0;iMaxBanks;i++) {
		XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
					  ((u32)(i) * XGPIOPS_REG_MASK_OFFSET) +
					  XGPIOPS_INTDIS_OFFSET, 0xFFFFFFFFU);
	}
	//将状态设置为初始化完成
	InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
	return Status;
}

1.3 void XGpioPs_SetDirectionPin(XGpioPs *InstancePtr, u32 Pin, u32 Direction);

  他的主要作用就是设置管脚的输出方向。

  很容易的就可以看明白,第一个值,就是我们1.2中注册好的设备,第二个参数是我们具体要操作的哪个GPIO管脚,第三个参数是设置方向,但是这里注意,0代表输入,1代表输出。和我们x86架构不太一样。

2. 中断相关

2.1 Xil_ExceptionInit函数

  该函数只是为了兼容性,没有任何其他作用。

对ZYNQ设备GPIO中断函数的详解 (一)_第5张图片

2.2 XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId)函数

2.2.1 整体功能

  和上面的XGpioPs_LookupConfig是类似的。

XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId)
{
	XScuGic_Config *CfgPtr = NULL;
	u32 Index;

	for (Index=0U; Index < (u32)XPAR_SCUGIC_NUM_INSTANCES; Index++) {
		if (XScuGic_ConfigTable[Index].DeviceId == DeviceId) {
			CfgPtr = &XScuGic_ConfigTable[Index];
			break;
		}
	}

	return (XScuGic_Config *)CfgPtr;
}

同样值得说明的是,我们传入的DeviceId = 0仍为首个设备。

2.2.2 XScuGic_Config结构体

typedef struct
{
	u16 DeviceId;		/**< Unique ID  of device */
	u32 CpuBaseAddress;	/**< CPU Interface Register base address */
	u32 DistBaseAddress;	/**< Distributor Register base address */
	XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**<
				 Vector table of interrupt handlers */
} XScuGic_Config;

2.2.2.1 u16 DeviceId:设备号,4字节

2.2.2.2 u32 CpuBaseAddress:CPU接口寄存器基地址,4字节

2.2.2.3 u32 DistBaseAddress:Distributor Register base address,四个字节

2.2.2.4 XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS]

2.2.2.4.1他是中断向量表

2.2.2.4.2XScuGic_VectorTableEntry
//是一个函数指针,用法参见1.2节
typedef void (*Xil_InterruptHandler)(void *data);
typedef struct
{
	Xil_InterruptHandler Handler; //函数指针变量
	void *CallBackRef;//回掉函数
} XScuGic_VectorTableEntry;
2.2.2.4.3显然,我们是定义了一个结构体数组

该结构数组成为HandlerTable,我把他翻译为函数表,换句话来说,它里面存储的是处理各种事务的函数的指针。访问该结构体数组的不同的单元,可以看成调用不同的函数。

2.2.2.4.4该结构体数组的大小为XSCUGIC_MAX_NUM_INTR_INPUTS

图片7

他是当前硬件设备所支持的最大中断个数。

2.2.3XScuGic_ConfigTable变量,他是一个数组

XScuGic_Config XScuGic_ConfigTable[XPAR_XSCUGIC_NUM_INSTANCES] =
{
	{
		XPAR_PS7_SCUGIC_0_DEVICE_ID,
		XPAR_PS7_SCUGIC_0_BASEADDR,
		XPAR_PS7_SCUGIC_0_DIST_BASEADDR,
		{{0}}		/**< Initialize the HandlerTable to 0 */
	}
};

2.2.3.1XPAR_PS7_SCUGIC_0_DEVICE_ID

2.2.3.2XPAR_PS7_SCUGIC_0_BASEADDR

2.2.3.3XPAR_PS7_SCUGIC_0_DIST_BASEADDR

2.2.3.4{{0}}对比1.5.2结构体,知道他把这个数组初始化成0了

对ZYNQ设备GPIO中断函数的详解 (一)_第6张图片
对ZYNQ设备GPIO中断函数的详解 (一)_第7张图片
对ZYNQ设备GPIO中断函数的详解 (一)_第8张图片

你可能感兴趣的:(ZYNQ)