|
|
更多课程笔记请查看:【zynq裸机编程课程笔记合集】
http://www.fpga.cn/forum.php?mod=viewthread&tid=29095
1 介绍
在前面课程中,我们带领大家一起学习了PS GPIO的原理及基本使用。并最终通过C编程,实现了按键控制LED的效果。
但是Zynq上GPIO无论是MIO还是EMIO,都是属于PS侧的资源,相当于是硬核。而作为一个PS与PL相互协作的平台,当PS侧的GPIO硬核不够用或者无法使用的场合,我们能否使用PL端的逻辑资源来构建一个或多个GPIO软核呢?当然是可以的, Xilinx官方为我们提供了一个名为AXI GPIO的软核。该核使用标准的AXI总线与PS交互,用户可以通过AXI总线,控制AXI GPIO的输入输出模式、输出值、读取指定引脚等。该IP核的结构如下:
图 3 4 AXI GPIO框图
从图中可以看出,最右侧有两个具有三态输出功能的端口,分别为GPIO和GPIO2,这也就意味着该控制器可以最多提供2个通道的GPIO。
每个通道的GPIO都有3个标准的信号,也就是输出值(GPIO_O)、输入值(GPIO_I)以及管脚输出使能控制信号(GPIO_T)。
对于输出值,使用一个名为GPIO_DAT的寄存器/D触发器存储所需要输出的值
对于输入值,使用一个名为GPIO_DATA_IN的寄存器/D触发器存储GPIO Pin管脚上的值。
对于输出使能控制信号,使用一个名为GPIO_TRI的寄存器/D触发器存储设置状态。
这两个三态缓冲器工作时,其输入输出状态受GPIO_T信号控制。当GPIO_T=0时,为输出态,三态缓冲器会输出GPIO_O的值;当GPIO_T=1时,为输入态,此时GPIO_I的值会输入到AXI GPIO中。
因此,我们只需要通过对这3个寄存器进行读写,就能够实现对该Pin的状态的控制和读取。
这些寄存器由谁读写,又是通过什么方式读写呢?这个就是我们刚刚提到的,PS通过AXI-Lite总线来读写这些寄存器。
另外该控制器还提供了中断检测逻辑,以及中断控制寄存器,用于中断检测以及中断使能/屏蔽/状态显示。
2 课程目的
接下来我们将以该IP核为例,通过拨码开关控制LED的设计,带领大家了解AXI GPIO核的使用,体会在使用时与PS GPIO的异同点。
本次设计中,将会使用到AXI GPIO的两个通道,其中一整个通道被设置为输入,用于获取拨码开关的电平;另一整个通道被设置为输出,用于驱动LED显示。
3 所包含硬件
AC880开发板的PL侧板载有8个用户LED和8个拨码开关,非常适合本次设计。
4 系统框图
在开始设计之前我们首先要对整个系统有个大致的认识,这里我给大家做了一张简易设计图。
在设计时,AXI GPIO的通道1(GPIO)被配置为输入模式,用来接拨码开关,通道2(GPIO2)被设置为输出模式,用来接LED灯。AXI GPIO会捕获用户通过拨码开关设置的电平,PS则通过AXI4-Lite接口读取对应寄存器,以获知该电平值,然后ARM将需要设置的LED的亮灭状态值通过AXI4-Lite接口传输回AXI GPIO,作为通道2(GPIO2)的输出,驱动LED的亮灭显示。
5 创建工程
介绍完了系统的模型之后,我们就可以使用Vivado和SDK来进行系统的设计和实现了。
1. 创建一个名为AXI_GPIO的Vivado工程
2. 新建blockdesign设计
3. 添加ZYNQ、AXI GPIO IP核
6 IP配置与端口连接
(1) Zynq核
配置DDR型号,MT41K256M16 RE-125
(2) AXI GPIO核
勾选Enable Dual Channel,使能双通道
设置两个通道的GPIO Width为8,对应8个拨码开关,8个LED
另外介绍一点,在GUI配置界面可以将某一通道设置为全输入/输出模式,这种情况下,该通道的GPIO就只能输入或者输出了,这种配置在GPIO功能和使用场景明确的情况下可以有效降低PL逻辑资源的使用虑。本实验考虑到灵活性以及让大家对GPIO的各项功能有较为明确的了解,这里不设置单输入或者输出模式。
(3) 端口连接
点击“Run Block Automation”和“Run Connection Automation”
7 管脚约束
当完成block design设计之后,我们还需要对该block design生成输出文件(generate output products),并生成一个例化了该设计的HDL格式的顶层文件。
当顶层文件生成好之后,点击“Open Elaborated Design”对设计进行详细的分析,分析完成后就可以自动打开管脚约束界面进行管脚约束,如果显示的界面不是管脚分配,大家可以通过手动在右上角的下拉列表中选择I/O planning选项卡以切换到IO分配界面。关于AC8802开发版各项功能对应的管脚信息,可以直接在下述帖子中查看。
【AC880】基于Xilinx ZYNQ-7020全功能教学开发板用户自助服务...
[url=【AC880】基于Xilinx ZYNQ-7020全功能教学开发板用户自助服务... https://fpga.cn/forum.php?mod=viewthread&tid=30234]https://fpga.cn/forum.php?mod=viewthread&tid=30234[/url]
以下为本次实验所用功能对应的引脚分配表:
Pin Name
| Signial Name
| Pin NO.
|
| Pin Name
| Signial Name
| Pin NO.
| SW7
| gpio_rtl_0_tri_io[7]
| H15
|
| LED7
| gpio_rtl_1_tri_io[7]
| R21
| SW6
| gpio_rtl_0_tri_io[6]
| N18
|
| LED6
| gpio_rtl_1_tri_io[6]
| R20
| SW5
| gpio_rtl_0_tri_io[5]
| N17
|
| LED5
| gpio_rtl_1_tri_io[5]
| P21
| SW4
| gpio_rtl_0_tri_io[4]
| N20
|
| LED4
| gpio_rtl_1_tri_io[4]
| P20
| SW3
| gpio_rtl_0_tri_io[3]
| N19
|
| LED3
| gpio_rtl_1_tri_io[3]
| T18
| SW2
| gpio_rtl_0_tri_io[2]
| M20
|
| LED2
| gpio_rtl_1_tri_io[2]
| R18
| SW1
| gpio_rtl_0_tri_io[1]
| M19
|
| LED1
| gpio_rtl_1_tri_io[1]
| P22
| SW0
| gpio_rtl_0_tri_io[0]
| M16
|
| LED0
| gpio_rtl_1_tri_io[0]
| N22
|
分配完成后将引脚电平标准修改为LVCMOS33,然后保存约束文件,命名为AXI_GPIO.xdc,
到此,我们已经完成了整个系统的硬件系统部分的设计,接下来,我们就可以点击generate bitstream来生成本系统的PL配置文件,也就是我们常说的bit文件。
8 查看编译报告
等待比特流生成完成,我们就可以通过点击Project Summary图标,并切换到Dashboard选项卡中,来查看当前设计的资源使用等信息。
9 创建SDK工程并添加实例源码
通过前面的操作,我们已经完成了本系统的硬件系统的设计,并得到了相应的FPGA配置文件(bit)。
接下来我们还需要针对该硬件系统,生成对应的描述文件,只有基于这些描述文件,我们才能在SDK中对该系统进行正确的编程。
在菜单栏中点击File->Export->Export Hardware,以生成本系统的硬件描述文件。导出时请务必勾选包含bitstream文件,因为本系统已经使用到了FPGA侧的逻辑资源(AXI_GPIO软核)。
然后我们就可以点击File->Launch SDK来打开Vivado提供的SDK集成开发软件。然后在软件中创建一个空白模板的SDK工程,创建好之后,将我们为本节课准备好的例程源码直接拷贝到工程中。
(添加头文件包含路径)
这样,我们的应用程序就创建完了,程序写完之后,我们首先运行一次程序,带领大家体验一下实验效果,然后再来回头分析我们的程序内容,通过分析程序实现的思路,带领大家掌握和学习AXI_GPIO的使用方法。
10 硬件连接
本次硬件连接如下图所示,
连接好开发板的DC电源接口以及板载下载器接口,随后为开发板上电,接下来便可以直接烧录程序。
(Run Configuration - SystemDebugger)
11 运行程序
现象:拓展板上拨码开关控制与之对应的LED灯亮灭(SW0控制LED0...SW7控制LED7)
12 代码分析
完成了程序效果的演示,接下来一起就来看看,我们的程序是按照怎样的思路来控制AXI_GPIO,实现我们想要的功能的。
[C] 纯文本查看 复制代码
#include"COMMON.h"
int main(void)
{
uint32_t State;
AXI_GPIO_Init(&AXI_GPIO0,GPIO_0_ID); //初始化AXI GPIO0
AXI_GPIO_Set_Channel(&AXI_GPIO0, XGPIO_IR_CH1_MASK, 0xFF, 0);//设置通道1为输入
while(1){
State = XGpio_DiscreteRead(&AXI_GPIO0,XGPIO_IR_CH1_MASK);//读取通道1输入的值
AXI_GPIO_Set_Channel(&AXI_GPIO0, XGPIO_IR_CH2_MASK, 0, State);//将通道2设置为输出,输出从通道1读取的值
}
return 0;
}
12.1 头文件分析:
对AXI GPIO的操作属于PL端的操作,所以包含的头文件为xgpio.h(GPIO对应xgpiops.h),该头文件包含Xilinx 通用 I/O (XGpio) 设备驱动程序的软件 API 定义。
12.2 代码分析:
1. 根据xparameters.h中查找到的器件ID查询配置表初始化AXI GPIO
2. 使用AXI_GPIO_Set_Channel()函数设置通道1为输入,并设置输出值为0
进入函数分析,这里分两步,首先是设置方向,本质使用的是XGpio_SetDataDirection()函数,底层是调用的XGpio_Out32()函数对指定通道的3态控制器寄存器直接写操作
XGpio_WriteReg(InstancePtr->BaseAddress,
((Channel - 1) * XGPIO_CHAN_OFFSET) + XGPIO_TRI_OFFSET,
DirectionMask);
接下来是输出值,这里使用的XGpio_DiscreteWrite()函数,通过XGpio_WriteReg()函数实现,底层是调用的XGpio_Out32()函数对指定通道的数据寄存器直接写操作
XGpio_WriteReg(InstancePtr->BaseAddress,
((Channel - 1) * XGPIO_CHAN_OFFSET) + XGPIO_DATA_OFFSET,
Data);
这里由于我们已经将通道1设置为了输入模式,此时对寄存器进行写操作无效。
3. 读取通道1状态,保存在state中。这里使用的是XGpio_DiscreteRead()函数,对通道1的数据寄存器读操作(输入状态,读操作能正常完成)
return XGpio_ReadReg(InstancePtr->BaseAddress,
((Channel - 1) * XGPIO_CHAN_OFFSET) +
XGPIO_DATA_OFFSET);
4. 再次使用AXI_GPIO_Set_Channel()函数,设置通道2 为输出,并将State的值作为通道2输出值。
寄存器空间及涉及寄存器描述
表 1 寄存器映射
偏移地址
| 寄存器名
| 访问类型
| 默认值
| 描述
| 0x0000
| GPIO_DATA
| R/W
| 0x0
| 通道1 AXI GPIO数据寄存器
| 0x0004
| GPIO_TRI
| R/W
| 0x0
| 通道1 AXI GPIO 3态控制寄存器
| 0x0008
| GPIO2_DATA
| R/W
| 0x0
| 通道2 AXI GP[IO数据寄存器
| 0x000C
| GPIO2_TRI
| R/W
| 0x0
| 通道2 AXI GPIO 3态寄存器
| 0x011C
| GIER
| R/W
| 0x0
| 全局中断使能寄存器
| 0x0128
| IP IER
| R/W
| 0x0
| IP中断使能寄存器
| 0x0120
| IP ISR
| R/TOW
| 0x0
| IP中断状态寄存器
|
表 2 AXI GPIO数据寄存器描述
位
| 字段名
| 访问类型
| 复位值
| 描述
| [GPIOx_Width-1:0]
| GPIOx_DATA
| R/W
| 对应通道默认输出值
| AXI GPIO数据寄存器
对于每个被配置为输入的I/O:
读:读取输入引脚的值
写:无效
对于每个被配置为输出的I/O:
读:读取这些位将会返回0
写:将值写入相应寄存器位和输出引脚。
|
这里需要注意的一点是,官方手册中给出的描述是对于每个被配置为输出的I/O进行读操作时都会返回0。如下图
但是在实际操作中我们发现,只有在IP核中将通道设置为“All Outputs”时,读取该通道的值才会为0;而如果是通过软件配置的,则能够正常读取。
表 3 AXI GPIO 3态寄存器位描述
位
| 字段名
| 访问类型
| 复位值
| 描述
| [GPIOx_Width-1:0]
| GPIOx_TRI
| R/W
| 对应通道默认3态输出值
| AXI GPIO 3态控制寄存器
每个 I/O 引脚都可单独编程为输入或输出。
对于每一位:
0=I/O引脚配置为输出
1=I/O引脚配置为输入
|
编程方法和思路:
到了这里,我们就完成了AXI GPIO 的拨码开关控制LED亮灭的实验,从这个实验我们可以看到,在进行编程设计时,无论是PS GPIO还是AXI GPIO,使用前都需要进行如下几个步骤:
1. 初始化GPIO驱动程序
2. 设置指定引脚方向
3. 读/写指定管脚的值/状态
不同点则在于PS GPIO在输出指定管脚的值/状态时,需要先使能该引脚作为输出。而在硬件逻辑系统设计方面,AXI GPIO则是与EMIO类似,由于使用的是PL端引脚,在导出后还需要进行管脚分配和约束。
13 思考题
13.1 总结
到这里,本节课程内容就已经基本结束了,在本次的设计中,我们通过软件编程,将AXI GPIO的两个通道分别设置为了输入和输出,以方便代码的设计。实际上AXI GPIO核每个通道的每一位都是能独立设置其输入/输出模式并单独读写的,具体的实现方式可以参考我们在AXI GPIO应用库中提前封装好的AXI_GPIO_SetPin_Dir()、AXI_GPIO_SetPin()以及AXI_GPIO_GetPin()函数,三个函数功能如下:
AXI_GPIO_SetPin_Dir() 修改AXI GPIO某通道的某PIN的输入输出模式
AXI_GPIO_SetPin() 控制指定GPIO口输出高/低电平
AXI_GPIO_GetPin() 读取指定GPIO口的输入电平
关于我们为大家提供的示例程序的内容,就给大家简单介绍到这里。接下来,给大家布置两个思考题,或者说是课后小作业。
13.2 课后习题
配置AXI GPIO为单通道16位位宽,通过软件编程,将高8位配置为输入,获取拨码开关电平。将低8位设置为输出,使用从拨码开关获取的电平控制LED,实现拨码开关控制LED亮灭设计。
有能力的同学尝试根据对寄存器的理解,或者对我们提供的示例程序的理解,使用直接操作寄存器的方式来实现本实验。
关于本节课程所使用到的文件、文档和程序等内容,大家可以到下述链接下载。
【zynq裸机编程课程笔记合集】
http://www.fpga.cn/forum.php?mod=viewthread&tid=29095
或者直接到www.fpga.cn中搜索“zynq裸机编程课程笔记合集”
|
|