SDK 驱动 外设

806 阅读2分钟

Zynq SDK 驱动探求(二):外设,从初始化到干活

所有的外设都有一套共用的初始化流程

外设驱动函数操作外设的实质是对寄存器的读写操作

配置外设

  • 过程:XScuTimer_LookupConfig函数根据ID查找XScuTimer_ConfigTable 中对应的配置表项并返回,其中TIMER_DEVICE_ID 对应寄存器基址。

宏定义 TIMER_DEVICE_ID 为 SDK 为这个定时器分配的 ID 号,分配 ID 号的原因是因为同种类型的外设数量不止一个。比如我们上篇文章中说过:乐意的话完全可以配置 30 路串口,那么 SDK 就会为这 30 个串口从 0 开始分配 ID。在初始化和使用这些串口的过程中,都需要讲清楚要使用哪个 ID 所对应的驱动。

XScuTimer_Config *XScuTimer_LookupConfig(u16 DeviceId)
{
	XScuTimer_Config *CfgPtr = NULL;
	u32 Index;

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

	return (XScuTimer_Config *)CfgPtr;
}

定时器配置结构体如下:

/**
 * This typedef contains configuration information for the device.
 */
typedef struct {
	u16 DeviceId;	/**< Unique ID of device */
	u32 BaseAddr;	/**< Base address of the device */
} XScuTimer_Config;

外设配置信息,由硬件生成,对于私有定时器外设来说其实只有外设寄存器基址。

XScuTimer_Config XScuTimer_ConfigTable[XPAR_XSCUTIMER_NUM_INSTANCES] =
{
	{
		XPAR_PS7_SCUTIMER_0_DEVICE_ID,
		XPAR_PS7_SCUTIMER_0_BASEADDR
	}
};

初始化

  • 过程:XScuTimer_CfgInitialize 使用配置结构体作为参数
    • 将外设实例地址初始化配置信息里的地址
    • 将外设实例的状态变量进行初始化 Timer结构体
typedef struct {
	XScuTimer_Config Config; /**< Hardware Configuration */
	u32 IsReady;		/**< Device is initialized and ready */
	u32 IsStarted;		/**< Device timer is running */
} XScuTimer;

初始化函数如下:

s32 XScuTimer_CfgInitialize(XScuTimer *InstancePtr,
			 XScuTimer_Config *ConfigPtr, u32 EffectiveAddress)
{
	s32 Status;
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(ConfigPtr != NULL);

	if (InstancePtr->IsStarted != XIL_COMPONENT_IS_STARTED) {
		InstancePtr->Config.DeviceId = ConfigPtr->DeviceId;

		InstancePtr->Config.BaseAddr = EffectiveAddress;

		InstancePtr->IsStarted = (u32)0;

		InstancePtr->IsReady = XIL_COMPONENT_IS_READY;

		Status =(s32)XST_SUCCESS;
	}
	else {
		Status = (s32)XST_DEVICE_IS_STARTED;
	}
	return Status;
}

XScuTimer_CfgInitialize 函数中首先通过 Xil_AssertNonvoid 函数判断传入的结构体参数是否为空。之后判断外设是否处于启动状态,如果仍处于启动状态,直接初始化未免不合适,这里返回 XST_DEVICE_IS_STARTED 作为错误代码。上层函数根据错误代码进行判断,先停止外设再初始化为好。

所有针对定时器的操作,包括配置,启动,停止函数都需要传入 XScuTimer 结构体指针作为参数。

配置

嵌入式寄存器操作

宏定义形式:

#define XScuTimer_EnableAutoReload(InstancePtr)				\
	XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr,		\
			XSCUTIMER_CONTROL_OFFSET,			\
			(XScuTimer_ReadReg((InstancePtr)->Config.BaseAddr, \
				XSCUTIMER_CONTROL_OFFSET) |		 \
				XSCUTIMER_CONTROL_AUTO_RELOAD_MASK))

XSCUTIMER_CONTROL 寄存器,地址为 BaseAddr + XSCUTIMER_CONTROL_OFFSET

操作

void XScuTimer_Start(XScuTimer *InstancePtr)
{
	u32 Register;

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

	/*
	 * Read the contents of the Control register.
	 */
	Register = XScuTimer_ReadReg(InstancePtr->Config.BaseAddr,
				  XSCUTIMER_CONTROL_OFFSET);

	/*
	 * Set the 'timer enable' bit in the register.
	 */
	Register |= XSCUTIMER_CONTROL_ENABLE_MASK;

	/*
	 * Update the Control register with the new value.
	 */
	XScuTimer_WriteReg(InstancePtr->Config.BaseAddr,
			XSCUTIMER_CONTROL_OFFSET, Register);

	/*
	 * Indicate that the device is started.
	 */
	InstancePtr->IsStarted = XIL_COMPONENT_IS_STARTED;
}

这里也提醒我们,在使用宏定义形式的配置函数之前,最好先对传入的参数进行检查。毕竟写错地址,写到 0 地址,可能是个大问题。