简言:同别的文章一样,各大厂商,正点原子,米联客,威视瑞等等,都对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初始化,中断初始化所需要调用的函数,以及流程。下面我们依次进行说明。
typedef struct {
u16 DeviceId; /**< Unique ID of device */
u32 BaseAddr; /**< Register base address */
} XGpioPs_Config;
同时为了方便,我同时说,DeviceId是唯一的设备ID为2个字节,u32BaseAddr是设备的基地址为4个字节。即该结构体一共是6个字节。
#define XPAR_XGPIOPS_NUM_INSTANCES 1 这个是该参量的宏定义
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结构体中
#define XPAR_PS7_GPIO_0_DEVICE_ID 0
默认是第一个ID 0,这个在对于其他设备来说,也是这样的。
#define XPAR_PS7_GPIO_0_BASEADDR 0xE000A000
这个对应的就是手册上GPIO的基地址。(摘自手册UG585)
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控制器初始化,之后包括其他设备也是一样的,初始化只需要给入第一个设备的地址就可以了。
typedef int32_t s32;
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.1.3中说明了,他占6个字节。
4个字节,用来指明设备初始化情况。
很有意思的遇到了这个栋栋,我截图一下,以便大家看的更清晰。
我们把这个东西叫做函数指针,就是指向一个函数的指针。大家看我下面这个图就明白他是怎么作用的了。
#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;
}
结果:
在了解了函数指针的用法之后,这个也就很自然的明白了,他就是定义了一个函数指针XGpioPs_Handler变量Handler。
这个是用来存储回掉函数指针的。
平台相关信息,4个字节
最大pins个数,4个字节。这个计算方法后面会介绍。
最大Bank数,一个字节。
他只是初始化的时候用于占位的。
这个函数会报告当前的文件目录和当前的行号,并反回XIL_ASSERT_OCCURRED,成功状态。(没有关系,知道他是提示作用就可以了。)
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.2中注册好的设备,第二个参数是我们具体要操作的哪个GPIO管脚,第三个参数是设置方向,但是这里注意,0代表输入,1代表输出。和我们x86架构不太一样。
该函数只是为了兼容性,没有任何其他作用。
和上面的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仍为首个设备。
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;
//是一个函数指针,用法参见1.2节
typedef void (*Xil_InterruptHandler)(void *data);
typedef struct
{
Xil_InterruptHandler Handler; //函数指针变量
void *CallBackRef;//回掉函数
} XScuGic_VectorTableEntry;
该结构数组成为HandlerTable,我把他翻译为函数表,换句话来说,它里面存储的是处理各种事务的函数的指针。访问该结构体数组的不同的单元,可以看成调用不同的函数。
他是当前硬件设备所支持的最大中断个数。
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 */
}
};