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

笔者在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源码以及说明文档等文件,下载成功后记得回来给作者点赞。

  • 29
    点赞
  • 146
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
在同一界面下放上所有的按钮去控制,按钮间的逻辑关系有点复杂。即使做出来用户用着也会感觉有些别扭,据此我们干脆分成几个界面来做。(总控与退出按钮不要也行) 第一界面:楼体、环境、退出; 第二界面:楼体1、楼体2、返回、退出; 第三界面:户型A01---A04、B01---B04、返回、退出; 第四界面:户型2-01---2-04、返回、退出。 按钮1>>楼体, 按钮2>>环境; 按钮3>>楼体1, 按钮4>>楼体2, 按钮5>>返回; 按钮6---按钮13>>[A01---A04][B01---B04], 按钮14>>返回; 按钮15---按钮18>>[2-01---2-04],按钮19>>返回; (0-F路为16继电器输出) 程序动作如下: 第一界面: 1. 按钮1开-开[0]路>>弹出第二界面;按钮1关-关[0]。 2. 按钮2开-开[1]路,按钮2关-关[1]路。 第二界面: 1. 点击按钮3-开[2]路,>>弹出第三界面。 2. 点击按钮4-开[3]路,>>弹出第四界面。 3. 点击按钮5>>返回第一界面。 第三界面: 1. 按钮6开-开[4]路,按钮6关-关[4]路。 2. 按钮7开-开[5]路,按钮7关-关[5]路。 3. 按钮8开-开[6]路,按钮8关-关[6]路。 4. 按钮9开-开[7]路,按钮9关-关[7]路。 5. 按钮10开-开[8]路,按钮10关-关[8]路。 6. 按钮11开-开[9]路,按钮11关-关[9]路。 7. 按钮12开-开[A]路,按钮12关-关[A]路。 8. 按钮13开-开[B]路,按钮13关-关[B]路。 9. 点击按钮14-关[2][4-B]路>>返回第二界面。 第四界面: 1. 按钮15开-开[C]路,按钮15关-关[C]路。 2. 按钮16开-开[D]路,按钮16关-关[D]路。 3. 按钮17开-开[E]路,按钮17关-关[E]路。 4. 按钮18开-开[F]路,按钮18关-关[F]路。 9. 点击按钮19-关[3][C-F]路>>返回第二界面。
Xilinx Zynq 7000嵌入式系统是一种基于ARM处理器和FPGA可编程逻辑的系统-on-Chip (SoC)平台。它结合了高性能的ARM Cortex-A9处理器和可编程逻辑的灵活性,为嵌入式系统设计带来了许多优势。 《Xilinx Zynq 7000嵌入式系统设计与实现》这本PDF书籍介绍了使用Xilinx Zynq 7000平台进行系统设计与实现的方法和技术。这本书主要包括以下几个方面的内容。 首先,该书介绍了Xilinx Zynq 7000平台的体系结构和特点。它详细解释了Zynq 7000 SoC的内部结构,以及其与外设和外部系统的交互方式。这对于读者了解这个平台的基本概念和原理非常有帮助。 接着,书中介绍了如何使用Xilinx Vivado设计套件进行系统设计和开发。Vivado是一种综合的开发工具,支持软硬实现的联合设计。它提供了设计、验证、综合和实现的功能,读者可以通过学习该书了解如何使用这个工具进行系统设计和开发。 此外,该书还包括了使用编程语言和软件工具进行系统编程和调试的内容。它介绍了如何使用C/C++和VHDL/Verilog语言进行系统编程,并提供了一些常用的软件调试技巧。 最后,这本书详细介绍了一些典型的嵌入式系统案例,在实践中展示了如何使用Zynq 7000平台进行系统设计和应用开发。这些案例涵盖了不同的应用领域,包括通信、图像处理、物联网和工业控制等。 总之,《Xilinx Zynq 7000嵌入式系统设计与实现》这本PDF是一本非常有用的书籍,它提供了对于使用Xilinx Zynq 7000平台进行嵌入式系统设计和实现的详细介绍和指导。无论是对于初学者还是有一定经验的工程师来说,这本书都能够帮助他们更好地理解和应用Zynq 7000平台。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求学者羽光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值