前言
最近学到了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 */
- 初始化GPIO端口
- 初始化INTC端口中断
- 等待中断
- 处理中断,清除中断标志位,继续等待
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通道上方向,即是输入还是输出。有三个输入量:
- XGPIO实体指针,即刚才设置的XGpio Gpio_LEDs; / XGpio Gpio_Key;
- GPIO的通道(1or2),因为只有两个通道,每个通道32位
- 掩码,设置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,文档里的代码都是最后自己跑通的代码,可以参考的~