ZYNQ学习笔记1-裸机-PS端中断配置、IO配置及PS/PL AXI交互

ZYNQ学习笔记1-裸机-PS端中断配置、IO配置及PS/PL数据的AXI交互(2-1)

最近在学习ZYNQ芯片的使用,都是一些基础的内容,主要是为了防止忘记和放弃,在这里打卡记录一下学习过程,以后有时间来翻看一下,复习复习。有需要的朋友们也欢迎参考讨论。

例程简介

本次的例程是通过PL端的IO触发PS端的中断。涉及到中断的配置,IO的配置和PS/PL间数据的AXI交互内容。由于涉及到PL的内容,所以需要做一个BD文件。这里涉及到的IP是ZYNQ及AXI_GPIO。

PL端配置

ZYNQ IP核配置

ZYNQ外设配置只配置了串口,主要是为了打印文本。不是必须的,如果不需要打印文本的话可以不设置,但一般为了创建的demo程序方便都会勾选上。勾选哪一个要看自己的硬件,串口在硬件原理图上分配的是哪一个引脚这里就要勾选对应的位置。

DDR配置主要配的是颗粒型号,也就是Memory Part 这里选的是MT41J256M16 RE-125。DDR的真实型号可能不是这个,但并不重要,一般而言只要位宽没选错就问题不大。打开MT41J256M16 RE-125手册看下型号所代表的具体含义如下:

可以看出MT41J256M16 RE-125的具体含义,MT41J是系列名,这里没有详细解释,打开ZYNQ的DDR配置页可以发现还有MT41K系列,翻看手册可以发现MT41J是DDR3颗粒只能1.5V供电,MT41K是DDR3L,支持1.5V和1.35V供电。256M16是颗粒的组织架构形式,具体是指地址==深度是256M位宽是16位,相乘256Mx16bit也就是颗粒单颗存储容量2Gb,也就是这个是一个2Gb,0.5GB的DDR颗粒。RE是指的封装,96球,宽体的封装。125是指速度等级,不同的等级的速度参数不一样,详细还需要翻翻手册,不过其实不纠结也可以,一般问题不大,随便选一个中间的时序过不了再翻手册也不晚,一般选颗粒选个位宽深度就可以了。

由于要触发PL到PS的中断,所以中断配置页是需要勾选的,选择IRQ中断就可以。看简介可以看到IRQ中断是指来自于PL的中断,中断位置是共享中断端口。其他的是从PL到私有中断。

AXI GPIO配置

AXI GPIO的设置页面如上图所示。查阅ip核手册可以了解各选项作用如下:
All inputs:将该GPIO通道的位设置为仅输入模式。默认情况为不选中
All Outputs:相反,将该GPIO通道的位设置为仅输出模式。默认情况也是不选中
GPIO Width:用来定义扩展的GPIO宽度,范围是1-32,默认是32。这里我只用了一个GPIO设置的值是1
Default Output Value:用来设置这个GPIO通道所有使能位的默认输出值,默认的输出是0,也就是低电平
Default Tri State Value:用来配置每个通道的方向。也就是GPIO是输入还是输出,默认是全1(输入模式)。
Enable Dual Channel:用于启用第二个GPIO通道(GPIO2)。默认情况下,此参数未选中,将AXI GPIO配置为单通道模式。当此参数启用时,GPIO2的相关选项将被激活,同时也会配置通道2(GPIO2)的寄存器。

GPIO2选项卡的内容和1一样就不啰嗦了-略
Enable Interrupt:用于启用GPIO模块中的中断控制逻辑和中断寄存器。默认情况下,此选项未选中,即中断未使能。

后续的操作就是套路了如自动连线,创建顶层,输出结果等。

PS端代码

完整代码

c 复制代码
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpiops.h"
#include "xgpio.h"
#include "xscugic.h"
#include "sleep.h"
#define GPIO_DEVICE_ID			XPAR_GPIO_0_DEVICE_ID
#define GPIO_AXI_ID				XPAR_AXI_GPIO_0_DEVICE_ID
#define INTC_DEVICE_ID			XPAR_SCUGIC_SINGLE_DEVICE_ID
#define GPIO_INTERRUPT_ID		XPAR_XGPIOPS_0_INTR
#define INTC_GPIO_INTERRUPT_ID	XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR //AXI中断ID
#define AXI_GPIO_CHANNEL1		1

 void IntrHandler(void *CallBackRef, u32 Bank, u32 Status);
XGpioPs Gpio_ps; /* The Instance of the GPIO Driver */
XGpio	axi_Gpio; /* The Instance of the GPIO Driver */
//XScuGic Intc; /* The Instance of the Interrupt Controller Driver */
XScuGic_Config *IntcConfig;
XScuGic GicInstancePtr;
XGpio_Config *Int_XGpio_Config;
#define 	LED 	0
u32 press;

int main()
{
		XGpioPs_Config *ConfigPtr;
		u32 value=0;
		//PS IO初始化
		//根据器件ID 查找器件的配置信息
		ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
		//对GPIO驱动初始化
		XGpioPs_CfgInitialize(&Gpio_ps, ConfigPtr, ConfigPtr->BaseAddr);
		//设置引脚方向 0:输入,1:输出
		XGpioPs_SetDirectionPin(&Gpio_ps, LED, 1);
		//设置输出使能 0:不使能,1:使能
		XGpioPs_SetOutputEnablePin(&Gpio_ps, LED, 1);
		//向LED写0
		XGpioPs_WritePin(&Gpio_ps, LED, 0x0);
		//初始化PL AXI GPIO IP
		XGpio_Initialize(&axi_Gpio, GPIO_AXI_ID);
		//获取中断控制器的配置信息
		IntcConfig = XScuGic_LookupConfig( INTC_DEVICE_ID);
		//根据配置信息初始化中断控制器
		XScuGic_CfgInitialize(&GicInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress);
		//关联GPIO中断处理程序
		XScuGic_Connect(&GicInstancePtr, INTC_GPIO_INTERRUPT_ID,
					(Xil_ExceptionHandler)IntrHandler,
					(void *) &axi_Gpio);
		//使能AXI GPIO 通道1中断
		XGpio_InterruptEnable(&axi_Gpio, AXI_GPIO_CHANNEL1);
		//使能AXI GPIO全局中断
		XGpio_InterruptGlobalEnable(&axi_Gpio);
		//使能GIC的 AXI GPIO中断
		XScuGic_Enable(&GicInstancePtr, INTC_GPIO_INTERRUPT_ID);
		//设置中断触发条件及优先级
		XScuGic_SetPriorityTriggerType(&GicInstancePtr, INTC_GPIO_INTERRUPT_ID,	0xA0, 0x3);
		//异常初始化
		Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
					(Xil_ExceptionHandler)XScuGic_InterruptHandler,
					&GicInstancePtr);
		//使能异常处理
		Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
		for(;;){
			if(press){
				usleep(2000);
				if(!XGpio_DiscreteRead(&axi_Gpio, AXI_GPIO_CHANNEL1))
				{
					print(" hello ");
					press = 0;
				}
				//使能AXI GPIO 通道1中断
				XGpio_InterruptEnable(&axi_Gpio, AXI_GPIO_CHANNEL1);
			}
		}
}
 void IntrHandler(void *CallBackRef, u32 Bank, u32 Status)
{
	XGpio *axi_Gpio = (XGpio *)CallBackRef;
	 press = 1;
	 //清除中断标志 保证下次中断可以重新进入
	 XGpio_InterruptClear(axi_Gpio, AXI_GPIO_CHANNEL1);
	 //关闭AXI GPIO中断
	 XGpio_InterruptDisable(axi_Gpio, AXI_GPIO_CHANNEL1);
}

分段解释

这是一个简单的中断演示程序,因为这些功能都比较基础没必要搞得花里胡哨,所以只是把中断功能实现就算了,具体功能收到中断之后会打印个hello。整体比较粗糙。其实本来想做一个PL的按键按一下,PS的灯就亮一下的,但做完发现还得加去抖,加判断,就拉倒把,放弃。主打一个能跑就行·。所以程序里有一个PS端的IO,有一个PL端的IO,又互不相关。

  • 宏定义

GPIO_DEVICE_ID:PS端GPIO的设备ID,在xparameters.h中定义

GPIO_AXI_ID:PL端AXI GPIO的设备ID,在xparameters.h中定义

INTC_DEVICE_ID:中断控制器(SCU GIC)的设备ID,在xparameters.h中定义

INTC_GPIO_INTERRUPT_ID:AXI GPIO连接到GIC的中断ID,,在xparameters.h中定义

AXI_GPIO_CHANNEL1:AXI GPIO的通道1,用于连接按键,在xparameters.h中定义

LED:PS端LED对应的引脚号(此处为0,实际需根据硬件调整)。

  1. 主函数

    (1)初始化PS端GPIO(控制LED)

    XGpioPs_LookupConfig 根据设备ID查找PS GPIO的配置结构体,包含基地址等。

    XGpioPs_CfgInitialize 用查到的配置信息初始化PS GPIO驱动实例Gpio_ps,完成硬件映射

    XGpioPs_SetDirectionPin

    XGpioPs_SetOutputEnablePin

    XGpioPs_WritePin 这三行将LED引脚配置为输出并初始化状态,不过后面就没有再改这个状态

    (2)初始化PL端AXI GPIO(读取按键)

    XGpio_Initialize 根据设备ID初始化AXI GPIO实例axi_Gpio,该IP核在PL中实现,用于读取外部按键输入

    XScuGic_LookupConfig:获取GIC的配置信息。

    XScuGic_CfgInitialize:初始化GIC驱动实例GicInstancePtr,建立与硬件的连接。

    (3)初始化中断控制器(GIC)

    XScuGic_LookupConfig:获取GIC的配置信息。

    XScuGic_CfgInitialize:初始化GIC驱动实例GicInstancePtr,建立与硬件的连接

    (4)连接中断处理函数

    XScuGic_Connect:这个程序中将AXI GPIO的中断ID与自定义的中断处理函数IntrHandler绑定。当该中断发生时,GIC会调用IntrHandler,并将axi_Gpio实例作为回调参数传递。

    (5)配置AXI GPIO内部中断

    XGpio_InterruptEnable

    XGpio_InterruptGlobalEnable:这两步是AXI GPIO自身的中断使能。必须先使能具体通道的中断,再使能全局中断,才能向外发出中断请求。

    (6)配置GIC中断参数并使能

    XScuGic_SetPriorityTriggerType:设置该中断的优先级(0xA0)和触发类型(0x3,通常为高电平敏感)。需与硬件设计匹配。

    XScuGic_Enable:在GIC中使能该中断,允许其传递给CPU。

    (7)注册异常处理函数并全局使能中断

    Xil_ExceptionRegisterHandler:将GIC的中断处理入口XScuGic_InterruptHandler注册为CPU IRQ异常的处理函数。这样,当CPU收到IRQ异常时,会跳转到该函数,再由它分发到具体的中断处理程序(即我们之前绑定的IntrHandler)。

    Xil_ExceptionEnableMask:使能CPU的IRQ异常,允许中断被响应。

    至此,所有硬件初始化完成,中断系统准备就绪。

    (8)主循环

    主循环持续检查press标志。该标志由中断处理函数设置。

    一旦press为真,先延时2ms进行硬件去抖(防止机械抖动导致误判)。

    然后读取AXI GPIO通道1的输入值。假设按键按下时输入为0,则执行打印操作,并清除press标志。

    最后重新使能AXI GPIO通道1的中断(因为在中断处理函数中被临时禁用了),为下一次按键做准备

  2. 中断处理函数

    XGpio_InterruptClear:清除AXI GPIO通道1的中断挂起状态,保证下次发生中断后可以重复响应

    XGpio_InterruptDisable:临时禁用该通道的中断。这是为了避免在去抖和读取期间再次触发中断,造成标志被反复设置或逻辑混乱。主循环中处理完成后会重新使能。

各函数功能分析

1. XGpioPs_LookupConfig函数

  • 函数功能
    XGpioPs_LookupConfig 是 Xilinx SDK 中 PS GPIO 驱动(XGpioPs)提供的一个查找函数,其核心作用是根据传入的设备ID,在系统自动生成的配置表 XGpioPs_ConfigTable 中检索匹配的配置项,并返回指向该配置结构体的指针。该函数通常用于驱动初始化之前,帮助上层代码获取设备的硬件信息(如寄存器基地址、中断号等),以便后续调用 XGpioPs_CfgInitialize 完成驱动实例的初始化。
  • 函数主体
c 复制代码
/*****************************************************************************/
/**
*
* 该函数根据唯一的设备 ID 查找对应的设备配置信息。
* 系统中的每个设备配置信息都保存在 XGpioPs_ConfigTable[] 表中。
*
* @param	DeviceId 是待查找设备的唯一设备 ID。
*
* @return	 指向与给定设备 ID 匹配的配置表项的指针;若未找到匹配项,则返回 NULL。
*
* @note		无。
*
******************************************************************************/
XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId)   // 函数定义:接收16位设备ID,返回配置结构体指针
{
	XGpioPs_Config *CfgPtr = NULL;                 // 初始化指针为NULL,用于保存找到的配置项地址
	u32 Index;                                     // 定义循环索引变量

	for (Index = 0U; Index < (u32)XPAR_XGPIOPS_NUM_INSTANCES; Index++) {
		// 遍历配置表,XPAR_XGPIOPS_NUM_INSTANCES 是系统生成的宏,表示PS GPIO实例总数
		if (XGpioPs_ConfigTable[Index].DeviceId == DeviceId) {
			// 如果当前表项的DeviceId与传入的DeviceId匹配
			CfgPtr = &XGpioPs_ConfigTable[Index]; // 将该表项的地址赋给CfgPtr
			break;                                 // 找到后立即退出循环
		}
	}

	return (XGpioPs_Config *)CfgPtr;               // 返回配置指针(若未找到则为NULL)
}
  • 编程逻辑
    首先,函数声明了一个指向配置结构体的指针 CfgPtr 并初始化为 NULL,这一步确保了在任何情况下(尤其是查找失败时)函数都有明确的返回值,避免了野指针风险。接着,函数通过一个 for 循环遍历全局配置表 XGpioPs_ConfigTable[],循环上限使用系统生成的宏 XPAR_XGPIOPS_NUM_INSTANCES,使得代码能自动适配不同的硬件设计,无需硬编码实例数量。在循环体内,逐一比对每个表项的 DeviceId 与传入的参数,若匹配则将该表项的地址存入 CfgPtr,并立即 break 跳出循环------因为设备 ID 具有唯一性,找到后无需继续查找。最终,函数返回 CfgPtr(可能是有效的配置指针或 NULL),调用者可根据返回值判断查找是否成功。
  • 辅助说明
    打开 XGpioPs_ConfigTable[]数据如下:
c 复制代码
XGpioPs_Config XGpioPs_ConfigTable[XPAR_XGPIOPS_NUM_INSTANCES] =
{
	{
		XPAR_PS7_GPIO_0_DEVICE_ID,//这里的值是0
		XPAR_PS7_GPIO_0_BASEADDR	//这里的值是0xE000A000
	}
};

可以看到指针返回值主要是ID和其相关的GPIO基地址。也就是说通过XGpioPs_LookupConfig函数系统获得了XGpioPs_Config 变量,其中的信息是GPIO DEVICE0的基址是0xE000A000。

打开UG585可以看到GPIO的基地址正是0xE000A000;

2. XGpioPs_CfgInitialize函数

XGpioPs_CfgInitialize 负责初始化一个 PS GPIO 驱动实例,其核心任务将ConfigPtr中包含的信息合并到InstancePtr变量(包括ID和基址信息),并根据运行平台(Zynq‑7000、Zynq UltraScale+ MPSoC 或 Versal)设置相应的引脚数与 Bank 数。

c 复制代码
/*****************************************************************************/
/*
 3.  4. 该函数初始化一个 XGpioPs 实例/驱动。
 5. XGpioPs 实例结构体的所有成员均被初始化,
 6. 并向 Bank 状态处理函数分配存根处理函数(StubHandlers)。
 7.  8. @param	InstancePtr 是指向 XGpioPs 实例的指针。
 9. @param	ConfigPtr 指向 XGpioPs 设备配置结构体。
 10. @param	EffectiveAddr 是虚拟内存地址空间中的设备基地址。
 11. 	如果不使用地址转换,则应传递物理地址。
 12. 	如果在调用此函数后更改地址映射,可能会发生意外错误。
 13.  14. @return	始终返回 XST_SUCCESS。
 15.  16. @note		无。
 17. ******************************************************************************/
s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, const XGpioPs_Config *ConfigPtr,
				u32 EffectiveAddr)
{
	s32 Status = XST_SUCCESS;                     // 定义状态变量,初始化为成功
	u8 i;                                          // 循环计数器
	Xil_AssertNonvoid(InstancePtr != NULL);        // 断言实例指针非空(非void版,不返回值)
	Xil_AssertNonvoid(ConfigPtr != NULL);          // 断言配置指针非空
	Xil_AssertNonvoid(EffectiveAddr != (u32)0);    // 断言有效地址非0
	/*
	 * 为实例数据设置一些默认值,在所有成员成功初始化之前,
	 * 不将设备标记为就绪可用。
	 */
	InstancePtr->IsReady = 0U;                     // 初始化为未就绪(0)
	InstancePtr->GpioConfig.BaseAddr = EffectiveAddr; // 设置基地址
	InstancePtr->GpioConfig.DeviceId = ConfigPtr->DeviceId; // 设置设备ID
	InstancePtr->Handler = (XGpioPs_Handler)StubHandler; // 设置中断处理函数为存根函数,具体见辅助说明
	InstancePtr->Platform = XGetPlatform_Info();   // 获取当前平台信息(Zynq/ZynqMP/Versal等),这里返回的值是:XPLAT_ZYNQ,具体见辅助说明;

	/* 根据平台初始化 Bank 数据 */
	if (InstancePtr->Platform == (u32)XPLAT_ZYNQ_ULTRA_MP) {  // 如果是 Zynq UltraScale+ MPSoC 平台
		/*
		 *	ZynqMP GPIO 设备的最大引脚数
		 *	0 - 25,  Bank 0
		 *	26 - 51, Bank 1
		 *	52 - 77, Bank 2
		 *	78 - 109, Bank 3
		 *	110 - 141, Bank 4
		 *	142 - 173, Bank 5
		 */
		InstancePtr->MaxPinNum = (u32)174;         // 最大引脚数174
		InstancePtr->MaxBanks = (u8)6;             // 最大bank数6
	}
        else if (InstancePtr->Platform == (u32)XPLAT_VERSAL) { // 如果是 Versal 平台
                if(InstancePtr->PmcGpio == (u32)FALSE)         // 如果不是 PMC GPIO(即 PS GPIO)
                {
                        /* PS_GPIO 设备的最大引脚数
                         *  0 -25, Bank 0
                         *  26-57, Bank 3
                         */
                        InstancePtr->MaxPinNum = (u32)58;      // 最大引脚数58
                        InstancePtr->MaxBanks = (u8)4;         // 最大bank数4
                }
                else                                           // 如果是 PMC GPIO
                {
                        /* PMC_GPIO 设备的最大引脚数
                         * 0  - 25,Bank 0
                         * 26 - 51,Bank 1
                         * 52 - 83,Bank 3
                         * 84 - 115, Bank 4
                         */
                        InstancePtr->MaxPinNum = (u32)116;     // 最大引脚数116
                        InstancePtr->MaxBanks = (u8)5;         // 最大bank数5
                }
        }
        else {                                                 // 其他平台其实就是ZYNQ;
		/*
		 *	GPIO 设备的最大引脚数
		 *	0 - 31,  Bank 0
		 *	32 - 53, Bank 1
		 *	54 - 85, Bank 2
		 *	86 - 117, Bank 3
		 */
		InstancePtr->MaxPinNum = (u32)118;         // 最大引脚数118
		InstancePtr->MaxBanks = (u8)4;             // 最大bank数4
	}

	/*
	 * 默认情况下,GPIO 中断未在硬件中被屏蔽。
	 * 为所有 4 个 bank 中的所有引脚禁用中断。
	 */
	for (i=(u8)0U; i < InstancePtr->MaxBanks; i++) {        // 遍历每个 bank
                if (InstancePtr->Platform == XPLAT_VERSAL){   // 如果是 Versal 平台
                        if(InstancePtr->PmcGpio == (u32)FALSE) // 非 PMC GPIO(PS GPIO)
                        {
                                if((i== (u8)XGPIOPS_ONE)||(i== (u8)XGPIOPS_TWO))
                                {
                                        continue;            // 跳过 bank 1 和 bank 2(这些 bank 在 PS GPIO 中不存在)
                                }
                                // 向中断禁用寄存器写入 0xFFFFFFFF,禁用该 bank 所有引脚的中断
                                XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
                                                ((u32)(i) * XGPIOPS_REG_MASK_OFFSET) +
                                                XGPIOPS_INTDIS_OFFSET, 0xFFFFFFFFU);
                        }
                        else                                 // 如果是 PMC GPIO
                        {
                                if(i==(u32)XGPIOPS_TWO)
                                {
                                        continue;            // 跳过 bank 2(PMC GPIO 中没有 bank 2)
                                }
                                XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
                                                ((u32)(i) * XGPIOPS_REG_MASK_OFFSET) +
                                               XGPIOPS_INTDIS_OFFSET, 0xFFFFFFFFU);
                       }
                }
                else                                         // 非 Versal 平台(Zynq/ZynqMP)
                {
		XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
					  ((u32)(i) * XGPIOPS_REG_MASK_OFFSET) +
					  XGPIOPS_INTDIS_OFFSET, 0xFFFFFFFFU); // 禁用该 bank 所有引脚中断,具体见辅助说明,
                }
	}

	/* 指示组件现在已就绪可用 */
	InstancePtr->IsReady = XIL_COMPONENT_IS_READY;   // 设置为就绪标志

	return Status;                                   // 返回状态(始终为 XST_SUCCESS)
}
  1. 辅助说明
    1> 打开StubHandler函数可以看到,这是一个空函数,就是用来占位的。
c 复制代码
void StubHandler(const void *CallBackRef, u32 Bank, u32 Status)
{
	(void) CallBackRef;
	(void) Bank;
	(void) Status;

	Xil_AssertVoidAlways();
}

2>XGetPlatform_Info函数用来反映当前的平台是哪一个。返回值由宏定义确定,宏定义由bsp生成。这里的返回值是XPLAT_ZYNQ

c 复制代码
u32 XGetPlatform_Info()
{
#if defined (versal)
	return XPLAT_VERSAL;
#elif defined (ARMR5) || (__aarch64__) || (ARMA53_32) || (PSU_PMU)
	return XPLAT_ZYNQ_ULTRA_MP;
#elif (__microblaze__)
	return XPLAT_MICROBLAZE;
#else
	return XPLAT_ZYNQ;
#endif
}

3>宏函数XGpioPs_WriteReg,作用是将data写到baseaddr+RegOffset的地址里。

c 复制代码
#define XGpioPs_WriteReg(BaseAddr, RegOffset, Data)	\
		Xil_Out32((BaseAddr) + (u32)(RegOffset), (u32)(Data))

在这里BaseAddr是0xE000A000(XGpioPs_LookupConfig查到的值),offset值是0x00000214U (XGPIOPS_INTDIS_OFFSET)。相加就是0xE000A214。查看UG585,可知定义如下

可以看出来寄存器是GPIO的中断禁止寄存器。设置1关闭GPIO的中断功能。函数关闭了所有GPIO的中断功能。

  1. XGpioPs_SetDirectionPin
  • 函数功能
    XGpioPs_SetDirectionPin 用于设置 GPIO 中单个引脚的方向(输入或输出)。它根据传入的引脚号和方向值,计算引脚所属的 bank 及在 bank 内的位号,然后修改对应 bank 的方向模式寄存器(DIRM)中的相应位
c 复制代码
/****************************************************************************/
/**
*
* 设置指定引脚的方向。
*
* @param	InstancePtr 是指向 XGpioPs 实例的指针。
* @param	Pin 是要写入数据的引脚编号。
*		在 Zynq 中有效值为 0-117,在 Zynq Ultrascale+ MP 中为 0-173。
* @param	Direction 是要为指定引脚设置的方向。
*		有效值为 0(输入方向)或 1(输出方向)。
*
* @return	无。
*
*****************************************************************************/
void XGpioPs_SetDirectionPin(const XGpioPs *InstancePtr, u32 Pin, u32 Direction)
{
	u8 Bank;                            // 用于存储引脚所在的 bank 编号(0~最大bank数-1)
	u8 PinNumber;                       // 用于存储引脚在 bank 内的位号(0~31)
	u32 DirModeReg;                     // 用于保存读出的方向模式寄存器值

	Xil_AssertVoid(InstancePtr != NULL);                      // 断言:实例指针非空
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 断言:实例已初始化就绪
	Xil_AssertVoid(Pin < InstancePtr->MaxPinNum);            // 断言:引脚号在有效范围内
	Xil_AssertVoid(Direction <= (u32)1);                     // 断言:方向值合法(0或1)

	/* 获取引脚所属的 bank 编号以及在 bank 内的位号。 */
#ifdef versal
	// 若是 Versal 平台,调用支持 PMC/PS 区分的版本(需要 InstancePtr 参数)
	XGpioPs_GetBankPin(InstancePtr, (u8)Pin, &Bank, &PinNumber);
#else
	// 其他平台(Zynq、ZynqMP)使用简单版本
	XGpioPs_GetBankPin((u8)Pin, &Bank, &PinNumber);
#endif

	// 读取对应 bank 的方向模式寄存器(DIRM)当前值
	DirModeReg = XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr,
				      ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
				      XGPIOPS_DIRM_OFFSET);

	if (Direction != (u32)0) {           // 若方向值非0(即为1),表示设置为输出方向
		// 将对应位设置为 1(输出)
		DirModeReg |= ((u32)1 << (u32)PinNumber);
	} else {                             // 否则为输入方向
		// 将对应位清零(输入)
		DirModeReg &= ~((u32)1 << (u32)PinNumber);
	}

	// 将修改后的值写回方向模式寄存器
	XGpioPs_WriteReg(InstancePtr->GpioConfig.BaseAddr,
			 ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
			 XGPIOPS_DIRM_OFFSET, DirModeReg);
}
  • 编程逻辑
    XGpioPs_SetDirectionPin 首先通过断言检查实例指针、实例就绪状态、引脚号是否越界以及方向值是否合法,确保参数有效。接着根据平台调用 XGpioPs_GetBankPin(Versal 平台需传入实例指针以区分 PMC/PS GPIO,其他平台仅传引脚号)将全局引脚号转换为所属的 bank 编号和 bank 内的位号。然后读取对应 bank 的方向模式寄存器(DIRM)当前值,根据传入的方向值修改目标位:若方向为输出(非 0)则将该位置 1,若为输入(0)则将该位清零。最后将更新后的寄存器值写回硬件,完成引脚方向配置。该函数通过读-改-写的方式保证不影响同一 bank 内其他引脚的方向设置。
  • 辅助说明
    1> XGpioPs_GetBankPin函数
c 复制代码
/****************************************************************************/
/*
* 获取给定 GPIO 引脚号对应的 Bank 编号以及在 Bank 内的引脚号。
*
* @param	PinNumber 是 GPIO 设备中的引脚编号。
* @param	BankNumber 返回该 GPIO 引脚所在的 Bank 编号。
*		有效值为 0 到 XGPIOPS_MAX_BANKS - 1。
* @param	PinNumberInBank 返回引脚在 Bank 内的编号。
*
* @return	无。
*
* @note		无。
*
*****************************************************************************/
#ifdef versal
// 如果是 Versal 平台,函数原型包含 InstancePtr 参数(用于区分 PMC/PS GPIO)
void XGpioPs_GetBankPin(const XGpioPs *InstancePtr, u8 PinNumber, u8 *BankNumber, u8 *PinNumberInBank)
#else
// 其他平台(Zynq、ZynqMP)函数原型不需要 InstancePtr
void XGpioPs_GetBankPin(u8 PinNumber, u8 *BankNumber, u8 *PinNumberInBank)
#endif
{
	u32 XGpioPsPinTable[6] = {0};   // 定义数组用于存储每个 bank 的最大引脚号,最多支持 6 个 bank
#ifdef versal
        u8 i=(u8)0;                     // 仅 Versal 平台需要,用作循环计数器
#endif
	u32 Platform = XGetPlatform_Info(); // 获取当前运行平台信息(Zynq/ZynqMP/Versal),前面介绍过

	if (Platform == (u32)XPLAT_ZYNQ_ULTRA_MP) {   // 如果平台是 Zynq UltraScale+ MPSoC
		/*
		 * 该结构定义了在单个引脚操作时,引脚号到 bank 的映射关系
		 */

		// 填充每个 bank 的最大引脚号(累计最大值)
		XGpioPsPinTable[0] = (u32)25;  /* 0 - 25, Bank 0 */
		XGpioPsPinTable[1] = (u32)51;  /* 26 - 51, Bank 1 */
		XGpioPsPinTable[2] = (u32)77;  /* 52 - 77, Bank 2 */
		XGpioPsPinTable[3] = (u32)109; /* 78 - 109, Bank 3 */
		XGpioPsPinTable[4] = (u32)141; /* 110 - 141, Bank 4 */
		XGpioPsPinTable[5] = (u32)173; /* 142 - 173 Bank 5 */

		*BankNumber = 0U;              // 从 bank 0 开始查找
		while (*BankNumber < XGPIOPS_SIX) {   // 遍历最多 6 个 bank
			if (PinNumber <= XGpioPsPinTable[*BankNumber]) { // 如果引脚号小于等于当前 bank 的最大值
				break;                // 则命中当前 bank,退出循环
			}
			(*BankNumber)++;          // 否则检查下一个 bank
		}
	}
#ifdef versal
        else if(Platform == XPLAT_VERSAL)   // 如果平台是 Versal
        {
                if(InstancePtr->PmcGpio == (u32)(FALSE))  // 如果是 PS GPIO(非 PMC)
                {
                        // PS GPIO 的引脚分布:bank 0 和 bank 3
                        XGpioPsPinTable[0] = (u32)25; /* 0 - 25, Bank 0 */
                        XGpioPsPinTable[1] = (u32)57; /* 26 - 57, Bank 3 */
                        *BankNumber =0U;               // 先假设为 bank 0
                        if(PinNumber <= XGpioPsPinTable[*BankNumber]) // 如果引脚在 0-25 范围内
                        {
                                *BankNumber = (u8)XGPIOPS_ZERO;       // 则 bank = 0
                        }
                        else                                         // 否则引脚在 26-57 范围内
                        {
                                *BankNumber = (u8)XGPIOPS_THREE;      // 则 bank = 3
                        }

                }
                else   // 如果是 PMC GPIO
                {
                        // PMC GPIO 的引脚分布:bank 0,1,3,4
                        XGpioPsPinTable[0] = (u32)25; /* 0 - 25, Bank 0 */
                        XGpioPsPinTable[1] = (u32)51; /* 26 - 51, Bank 1 */
                        XGpioPsPinTable[2] = (u32)83; /* 52 - 83, Bank 3 */
                        XGpioPsPinTable[3] = (u32)115; /*84 - 115, Bank 4 */

                        *BankNumber =0U;               // 从 bank 0 开始查找
                        while(i < XGPIOPS_FOUR)        // 遍历 4 个表项(实际对应 4 个 bank)
                        {
                                if(i <= (u8)XGPIOPS_ONE)  // 对于 i=0 和 i=1,即 bank 0 和 bank 1
                                {
                                        if (PinNumber <= XGpioPsPinTable[i]) // 如果引脚号小于等于当前表项的最大值
                                        {
                                                *BankNumber = (u8)i;          // 则 bank = i
                                                break;                        // 退出循环
                                        }
                                        i++;                                  // 否则继续检查下一个表项
                                }
                                else   // 对于 i=2 和 i=3,实际对应 bank 3 和 bank 4(中间跳过了 bank 2)
                                {
                                        if (PinNumber <= XGpioPsPinTable[i]) // 如果引脚号小于等于当前表项的最大值
                                        {
                                                *BankNumber = (u8)i+1U;       // 则 bank = i+1(i=2 -> bank=3, i=3 -> bank=4)
                                                break;
                                        }
                                        i++;
                                }

                        }
                }

        }
#endif
//下面是这个函数的核心代码 定位引脚在哪个bank中
        else {   // 默认平台:Zynq-7000
		// Zynq-7000 的引脚分布:4 个 bank,0-31, 32-53, 54-85, 86-117
		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;   // 从 bank 0 开始
		while (*BankNumber < XGPIOPS_FOUR) {   // 遍历 4 个 bank
			if (PinNumber <= XGpioPsPinTable[*BankNumber]) { // 找到第一个最大引脚号 >= 引脚号 的 bank
				break;
			}
			(*BankNumber)++;
		}
	}
	// 以下是计算 PinNumberInBank(引脚在 bank 内的偏移,也就是当前引脚在bank中的位置)
	if (*BankNumber == (u8)0) {   // 如果 bank 为 0,则引脚号就是偏移
		*PinNumberInBank = PinNumber;
	}

#ifdef versal
        else if(Platform == XPLAT_VERSAL)   // Versal 平台需要特殊处理偏移计算
        {
                if(InstancePtr->PmcGpio == (u32)(FALSE))  // PS GPIO 情况
                {
                        // 偏移 = 引脚号 - (bank 0 的最大值 + 1)
                        *PinNumberInBank = (u8)((u32)PinNumber - (XGpioPsPinTable[0] + (u32)1));
                }
                else   // PMC GPIO 情况
                {
                        if((*BankNumber ==(u8)XGPIOPS_THREE) || (*BankNumber ==(u8)XGPIOPS_FOUR))
                        {
                                // 对于 bank 3 或 bank 4,偏移 = 引脚号 % (前一表项最大值 + 1)
                                // 注意这里使用了 XGpioPsPinTable[*BankNumber - 2] 对应前一个 bank 的累计最大值
                                *PinNumberInBank = (u8)((u32)PinNumber %
                                                (XGpioPsPinTable[*BankNumber - (u8)XGPIOPS_TWO] + (u32)1));
                        }
                        else   // 对于 bank 0 或 bank 1
                        {
                                // 偏移 = 引脚号 % (前一表项最大值 + 1)
                                *PinNumberInBank = (u8)((u32)PinNumber %
                                                (XGpioPsPinTable[*BankNumber - (u8)1] + (u32)1));
                        }
               }

        }
#endif

        else {   // 非 Versal 平台(Zynq/ZynqMP)的偏移计算
		// 偏移 = 引脚号 % (前一个 bank 的最大值 + 1)
		*PinNumberInBank = (u8)((u32)PinNumber %
					(XGpioPsPinTable[*BankNumber - (u8)1] + (u32)1));
        }
}
  • 编程逻辑
    XGpioPs_GetBankPin·是一个根据平台和引脚号计算所属 bank 及 bank 内偏移的辅助函数,其实现根据平台(Zynq‑7000、Zynq UltraScale+ MPSoC、Versal)和 GPIO 类型(PS GPIO 或 PMC GPIO)采用不同的引脚分布表。函数首先获取当前平台信息,对于 ZynqMP 平台使用 6 个 bank(引脚 0‑173)的映射表,通过循环比较确定引脚所在 bank;对于 Versal 平台则进一步区分 PMC GPIO 与 PS GPIO,分别使用不同的 bank 分布(PS GPIO 为 bank 0 与 bank 3,PMC GPIO 为 bank 0、1、3、4),并采用条件分支快速定位 bank;对于默认的 Zynq‑7000 平台则使用 4 个 bank(引脚 0‑117)的映射表。确定 bank 后,根据平台与 bank 编号计算引脚在 bank 内的位号:对于非 Versal 平台或 Versal 下非 PMC 的特殊情况,通常用引脚号减去前一 bank 的累计最大值再加 1 的方式获得;而对于 Versal 平台中的 PMC GPIO 情况,则采用取模运算得到引脚在 bank 内的偏移。整个函数通过条件编译(versal宏)和运行时的平台判断,实现了对多种硬件架构的统一适配,为上层引脚级操作提供了准确的 bank 与位号解析。

    根据UG585介绍可以看出来,除了54个MIO还有两个EMIObank,分别是bank2,bank3,和程序一一对应。
    2> XGpioPs_ReadReg(InstancePtr->GpioConfig.BaseAddr, ((u32)(Bank) * XGPIOPS_REG_MASK_OFFSET) +
    XGPIOPS_DIRM_OFFSET);
    XGpioPs_ReadReg函数的功能不再赘述,直接定位两个宏定义 XGPIOPS_REG_MASK_OFFSET(地址0x00000040)和XGPIOPS_DIRM_OFFSET(地址0x00000204)相加是0x00000244,正好是DIRM_1寄存器(方向寄存器,详见UG585)。之所以用XGPIOPS_REG_MASK_OFFSET和XGPIOPS_DIRM_OFFSET拼是因为XGPIOPS_REG_MASK_OFFSET之前(即偏移地址小于0x00000040的地址)均为Maskable寄存器,从XGPIOPS_DIRM_OFFSET后才是真正用来配置的寄存器。所以真正的偏移地址是两者之和。而基址是来自InstancePtr也就是在XGpioPs_CfgInitialize函数中绑定的基址信息(0xE000A000)。写方向寄存器和这个分析方式向相同不再赘述。

太长了分成两篇吧,后边几个函数写在下一篇

相关推荐
咬_咬5 小时前
go语言学习(变量定义与输入输出)
开发语言·学习·golang·io·go语言··go变量定义
s09071364 天前
Zynq-7000 PetaLinux 千兆网卡 Link UP 但无法 Ping 通的终极排查与解决(以 KSZ9031 为例)
linux·skew·zynq·ksz9031·ping不通
weixin_450907284 天前
[ZYNQ Linux] V4L2视频驱动
zynq
CappuccinoRose4 天前
输入/输出及其控制 - 软考备战(五)
计算机·dma·软考·通道·中断·外设·程序查询
CinzWS6 天前
中断向量表中断号与 CMSIS IRQn 映射关系深度剖析:从硬件索引到软件句柄的桥梁
arm开发·架构·系统架构·嵌入式·cortex-m3·中断
s09071367 天前
【Zynq 进阶一】深度解析 PetaLinux 存储布局:NAND Flash 分区与 DDR 内存分配全攻略
linux·fpga开发·设备树·zynq·nand flash启动·flash分区
s09071367 天前
【Zynq开发避坑指南】PetaLinux核心配置与 Vivado DMA 地址分配深度解析
内存·zynq·petalinux·地址映射
s090713610 天前
ZYNQ无SD卡纯NAND Flash启动Linux全攻略
linux·fpga开发·zynq·nand flash启动
嵌入小生00712 天前
硬件 --- GPIO/中断/定时器/蜂鸣器
单片机·嵌入式硬件·定时器·pwm·gpio·蜂鸣器·中断