因为刚拿到zedboard这块板子,所以一直在尝试先跑上一个例程。这篇博客https://blog.csdn.net/weixin_42639919/article/details/81130581的整个流程相当详细,而且在vivado2018.2上尝试后确实是可以正常运行的。但有几个点想补充一下,也是在尝试的过程中碰到的问题。
2.1 vivado2018在界面上相对于之前的版本有些不一样,但基本的选项还是一样的。
2.2 在IP diagram连线的时候,注意按照步骤中,连接GPIO至led引脚,这一点要注意,不要选择默认的选项
2.3 之前的很多教程里,都没有提到hardware manager这一过程,都是直接让看设备管理器里的端口配置。但是很遗憾,我的电脑无论如何都显示不出有探测到zedboard设备,还是之前做项目时添加的几个蓝牙com。所以检测有没有连上还是要看hardware manager。
然后,别人的博客也说了,如果板子的status是closed,要右键,然后open
2.4 uart to usb是可以在设备管理器的端口中被发现的,而且一般会提示你安装相应的驱动,如果没有相应提示,建议换一条数据线试试。
做这么个例程,说白了还是想要借助来理解整个zynq7 ip的使用过程和流程。但我也才刚开始看,很多理解和补充可能不到位,仅供参考,之后也就慢慢学,慢慢完善咯。
参考的资料有《vivado从此开始》、《Xilinx Zynq-7000嵌入式系统设计与实现基于ARM Cortex-A9双核处理器和Vivado的设计方法》。对应的资源下载https://download.csdn.net/download/iatkotw1998/10840227。抱歉收了一个积分。
3.1 理解VIVADO和SDK的关系:一个最直观上的感受便是VIVADO负责硬件部分的设计,然后导入至SDK中进行软件部分的设置。这和以前使用的系列单片机是不一样的,当然,这也归功于zynq架构的设计理念。其次,很多之前的教程都还是基于像是PlanAhead、XPS或者ISE,近些年VIVADO的教程才开始多了起来,但是VIVADO是一个大综合,把几乎所有的流程功能集成到一个框架下,不用四处切换工具。
3.2 国内的原创稿比例还是比较少,像如果直接搜vivado+zedboard之流水灯例程,几乎全是一样的,转过来转过去,流程多,理解少。反正也都是在入门,之后可能会写几篇有关自己对zynq和vivado开发工具的理解和学习笔记。这一篇还是回到结合例子理解流程的话题上来。
3.3 zynq这个ip的使用流程与传统的FPGA在VIVADO的设计流程很不一样,我们甚至可以在使用zynq架构的板子时,一句HDL代码都不用写,直接上ip integrator部分就行。我们知道,zynq架构有PS和PL两个重要的组成部分。二者之间的互联通过AXI(AMBA协议中的的一种规范)总线。还有AXI4-Lite功能和AXI4-Stream功能,前者专用与和原件内的控制寄存器进行通信,后者则用于连接希望交换数据的元件,如果不是很清楚的话可以先看一看这个博客进行学习(https://www.cnblogs.com/milinker/p/6474706.html)。说到这里,先上来这个例程的整体框图,图片转自博客http://blog.chinaaet.com/cuter521/p/35978,非常具有原创性的一个博主。
图1可以看到是调用了PL中的GPIO IP的,ARM GPIO的调用的方式有两种,其一直接对Cortex-A9处理器内GPIO模块的寄存器进行直接读写操作;其二是调用SDK工具提供的应用程序接口函数API。当然这只不过是两种不同的风格的代码,之后会结合代码具体分析,但更重要的是了解这种结构的GPIO调用的基层形式。我们首先需要了解一些有关的基础知识,这些在资料中的zynq书中有详细介绍,这里只简要提一下:
3.4 在添加IP的流程,首先需要ZYNQ和AXI GPIO两个部分的IP,ZYNQ这个之后还会接着围绕其为中心细讲,这里先接着上面来学习AXI GPIO部分,其实xilinx是有对其进行详细介绍的https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/v2_0/pg144-axi-gpio.pdf。在参考书的GPIO中虽然没有对AXI GPIO的介绍,但是有专门的两章对定制简单的AXI-Lite IP进行了介绍,定制封装后的模块长这样
图3是不是和AXI GPIO模块巨像,所以这其实就是借助AXI总线利用PL编程实现了一个GPIO的定制功能IP。再看前面给出的官方PDF,特征(features)中提到支持1到32位的GPIO引脚,支持单一或双GPIO通道,还支持中断响应。主要的信号描述如下
随后,在Designing with the core中的Operation和Programming Sequence以及Design Flow Steps中相当详细的给出了使用方法,这里便不再赘述。
3.5 按照步骤Run Connection Automation后,看整个结构图,GPIO的输出连接着外设LED,这其实有一个问题,管脚约束什么时候添加的?我们在Source目录下其实并不能看到相应的约束文件。
但是打开I/O prot的确是能够看到已经完成了相应的引脚分配的
所以说Run Connection Automation这个过程自动的完成了引脚分配的过程,而且确实是生成了约束文件的,只不过路径却不再和以前一样了而已,如下图的路径可以找到对应的物理约束。
这其实与我们常规的FPGA设计流程中有关引脚约束的步骤有一些不一样,需要注意。
3.6 AXI GPIO的输入S_AXI,s_axi_aclk,s_axi_aresetn都直接间接的通过另一个模块,AXI SmartConnect
有关这个模块,在zynq参考书的AMBA AXI4互联结构中有详细的提到。首先我们要明白,有关AMBA协议规范,APB、AHB、AXI是其中的三种规范。而AXI是指Advanced Extensive Interface,即高级可扩展接口,用于高性能的互联。AXI协议指定的不是总线,而是一一对应的接口,当有多个外设需要交互数据时,就需加入AXI Interconnect模块,smartconnect相当于是高级版本的interconnect。AXI Interconnect的作用是将一个或多个AXI主设备连接到一个或多个AXI从设备的一种交换机制。AXI Interconnect IP核最多支持16个主设备和16个从设备,如果需要更多的接口可以在设计中加入多个IP核。下图有助于我们理解主设备、从设备、AXI总线和AXI Interconnect core的关系,注意图中的握手信号及数据信号的宽度。(部分结论转自博客http://www.eefocus.com/tastier/blog/13-04/293148_06eee.html)
Zynq中的AXI接口总共有9个,三类:AXI_ACP、AXI_HP(4个)、AXI_GP(4个),AXI_GP为通用接口,使用频率很高,分为两个主接口和两个从接口,当需要连接更多外设时,在这个接口接上AXI_Interconnect模块即可。下图可以直观的看到M_AXI_GP0与AXI_Smartconnect的连接:
当然其实最原汁原味最详细的介绍还是得参考官方给的pdf文档https://www.xilinx.com/support/documentation/ip_documentation/smartconnect/v1_0/pg247-smartconnect.pdf(介绍相当详细)。之后会考虑专门写一篇对这个模块以及其他相关模块的解读。这次仍先点到为止。
3.7 那么差不多就到了最后一个了Processor System Reset,PDF(https://www.xilinx.com/support/documentation/ip_documentation/proc_sys_reset/v5_0/pg164-proc-sys-reset.pdf),开篇的Introduction里就很明确的指出The Xilinx LogiCORE™ IP Processor System Reset Module core provides customized resets for an entire processor system。其在连线中的表现也确实如此,控制所有设备的reset。
其中FCLK_RESETO_N是一个zynq提供的复位信号,需要注意的是zynq的寄存器具有“写保护”,如果直接将数据写入FCLK——RESET0_N的对应寄存器写数据之前应该把“写保护”模式关闭,然后再把数据写入FCLK_RESET0_N的寄存器中,这样才能有效实现想要的功能。最后不要忘记再将“写保护”模式开启。(参考博客http://blog.sina.com.cn/s/blog_16bd70da40102ybc8.html)
3.8 最后我们来分析sdk中的代码,其实可以说虽然在整个结构中有那么多的模块,但SDK中需要自己写的只有xgpio部分,前面提到的博客中已经给出了,这里只再对其中的几个部分进行补充,整体的功能其实很好理解。
/*
* zj.c
*
* Created on: 2018年12月9日
* Author: Tiputer
*/
#include "xparameters.h" /* Peripheral parameters */
#include "xgpio.h" /* GPIO data struct and APIs,这个头文件专门针对GPIO IP的*/
#include "xil_printf.h"
#include "xil_cache.h"
#define GPIO_BITWIDTH 8 /* This is the width of the GPIO */
#define GPIO_DEVICE_ID 0//device id
#define LED_DELAY 10000000/* times delay*/
#define LED_MAX_BLINK 0x1 /* Number of times the LED Blinks */
#define LED_CHANNEL 1 /* GPIO channel,可以回忆一下GPIO IP的结构只有两个channel*/
#define printf xil_printf /* A smaller footprint printf */
XGpio Gpio; /* The Instance of the GPIO Driver */
XGpio GpioOutput; /* The driver instance for GPIO Device configured as O/P */
int GpioMarquee (u16 DeviceId, u32 GpioWidth)
{
volatile int Delay;
/*volatile 是个关键字。在一个变量前加上这个关键字,表示的含义是告诉编译器在编译的时候不要优
化掉这个变量,因为一般的编译器都有优化选项,某些优化过程就会把一些变量优化掉。这个在嵌入式系
统中很重要,比如说你要在某个PROT不停的读取数据,而且这个PORT的数据时实时更新的,那么你就要在
你的变量前面加上volatile ,否则编译器很有可能就只读取一遍,以后都不读取仍然使用上一个值 例如
int ValueRead; ValueRead = PORTB 这样的话重复读就会被优化掉,要volatile int ValueRead;
ValueRead = PORTB 这样就OK了*/
u32 LedBit;
u32 LedLoop;
int Status;
/*
* Initialize the GPIO driver so that it's ready to use,
* specify the device ID that is generated in xparameters.h
*/
Status = XGpio_Initialize(&GpioOutput, DeviceId);
if (Status != XST_SUCCESS)
{
return XST_FAILURE;
}
//Set the direction for all signals to be outputs
XGpio_SetDataDirection(&GpioOutput, LED_CHANNEL, 0x0);
// Set the GPIO outputs to low
XGpio_DiscreteWrite(&GpioOutput, LED_CHANNEL, 0x0);
for (LedBit = 0x0; LedBit < GpioWidth; LedBit++)
{
for (LedLoop = 0; LedLoop < LED_MAX_BLINK; LedLoop++)
{
//Set the GPIO Output to High
XGpio_DiscreteWrite(&GpioOutput, LED_CHANNEL,1 << LedBit);
//Wait a small amount of time so the LED is visible
for (Delay = 0; Delay < LED_DELAY;Delay++);
//Clear the GPIO Output
//XGpio_DiscreteClear(&GpioOutput, LED_CHANNEL,1 << LedBit);
//XGpio_DiscreteWrite(&GpioOutput, LED_CHANNEL,0); //这一句和上面一句的效果一样
// Wait a small amount of time so the LED is visible
for (Delay = 0; Delay < LED_DELAY; Delay++);
}
}
return XST_SUCCESS;
}
int main(void)
{//Application start
/* loop forever*/
int cnt=0;
while(1)
{
u32 status;
status = GpioMarquee (GPIO_DEVICE_ID,GPIO_BITWIDTH);
if (status == 0)
{
printf("%d:SUCESS!.\r\n",cnt++);
if(cnt>=1000)
cnt=0;
}
else
printf("FAILED.\r\n");
}
return XST_SUCCESS;
}
嗯,本文到这里就结束了,可以说是一个简单的入门吧,虽然知道了很多东西的作用,但还是有很多步骤不清楚,在之后的学习中会继续总结,欢迎指正,互相学习。