【ZYNQ初学】对AXI_GPIO的一点思考记录

前言

最近学到了PS与PL交互的部分,AXI是一个很重要的点,这里主要跟着ALINX ZYNQ7000教程,做了一下基于AXI的4个LED闪烁/LED长亮+KEY中断串口打印的练习。

于是想做一个按下KEY进入中断,让4个LED闪一下的简单练习,结果发现怎么都实现不了,就把官方文档的范例代码和函数里变量的意义都尽菜鸡所能分析了,所以简单记录一下~

一、AXI_GPIO的vivado设计

AXI是PS与PL的交互接口,AXI_GPIO有两个通道,在vivado的块设计里,分别设置了两个AXI_GPIO模块(LEDS,KEY)
在这里插入图片描述
块设计完成后,端口分配会反映在.v文件里,可以发现我设计了4路led和1路key。.V文件里的变量名和之后写的xdc管脚约束文件中的名字必须对应起来。
在这里插入图片描述
然后进行synthesis,implementation,generate bitsream三步骤后,vivado硬件部分设计基本结束,此时可以理解为硬件电路已经形成,就看arm怎么控制了。

二、SDK编程

1. 编程思路

我以前用过32,所以可以读懂例程在干嘛,虽然再往深只能读个大概了…
我理解的是,ZYNQ在vivado设计阶段,AXI_GPIO端口的设置基本完成了,用户只需要定位端口ID,就能把基本的设置导进结构体实例里。
于是整个程序大致是:

0.创建结构体实例和必要的宏定义
两个AXI_GPIO和一个中断的ID都可以在xparameters.h中找到!
因为key和led是两个axi_gpio模块,虽然他们都是一个gpio下的,但是也可以建两个实例分别指代。(我之前以为它们是32位gpio0的连在一起的后五位…卡了很久,大家不要像我一样开莫名其妙的脑洞…)

/*===================用户自定义宏======================*/
//AXI_GPIO
#define Key_GPIO_ID 	XPAR_KEY_DEVICE_ID
#define Key_CHANNEL 	1
#define Key_bits		0x01


#define LEDS_GPIO_ID	XPAR_LEDS_DEVICE_ID
#define LEDs_CHANNEL	1
#define LEDs_bits		0x0F

//AXI_INTC
#define Key_INTC_ID XPAR_FABRIC_KEY_IP2INTC_IRPT_INTR
#define INTC_DEVICE_ID	XPAR_SCUGIC_SINGLE_DEVICE_ID

//延迟
#define LED_DELAY 5
/*==================结构体定义====================*/
XGpio 	Gpio_LEDs;
XGpio 	Gpio_Key;
INTC	Intc_Key;

static volatile int flag_key=0;
static u16 GlobalIntrMask; /* GPIO channel mask that is needed by
			    * the Interrupt Handler */

  1. 初始化GPIO端口
  2. 初始化INTC端口中断
  3. 等待中断
  4. 处理中断,清除中断标志位,继续等待
int main()
{
	int Status;
	volatile int Delay;
	//int flag_key_rc=0;
	/*============LED初始化==============*/
	//初始化,指定IO_ID
	Status = XGpio_Initialize(&Gpio_LEDs, LEDS_GPIO_ID);
	if (Status != XST_SUCCESS) {
		xil_printf("Gpio Initialization Failed\r\n");
		return XST_FAILURE;
	}

	//设置LED_IO方向
	XGpio_SetDataDirection(&Gpio_LEDs, LEDs_CHANNEL, ~LEDs_bits);



	//KEY AXI_IO初始化
	Status = XGpio_Initialize(&Gpio_Key, Key_GPIO_ID);
	if (Status != XST_SUCCESS) {
		xil_printf("Gpio Initialization Failed\r\n");
		return XST_FAILURE;
	}

	//设置KEY_IO方向
	XGpio_SetDataDirection(&Gpio_Key, Key_CHANNEL, Key_bits);

	//KEY AXI_IO中断设置
	Status = GpioSetupIntrSystem(&Intc_Key, &Gpio_Key, INTC_DEVICE_ID,
			Key_INTC_ID, Key_bits);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	//等待KEY2,翻转信号
	while(1)
	{


		if(flag_key==1)
		{
			XGpio_DiscreteWrite(&Gpio_LEDs, LEDs_CHANNEL, LEDs_bits);
			for (Delay = 0; Delay < LED_DELAY; Delay++);
			XGpio_DiscreteClear(&Gpio_LEDs, LEDs_CHANNEL, LEDs_bits);
			for (Delay = 0; Delay < LED_DELAY; Delay++);

			flag_key=0;
		}
	}

    return 0;
}

以下是相关的函数


/******************************************************************************/
/**
*
* This function performs the GPIO set up for Interrupts
*
* @param	IntcInstancePtr is a reference to the Interrupt Controller
*		driver Instance
* @param	InstancePtr is a reference to the GPIO driver Instance
* @param	DeviceId is the XPAR_<GPIO_instance>_DEVICE_ID value from
*		xparameters.h
* @param	IntrId is XPAR_<INTC_instance>_<GPIO_instance>_IP2INTC_IRPT_INTR
*		value from xparameters.h
* @param	IntrMask is the GPIO channel mask
*
* @return	XST_SUCCESS if the Test is successful, otherwise XST_FAILURE
*
* @note		None.
*
******************************************************************************/
int GpioSetupIntrSystem(INTC *IntcInstancePtr, XGpio *InstancePtr,
			u16 DeviceId, u16 IntrId, u16 IntrMask)
{
	int Result;

	GlobalIntrMask = IntrMask;

#ifdef XPAR_INTC_0_DEVICE_ID

#ifndef TESTAPP_GEN
	/*
	 * Initialize the interrupt controller driver so that it's ready to use.
	 * specify the device ID that was generated in xparameters.h
	 */
	Result = XIntc_Initialize(IntcInstancePtr, INTC_DEVICE_ID);
	if (Result != XST_SUCCESS) {
		return Result;
	}
#endif /* TESTAPP_GEN */

	/* Hook up interrupt service routine */
	XIntc_Connect(IntcInstancePtr, IntrId,
		      (Xil_ExceptionHandler)GpioHandler, InstancePtr);

	/* Enable the interrupt vector at the interrupt controller */
	XIntc_Enable(IntcInstancePtr, IntrId);

#ifndef TESTAPP_GEN
	/*
	 * Start the interrupt controller such that interrupts are recognized
	 * and handled by the processor
	 */
	Result = XIntc_Start(IntcInstancePtr, XIN_REAL_MODE);
	if (Result != XST_SUCCESS) {
		return Result;
	}
#endif /* TESTAPP_GEN */

#else /* !XPAR_INTC_0_DEVICE_ID */

#ifndef TESTAPP_GEN
	XScuGic_Config *IntcConfig;

	/*
	 * Initialize the interrupt controller driver so that it is ready to
	 * use.
	 */
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	if (NULL == IntcConfig) {
		return XST_FAILURE;
	}

	Result = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
					IntcConfig->CpuBaseAddress);
	if (Result != XST_SUCCESS) {
		return XST_FAILURE;
	}
#endif /* TESTAPP_GEN */

	XScuGic_SetPriorityTriggerType(IntcInstancePtr, IntrId,
					0xA0, 0x3);

	/*
	 * Connect the interrupt handler that will be called when an
	 * interrupt occurs for the device.
	 */
	Result = XScuGic_Connect(IntcInstancePtr, IntrId,
				 (Xil_ExceptionHandler)GpioHandler, InstancePtr);
	if (Result != XST_SUCCESS) {
		return Result;
	}

	/* Enable the interrupt for the GPIO device.*/
	XScuGic_Enable(IntcInstancePtr, IntrId);
#endif /* XPAR_INTC_0_DEVICE_ID */

	/*
	 * Enable the GPIO channel interrupts so that push button can be
	 * detected and enable interrupts for the GPIO device
	 */
	XGpio_InterruptEnable(InstancePtr, IntrMask);
	XGpio_InterruptGlobalEnable(InstancePtr);

	/*
	 * Initialize the exception table and register the interrupt
	 * controller handler with the exception table
	 */
	Xil_ExceptionInit();

	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
			 (Xil_ExceptionHandler)INTC_HANDLER, IntcInstancePtr);

	/* Enable non-critical exceptions */
	Xil_ExceptionEnable();

	return XST_SUCCESS;
}

/******************************************************************************/
/**
*
* This is the interrupt handler routine for the GPIO for this example.
*
* @param	CallbackRef is the Callback reference for the handler.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void GpioHandler(void *CallbackRef)
{
	XGpio *GpioPtr = (XGpio *)CallbackRef;

	flag_key=1;

	/* Clear the Interrupt */
	XGpio_InterruptClear(GpioPtr, GlobalIntrMask);

}

/******************************************************************************/
/**
*
* This function disables the interrupts for the GPIO
*
* @param	IntcInstancePtr is a pointer to the Interrupt Controller
*		driver Instance
* @param	InstancePtr is a pointer to the GPIO driver Instance
* @param	IntrId is XPAR_<INTC_instance>_<GPIO_instance>_VEC
*		value from xparameters.h
* @param	IntrMask is the GPIO channel mask
*
* @return	None
*
* @note		None.
*
******************************************************************************/
void GpioDisableIntr(INTC *IntcInstancePtr, XGpio *InstancePtr,
			u16 IntrId, u16 IntrMask)
{
	XGpio_InterruptDisable(InstancePtr, IntrMask);
#ifdef XPAR_INTC_0_DEVICE_ID
	XIntc_Disable(IntcInstancePtr, IntrId);
#else
	/* Disconnect the interrupt */
	XScuGic_Disable(IntcInstancePtr, IntrId);
	XScuGic_Disconnect(IntcInstancePtr, IntrId);
#endif
	return;
}

2. axi_gpio模块和函数中的mask

这个是我自己踩的坑,关于这个mask我想了很久是啥意思。
一切起源于这个函数,Xgpio方向设置,来自xgpio.h/c 文件

/****************************************************************************/
/**
* Set the input/output direction of all discrete signals for the specified
* GPIO channel.
*
* @param	InstancePtr is a pointer to an XGpio instance to be worked on.
* @param	Channel contains the channel of the GPIO (1 or 2) to operate on.
* @param	DirectionMask is a bitmask specifying which discretes are input
*		and which are output. Bits set to 0 are output and bits set to 1
*		are input.
*
* @return	None.
*
* @note		The hardware must be built for dual channels if this function
*		is used with any channel other than 1.  If it is not, this
*		function will assert.
*
*****************************************************************************/
void XGpio_SetDataDirection(XGpio *InstancePtr, unsigned Channel,
			    u32 DirectionMask)
{
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid((Channel == 1) ||
		     ((Channel == 2) && (InstancePtr->IsDual == TRUE)));

	XGpio_WriteReg(InstancePtr->BaseAddress,
			((Channel - 1) * XGPIO_CHAN_OFFSET) + XGPIO_TRI_OFFSET,
			DirectionMask);
}

这个函数还是很好理解,就是按位设置一个GPIOI通道上方向,即是输入还是输出。有三个输入量:

  1. XGPIO实体指针,即刚才设置的XGpio Gpio_LEDs; / XGpio Gpio_Key;
  2. GPIO的通道(1or2),因为只有两个通道,每个通道32位
  3. 掩码,设置0的为输出,设置1的为输入。LED的4位肯定是输出,KEY肯定是输入啦

最初我只测试了4个LED闪烁,按这个设置了没有问题,然后就进入到中断初始化,用了这个函数:
int GpioSetupIntrSystem(INTC *IntcInstancePtr, XGpio *InstancePtr,
u16 DeviceId, u16 IntrId, u16 IntrMask)
然后这里面也有一个掩码IntrMask,具体是在使能中断的这个函数里用到的:

/****************************************************************************/
/**
* Enable interrupts. The global interrupt must also be enabled by calling
* XGpio_InterruptGlobalEnable() for interrupts to occur. This function will
* assert if the hardware device has not been built with interrupt capabilities.
*
* @param	InstancePtr is the GPIO instance to operate on.
* @param	Mask is the mask to enable. Bit positions of 1 are enabled.
*		This mask is formed by OR'ing bits from XGPIO_IR* bits which
*		are contained in xgpio_l.h.
*
* @return	None.
*
* @note		None.
*
*****************************************************************************/
void XGpio_InterruptEnable(XGpio *InstancePtr, u32 Mask)
{
	u32 Register;

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

	/*
	 * Read the interrupt enable register and only enable the specified
	 * interrupts without disabling or enabling any others.
	 */

	Register = XGpio_ReadReg(InstancePtr->BaseAddress, XGPIO_IER_OFFSET);
	XGpio_WriteReg(InstancePtr->BaseAddress, XGPIO_IER_OFFSET,
			Register | Mask);

}

可以看到这里面也有一个u32的掩码,和上一个函数一样,于是我的思考自然而然就变成:

【下面这段完全是错的请大家不要看】

GPIO0是32位带宽的双向I/O -> 每1位控制一个I/O引脚 -> GPIO和INTC的函数掩码都是32位 -> LED4位和KEY1位是GPIO最低5位 ->所以针对KEY引脚的掩码应该是 0X10(第5位置1)

【上面这段完全是错的请大家不要看】

最后卡了很久才理顺,大概是这样的模型:
LED和KEY是两个独立的AXI_GPIO模块,即使他们都是基于GPIO0的,也不要考虑他们是不是连在一起的。
用哪个,就只将对应的实例联系到这个模块的ID,然后在函数中设置对应位数的掩码,比如LED就是~0X0F,KEY就是0x01。
至于INTC,是中断设置的一个实例,也只用单独考虑就行了,比如我有两个按键,但是我只给其中一个掩码设置中断,应该也是可以的。
在这里插入图片描述

总结

多学英语少脑补TAT,文档里的代码都是最后自己跑通的代码,可以参考的~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值