欢迎光临散文网 会员登陆 & 注册

7、利用LabVIEW/C/Python库函数节点直接调用Linux RT动态链接库so文件里面的PCIe函数

2021-06-16 11:42 作者:神电测控  | 我要投稿

    7.1、利用sudo cp拷贝指令将上位机下发给树莓派的so文件拷贝到指定路径下!

        在后续第8节里面可以看到,当我们将Linux RT下的LabVIEW VI编译生成rtexe可执行程序后,LabVIEW默认会在树莓派的home根目录下创建一个lvuer文件夹,这个文件夹里面存放的都是LabVIEW RT相关的文件。

        因此,为了统一起见,我们把上面编译出来的so动态链接库也拷贝到这个路径下,这样LabVIEW在自动搜索相关驱动文件时,一瞬间就能找到并快速进行链接。具体的路径是/home/lvuser/natinst/bin;为了减少树莓派里面的路径深度,我们将前面的so文件复制一份,然后重新命名为:xillybus-LV-DLW30.so,如图9-111所示。

                                             

图9-111:将编译出来的so文件复制一份重命名,缩短名称

         接着利用VNC软件将本地电脑上的so文件传输到树莓派桌面上,如图9-112所示。此时,树莓派桌面上就有了一个名为“xillybus-LV-DLW30.so”的驱动文件,如图9-113所示。

图9-112:将重命名后的so文件利用VNC拷贝到树莓派桌面上
图9-113:传输到树莓派桌面上的so文件

        最后,我们需要利用带管理员权限的cp指令,将刚刚传输到树莓派桌面上的so文件复制到NI LabVIEW Linux RT创建的bin文件夹(/home/lvuser/natinst/bin)里面,因为直接鼠标拖拽不行。用户可以在树莓派内置的LXterminal终端里面输入带管理员权限的复制指令:sudo cp /home/pi/Desktop/xillybus-LV-DLW30.so   /home/lvuser/natinst/bin

按下回车之后,没有报错,如图9-114所示。然后打开/home/lvuser/natinst/bin这个目录,可以看到,我们刚刚利用cp指令拷贝进去的so动态链接库驱动文件,如图9-115所示。

图9-114:利用带管理员权限的cp指令将树莓派桌面上的so文件拷贝到指定路径下
图9-115:拷贝到LabVIEW Linux RT文件夹里面(与startup.rtexe同一个目录)


    7.2、树莓派Linux RT端PCIe DMA FIFO驱动程序讲解(LabVIEW/C/Python/QT调用so文件,神电提供lvlib库)

        当带PCIe的FPGA硬件被树莓派里面的Linux系统识别成功后,我们就可以在树莓派里面编写一个Linux RT端的应用程序来与之通信,进行数据交互了。为了方便广大用户的使用,我们将8上8下共计16个通道的中间层DMA高速传输封装成了Linux系统下的so动态链接库,这样对于使用不同编程语言(LabVIEW\C\C++\C#\Python)开发RT端实时应用程序的用户来说,直接调用我们封装好的so驱动就可以了。这个so文件前面我们已经拷贝到树莓派里面了,如图9-116所示。

图9-116:我们给用户编译好的8上8下共计16个通道的Linux系统下的so动态链接库

        下面重点给用户讲解一下我们针对LabVIEW Linux RT系统封装好的一个lvlib库文件,对于熟悉LabVIEW编程的工程师来说,直接将lvlib里面的VI拖拽到自己的RT程序里面就可以完成RT端跟下位机FPGA之间的PCIe通信了,一目了然。图9-117显示的是我们封装好的树莓派Linux系统下的LabVIEW My FPGA PCIe Toolkit软件工具包项目浏览器截图。

图9-117:封装出来的FPGA PCIe DMA树莓派Linux系统下的LabVIEW项目库

        里面主要包含两大块,分别是Linux RT端的FPGA PCIe范例程序(Example-PC虚拟文件夹)和中间层的lvlib库文件。其中Linux RT端的范例程序在后续的树莓派PCIe基础和高级实验里面再给大家做详细的讲解;本节先着重介绍一下我们封装的Linux系统下的 lvlib库函数(VI)。

        首先,在参照了NI FPGA 板卡的DMA传输工作流程之后,我们特地有针对性的封装,将一个上行(FPGA-->Host)DMA传输过程分成7个步骤,分别对应7个子VI,下行则不需要DMA机制:

l  PCIe DMA通道初始化并开辟指定深度的缓冲区(PC_FIFO_DMA_Poly_Init_DLW30.vi)

l  PCIe DMA通道启动传输(PC_FIFO_DMA_Poly_Start_DLW30.vi)

l  PCIe DMA通道缓冲区存在的字节数数量(PC_FIFO_Poly_Buffer_Length_DLW30.vi)

l  PCIe DMA通道缓冲区读取指定长度的数据(PC_FIFO_DMA_Poly_Read_DLW30.vi)

l  PCIe DMA通道停止传输(PC_FIFO_DMA_Poly_Stop_DLW30.vi)

l  PCIe DMA通道等待退出(PC_FIFO_DMA_Poly_Exit_Wait_DLW30.vi)

l  PCIe DMA通道销毁(PC_FIFO_DMA_Poly_Destroy_DLW30.vi)

        注意:所有调用so文件的子VI都要设置成“任意线程”,切不可设置成“用户线程”,如图9-118所示。否则一旦so里面的函数出现读写阻塞之后,会导致LabVIEW界面无响应,卡死状态,更没有办法进行探针调试,切记!

图9-118:LabVIEW调用底层有阻塞函数的so函数时,一定要设置成“任意线程运行”

        下面我们对这7个多态子VI分别进行讲解,之所以设计成多态VI,是为了方便用户编程的时候,可以直接选择切换通道。

        1)PC_FIFO_DMA_Poly_Init_DLW30.vi

        这个子VI可以用来完成对指定的FPGA PCIe通道进行初始化,同时还可以按照用户输入的数值向树莓派申请开辟一个指定深度(长度或者大小,单位字节)的缓冲区,如图9-119所示。图中所示可以这样理解:针对64位位宽的PCIe DMA ch0(通道0)向树莓派Linux(主机)申请开辟一个512M字节的缓冲区,缓冲区首地址或者说指针放在第3个DMA引用当中,如果申请的缓冲区成功,那么对应输出的指示灯会点亮。

图9-119:Linux RT主机端(树莓派)的PCIe DMA通道初始化VI函数

        注意:初始化函数的缓冲区深度设置,跟后面的(PC_FIFO_DMA_Poly_Read_DLW30.vi)读取长度之间需要满足一定的关系,否则会出现读取丢点的情况。原因是我们开辟的缓冲区(假设FIFO深度为M),实际上是一个异步的环形FIFO,FPGA端会把数据源源不断的往这个FIFO里面写,然后应用层,比如LabVIEW会从这个FIFO里面取数据,至于什么时候读,读多少?取决于这个FIFO里面现有的数据长度,一般的上位机读取机制是:当上位机判断到这个FIFO已经有了至少N个点时,我们就调用这个函数(PC_FIFO_DMA_Poly_Read_DLW30.vi)把N个点读取出来,余下的数据还会留在FIFO里面,由于开辟的是环形FIFO,所以读取位置会不断的从开头到末尾扫描,因此,在跨越边界长度的时候,会出现读取截断或者丢点的现象。结论是M要能整除N才行。

        举例说明:比如我们开辟了一个500MByte深度的缓冲区,然后每次读取2048个Byte点,实际上500M/2048无法整除,这样就会导致在读取最后一帧的时候,读出来的数据有问题,为了避免这个问题,我们有两种解决方法:如果读取的长度事先确定了,那么我们在申请开辟缓冲区长度的时候,将读取长度乘上一个整数就可以了,比如开辟2048×100000=204.8MByte;如果读取长度是变化的,那么需要满足整除这个条件,比如我们开辟的环形FIFO大小是500M,那么读取长度可以选择5、10、100、1000之类的,不能选择1024或者3之类的。上面讲解的机制和原理,用户一定要记在心里!!!

        小心:在后续的PCIe+OV5640摄像头例程里面,可以看到,720p RGB565格式下的每帧图像字节是1280×720×2=1843200Byte,如果用户想要每次读取一幅图像刷新显示,那么在调用初始化函数(PC_FIFO_DMA_Poly_Init_DLW30.vi)开辟缓冲区长度的时候,一定要设置为1843200Byte的整数倍,比如184.32MByte或者368.64MByte。

        2)PC_FIFO_DMA_Poly_Start_DLW30.vi

        这个多态子VI的作用是用来控制Linux RT主机端的PCIe DMA FIFO通道是否接收下位机FPGA发送的数据,相当于启动了DMA传输,一旦调用了这个VI,那么只要下位机FPGA有数据在发送,通过Linux任务管理器就能看到我们前面申请开辟的缓冲区里面就会有源源不断的数据进来,如果下位机FPGA不发送上行数据,那么这个DMA通道也会一直处于等待接收状态,相当于线程阻塞了。这个子VI不需要输入参数,如图9-120所示。通过显示控件“DMA启动传输成功?”指示灯状态,可以判断PCIe DMA通道是否成功开启传输模式,由于我们支持8个DMA通道同时并行传输数据,所以前面我们初始化了哪些DMA通道,这里我们就需要利用“PC_FIFO_DMA_Poly_Start_DLW30.vi”函数来开启这些通道的数据传输使能。

图9-120:Linux RT主机端(树莓派)的PCIe DMA启动通道数据传输VI函数

        3)PC_FIFO_Poly_Buffer_Length_DLW30.vi

        这个子VI的功能跟我们平时常用的一个VISA属性节点“串口缓冲区字节数”功能类似,就是用户可以通过轮询的方式,查看当前Linux RT主机端(树莓派)的DMA通道对应的缓冲区里面存在多少字节的数据,比如,当字节数量大于10KBytes的时候,我们再调用读取VI将这些数据从FIFO缓冲区里面取走,这样可以提高读取效率,同时也能降低读取频率,释放CPU给其他线程使用。通过调用这个子VI可以实时观察到缓冲区里面的数据增加或者减少,对于不定长传输也会起到一定的作用。这个子VI返回的长度单位是Byte,通常会跟FIFO Read函数一起使用。

图9-121:Linux RT主机端(树莓派)的PCIe DMA缓冲区字节数量查询函数

        4)PC_FIFO_DMA_Poly_Read_DLW30.vi

        当我们通过前面的“PC_FIFO_Poly_Buffer_Length_DLW30.vi”函数查询到树莓派Linux缓冲区里面已经有了我们需要的数据时,接下来就可以利用这个“PC_FIFO_DMA_Poly_Read_DLW30.vi”函数从FIFO里面把数据读取出来,给到我们的应用程序进行处理、显示或者流盘保存等操作了。如图9-122所示,这个VI需要用户给定读取数据的长度值,单位是Byte字节,指定读取的长度不能超过缓冲区已有的数据长度;其次,从缓冲区里面读取出来的数据都是最原始的字节数组或者说是字符串,用户可以根据实际情况,利用数值选板里面的“强制类型转换”函数将原始的字节数组转成需要的数据类型,比如下位机FPGA发送的是有符号32位类型的波形数据,那么我们可以参考图9-122所示的那样,将真实的数据转换出来,如果用户在界面上给定读取的是数据是波形点数,那么Size_Read应该×4比较好,这样转换后的点数就跟实际的点数对应上了。

图9-122:从Linux RT主机端(树莓派)的PCIe DMA通道FIFO缓冲区里面读取指定长度的字节数据

        注意:初始化函数的缓冲区深度设置,跟后面的(PC_FIFO_DMA_Poly_Read_DLW30.vi)读取长度之间需要满足一定的关系,否则会出现读取丢点的情况。原因是我们开辟的缓冲区(假设FIFO深度为M),实际上是一个异步的环形FIFO,FPGA端会把数据源源不断的往这个FIFO里面写,然后应用层,比如LabVIEW会从这个FIFO里面取数据,至于什么时候读,读多少?取决于这个FIFO里面现有的数据长度,一般的上位机读取机制是:当上位机判断到这个FIFO已经有了至少N个点时,我们就调用这个函数(PC_FIFO_DMA_Poly_Read_DLW30.vi)把N个点读取出来,余下的数据还会留在FIFO里面,由于开辟的是环形FIFO,所以读取位置会不断的从开头到末尾扫描,因此,在跨越边界长度的时候,会出现读取截断或者丢点的现象。结论是M要能整除N才行。

        举例说明:比如我们开辟了一个500MByte深度的缓冲区,然后每次读取2048个Byte点,实际上500M/2048无法整除,这样就会导致在读取最后一帧的时候,读出来的数据有问题,为了避免这个问题,我们有两种解决方法:如果读取的长度事先确定了,那么我们在申请开辟缓冲区长度的时候,将读取长度乘上一个整数就可以了,比如开辟2048×100000=204.8MByte;如果读取长度是变化的,那么需要满足整除这个条件,比如我们开辟的环形FIFO大小是500M,那么读取长度可以选择5、10、100、1000之类的,不能选择1024或者3之类的。上面讲解的机制和原理,用户一定要记在心里!!!

        小心:在后续的PCIe+OV5640摄像头例程里面,可以看到,720p RGB565格式下的每帧图像字节是1280×720×2=1843200Byte,如果用户想要每次读取一幅图像刷新显示,那么在调用初始化函数(PC_FIFO_DMA_Poly_Init_DLW30.vi)开辟缓冲区长度的时候,一定要设置为1843200Byte的整数倍,比如184.32MByte或者368.64MByte。

        5)PC_FIFO_DMA_Poly_Stop_DLW30.vi

        当我们不需要PCIe DMA通道或者退出Linux RT主机端(树莓派)应用程序之前,用户可以调用这里的“PC_FIFO_DMA_Poly_Stop_DLW30.vi”函数来关停Linux RT主机端(树莓派)的DMA通道的数据接收功能,此时,即使下位机FPGA还在发送数据,Linux RT主机端的FIFO缓冲区也不会接收任何数据,处于关闭状态。这个VI跟前面的第2个函数(PC_FIFO_DMA_Poly_Start_DLW30.vi)其实是一对功能互反的VI。用户调用的时候,只需要选择想要关闭的DMA通道就可以了,如图9-123所示。

图9-123:关闭Linux RT主机端(树莓派)的PCIe DMA通道的数据传输

        6)PC_FIFO_DMA_Poly_Exit_Wait_DLW30.vi

        虽然前面我们把Linux RT主机端(树莓派)的DMA缓冲区接收功能关闭了,但是中间层的驱动里面,还有一些人为开辟的线程没有关掉,我们特地给用户封装了一个等待线程退出函数“PC_FIFO_DMA_Poly_Exit_Wait_DLW30.vi”,通过这个函数可以确保中间层的so里面所有线程都退出了,防止出现线程阻塞卡死等状态。这个VI的调用非常简单,用户只需要选择想要退出的DMA通道就可以了,如图9-124所示。

图9-124:等待Linux RT主机端(树莓派)的中间层的so里面的PCIe DMA通道退出

        7)PC_FIFO_DMA_Poly_Destroy_DLW30.vi

        当DLL里面的DMA Read线程退出之后,最后我们还需要调用一下这里的“PC_FIFO_DMA_Poly_Destroy_DLW30.vi”函数将so里面申请开辟的FIFO缓冲区句柄、读取和状态轮询线程的引用以及对应的句柄全部销毁掉,防止出现内存泄露导致Linux系统崩溃。这个VI跟前面的第一个函数“PC_FIFO_DMA_Poly_Init_DLW30.vi”功能是反的,前面那个函数在so里面负责申请开辟资源,创建线程等等,这里的VI则负责将其全部销毁,否则一旦发生内存泄露,后果不堪设想。这个VI的用法也很简单,用户只需要选择想要销毁的DMA通道资源就可以了,如图9-125所示。

图9-125:销毁Linux RT的中间层so里面开辟的FIFO缓冲区指针、引用句柄和线程句柄

        注意:最后这3个VI,一般是在退出Linux RT应用程序之前,连在一起使用的,如图9-126所示。但是千万需要注意的是,在执行DMA停止、退出、销毁这3个VI的时候,下位机FPGA里面的PCIe DMA Write不能停发数据,也就是说,下位机FPGA要一直往PCIe DMA CLIP对应的通道里面写数据,否则会造成中间层so里面的DMA缓冲区Read读取线程处于-1,永不超时状态,也就是线程阻塞了,Linux RT主机端(树莓派)应用程序会陷入卡死状态。因此,我们建议用户,等到so里面的PCIe DMA FIFO和读取线程销毁之后,Linux RT主机端(树莓派)再发送“停止写入指令”给下位机FPGA,最后再关闭下行的PCIe通道资源。

图9-126:退出Linux RT主机端(树莓派)应用程序之前,依次调用FIFO停止、等待线程退出、销毁FIFO和引用句柄

        介绍完上行的PCIe DMA Read这7个函数之后,下面来看看我们给大家封装的3个下行(PC-->FPGA)Write子VI,如图9-127所示。这3个函数就可以实现将Linux RT主机端(树莓派)的数据或者指令或者参数直接通过PCIe总线下发传输写到FPGA芯片里面。

图9-127:lvlib库里面的3个下行PCIe通道写函数

l  FPGA_FIFO_Write_Pipe_Init_DLW30_Linux.vi

l  FPGA_FIFO_Write_Pipe_Send_DLW30_Linux.vi

l  FPGA_FIFO_Write_Pipe_Close_DLW30_Linux.vi

        下面我们逐一给大家讲解一下这3个完成PCIe下行通信的VI含义和用法。

        1)FPGA_FIFO_Write_Pipe_Init_DLW30_Linux.vi

        这个VI可以打开用户指定的PCIe下行(Host-->FPGA)通道并完成初始化工作,如图9-128所示。因为我们给用户封装了8个下行写通道(当然了,还有前面介绍的8个上行读通道),因此,调用这个VI的时候,需要根据实际情况选择一个合适的下行通道作为Linux RT主机端(树莓派)发送数据给FPGA的Pipe管道,比如,我们发送的是一些指令形式的数据,或者数据量非常小的一些参数等等,那么可以选择低速的8位位宽的Channel4~7作为载体;如果我们需要快速下发一些诸如任意信号发生器的波形数据,那么可以选择吞吐率高的64位或者32位位宽的Channel0或者Channel1作为下行传输通道。如果下行写通道初始化成功,这个VI会返回一个唯一的句柄“fd_write”,后面所有跟写通道相关的函数VI都需要借助这个引用指针“fd_write”。

图9-128:PCIe下行写通道初始化VI

        2)FPGA_FIFO_Write_Pipe_Send_DLW30_Linux.vi

        当下行的写通道成功打开之后,我们就可以利用这里的“FPGA_FIFO_Write_Pipe_Send_DLW30_Linux.vi”函数将Linux RT主机端(树莓派)的数据下发给FPGA了,如图9-129所示。需要注意的地方是,这个通道写VI的数据输入类型是字节数组,所以,如果外部的波形信号或者参数或者指令等数据类型不是字节,那么我们需要通过数值选板里面的“强制类型转换”VI先转一下,图9-129中显示的是把Linux RT主机端(树莓派)一个浮点型的正弦信号先通过I64转成有符号64位整形数组,然后通过“强制类型转换”变成U8字节数组给到我们的“FPGA_FIFO_Write_Pipe_Send_DLW30_Linux.vi”函数,写到FPGA芯片里面去。

图9-129:PCIe下行通道数据发送VI(Host-->FPGA)

        3)FPGA_FIFO_Write_Pipe_Close_DLW30_Linux.vi

        当我们需要退出Linux RT主机端(树莓派)应用程序之前,需要利用这里的“FPGA_FIFO_Write_Pipe_Close_DLW30_Linux.vi”函数来把先前打开的PCIe写通道引用句柄指针关掉,如图9-130所示。防止出现内存泄露,更重要的是,如果不执行关闭的话,下次再打开这个通道的时候,会提示这个通道被占用了,所以务必要检查一下之前开启的通道句柄是否销毁了。

图9-130:关闭PCIe下行写通道引用句柄,释放通道占用

        总结:用户在熟悉和掌握了上面介绍的这10个Linux系统下的子函数(VI)含义和用法之后,就可以大刀阔斧的开始编写Linux RT主机端(树莓派)的PCIe应用程序了。关于这部分内容,我们会在后面的第9和第10节实验部分,做详细的讲解。

    7.3、树莓派Linux RT端PCIe Memory驱动程序讲解(C/LabVIEW调用so文件,神电提供lvlib库,可以变相实现通过PCIe读写FPGA前面板控件等状态信息)

        当带PCIe的FPGA硬件被系统识别成功后,我们就可以编写一个Linux RT主机端(树莓派)的应用程序来与之通信,比如用来监控下位机FPGA前面板上的控件值或者下发控制指令给FPGA了。为了方便广大用户的使用,我们将2上2下共计4个通道的中间层Memory读写通道也封装到前面给用户介绍过的那个Linux系统下的so动态链接库里面了,这样对于使用不同编程语言(LabVIEW\C\C++\C#\Python)开发Linux RT主机端(树莓派)应用程序的用户来说,直接调用我们封装好的so驱动就可以了。这个so位于本书配套的云盘里面,如图9-131所示。

图9-131:我们给用户封装好的Linux系统下包含8对DMA FIFO和2对Memory通道的so动态链接库文件

        下面再回顾一下前面我们针对LabVIEW软件封装好的一个lvlib库文件,对于熟悉LabVIEW编程的工程师来说,直接将lvlib里面的VI拖拽到自己的程序里面就可以完成树莓派跟下位机FPGA之间的PCIe通信了,一目了然。图9-132显示的是我们封装好的Linux RT主机端(树莓派)的LabVIEW My FPGA PCIe Toolkit软件工具包项目浏览器截图。

图9-132:封装出来的FPGA PCIe DMA和Memory树莓派主机端的LabVIEW项目库

        里面主要包含两大块,分别是PCIe DMA FIFO相关的多态VI和Memory通道读写相关的多态VI;DMA FIFO相关函数我们在前面7.2节已经做了详细介绍,本节着重介绍一下我们封装的lvlib库里面的Memory相关函数(VI),这些函数位于这个虚拟文件夹“PC_Memory_RW_2Chs_Func”里面。

        为了提高Memory读写的效率,我们特地有针对性的封装,将Memory上行读数据(FPGA-->Host)和下行写数据(Host-->FPGA)的传输过程分成3个步骤,分别对应6个子VI,也就是打开、读写、关闭,如图9-133所示。

图9-133:lvlib库里面的6个PCIe Memory通道读写函数

l  PCIe Memory读通道初始化(FPGA_Memory_Read_Poly_Init_DLW30.vi)

l  PCIe Memory读通道数据读取(FPGA_Memory_Read_Poly_Receive_DLW30.vi)

l  PCIe DMA读通道关闭(FPGA_Memory_Read_Poly_Close_DLW30.vi)

 

l  PCIe Memory写通道初始化(FPGA_Memory_Write_Poly_Init_DLW30.vi)

l  PCIe Memory写通道数据下发(FPGA_Memory_Write_Poly_Send_DLW30.vi)

l  PCIe DMA写通道关闭(FPGA_Memory_Write_Poly_Close_DLW30.vi)

        再次强调:所有调用so的子VI都要设置成“任意线程”,切不可设置成“用户线程”,如图9-134所示。否则一旦DLL里面的函数出现读写阻塞之后,会导致LabVIEW界面无响应,卡死状态,更没有办法进行探针调试,切记!

图9-134:LabVIEW调用底层有阻塞函数的so时,一定要设置成“任意线程运行”

        下面我们对这6个多态子VI分别进行讲解,之所以设计成多态VI,是为了方便用户编程的时候,可以直接选择切换通道。下面我们逐一给大家讲解一下这6个PCIe Memory通道读写的VI含义和用法。

        1)FPGA_Memory_Read_Poly_Init_DLW30.vi

        这个子VI可以用来完成对指定的FPGA PCIe Memory读通道进行初始化,本质上就是打开Xillybus里面的Memory通道文件名,如果Memory读通道初始化成功,该VI会返回一个唯一的句柄“fd_read”,后面所有跟读通道相关的函数VI都需要借助这个引用指针“fd_read”,如图9-135所示。用户可以根据实际情况,在左侧的下拉列表“Pipe_Read_Name”里面选择16位或者32位位宽的Memory通道作为读取通道,其中,后缀mem0对应16位位宽,mem1对应32位位宽,也就是每次操作的数据是双字节还是4字节。

图9-135:PCIe Memory读通道初始化VI

        2)FPGA_Memory_Read_Poly_Receive_DLW30.vi

        当上行的Memory读取通道打开成功之后,我们就可以利用这里的“FPGA_Memory_Read_Poly_Receive_DLW30.vi”函数按照指定的地址把下位机FPGA里面的数组或者Memory元素读取出来了,如图9-136所示。该VI除了错误簇外,还有4个连接端口,最上面的左右两侧分别是Memory读通道打开后的引用句柄,另外就是地址输入控件“address_read_U16”和数据输出控件“data_read_U16”。就是用户可以将FPGA里面的数组或者Memory指定位置(地址)处的元素读取到Linux RT主机端(树莓派)来。

图9-136:PCIe Memory读通道读取下位机FPGA Memory函数

        这个VI本质上调用的是一个so函数,里面实际上包含了两个系统函数,分别是lseek文件位置偏移函数和read文件内容读取函数。如果用户不会LabVIEW的话,也可以使用其他语言,比如Python、C++、C#等直接调用系统里面的lseek和read函数就可以实现图9-136这个VI一样的功能和效果,因此我们开发的FPGA PCIe CLIP不限制操作系统,无论是Windows还是Linux都支持。

        下面我们双击打开这个VI,看看程序框图里面有没有什么需要注意的地方,如图9-137所示。

图9-137:PCIe Memory读通道读取VI程序框图

        为了让用户在执行读取操作的时候,地址是连续的,也就是地址0对应的U16或者U32数组的第1个元素,地址1对应第2个元素,地址2对应第3个元素,以此类推,而不需要考虑什么双字节或者4字节的倍数整除问题,为此,我们特地在这个VI里面加入了一个转换公式,如图9-138~9-139所示,其中,16位位宽的Memory地址,我们下发之前给FPGA的地址做了偏移(如果是Linux系统,直接将外面给进来的地址×2;如果是Windows系统,需要×2+1;对于32位位宽的Memory地址做了偏移(如果是Linux系统,直接将外面给进来的地址×4;如果是Windows系统,需要×4+3千万要注意:如果地址赋值不正确,会导致LabVIEW应用程序死机!

图9-138:16位的Memory地址偏移算法(Linux系统下的地址偏移)
图9-139:32位的Memory地址偏移算法(Linux系统下的地址偏移)

        最后需要注意的一点是:PCIe Memory跟FIFO一样,对超过8位位宽的数据,存在大小端格式转换问题,为了减少FPGA资源的消耗,鉴于Memory读写速度很慢,我们特地将大小端格式转换代码放在了Linux RT主机端(树莓派),也就是本节介绍的Memory通道读写VI里面,如图9-140所示。将读出来的字节数组进行翻转,然后再利用“强制类型转换”函数变成U16或者U32就可以了。

图9-140:将Memory大小端格式转换放在Linux RT主机端(树莓派)VI里面处理,下位机FPGA则不需要

        3)FPGA_Memory_Read_Poly_Close_DLW30.vi

        当我们需要退出上位机应用程序之前,需要利用这里的“FPGA_Memory_Read_Poly_Close_DLW30.vi”函数来把先前打开的PCIe Memory读通道引用句柄指针关掉,如图9-141所示。防止出现内存泄露,更重要的是,如果不执行关闭的话,下次再打开这个通道的时候,会提示这个通道被占用了,所以务必要检查一下之前开启的通道句柄是否销毁了。另外,为了保证Memory通道引用被强制关闭,我们在输入错误簇之前加入一个“清除错误簇”函数,防止上游的错误导致该VI不执行关闭功能。

图9-141:关闭PCIe Memory上行读通道引用句柄,释放通道占用

        4)FPGA_Memory_Write_Poly_Init_DLW30.vi

        这个子VI可以用来完成对指定的FPGA PCIe Memory写通道进行初始化,本质上就是打开Xillybus里面的Memory通道文件名,如果Memory写通道初始化成功,该VI会返回一个唯一的句柄“fd_write”,后面所有跟写通道相关的函数VI都需要借助这个引用指针“fd_write”,如图9-142所示。用户可以根据实际情况,在左侧的下拉列表“Pipe_Write_Name_Linux”里面选择16位或者32位位宽的Memory通道作为写入通道,其中,后缀mem0对应16位位宽,mem1对应32位位宽,也就是每次操作的数据是双字节还是4字节。

图9-142:PCIe Memory写通道初始化VI

        5)FPGA_Memory_Write_Poly_Send_DLW30.vi

        当下行的Memory写取通道打开成功之后,我们就可以利用这里的“FPGA_Memory_Write_Poly_Send_DLW30.vi”函数将Linux RT主机端(树莓派)的地址和数据下发给下位机FPGA,按照索引值(地址)将数组或者Memory里面的元素覆盖更新,如图9-143所示。该VI除了错误簇外,还有4个连接端口,最上面的左右两侧分别是Memory写通道打开后的引用句柄,另外就是地址输入控件“address_write_U16”和数据输入控件“data_write_U16”。就是用户可以将Linux RT主机端(树莓派)的数据按照给定的地址对FPGA里面的数组或者Memory指定位置(地址)处的元素进行覆盖更新。

图9-143:PCIe Memory写通道下位机FPGA Memory写入更新函数

        这个VI本质上调用的是一个so函数,里面实际上包含了两个系统函数,分别是lseek文件位置偏移函数和write文件内容写入函数。如果用户不会LabVIEW的话,也可以使用其他语言,比如Python、C++、C#等直接调用系统里面的lseek和write函数就可以实现图9-143这个VI一样的功能和效果,因此我们开发的FPGA PCIe CLIP不限制操作系统,无论是Windows还是Linux都支持。

        下面我们双击打开这个VI,看看程序框图里面有没有什么需要注意的地方,如图9-144所示。

图9-144:PCIe Memory写通道写入VI程序框图

        为了让用户在执行写入操作的时候,地址是连续的,也就是地址0对应的U16或者U32数组的第1个元素,地址1对应第2个元素,地址2对应第3个元素,以此类推,而不需要考虑什么双字节或者4字节的倍数整除问题,为此,我们特地在这个VI里面加入了一个转换公式,如图9-145~9-146所示,其中,16位位宽的Memory地址,我们下发之前给FPGA的地址做了偏移(如果是Linux系统,直接将外面给进来的地址×2;如果是Windows系统,需要×2+1;对于32位位宽的Memory地址做了偏移(如果是Linux系统,直接将外面给进来的地址×4;如果是Windows系统,需要×4+3千万要注意:如果地址赋值不正确,会导致LabVIEW应用程序死机!

图9-145:16位的Memory地址偏移算法(Linux系统下的地址偏移)
图9-146:32位的Memory地址偏移算法(Linux系统下的地址偏移)

        最后需要注意的一点是:PCIe Memory跟FIFO一样,对超过8位位宽的数据,存在大小端格式转换问题,为了减少FPGA资源的消耗,鉴于Memory读写速度很慢,我们特地将大小端格式转换代码放在了Linux RT主机端(树莓派),也就是本节介绍的Memory通道读写VI里面,如图9-147所示。先利用“强制类型转换”函数将U16或者U32转换成字节数组,再利用“反转一维数组”函数进行反转就可以了。

图9-147:将Memory大小端格式转换放在Linux RT主机端(树莓派)VI里面处理,下位机FPGA则不需要

        6)FPGA_Memory_Write_Poly_Close_DLW30.vi

        当我们需要退出上位机应用程序之前,需要利用这里的“FPGA_Memory_Write_Poly_Close_DLW30.vi”函数来把先前打开的PCIe Memory写通道引用句柄指针关掉,如图9-148所示。防止出现内存泄露,更重要的是,如果不执行关闭的话,下次再打开这个通道的时候,会提示这个通道被占用了,所以务必要检查一下之前开启的通道句柄是否销毁了。另外,为了保证Memory通道引用被强制关闭,可以在输入错误簇前面加一个“清除错误簇”函数,防止上游的错误导致该VI不执行关闭功能。

图9-148:关闭PCIe Memory下行写通道引用句柄,释放通道占用

        总结:用户在熟悉和掌握了上面介绍的这6个PCIe Memory子函数(VI)的含义和用法之后,就可以大刀阔斧的开始编写Linux RT主机端(树莓派)上的PCIe Memory应用程序了。关于这部分内容,我们会在后续的实验68里面,做详细的讲解。

7、利用LabVIEW/C/Python库函数节点直接调用Linux RT动态链接库so文件里面的PCIe函数的评论 (共 条)

分享到微博请遵守国家法律