深度解析Xilinx驱动包程序编写特点和使用方法--以GPIO为例

前言
前面先简单介绍这个实验的基本流程,了解了基本背景后,再重点讲解驱动程序的编写特点,以及用户面对Xilinx官方驱动包时如何使用。
一、功能描述
通过MIO实现对LED灯的控制,使其以固定频率闪烁。
二、实验步骤
1、硬件部分
① 创建系统工程
② 创建BD工程
③ 配置zynq IP:配置电压、串口、时钟、GPIO
④ 连线、编译、导出
2、软件部分
① 创建软件工程
② 添加对应驱动的头文件
③ 查看头文件中关于此驱动的相关参数定义和函数定义
④ 查找硬件配置后对应接口模块是否存在
⑤ 初始化一个接口
⑥ 设置GPIO输出方向
⑦ 配置连接LED的那一位GPIO引脚输出
⑧ 循环给定值0和1值给连接LED的GPIO引脚
三、GPIO驱动程序结构性分析
这里使用的是GPIO驱动程序包,所以首先找到相关的头文件。
include和libsrc中都可以看到
在include和libsrc中均可以看到

接下来看下这个头文件的结构,都定义了哪些东西,在xgpiops.h头文件中。
首先,定义了中断类型、bank、引脚数目等信息。

其次,定义了两个结构体,一个保存设备号和基地址,用于查找外设GPIO的存在,另一个保存调用GPIO实例所需要的基本信息,用于初始化操作,分别如下:

最后是函数定义,这里主要关注注释部分说明,定义了哪些函数以及函数所在的文件位置信息,方便我们查看和调用。具体表现为:

/* Functions in xgpiops.c */
/* Bank APIs in xgpiops.c */
/* Pin APIs in xgpiops.c */
/* Diagnostic functions in xgpiops_selftest.c */
/* Functions in xgpiops_intr.c */
/* Bank APIs in xgpiops_intr.c */
/* Pin APIs in xgpiops_intr.c */
/* Functions in xgpiops_sinit.c */

四、调用函数实现实验软件功能
按照第二部分说明的软件顺序,首先需要添加头文件,这里需要添加的头文件有:

#include "xgpiops.h"
#include "sleep.h"

之所以需要添加sleep.h头文件,是因为循环点亮LED,中间需要延时操作,方便用户观察亮灭情况。

1、查找和初始化GPIO外设资源
按照Xilinx的套路,首先需要查找GPIO定义,调用函数

XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId);

函数位于xgpiops_sinit.c文件中,函数详情:

XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId)
{
	XGpioPs_Config *CfgPtr = NULL;
	u32 Index;

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

	return (XGpioPs_Config *)CfgPtr;
}

先定义一个XGpioPs_Config类型的指针,准备接收特定设备号下 查找到的基地址;依次循环查表,表以数组的形式存放于xgpiops_g.c中,如下:

当输入的设备号一致时,就将表中的内容一一对应复制给XGpioPs_Config结构体中元素并返回。(设备号device和定义的GPIO驱动个数XPAR_XGPIOPS_NUM_INSTANCES是自动生成,位于xparameters.h文件中)
接下来回到主函数,判断下查找是否成功,返回状态值。
接着初始化外设GPIO,调用初始化函数

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

初始化函数调用之前,先定义一个XGpioPs结构体类型的指针实例,用于保存初始化后的关于GPIO的参数,这样做的目的可以使用户初始化很多这样的GPIO以便使用。
初始化函数中,在进行一个判断传入的三个参数是否为空后,然后对保存GPIO基本信息的参数进行赋值。赋值操作如下:

InstancePtr->IsReady = 0U;
InstancePtr->GpioConfig.BaseAddr = EffectiveAddr;
InstancePtr->GpioConfig.DeviceId = ConfigPtr->DeviceId;
InstancePtr->Handler = (XGpioPs_Handler)StubHandler;
InstancePtr->Platform = XGetPlatform_Info();

参数分别为:
① 准备信号赋值0,表示暂时没有初始化完成;
② 基地址
③ 设备号
④ 所有状态的处理程序
⑤ 设备数据
接下来,根据设备参数确定初始化结构体中最大引脚数和最大bank数目。

接下来,关闭GPIO所有中断,这里没有使用GPIO的中断功能,最后将初始化完成后的准备信号赋值,表示此次对GPIO的、初始化工作已经完成,可以使用。

回到主函数,根据初始化函数的返回值,同样做一个判断,是否完成初始化工作。

至此,关于外设GPIO的查找和初始化工作已经全部完成,接下来就是功能性程序。

2、设置GPIO输出方向
调用设置GPIO输出方向的函数

void XGpioPs_SetDirectionPin(XGpioPs *InstancePtr, u32 Pin, u32 Direction)
void XGpioPs_SetDirectionPin(XGpioPs *InstancePtr, u32 Pin, u32 Direction)
{
	u8 Bank;
	u8 PinNumber;
	u32 DirModeReg;

	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);
	Xil_AssertVoid(Direction <= (u32)1);

	/* Get the Bank number and Pin number within the bank. */
	XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);

	DirModeReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
				      ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
				      XGPIOPS_DIRM_OFFSET);

	if (Direction!=(u32)0) { /*  Output Direction */
		DirModeReg |= ((u32)1 << (u32)PinNumber);
	} else { /* Input Direction */
		DirModeReg &= ~ ((u32)1 << (u32)PinNumber);
	}

	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			 ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
			 XGPIOPS_DIRM_OFFSET, DirModeReg);
}

分析1;判断参数的传入是否为空,具体包括初始化结构体是否为空,结构体中准备信号是否表示为初始化已经准备好,引脚传入是否大于最大引脚数,方向设置参数是否大于1(方向设置参数定义为0或者1)。

Xil_AssertVoid(InstancePtr != NULL);
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);
Xil_AssertVoid(Direction <= (u32)1);

分析2:获取bank号、pin脚在所在bank位置的编号

XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);
void XGpioPs_GetBankPin(u8 PinNumber, u8 *BankNumber, u8 *PinNumberInBank)
{
	u32 XGpioPsPinTable[6] = {0};
	u32 Platform = XGetPlatform_Info();

	if (Platform == XPLAT_ZYNQ) 
 	{
		XGpioPsPinTable[0] = (u32)31; /* 0 - 31, Bank 0 */
		XGpioPsPinTable[1] = (u32)53; /* 32 - 53, Bank 1 */
		XGpioPsPinTable[2] = (u32)85; /* 54 - 85, Bank 2 */
		XGpioPsPinTable[3] = (u32)117; /* 86 - 117 Bank 3 */

		*BankNumber = 0U;
		while (*BankNumber < 4U) {
			if (PinNumber <= XGpioPsPinTable[*BankNumber]) {
				break;
			}
			(*BankNumber)++;
		}
	}
	if (*BankNumber == (u8)0) {
		*PinNumberInBank = PinNumber;
	} else {
		*PinNumberInBank = (u8)((u32)PinNumber %
					(XGpioPsPinTable[*BankNumber - (u8)1] + (u32)1));
	}
}

还是根据设备数据进行判断,这里我做了代码简化,删除了XPLAT_ZYNQ_ULTRA_MP设备下的bank和pin数据信息。
XPLAT_ZYNQ一共118个引脚,4个bank,通过数组的形式进行划分:

	XGpioPsPinTable[0] = (u32)31; /* 0 - 31, Bank 0 */
	XGpioPsPinTable[1] = (u32)53; /* 32 - 53, Bank 1 */
	XGpioPsPinTable[2] = (u32)85; /* 54 - 85, Bank 2 */
	XGpioPsPinTable[3] = (u32)117; /* 86 - 117 Bank 3 */

通过while语句,定位传入的PIN脚属于哪一个bank的范围。

		*BankNumber = 0U;
		while (*BankNumber < 4U) {
			if (PinNumber <= XGpioPsPinTable[*BankNumber]) {
				break;
			}
			(*BankNumber)++;
		}

bank确定了以后,开始确定pin脚在此bank中的位置。如果属于bank0,则引脚号就是这个引脚所在bank的位置,如果不是bank0,则需要计算,将引脚数除以上个bank引脚最大数取余数。具体表现为:

	if (*BankNumber == (u8)0) {
		*PinNumberInBank = PinNumber;
	} else {
		*PinNumberInBank = (u8)((u32)PinNumber %
					(XGpioPsPinTable[*BankNumber - (u8)1] + (u32)1));
	}

在获取了引脚号所在bank以及所在bank的位置信息以后,便可以通过写特定寄存器的方式设置对应GPIO引脚的方向了。
分析3:写寄存器,设置对应GPIO方向
首先,查找datasheet,找到设置GPIO方向的寄存器地址。


在写寄存器之前,先读取寄存器的值,存放于一个变量,然后将变量的值进行重新赋值,最后将重新赋值的变量写入寄存器。

	DirModeReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
				      ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
				      XGPIOPS_DIRM_OFFSET);

	if (Direction!=(u32)0) { /*  Output Direction */
		DirModeReg |= ((u32)1 << (u32)PinNumber);
	} else { /* Input Direction */
		DirModeReg &= ~ ((u32)1 << (u32)PinNumber);
	}

	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			 ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
			 XGPIOPS_DIRM_OFFSET, DirModeReg);

这里涉及读、写函数:

#define XGpioPs_ReadReg(BaseAddr, RegOffset)		\
		Xil_In32((BaseAddr) + (u32)(RegOffset))
#define XGpioPs_WriteReg(BaseAddr, RegOffset, Data)	\
		Xil_Out32((BaseAddr) + (u32)(RegOffset), (u32)(Data))

下面以写函数为例具体分析,函数可以拆分为以下几个步骤:
① 定义了一个函数

#define XGpioPs_WriteReg(BaseAddr, RegOffset, Data)

② 给这个函数赋予具体操作,也就是函数体

Xil_Out32((BaseAddr) + (u32)(RegOffset), (u32)(Data))

函数体里调用了static INLINE void Xil_Out32(UINTPTR Addr, u32 Value)函数。

static INLINE void Xil_Out32(UINTPTR Addr, u32 Value)
{
#ifndef ENABLE_SAFETY
	volatile u32 *LocalAddr = (volatile u32 *)Addr;
	*LocalAddr = Value;
#else
	XStl_RegUpdate(Addr, Value);
#endif
}

这个函数的意义就是像特定的地址写入特定的值,参数一是地址信息,参数二是数据信息。

3、配置MIO第7位输出(实验中MIO第7位连接到外部LED)
查找datasheet,找到使能对应位输出的寄存器。

void XGpioPs_SetOutputEnablePin(XGpioPs *InstancePtr, u32 Pin, u32 OpEnable)
void XGpioPs_SetOutputEnablePin(XGpioPs *InstancePtr, u32 Pin, u32 OpEnable)
{
	u8 Bank;
	u8 PinNumber;
	u32 OpEnableReg;

	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);
	Xil_AssertVoid(OpEnable <= (u32)1);

	/* Get the Bank number and Pin number within the bank. */
	XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);

	OpEnableReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
				       ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
				       XGPIOPS_OUTEN_OFFSET);

	if (OpEnable != (u32)0) { /*  Enable Output Enable */
		OpEnableReg |= ((u32)1 << (u32)PinNumber);
	} else { /* Disable Output Enable */
		OpEnableReg &= ~ ((u32)1 << (u32)PinNumber);
	}

	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			  ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
			  XGPIOPS_OUTEN_OFFSET, OpEnableReg);
}

首先,同样进行参数传入的确认工作,不再赘述。
其次,获取引脚所在bank和引脚所在bank的位置信息,和上面设置GPIO输出方向里面调用的函数一样,不再赘述。
接下来就是写特定寄存器使能特定位输出。
方法和上面写方向寄存器一一致,具体表现为:

	OpEnableReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
				       ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
				       XGPIOPS_OUTEN_OFFSET);

	if (OpEnable != (u32)0) { /*  Enable Output Enable */
		OpEnableReg |= ((u32)1 << (u32)PinNumber);
	} else { /* Disable Output Enable */
		OpEnableReg &= ~ ((u32)1 << (u32)PinNumber);
	}

	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			  ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
			  XGPIOPS_OUTEN_OFFSET, OpEnableReg);

4、写引脚值,点亮和熄灭外部连接的LED

	 while(1)
	 {
		XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1);//点亮MIO的第7位输出1
		sleep(1);	//延时
		XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0);//熄灭MIO的第7位输出0
		sleep(1);	//延时
	 }

引用了sleep()函数,参数为1,表示延时1S时间,让LED的亮灭肉眼可见。这就是为什么头文件加入sleep.h的原因。
具体分析写引脚值函数:

void XGpioPs_WritePin(XGpioPs *InstancePtr, u32 Pin, u32 Data)
void XGpioPs_WritePin(XGpioPs *InstancePtr, u32 Pin, u32 Data)
{
	u32 RegOffset;
	u32 Value;
	u8 Bank;
	u8 PinNumber;
	u32 DataVar = Data;

	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);

	/* Get the Bank number and Pin number within the bank. */
	XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);

	if (PinNumber > 15U) {
		/* There are only 16 data bits in bit maskable register. */
		PinNumber -= (u8)16;
		RegOffset = XGPIOPS_DATA_MSW_OFFSET;
	} else {
		RegOffset = XGPIOPS_DATA_LSW_OFFSET;
	}

	/*
	 * Get the 32 bit value to be written to the Mask/Data register where
	 * the upper 16 bits is the mask and lower 16 bits is the data.
	 */
	DataVar &= (u32)0x01;
	Value = ~((u32)1 << (PinNumber + 16U)) & ((DataVar << PinNumber) | 0xFFFF0000U);
	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			  ((u32)(Bank) * XGPIOPS_DATA_MASK_OFFSET) +
			  RegOffset, Value);
}

基本思路如下:
① 判断参数传入是否为空
② 获取bank号和pin所在bank的位置信息
③ 由于bank中对于低16位引脚和高16位引脚的赋值在不同的寄存器,所以需要做一个判断,确定写入寄存器地址

	if (PinNumber > 15U) {
		/* There are only 16 data bits in bit maskable register. */
		PinNumber -= (u8)16;
		RegOffset = XGPIOPS_DATA_MSW_OFFSET;
	} else {
		RegOffset = XGPIOPS_DATA_LSW_OFFSET;
	}

④ 因为写入的数据只能是0或者1,所以做一个与运算,再根据pin所在位置是高16位还是低16位,进行待写入数据的移位。

	DataVar &= (u32)0x01;
	Value = ~((u32)1 << (PinNumber + 16U)) & ((DataVar << PinNumber) | 0xFFFF0000U);

⑤ 写入待写入数据至特定寄存器,寄存器地址同样从datasheet查找。

	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			  ((u32)(Bank) * XGPIOPS_DATA_MASK_OFFSET) +
			  RegOffset, Value);

完毕!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值