写这个话题是我看到我很多文章都只是介绍了ZYNQ一些操作方法。关于每一步操作的作用,它们之间的联系等问题还是让我很迷惑。所以写这篇文章,希望可以和大家去搞懂怎么用这东西,以及使用背后的逻辑。
ZYNQ是个啥
赛灵思(Xilinx)提出的一种集成处理器和FPGA(现场可编程门阵列)功能的系统芯片(SoC)。赛灵思Zynq-7000系列SoC具有ARM处理器和FPGA逻辑单元的结合,能够提供高度灵活性和性能优化。也就是在FPGA上加了个ARM,这么做有什么好处呢?
FPGA比较适合处理大量并发的数据流,因为它是直接从电路层面解决问题的,比如一个与的逻辑,两个信号一输入马上就得出结果。如果在ARM处理,就涉及CPU的调度,寄存器配置,操作系统的开销等。优点就是快,但问题也出在这里,设计了一个与门,或门又得重新弄了。ARM就不一样,管你与门或门都一样搞。所以要是能把它们的优点互补,缺点互避一下,那不是非常美妙?ZYNQ就是这么一个东西。
举一个实际的例子:
无人机(UAV)的视觉处理和飞行控制系统
无人机通常需要执行复杂的图像处理任务,以进行目标跟踪、避障、地理定位等功能,同时还需要处理与飞行控制相关的各种数据并实时做出响应。
单独使用FPGA或ARM处理器的局限性
- 如果仅使用FPGA完成整个任务,虽然FPGA可以很好地执行图像识别和处理任务,对时间敏感的操作可以快速完成,但它在处理复杂算法、网络通信、用户输入等方面则不如ARM处理器。
- 如果仅使用ARM处理器,它可以管理复杂的任务调度、网络通信等,但对于高带宽和低延迟的实时图像处理任务来说,则处理能力可能不足,并且功耗可能较高。
FPGA和ARM处理器结合的优势
在无人机的应用中结合使用两者,将利用FPGA的并行处理能力来实现实时的图像处理和目标跟踪算法。例如,FPGA可以同时处理来自摄像头的大量原始像素数据,并在毫秒级的时间内进行图像识别或图像增强运算,这有助于无人机快速响应环境变化并做出飞行调整。同时,ARM处理器可以负责无人机的高层次飞行控制逻辑,包括接收遥控信号、处理GPS数据、执行飞行路径规划等任务。当处理完这些飞行控制逻辑后,ARM处理器可以将关键的控制指令发送到FPGA执行更加具体的实时控制任务,比如电机控制等。结合使用FPGA和ARM处理器可以实现高效的任务分配,FPGA负责高速大流量的处理需要,并行执行,而ARM处理器则处理需要顺序逻辑和系统维护的任务。这样组合,简直不要太爽。
软件和硬件是怎么联系起来的
在VIVADO中完成硬件设计生成bit流后,想要协同ARM进行工作第一步是
导出硬件
这为软件开发提供必要的硬件上下文,包括如IP实例的地址映射、外设连接信息等,等下到SDK界面的时候会介绍相关的文件。导出的时候设计了一个是否包含bitstream的选项
一般都会选包含,除非硬件还未最终化,或者不想在每次软件开发迭代中都重新编程FPGA。打开SDK
system_wrapper_hw_platform_0:这就是我们刚刚导出来的硬件平台,它是根据您从 Vivado 导出的硬件描述文件生成的。这个硬件平台包含了FPGA设计的所有信息,比如引脚配置、时钟设置、处理器和IP核的详情等。整个文件夹充当了硬件与软件之间交互的基础。
system.hdf这个文件是最底层的,里面包含了每一个外设的地址信息(框2的部分),当然我们一般也不用管,直接对寄存器进行操作那不是回到了汇编时代嘛。
硬件基础有了,之后就是软件方面的工作。下面可以理解为,要开始玩一块单片机了。首先创建软件工程
这一步选择硬件平台,就把现在要设计的软件和之前咱们设计的硬件联系起来了。创建完成后会得到两个文件夹
这个后缀带BSP的文件夹内部包含了为你的FPGA硬件配置而自动生成的代码,也就是给我们提供了一堆的API,再也不用从底层去写了。具体是放在libsrc下面的。比如些对gpio进行操作的API
不带BSP后缀的文件夹:就是你的应用程序代码所在的地方,把main文件放在src里面。
到了这里,我们就又有了硬件,也有了封装好的可以直接调用的软件了。
代码的逻辑:
这里举一个操控外设gpio去点亮我板子上的灯的例子,整体代码如下
#include "xgpiops.h"
#include "sleep.h"
int main()
{
static XGpioPs psGpioInstancePtr;
XGpioPs_Config* GpioConfigPtr;
int iPinNumber= 7; //LD9连接的是MIO7
u32 uPinDirection = 0x1; //1表示输出,0表示输入
int xStatus;
//--MIO的初始化
GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
if(GpioConfigPtr == NULL)
return XST_FAILURE;
xStatus = XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr, GpioConfigPtr->BaseAddr);
if(XST_SUCCESS != xStatus)
print(" PS GPIO INIT FAILED \n\r");
//--MIO的输入输出操作
XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber,uPinDirection);//配置MIO输出方向
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber,1);//配置MIO的第7位输出
while(1)
{
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1);//点亮MIO的第7位输出1
sleep(1); //延时
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0);//熄灭MIO的第7位输出0
sleep(1); //延时
}
return 0;
}
ARM端的程序大都是这么一个套路:你要用什么外设,第一步就是调用**xxxx_LookupConfig()**这个程序去确认一下该硬件平台有没有想要用的外设,比如这里的
XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
就是去找有没有GPIO,输入的参数为我们选择的硬件的id,可以在标bsp文件下里面的libsrc里面的"xparameters_ps.h"找到。然后找到这个东西之后把它实例化一下才能用,实例化的方式是**xxxx_CfgInitialize()**这样的程序。
xStatus = XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr, GpioConfigPtr->BaseAddr);
之后就是把你的实例作为各种API的输入参数,比如配置,使能,或者驱动输入输出等。也就是程序中的
XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber,uPinDirection);//配置MIO输出方向
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber,1);//配置MIO的第7位输出
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1);//点亮MIO的第7位输出1
硬件和软件的数据交互
就像我们刚开始时候说的,要想充分发挥ZYNQ的优势,就要把适合硬件处理的数据给硬件,软件处理的数据给软件。这里面就涉及到一个数据交互的问题。主要有以下几种方式:
- AXI接口: 目前最常用的是使用高级扩展接口(Advanced eXtensible Interface,AXI)总线。Zynq SoC通过AXI接口连接PS和PL,可以支持多种不同的数据交换情况:
- AXI4-Stream: 用于PS和PL之间的点对点数据流通信;
- AXI4-Memory Mapped: 用于PS到PL或PL到PS的存储器映射访问,适合大块数据传输,通常作为读写外设注册表的方式;
- AXI4-Lite: 是AXI4的简化版本,常用于配置型事务与低带宽数据传输。
- EMIO与MIO:
- MIO(Multiplexed I/O): PS端有限数量的多功能I/O接口,可以连接到外部设备,但也可以用于与PL侧的逻辑模块接口。
- EMIO(Extended Multiplexed I/O): 当MIO引脚不足时,可以使用EMIO引脚。EMIO不直接走PIN,而是把PS内置外设的接口引入PL进行扩展,在PL侧需要相应的逻辑来驱动。
- DMA传输: PS侧的DMA控制器可以配置为在PS与PL间进行数据传输,适用于高速大数据量的场景。
- 中断: PL可以通过IRQ(中断请求)向PS发送中断信号,用以触发PS端的中断服务程序,实现基于事件的控制。
- GPIO(General Purpose I/O): 普通的I/O接口用于简单信号的传递,如旗标、触发信号等。
- 高速收发器: 对于需要高速串行通信的应用,PL侧可以配置高速SerDes收发器以实现与外界或PS端的快速数据传输。
写在最后
刚学ZYNQ的时候,虽然跟踪工程一步步操作可以把最后的结果复现,但就是感觉迷迷糊糊的,好像啥也没学会,所以写这篇文章把整体的逻辑捋了一遍,欢迎大家一起讨论~