目录
例程开发环境:
SOC芯片:ZYNQ7020
开发环境:Vivado2020.2,Vitis2020.2
目前网上对于AXI GPIO的详细使用方法介绍的很少,基本都是一个通道一个IO,或者几个IO,统一进行配置,并没有对不同bit的不同操作进行深入的讲解,因此才写了本篇文章,也是希望大家学习过程中少走一些弯路,坑我都替你们踩过了。
-
一、ZYNQ中PL端AXI GPIO简介
1.AXI GPIO IP核介绍
每个AXI GPIO IP核具有两个通道GPIO和GPIO2,添加IP后,默认只开启通道1,如果使用双通道,则勾选上Enable Dual Channel
每个通道具有32bit的可控制IO,每个bit可控制一个IO的输入或输出,在IP核中可以将整个通道32bit全部设置成输入模式或者输出模式
GPIO Width:选择需要使用几个bit的IO;
Default Output Value:32个bit上电后默认输出值,也可在Vitis中通过代码配置
Default Tri State Value:32个bit的IO默认方向模式,FFFFFFFF即全部是输入模式
如果需要使用AXI GPIO的中断输入,则需要勾选上Enable Interrupt,并将irpt中断引脚连接到PS端,用于PS端读取中断状态进行控制
-
二、搭建Block Design
本项目工程是在ZYNQ-Vitis(SDK)裸机开发之(一)串口实验工程基础上开发的,一些block design的设计方法,Vitis工程的建立方法等,均在该篇文章中进行了详细的讲解,大家可以去参考:
ZYNQ-Vitis(SDK)裸机开发之(一)串口收发使用:PS串口+PL串口、多个串口使用方法
1.添加AXI GPIO的IP核
在原有串口工程基础上添加AXI GPIO的IP核
右击block design空白处,点击Add IP,弹出窗口中输入AXI GPIO,双击添加即可
添加后,按照如下配置
在这里我需要使用两个IO,因此只需要使用通道1,并且在通道1中GPIO Width只需要填2个即可;其中bit0作为蜂鸣器的输出控制IO,bit1作为按键输入读取IO,由于不知道外部按键何时会被按键,因此需要通过中断来检测外部按键的状态,将Enable Interrupt勾选上,点击OK。
2.Block Design连线设计
AXI GPIO IP核添加后,按照下图进行连线,通过concat将AXI GPIO的中断信号连接到PS端
输出产品
生成顶层文件
在xdc文件中添加AXI GPIO引脚的约束,我这里将bit0约束到蜂鸣器,将bit1约束到外部按键
最后综合、实现、生成bit文件。
-
三、编写Vitis工程
1.AXI GPIO Vitis操作说明
/*
* AXI GPIO使用说明:
* 1.每个AXI GPIO IP核有两个通道,在vivado中搭建block design时可以选择启用哪个通道,或者两个通道都启用
* 2.如果要使用AXI GPIO中断功能,在在vivado中搭建block design时需要将中断使能勾选上
* 3.AXI GPIO每个通道具有32个bit的可控IO,每个IO,即每个bit均可进行配置操作
* 4.设置输出方向、设置IO值以及读取IO值时,操作对象均是对应每个bit,而不是每个通道,每个bit都可以单独设置
* 5.设置中断启动、关闭时,是对每个通道进行操作,例如打开中断是直接打开一整个通道,也就是32bit的IO全部的中断全部打开,无法单独对某一bit进行中断配置
* 6.该代码实现AXI GPIO读取按键中断输出,然后打开蜂鸣器,按键一次,打开蜂鸣器,再按键一次,关闭蜂鸣器,蜂鸣器:channel1bit0,按键:channel1bit1
*/
2. 添加AXI GPIO操作代码
头文件axi_gpio_hdl.h:
- 定义一些位操作的接口,因为AXI GPIO的官方接口都是按整个通道进行读写操作的,但是一个通道有32bit的IO,因此需要在自己代码中再将IO操作封装一层,细化到按bit操作
- 定义AXI GPIO设备ID号、中断ID号、通道号、通道中使用的bit号等
- 定义高低电平的枚举
- 定义AXI GPIO操作配置的结构体实例
- 声明AXI GPIO驱动初始化、中断初始化、中断处理、写操作、读操作的方法
具体头文件代码如下:
/*!
\file axi_gpio_hdl.h
\brief firmware functions to manage intr
\version 2024-03-12, V1.0.0
\author tbj
*/
#ifndef AXI_GPIO_HDL_H
#define AXI_GPIO_HDL_H
#include "crc_cal.h"
#include "xgpio.h"
#ifdef __cplusplus
extern "C" {
#endif
//位操作定义
#define SETBIT(x,y) x|=(1<<y) //把x中的第y位置1,从0开始数
#define CLRBIT(x,y) x&=~(1<<y) //把x中的第y位置0,从0开始数
#define RESERVEBIT(x,y) x^=(1<<y) //把x中的第y位取反,从0开始数
#define GETBIT(x,y) ((x)>>(y)&1) //获取x中第y位的值,移位运算符的优先级大于逻辑按位运算符优先级
//AXI_GPIO设备ID
#define AXI_GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
//AXI_GPIO中断ID
#define AXI_GPIO_INT_ID XPAR_FABRIC_GPIO_0_VEC_ID
//AXI_GPIO使用的通道号(一个AXI_GPIO一共两个通道,每个通道有32bit I/O 可控)
#define AXI_GPIO_CHANNEL1 XGPIO_IR_CH1_MASK
//定义外设连接AXI GPIO对应哪个bit
#define AXI_GPIO_BEEP 0
#define AXI_GPIO_KEY 1
typedef enum{
AXI_GPIO_VALUE_OFF = 0,
AXI_GPIO_VALUE_ON,
}AXI_GPIO_VALUE;
//PL端 AXI GPIO 驱动实例
XGpio axi_gpio;
//AXI GPIO外设初始化函数
int axi_gpio_init(XGpio *axi_gpio_ptr, u8 channel_num, u32 direction);
//AXI GPIO中断初始化函数
int axi_gpio_intr_init(XScuGic *gic_inst_ptr, XGpio *axi_gpio_ptr, u16 AXI_GpioIntrId);
//AXI GPIO输入中断处理函数
void axi_gpio_key_Intr_Hander(void *CallBackRef);
//读取AXI GPIO相应通道值
u8 axi_gpio_read(XGpio *axi_gpio_ptr, u8 channel_num, u8 gpio_bit);
//设置AXI GPIO相应通道值
void axi_gpio_write(XGpio *axi_gpio_ptr, u8 channel_num, u8 gpio_bit, u8 gpio_bit_val);
#ifdef __cplusplus
}
#endif
#endif /* AXI_GPIO_HDL_H */
源文件axi_gpio_hdl.c:
- 对头文件中AXI GPIO驱动初始化、中断初始化、中断处理、写操作、读操作的方法进行实现
具体代码如下:
/*!
\file axi_gpio_hdl.c
\brief firmware functions to manage axi_gpio
\version 2024-04-11, V1.0.0
\author tbj
*/
/*
* AXI GPIO使用说明:
* 1.每个AXI GPIO IP核有两个通道,在vivado中搭建block design时可以选择启用哪个通道,或者两个通道都启用
* 2.如果要使用AXI GPIO中断功能,在在vivado中搭建block design时需要将中断使能勾选上
* 3.AXI GPIO每个通道具有32个bit的可控IO,每个IO,即每个bit均可进行配置操作
* 4.设置输出方向、设置IO值以及读取IO值时,操作对象均是对应每个bit,而不是每个通道,每个bit都可以单独设置
* 5.设置中断启动、关闭时,是对每个通道进行操作,例如打开中断是直接打开一整个通道,也就是32bit的IO全部的中断全部打开,无法单独对某一bit进行中断配置
* 6.该代码实现AXI GPIO读取按键中断输出,然后打开蜂鸣器,按键一次,打开蜂鸣器,再按键一次,关闭蜂鸣器,蜂鸣器:channel1bit0,按键:channel1bit1
*/
#include "axi_gpio_hdl.h"
/* @brief:初始化GPIO外设
* @param:AXI GPIO对象指针
* @param:AXI GPIO通道号
* @param:AXI GPIO通道输入输出方向
* @return:初始化结果
*/
int axi_gpio_init(XGpio *axi_gpio_ptr, u8 channel_num, u32 direction)
{
int Status;
//初始化 PL端 AXI GPIO 驱动
Status = XGpio_Initialize(axi_gpio_ptr, AXI_GPIO_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//设置 AXI GPIO通道方向
XGpio_SetDataDirection(axi_gpio_ptr, channel_num, direction);
return XST_SUCCESS;
}
/* @brief:初始化AXI GPIO的中断
* @param:中断控制器对象指针
* @param:AXI GPIO对象指针
* @param:AXI GPIO中断号
* @return:初始化结果
*/
int axi_gpio_intr_init(XScuGic *gic_inst_ptr, XGpio *axi_gpio_ptr, u16 AXI_GpioIntrId)
{
//设置中断优先级和中断触发方式,0x01高电平中断,0x02是总触发中断,0x03是上升沿儿中断
XScuGic_SetPriorityTriggerType(gic_inst_ptr, AXI_GpioIntrId, 0xA0, 0x01);
//关联中断 ID 和中断处理函数
XScuGic_Connect(gic_inst_ptr, AXI_GpioIntrId,
(Xil_ExceptionHandler) axi_gpio_key_Intr_Hander, (void *) axi_gpio_ptr);
//使能来自于 axi_Gpio 器件的中断
XScuGic_Enable(gic_inst_ptr, AXI_GpioIntrId);
//使能AXI GPIO通道1的中断
XGpio_InterruptEnable(axi_gpio_ptr, AXI_GPIO_CHANNEL1);
//使能全局中断-AXI GPIO中断依赖于全局中断
XGpio_InterruptGlobalEnable(axi_gpio_ptr);
return XST_SUCCESS;
}
/* @brief:AXI GPIO中断处理函数
* @param:回调指针
* @return:初始化结果
*/
void axi_gpio_key_Intr_Hander(void *CallBackRef){
//记录上次蜂鸣器状态
static u8 beep_value_temp = 0;
//本次配置蜂鸣器的状态
u8 beep_value = 0;
XGpio *axi_gpio_ptr = (XGpio *)CallBackRef;
usleep(20000); //延时 20ms,按键消抖
//通过读取AXI GPIO channel1的bit1来判断按键有没有被按下
if (axi_gpio_read(axi_gpio_ptr, AXI_GPIO_CHANNEL1, AXI_GPIO_KEY) == 0) {
beep_value = RESERVEBIT(beep_value_temp, 0);
//改变蜂鸣器状态
axi_gpio_write(axi_gpio_ptr, AXI_GPIO_CHANNEL1, AXI_GPIO_BEEP, beep_value);
XGpio_InterruptDisable(axi_gpio_ptr, AXI_GPIO_CHANNEL1);
}
//清除中断并重新使能中断
XGpio_InterruptClear(axi_gpio_ptr, AXI_GPIO_CHANNEL1);
XGpio_InterruptEnable(axi_gpio_ptr, AXI_GPIO_CHANNEL1);
}
/* @brief:读取AXI GPIO相应通道值
* @param:AXI GPIO对象指针
* @param:AXI GPIO通道号
* @param:AXI GPIO需要操作的bit
* @return:返回AXI GPIO对应bit的值
*/
u8 axi_gpio_read(XGpio *axi_gpio_ptr, u8 channel_num, u8 gpio_bit){
u32 read_val = 0;
read_val = XGpio_DiscreteRead(axi_gpio_ptr, channel_num);
return GETBIT(read_val, gpio_bit);
}
/* @brief:设置AXI GPIO相应通道值
* @param:AXI GPIO对象指针
* @param:AXI GPIO通道号
* @param:AXI GPIO需要操作的bit
* @param:AXI GPIO需要操作的bit写入的值
* @return:无
*/
void axi_gpio_write(XGpio *axi_gpio_ptr, u8 channel_num, u8 gpio_bit, u8 gpio_bit_val){
u32 write_val = 0;
//先读取AXI GPIO整个通道的值,防止写入某一bit将其余bit清除掉
write_val = XGpio_DiscreteRead(axi_gpio_ptr, channel_num);
//判断写1还是写0
if(gpio_bit_val == 1){
SETBIT(write_val, gpio_bit);
}else if(gpio_bit_val == 0){
CLRBIT(write_val, gpio_bit);
}else{
;
}
//将对应值写入AXI GPIO寄存器
XGpio_DiscreteWrite(axi_gpio_ptr, channel_num, write_val);
}
最后在main.c中调用AXI GPIO初始化相关功能
int main()
{
//AXI GPIO测试
#ifdef AXI_GPIO_Test
//初始化AXI GPIO每个bit的输入输出方向,0:输出,1:输入;0x020即bit0为输出,bit1为输入
u32 axi_gpio_dir = 0x02;
axi_gpio_init(&axi_gpio, AXI_GPIO_CHANNEL1, axi_gpio_dir);
//初始化AXI GPIO中断
axi_gpio_intr_init(&GicIntrDevice, &axi_gpio, AXI_GPIO_INT_ID);
#endif
while(1){
}
return 0;
}
创作不易,希望大家点赞、收藏、关注哦!!!ヾ(o◕∀◕)ノ