笔者在CSDN的第一篇万字长文,请多多支持。

本文是笔者的公众号 IC设计者笔记 文章的转载。很多优质原创内容都会第一时间发布在公众号,欢迎关注公众号,一起交流学习。公众号后台回复“ZYNQ图像传输”即可免费下载包括Vivado工程、Python源码以及说明文档等文件。

前言

前段时间接到老板匆忙打电话,大概内容是:之前师兄流片的CMOS图像传感器马上要提交结题报告,需要帮忙用ZYNQ系列FPGA将图像传感器的数据实时传输到PC​,并且通过上位机拍照。由于时间紧急,要求两三天内完成。当时自己心想:“​FPGA开发+ARM程序编写+PC端上位机开发” 两三天完成,还包括调试。。。Are you kiding me ? 自己直言,两三天不现实,经过一番讨论,最终答应尽量在​一周内完成。

自己之前过FPGA和PC通信的设计,只是当时数据量不大,直接用的串口。​图像的实时传输,串口显然无法胜任。可供选择的只有USB和网口。自己没有接触USB接口开发,但接触过UDP通信,而且ZYNQ的教程有网络通信实验,故选择网口传输数据。至于PC端上位机部分,最方便的肯定是Matlab和Python,尤其是涉及到图像处理。正好自己最近学习Python,感觉开发起来效率挺高,所以决定“人生苦短,我选Python”。

下面自己分三部分讲解具体的实现过程:

FPGA 部分

自己接手的时候,情况是这样:​FPGA端的CMOS sensor 图像控制部分Verilog代码师兄已经写好,自己的任务是写一个数据处理模块将CMOS sensor 输出的数据读取出来,然后将控制部分和数据处理部分封装成AXI接口的IP接到ZYNQ的PS (ARM) 端。FPGA和ARM通信的AXI接口类型常用的有两种:AXI-lite和AXI-steam。AXI-lite主要通过寄存器实现ARM和FPGA之间的数据交互,带宽较低,适合数据量不大的情形。AXI-steam则属于高带宽类型,一般都和DMA配合使用,实现大量数据的交互。相比而言,AXI-lite要简单很多,但要评估是否满足要求。显然AXI-steam和DMA更适合当前的应用,但经过认真考虑,还是采用了保守方案——AXI-lite总线。原因如下:1.师兄做的Image sensor像素不高,只有128x128。虽然是16个通道并行输出,但每个通道输出一个Byte需要16 clk,而且时钟频率只有10Mhz。如果AXI-lite总线用100Mhz时钟,且数据线宽为32Bit,这就意味着只需要在160个总线时钟内读取4次,就可以把16 Byte的数据全部从FGPA中读出。而且ARM的频率是666Mhz,用查询方式读取,应该不会出现数据丢失问题。2.自己没有用过DMA和AXI-steam使用经验,中途一旦出现问题,可能就要拖一两天时间(这才是自己最担心的),后面有时间了再去学习用这两种方法实现。*

具体实现步骤如下:

1)创建自定义IP

vivado工程创建过程我就不写了,下面主要介绍如何将AXI-lite总线与自己设计的目标电路连接起来并并封装成一个IP。

任何总线都是通过 ‘读’ 和‘ 写’ 两种操作实现CPU和硬件外设的通信。通信的数据主要包括控制信号,状态信号以及需要读写的数据,这些信息的传递都是通过中间的寄存器实现。

我们在用vivado 新建 AXI-lite总线的IP时,可以选择中间寄存器的个数,这里我设置了20个,接着Vivado会自动生成 AXI-lite总线的代码,自己需要做的就是将这些信号和目标电路连接起来。

2)CPU写操作

我们首先来看一下刚刚设置的这20个寄存器,这些寄存器就是用来存放CPU写入的数据。每一个寄存器都有唯一的32位地址,而且这些寄存器的地址是根据其编号从低到高连续累加的,内部地址是(0~19)。当CPU向某一个地址写入数据时,数据就会被存放至与该地址对应的寄存器中。目标电路所需要的输入信号(控制信号和配置参数等)就是通过这些寄存器传递。

下面就是目标电路的顶层输入信号,其中启动信号和测试模式的开关由 slv_reg0 的最低两个Bit实现,而switch0-5是控制image sensor 的开关信号,由slv_reg0 的 2-7 bit 控制。
另外还有两个控制曝光时间长度的信号,它们分别由 slv_reg1slv_reg3 控制。 这样我们就实现了输入信号的连接。

3)CPU 读操作

读操作就是将Image Sensor的图像输出读出来,先看一下目标电路的顶层输出信号,输出信号主要包括数据和相应的标志信号。CPU 查询方式读出数据的原理是:循环读取标志信号,当标志信号为 1 时即说明当前有数据输出,这时再读取数据。

CPU读取数据和状态信号的关键部分如下,十六进制 0-13即内部的20个偏移地址,当CPU读取某一个地址时,相应信号的值就会传给CPU。

值得注意的是,上面的CPU读信号中并没有目标硬件电路的标志信号,因为硬件标志信号一般都是持续一个时钟周期,而CPU查询一次往往需要多个时钟周期,如果将标志信号直接传给总线,CPU是无法正常读出的。因此需要设计一个简单的电路来锁存标志信号,同时CPU读完以后信号还要自动释放。
以标志信号 word_en为例,该电路实现方法如下,software_flag即锁存的word_en信号:

代码讲解如下:word_en上升沿到来后将software_flag 置 1,而software_flag清除的条件是 总线处于读使能且总线地址位的内部偏移地址为 4。因为 word_en 是数据 word1~4 的标志信号,而读操作的地址 4 即对应 word1的数据。CPU开始读 word1 则表明 CPU 已经检测到了 word_en 有效。当然也可以将 software_flag 清除的条件改为 总线处于读使能且总线地址位的内部偏移地址为 0,因为读操作的地址 0 即对应 software_flag ,这样software_flag持续的时间会比较短。

ARM裸机程序部分

ZYNQ芯片内部有两个ARM Cortex-A9 CPU,主频为766MHz, 虽然可以运行Linux,但是需要为Image Sensor控制电路编写驱动程序,为了节省时间,直接运行裸机程序。ARM处理器主要负责配置CMOS sensor的工作状态,同时将数据从FPGA中读出来并与上位机通信实现图像实时传输。

1) TCP 通信

网络传输采用的TCP协议,由于我们采用的裸机程序,实现TCP通信最合适的方法就是使用light-weight IP stack (lwIP)小型开源TCP/IP协议栈,Xilinx SDK中提供有Demo工程,如下图

本文中的代码是在 LWIP Demo工程的基础上进行改写。TCP协议中通信的双方分为Server和Client,Client只要知道了Server的IP和端口号就可以建立连接。根据上图可知,LWIP的demo工程中FPGA作为Server,监听端口号为 Port 7,程序实现的功能是将收到的数据自动回传给client。

Demo工程建立好以后对LWIP进行以下配置,以提高传输效率。

使用Demo工程最大的好处就是我们不需要懂TCP协议的具体通信过程,只需要修改数据的接收和发送函数即可。

Demo工程中负责发送和接收数据的函数在echo.c中,函数内容如下:

从上图中的代码可以看到:
64行接收数据,71发送数据,而发送的内容为 p->payload所指向的内存区,即接收的数据存放在 p->payload所指的地址处,数据长度为 p->len。读懂了函数的功能就可以根据自己的需要进行修改了。

首先将数据发送部分替换成自己写的函数 recv_process(tpcb, p),该函数负责处理上位机的指令并发送图像数据。

我们所定义的上位机的指令以0x55 开头,第二个字节即命令,命令分为4种:状态查询,启动图像采集,关闭图像采集,以及工作模式设置。

要想控制硬件,首先需要定义一个结构体,该结构体中的变量与硬件电路中的寄存器一一对应,如下图:

因为ZYNQ内部采用的32位AXI总线,因此结构体内所有变量都是32位。这些变量的顺序与FPGA控制电路的寄存器相对应。当我们向这些变量中写入数据时,数据会自动传递到如下寄存器

而当读取这些变量的值时我们得到的是硬件电路中如下信号的值

结构体定义好以后,最重要的一步就是定义结构体指针,并将指针指向 FPGA控制电路的基地址(XPAR_IMAGE_CTL_0_S00_AXI_BASEADDR,这样就可以对硬件进行读写了。同时还需要定义两个BUF,用于保存从Sensor中读取的图像数据。

硬件控制电路的驱动函数如下。该函数的核心是用软件读写Image sensor 的控制电路的寄存器,从而实现图像数据的读入,同时控制传感器的工作模式。函数内容如下图:

该函数的具体流程是:设置曝光时间、工作模式、启动Sensor、通过查询方式读取图像数据。
因为我们定义的ImageBuf是8位数组,而从总线中读到的数据是32位。一般的操作需要四次才能将32位的数据写入4个8位的变量中,而通过如下方式,可以一次将 32位数据写入 4个变量

*(int *)(ImageBuf[1]+pixel_num)     = a0;

下面简单介绍一下图像传输函数 trans_image_data(tpcb)

该函数首先调用图像驱动函数读取数据。图像数据读完以后,调用 tcp_write() 将数据写入到 TCP 缓存,最后调用 tcp_output() 将数发送出去。
正常情况下,写入TCP缓存前需要先判断剩余空间 pcb->snd_buf 大小。由于我们在TCP BSP 参数配置时,将 tcp_snd_buf 设置为最大值 65535,而图像数据小于此值,故此处没有判断剩余空间,直接将数据全部写入。

到此ARM裸机程序编写完成。梳理一下,包括三个部分:上位机命令接收和处理、Image Sensor 控制及数据读取、数据发送。

Python上位机开发

Python上位机是自己用两天的时间做的一个简单demo。
下面我对上位机程序进行简单整理。
上位机功能可分为四个方面:1. TCP通信;2.OpenCV图像实时显示及拍照;3.GUI界面;4.将所拍照片保存为CSV文件。
需要使用的库包括:numpy, cv2, socket, tkinterpandas

1) TCP 通信

Python实现TCP通信和UDP通信一样简单,只需要简单调用几个函数即可,具体步骤如下(Python为客户端):

  • 创建socket套接字
 soc=socket.socket()
  • 建立连接
 soc.connect((server_ip_addr, port))

server_ip_addr为 server端的IP地址,port为server监听的端口号

  • 发送数据
 soc.send(send_data)

send_data 为我们需要发送的数据。注意:发送的数据必须为bytes 类型,如果是其他数据类型,需要用 bytes()函数转换。

  • 接收数据
 soc=socket.socket()

其中buffer_size 为一次最多接收数据的个数,tcp_dat 为接收到的数据。接收数据有两点需要注意:

  1. 如果server端一次发送的数据量较大,网网关会自动将数据分成多批发送,如果我们只调用一次 recv(buffer_size),会导致数据接收不完整 (自己有实际踩坑经历)。
  2. 接收到的数据类型为 bytes 类型,需要转换成 自己所需的类型。
    完整数据接收及类型转换可通过如下方法实现(当然,此方法的前提是我们知道每张图像的大小,即上位机一次发送了多少数据):

    int_dat 保存接收到的int型数据,当接收到我们设定的数据量时停止接收。

2)OpenCV 图像实时显示

因为我们采用的opencv库,图像实时显示非常简单。
首先将接收到的图像数据转换成 numpy 整数类型,由于数据是一维的,需要进行图像重构变成128x128的二维数组。然后调用 cv2.resize( )函数对图片大小进行缩放,下图中的 img_test1 即为调整大小后的二维图像矩阵。最后调用cv2.imshow( )即可显示图像。

拍照通过 cv2.waitKey(1) & 0xFF == ord(‘c’) 实现,当键盘按下‘c’ 键时即停止图像采集和显示,从而实现拍照功能。

3)GUI 界面

GUI界面采用 tkinter 库实现。首先我们需要添加所需的控件(比如:输入框、显示框、按键等),代码如下:


该程序的具体步骤包括:
创建主窗口,并设置窗口title和大小。
self.root = tkinter.Tk()
创建一个输入框,并设置宽度。该输入框用于设置server端IP
tkinter.Entry(self.root, width = 50);

创建一个显示框,用于显示提示信息,并设置宽度。

tkinter.Listbox(self.root);

创建三个按钮,分别是Connect (建立以太网连接), Start(启动图像传输) 和 Save (保存图像数据)。
tkinter.Button(self.root, command = self.connect_fun, text = "connect");
注意该函数有三个参数,第二个参数为按键被点击时所调用的函数,第三个参数为按键上显示的名称。

GUI界面所需的控件添加完成后需要对它们进行布局,有两种方法可以实现
place()pack(), place()需要指定起始坐标以及长度和宽度(坐标原点为GUI界面左上角),pack()函数则可以自动调整布局。一般我们需要两种方法结合使用。
如果我们全部使用pack()自动布局,代码如下

布局效果如下图,按键大小根据文本长度自动调整的,好像不太美观。哈哈哈。。。

如果我们对三个按键如下调整,效果就好多了:


布局完成后,还有最重要的一步,调用 tkinter.mainloop()函数,这样运行程序是GUI界面才会出现。

4)保存数据

将数据保存至 .csv 文件可以用 pandas 库实现,
保存数据前我们可以用python自动创建文件夹并用当前时间作为文件夹名称,具体代码如下:

数据保存只需要两个函数即可实现:
save = Pd.DataFrame(self.img_buf[0]); # 转换数据格式, 此处 self.img_buf[0] 为需要保存的数据,此处为128x128的二维数组。
save.to_csv(file_name) # 保存数据

结束语

如果本文对你有帮助,请多多点赞支持,让作者有动力创作更多优质内容。最后,欢迎关注微信公众号IC设计者笔记,很多技术干货内容会第一时间在公众号发布。公众号后台回复“ZYNQ图像传输”即可免费下载包括Vivado工程、Python源码以及说明文档等文件,下载成功后记得回来给作者点赞。

“Xilinx ZYNQ+TCP通信+Python上位机”实现实时视频传输系统相关推荐

  1. python3中利用serial模块实现单片机与python上位机的通信(串口调试助手)

    1.指标:    python上位机向单片机发送字符,单片机如果收到的字符为'1',则点亮灯1,如果收到的字符为'2',则点亮灯2:单片机若接受到字符,读取字符后,向python上位机发送字符(1-& ...

  2. python上位机开发实例-python上位机

    广告关闭 腾讯云双11爆品提前享,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高满返5000元! 若python上位机接受到的字符为"1',则print出ok,如果字符是 ...

  3. 遵循Modbus协议通过Usb(Ch375)通信的上位机传输问题

    遵循Modbus协议通过Usb(Ch375)通信的上位机传输问题 Delphi / Windows SDK/API http://www.delphi2007.net/DelphiNetwork/ht ...

  4. C#实现串口通信的上位机开发

    目录 上位机 串口通信 C#串口通信:SerialPort类 列出所有的串口 C#串口通信:读写数据 写数据: 读数据: DataReceived事件: 数据发送不同步问题: 界面设计 波形显示(ch ...

  5. C#做一个简单的进行串口通信的上位机

    C#做一个简单的进行串口通信的上位机 乱世中的单纯 发布于 1年前,共有 10 条评论 1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来 ...

  6. python上位机开发经验总结01

    文章目录 python上位机开发经验总结01 python变量与文件的处理 全局变量与局部变量 文件间的变量处理 threading模块使用经验 管理线程 定义线程 tkinter使用经验 tkint ...

  7. C# / VB / LabVIEW / VC / Python 上位机使用S7-TCP协议与西门子PLC进行网口通信的教程 (Win/Linux)

    现在越来越多的项目开始使用上位机了,在上位机实现数据存储.曲线绘制时,使用高级语言自行开发程序,比各种组态软件更加自由,更加强大.在进行上位机软件开发时,第一步就是要跟PLC取得通信,能够读写PLC内 ...

  8. 用python做一个上位机串口通信_【教程】简易Python上位机之LED控制

    电子爱好者应该不会对"上位机"这个词感到陌生,毕竟或多或少有过接触.但若是说到上位机的开发的话,大家就不一定熟悉了.很多电子爱好者完全没有接触过上位机的开发工作,他们真的没有相应的 ...

  9. C# 实现自定义的USB设备与上位机进行通信(上位机部分)

    因为以前没用过USB,对USB也不了解,于是上网查了很多资料,不过网上的资料都是零零散散,不清不楚的,于是我自己总结了一下,下面几个链接是网上这么多零散资料里,我觉得比较有参考意义的. USB设备连接 ...

最新文章

  1. VMware 6.5开始,VMware vSphere ESXI只能通过浏览器访问
  2. python小项目案例-Python小项目:快速开发出一个简单的学生管理系统
  3. 基于php在线相册,基于PHP的图片相册管理分享系统设计
  4. VTK:PolyData之CenterOfMass
  5. 最简单代码ASP.NET开源QQ登陆for Oauth2.0
  6. Spring Boot 你所不知道的超级知识学习路线清单
  7. java学生考勤代码免费,基于jsp的学生考勤管理-JavaEE实现学生考勤管理 - java项目源码...
  8. preHandle、postHandle与afterCompletion
  9. 转载:HTML5及移动端BUG
  10. 生信SCi好用的画图软件
  11. 用上就不会停下的效率利器—Automator
  12. 堪比巨著:饿了么交易系统5年演化血泪史
  13. haproxy配置负载均衡
  14. css33d画梯形,CSS3 matrix3d矩形到梯形转换
  15. 蓝桥杯python-找单词出现次数最多的字母
  16. 《构建之法》第十二章 用户体验
  17. 赛灵思FPGA——ZYNQ介绍
  18. MYSQL字符集与校对规则
  19. stc89c51单片机音乐盒系统设计_801【毕设课设】基于单片机心型音乐盒系统DIY设计...
  20. Vue:使用vue-codemirror编写文本比对功能

热门文章

  1. 打印JVM所有参数列表的方法 -XX:PrintFlagsFinal、–XX:PrintCommandLineFlags
  2. GitHub 上值得收藏的 100 个精选前端项目!
  3. android 中动态加载广告sdk
  4. clover安装黑苹果10.15.3常见问题集合
  5. win10多显示器设置只有主显示屏显示任务栏
  6. 7-10 学生成绩排序 (15 分)
  7. css斜切角/内阴影
  8. java string.concat_Java String concat() 使用方法及示例
  9. 有些钱,即便不脏,但也有毒。
  10. 有哪些既实用又好看的蓝牙耳机?高颜值实用蓝牙耳机排行