1.场景:
将存储在FPGA片上BlockRAM中的图片数据通过 口传输到上位机显示,目标是FPGA通过 口发送图片,其大小为1920*1200,位深为8bit,30fps,上位机可以实时显示即可。这个小项目中考虑的问题有①使用FPGA的Block Memory Generate IP,②开发图片数据转换成coe文件的软件,①②两步实现将图片数据存储到FPGA片上。③通过千兆以太 口将ROM图片数据传输到上位机。④使用Qt开发 口数据接收上位机,先可以显示一张图片,然后通过多线程配置使之能够动态显示图片流即视频数据。该项目主要用来验证, 口传输到上位机多线程显示两个问题,ROM图片数据可以看成是模拟的视频流,实际应用可以来自其他数据源如CMOS图像传感器等。大的验证步骤分为两个,一个是验证静态图片即一张图片的数据流,在此基础上验证视频流传输与显示。该小项目开发环境基于Qt5.15.2,vivado2020.2,winsock2.h ,以及xc7a35tfgg芯片。
2.实现思路:
- 使用qt制作coe文件生成程序,生成coe文件。先用画图板构造一个1920*1200(注意像素生成错了为1900*1200费了好大的劲才找见)大小的图片,在qt程序中,只写前面1920*100个像素数据到coe文件中(据缓存大小决定)参考C5—Qt生成特定应用的COE文件 2021-11-18修改部分代码即可,我在此基础上修改了图像像素大小,并且将最后一行的逗 改为分 。如下图所示。
- 创建FPGA工程(为验证单张图片的工程)。程序概述:上电后,等待上位机发出ARP请求(因此上位机程序应主动发出ARP请求,此处也是程序需要完善的地方,只能应答ARP而不能发出ARP请求),当收到ARP请求,可在PC端通过arp – a查看arp映射表的情况,按键按下一次只发送一张图片(即发送12次ROM数据),让上位机接收,以验证单张图像的显示。此处关于 口的描述可参考E1–千兆以太 接口测试应用2022-09-07
- 使用qt制作 口数据显示程序,发现 上和Qt官方有一直言论Qt5对于UDP Socket的支持不完善,果断采用Windows对于 络编程的支持,即socket编程。对于socket编程的简介请看下节。
- 关于发送格式的确定。添加从ROM到 口的数据,每一张图片都12部分相同的数据组成,每一次传输(一个UDP数据包)1232B数据包,其中包含32B帧头信息和1200B图片信息,一张图片由1920个图像帧传输完成;帧头信息采用以下结构体。(上述3,4部分均要遵循)
- 开始测试,明确下位机关于配置,端口为6000,IP地址为192.168.1.10,Mac地址为0x00-11-22-33-44-55,本地端口为6001,应该设置本地IP为同一个 段,即手动ip设为192.168.1.102。(使用arp -a命令查看ARP地址映射表)
-
查看结果如下,单张图片显示正常,以下两图分别为原图和复原图。(注单张图片显示的上下位机代码未贴出,只贴出最后动态显示的代码)
- 下面考虑发送视频流时数据的恢复,基于单张显示成功的FPGA工程创建V2 FPGA工程,控制每30fps速度发送ROM至 口;开发上位机程序,划分为主线程、接收线程、处理线程三个线程。主要开发的代码是 口数据接收、数据解码、图片恢复、多线程控制等内容。主要考虑的问题是线程同步和线程安全。即缓存大小、类型、位置以及共享内存信 量和互斥量的设计。其数据流向简要如下:
3.Win Socket编程
据 上多数开发者经验,以及Qt creator书,Qt5版本的Network模块并不完善,在使用UdpSocket套接字接受数据时,无论是否使用线程去接收,常常出现丢包的现象。索性直接使用win socket编程,其实就是用微软官方提供的关于 口编程的库winsock2.h。
环境搭建
添加头文件#include “Winsock2.h”,并且在.pro文件中添加LIBS += -lWS2_32,即可使用windows 络编程。
流程
与Qt提供的UDP Socket相比,获取 口收发数据的流程大同小异,socket()–>bind( )–>listen()–>accept()–>read()/write()—>close()。关于UDP Socket可参考Day30Qt实现UDP传输2022-01-07,关于socket的教程 上很多,下面我把用到的几个函数罗列如下。
①初始化 DLL
②创建套接字
af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址 type 为数据传输方式/套接字类型,流格式套接字(Stream Sockets)(TCP套接字)数据 格式套接字(SOCK_DGRAM)(UDP套接字)protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议 如果使用 SOCK_DGRAM 传输方式,且位ipv4,那么满足这两个条件的协议只有 UDP返回值是 SOCKET 类型,用来表示一个套接字。
③结构体sockaddr_in
④将一个无符 短整型数值转换为 络字节序,即大端模式(big-endian)。htonl函数可以用来进行 络字节和主机字节的转换。
⑤bind()函数:
⑥INADDR_ANY
作为接收端,当你调用bind()函数绑定IP时使用【INADDR_ANY】,表明接收来自任意IP、任意 卡的发给指定端口的数据。作为发送端,当你调用bind()函数绑定IP时使用【INADDR_ANY】,表明使用 卡 最低的 卡进行发送数据,也就是UDP数据广播。
⑦setsockopt
⑧recvfrom函数
4.上位机代码分析
默认main.cpp文件
主线程widget文件-widget.h
该文件主要是创建数据接收线程类对象和数据处理线程类对象,声明必要的函数和变量,具体功能在cpp文件中描述。
- #ifndef WIDGET_H
- #define WIDGET_H
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "recvthread.h"
- #include "handlethread.h"
- QT_BEGIN_NAMESPACE
-
namespace Ui { class Widget; }
- QT_END_NAMESPACE
-
class Widget : public QWidget
- {
- Q_OBJECT
-
-
public:
- Widget(QWidget *parent = nullptr);
- ~Widget();
-
private slots:
- //udp recv thread slot
- void serverStartedSlot(bool bok);
- //handle thread slot
- void showPixmapSlot(QPixmap pix);
- void slotSpeedOut();
- void on_startBtn_clicked();
- void on_stopBtn_clicked();
- void recvThdMessage(QString);
- void on_clearBtn_clicked();
-
-
private:
- Ui::Widget *ui;
- QUdpSocket *udpSocket;//udp管理对象
- recvThread* recvthread;