基于zynq的图像视频处理项目二

基于zynq的图像视频处理项目二

1.整体架构

在这里插入图片描述

2.整体的时钟和复位设计

时钟设计:
1.axi读写时钟:它由ps端给出的axi时钟150MHz
2.视频输出时钟:它由pll(clkin由ps端给出的100MHz)产生的108MHz(1280x1024 @60Hz)
3.用户自定义算法时钟:暂时定为与视频输出时钟相同
4.给到serdes的管脚时钟:为视频输出时钟的5倍

复位设计:(都通过了异步复位同步释放)
1.axi wr/rd复位:它由ps给出PS-PL configuration配置给出,通过复位ip核同步于ps端给出的axi时钟150MHz
2.视频输出复位:pll的locked信号
3.用户自定义算法模块复位:暂时定为与视频输出复位相同

3.详细方案设计

3.1 lwip的使用

LwIP 是 Light Weight (轻型)IP 协议,有无操作系统的支持都可以运行。 LwIP 实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用,它只需十几 KB的 RAM 和 40K 左右的 ROM 就可以运行,这使 LwIP 协议栈适合在的嵌入式系统中使用。
采用易用性和移植性都比较好的Socket 模式,采用freertos10_xlinx里面的实时操作系统,选择server的模板进行开发
在这里插入图片描述

其中比较重要的是这两个函数,基于这两个函数进行开发
在main函数中进行ip地址的设置
在这里插入图片描述

注意上位机和板卡要在同一网段 192.168.1.xxx
修改pc的网段使之与FPGA的网段相同
在这里插入图片描述

在freertos_tcp_perf_server.h中设置fpga服务端的端口号
在这里插入图片描述

3.1.1 main函数的讲解
int main()
{
	initGpio();
	main_thread_handle = sys_thread_new("main_thread", main_thread, 0,
			THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
	vTaskStartScheduler();
	while(1);
	return 0;
}

sys_thread_new来创建一个新的进程, 这里其实就是开启一个新的任务main_thread,在main_thread中又开启了一个新任务network_thread

	/* any thread using lwIP should be created using sys_thread_new */
	sys_thread_new("nw_thread", network_thread, NULL,
			THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);

用于设置mac

一些打印信息的设置

还有一些DHCP的设置,这里没有用到

还用就是最重要的启动了freertos_tcp_perf_server.c中的start_application任务
在这里插入图片描述

3.1.2 freertos_tcp_perf_server.c函数的讲解

主要掌握lwip_recvfrom和lwip_send两个函数的使用就行了

lwip_recvfrom( SOCKET s, char FAR* buf, int len, int flags,
struct sockaddr FAR* from, int FAR* fromlen);
s:标识一个已连接套接口的描述字。
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。
from:(可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向 from 缓冲区长度值。

flags 解释
0:常规操作,与 read()相同
MSG_DONTWAIT:将单个 I/ O 操作设置为非阻塞模式
MSG_OOB:指明发送的是带外信息
MSG_PEEK:可以查看可读的信息,在接收数据后不会将这些数据丢失
MSG_WAITALL:通知内核直到读到请求的数据字节数时,才返回。

lwip_sendto( SOCKET s, const char FAR* buf, int len, int flags,
const struct sockaddr FAR* to, int tolen);
s:一个标识套接口的描述字。
buf:包含待发送数据的缓冲区。
len: buf 缓冲区中数据的长度。
flags:调用方式标志位。
to:(可选)指针,指向目的套接口的地址。
tolen: (可选) to 所指地址的长度。
0: 常规操作, 与 write()无异
MSG_DONTROUTE:告诉内核,目标主机在本地网络,不用查路由表
MSG_DONTWAIT:将单个 I/ O 操作设置为非阻塞模式
MSG_OOB:指明发送的是带外信息

3.2 axi的读写

1.参考ug585
2.https://blog.csdn.net/qq_41538901/article/details/132433375
3.FPGA小白

3.2.1 hp acp gp的区别

ug585
在这里插入图片描述

在这里插入图片描述

https://blog.csdn.net/qq_41538901/article/details/132433375
这个链接讲的不错

1.主从关系
在这里插入图片描述

gp是ps主,pl为从用的比较多
AXI_ACP 和 AXI_HP, PL 只能是主机, PS 只能是从机!!

2.理论带宽
在这里插入图片描述

3.使用方法
gp口一般都是ps端作为主机pl作为从机的慢速接口,hp口使用的时候要注意cache的一致性问题,什么是一致性问题?

arm接收到数据,fpga来读ddr里面的数据的话要用Xil_DCacheFlushRange,将cache的数即使刷到ddr里面,不然读出来不一致

如果是fpga往ddr里面写,那么arm读ddr里面的数的时候要用Xil_DCacheInvalidateRange,使得cache重新加载ddr里面的数,从而解决一致性问题

acp口就不需要,它直接连接到了cpu的l1和l2cache上,但他只有一个,数量比较少

3.2.2 主ps通过gp0对从pl寄存器进行写操作

如果 PS 端采用 AXI_GP 接口,且 PS 端为主机, PL 为从机,那么 PL 的接
口模块相对于 PS 来说,就是一个外设。(下面的图片加强理解)
在这里插入图片描述

既然是外设,那么就会有对应的地址和寄存器。
在这里插入图片描述

其中 0x4300_0000 为起始地址,又称之为基地址; 0x4300_FFFF 为结束地址。
其中,基地址和结束地址的值,是由 PL 端分配的,也就是在 PL 端可以使
用 VIVADO 软件更改。
第二个地址为 0x4300_0004,相比于 0x4300_0000 偏移量为 4。所以 4 就是
偏移地址。
可以通过 “基地址 + 偏移地址”表示任意的地址。比如基地址为
0x4300_0000,偏移地址为 8,表示的地址为 0x4300_0008。
PS 如何通过 AXI_GP 接口写入数据到 PL ?
自己creat 一个axi lite的fpga从机程序,然后PS 先将数据写入到寄存器里面,然后写入到 PL。
在这里插入图片描述

用bit文件生成的platform里面有
基地址和偏移地址来存储数据
在这里插入图片描述

PS 如何通过 AXI_GP 接口从 PL 端读取数据 ?
PL 端的数据先通过 AXI_GP 接口发送 PS 的寄存器里面, PS 从寄存器里面
读取数据。如下图:
在这里插入图片描述

3.2.3 主pl通过hp和acp口对从ps端ddr的读写操作

PL 发送给 PS 的数据,通过 AXI_HP 接口后,进入 DDR3 控制器,然后写
入到 DDR3 里面了。反过来, PL 也可以通过 AXI_HP 接口读取 DDR3 里面的数据。怎么理解,如下图:
在这里插入图片描述

acp口也如此,只不过acp口自带解决cache一致性

3.3 ps端对以太网数据包的处理流程

首先ps端接收到以太网的数据后都需要将cache里面的数据刷进ddr,不然pl读出来的数据不正确。

Xil_DCacheFlushRange(recv_buf,read_bytes);//sync to ddr3

在这里插入图片描述

业务逻辑如图所示ps负责识别处理这三种数据包,

在这里插入图片描述

整体的逻辑如图所示,ps端判断接收到的是配置数据包还是帧头,接收到配置包的话,完成ps端通过GP口往pl端寄存器写入配置信息的操作,由于网络报文是大端发送,所以在接收的32bit的配置信息时,需要进行高低位的反转,从而写入到pl的寄存器中

regdata = recv_buf[10]<<24;
regdata |=recv_buf[11]<<16;
regdata |=recv_buf[12]<<8;
regdata |=recv_buf[13];

接收到的是帧头的话,就拉高frame_head和image_flag标志,表明接下来接收的数据是一帧图像数据,接收到一帧图像后,会启动pl端的axi读模块,并把pl端算法处理完之后的图像重新传递给上位机,然后请求新的一帧图像数据

if(image_flag == 1){//rec image pixel data
			recv_buf += read_bytes;
			pixel_cnt += read_bytes;
			if(pixel_cnt == imagewidth*imagehigh*2){
				frame_head =0;//detect new frame
				image_flag =0;
				set_hdmi_start(0x01);//dma read to hdmi start
				set_hdmi_start(0x00);

				set_read_start(0x01);
				set_read_start(0x00);

				if (readback == 1) {
					if (imagewidth == 1280) {
						usleep(3200);//加延时
					}
					else
						usleep(800);//加延时

					if((lwip_send(sock,0x3000000,imagewidth*imagehigh*2,0))<0){
						xil_printf("%s : Send Error! \r\n",sock);
						break;

						usleep(10);//把pl端算法处理完之后的图像重新传递给上位机
					}
				}

				u8 readReq[8] = {0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb};
				lwip_send(sock,readReq,sizeof(readReq),0);//请求新的一帧图像数据
			}

这里加延时才把pl端算法模块处理完的图像发送给上位机,是因为算法处理模块处理需要时间,这里直接估算为(一帧图像的大小1280x1024x16除以axi的突发带宽)乘以2,因为读出来,又写进去

3.4 跨时钟域的处理

3.4.1 fifo多bit处理

1.axi_rd_HP0多bit处理:
深度的考量
自定义算法模块是虚拟的(没有完成,只是有个框架)暂时使用的是hdmi的clk 108.5MHz,axi rd的时钟是200MHz,需要设置fifo两端的读写条件来使得fifo两端的读写带宽度对等,按照公式
d e p t h max ⁡ v w r − min ⁡ v r d > max ⁡ ( l e n g t h b a c k    t o    b a c k ) max ⁡ v w r \frac{depth}{\max v_{wr}-\min v_{rd}}>\frac{\max \left( length_{back\,\,to\,\,back} \right)}{\max v_{wr}} maxvwrminvrddepth>maxvwrmax(lengthbacktoback)
设读速率为0,这里的深度为1次64位宽的256突发,我设置的是2次64位宽的256突发即512*64,这样的话,读条件设置为一旦>=一次突发长度的数据就开始读,在hdmi clk下连续读出的数据长度为一次突发长度的数据,写条件设置为一旦<一次突发长度,就触发一次突发写。(这里从ddr中读出一帧数据的时候就不读了,等待ps接收到下一帧给出的gpio信号再继续读一帧)
2.axi_wr_ACP多bit处理:
深度的考量
自定义算法模块是虚拟的(没有完成,只是有个框架)暂时使用的是hdmi的clk 108.5MHz,axi rd的时钟是200MHz,需要设置fifo两端的读写条件来使得fifo两端的读写带宽度对等.
这个模块fifo的深度也设置为2次突发长度的操作,fifo的写入为axi_rd_HP0中fifo的读出,写条件为当fifo里面的数据>=1次突发长度数据时,即启动一次突发写操作

3.axi_rd_HP2多bit处理:
深度的考量
显示模块使用的是hdmi的clk 108.5MHz,axi rd的时钟是200MHz,需要设置fifo两端的读写条件来使得fifo两端的读写带宽度对等,
这个模块的fifo深度想要有缓存一行图像的能力,考虑hdmi模块读fifo的back to back最大数据长度是一行1280 x 16bit,fifo里面存储到一行数据的时候,才启动hdmi模块读,显示更能流畅.

那fifo的写条件为当fifo里面的数据 < 一行图像数据,就启动一次突发读操作,由于突发一次数据写入到FIFO的带宽大于hdmi读数据的带宽,(写入的back to back的长度为256x64bit,假设读的速率为0)那么fifo深度设置为((1280 + 256 * 4)* 16bit)————4096 x 16bit

3.4.2 mux同步器的多bit处理

这里可以参考我的文章
https://blog.csdn.net/weixin_45284871/article/details/141234262?spm=1001.2014.3001.5501

由于hdmi_bit_sel、img_width、img_high是在时钟S_AXI_ACLK产生的,会在hdmi的clk下使用,S_AXI_ACLK的频率大于hdmi的clk,这是一个快到慢的多bit跨时钟问题,所以采用mux同步器的多bit跨时钟设计来处理这个问题,所以在S_AXI_ACLK产生一个和hdmi_bit_sel、img_width、img_high的同步信号sync,这个同步信号一直拉高就行,不需要做展宽,把sync在 hdmi的clk 打拍,检测到sync的上升沿时,再在S_AXI_ACLK时钟域下锁存hdmi_bit_sel、img_width、img_high完成跨时钟的操作。


always	@(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)begin
		r_img_width   <= 'd0;
		r_img_high    <= 'd0;
		r_sel_rgb_bit <= 'd0;
	end
	else if (!r_i_sync_3d && r_i_sync_2d) begin
		r_img_width   <= img_width;
		r_img_high    <= img_high;
		r_sel_rgb_bit <= sel_rgb_bit;
	end
    else begin
		r_img_width   <= r_img_width;
		r_img_high    <= r_img_high;
		r_sel_rgb_bit <= r_sel_rgb_bit;		
	end

3.5 数据流处理过程中的bit反转

首先我们要清楚网络字节序(大端字节序(Big Endian)\小端字节序(Little Endian))
https://blog.csdn.net/JMW1407/article/details/108637540

“大端”和”小端”表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序;具体的说:

①大端字节序(Big Endian):最高有效位存于最低内存地址处,最低有效位存于最高内存处;
②小端字节序(Little Endian):最高有效位存于最高内存地址,最低有效位存于最低内存处。

DP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节

所以:网络字节序就是大端字节序, 有些系统的本机字节序是小端字节序, 有些则是大端字节序, 为了保证传送顺序的一致性, 所以网际协议使用大端字节序来传送数据。

其次我们要知道读写位宽不同的FIFO,数据输入输出顺序是怎么样的
来自 https://blog.csdn.net/weixin_39455125/article/details/107688080

上位机传过来的图像数据形式
上位机是一个像素一个像素的发,每个像素是2 bytes,接收函数

lwip_recvfrom(sock, recv_buf, RECV_BUF_SIZE,0, NULL, NULL))

recv_buf的地址首先是0x2000000,假如发了4个像素,那么ddr里面的数据分布如下:
在这里插入图片描述

通过hp口从ddr里面axi突发读1次64位的数据
我们从0x2000000的地址里面突发读64位数据到pl端,那么这64位数据M_AXI_RDATA为[像素4低8位,像素4高8位,像素3低8位,像素3高8位,像素2低8位,像素2高8位,像素1低8位,像素1高8位],
因为 pl数据低字节对应ddr低地址

read_dma模块第一次从ddr读出数据的数据反转
在这里插入图片描述

因为我们这个模块下一级模块是留给isp算法模块的,因此在写入fifo时将其反向

write_fifo_data = {M_AXI_RDATA[15:0],M_AXI_RDATA[31:16],M_AXI_RDATA[47:32],M_AXI_RDATA[63:48]};

write_fifo_data = [像素1低8位,像素1高8位,像素2低8位,像素2高8位,像素3低8位,像素3高8位,像素4低8位,像素4高8位]

fifo的读出pixel第1次为[像素1低8位,像素1高8位],第2次为[像素2低8位,像素2高8位],第3次为[像素3低8位,像素3高8位],第4次为[像素4低8位,像素4高8位],

write_dma将read_dma模块读出的数据写入ddr
在这里插入图片描述

对于write_dma的fifo来说写入write_data第1次为[像素1低8位,像素1高8位],第2次为[像素2低8位,像素2高8位],第1次为[像素3低8位,像素3高8位],第2次为[像素4低8位,像素4高8位],

读出的read_fifo_data为[像素1低8位,像素1高8位,像素2低8位,像素2高8位,像素3低8位,像素3高8位,像素4低8位,像素4高8位],为了像素数据在ddr里面满足低像素在低地址,我们进行翻转

axi_wdata <= {read_fifo_data[15:0],read_fifo_data[31:16],read_fifo_data[47:32],read_fifo_data[63:48]};

axi_wdata = [像素4低8位,像素4高8位,像素3低8位,像素3高8位,像素2低8位,像素2高8位,像素1低8位,像素1高8位]

pl数据低字节对应ddr低地址

那么写入ddr的地址为0x3000000,那么ddr中的数据分布为:
在这里插入图片描述

再一次的read_dma将ddr的数据读出来
在这里插入图片描述

M_AXI_RDATA = [像素4低8位,像素4高8位,像素3低8位,像素3高8位,像素2低8位,像素2高8位,像素1低8位,像素1高8位],

wr_fifo_data = {M_AXI_RDATA[15:0],M_AXI_RDATA[31:16],M_AXI_RDATA[47:32],M_AXI_RDATA[63:48]};

反转之后wr_fifo_data = [像素1低8位,像素1高8位,像素2低8位,像素2高8位,像素3低8位,像素3高8位,像素4低8位,像素4高8位]

fifo的读出rgb_pixel第1次为[像素1低8位,像素1高8位],第2次为[像素2低8位,像素2高8位],第3次为[像素3低8位,像素3高8位],第4次为[像素4低8位,像素4高8位],

vga timing里面的翻转
在这里插入图片描述

因为读出来的rgb_pixel第1次为[像素1低8位,像素1高8位],第2次为[像素2低8位,像素2高8位],第3次为[像素3低8位,像素3高8位],第4次为[像素4低8位,像素4高8位],以此类推

所以要将rgb_pixel的高低位反转,才是像素正确的值

tmp_pixel = {rgb_pixel[7:0],rgb_pixel[15:8]};

3.6 视频输出的处理

3.6.1 异构分辨率模式的兼容

在这里插入图片描述

这里只支持1280x1024和 640x512两种分辨率
最大显示分辨率是 1280x1024, 那么向下兼容 640x512 分辨率,当 640x512 分
辨率下, 我把640x512区域放到1280x1024居中, 这样有效显示区是640x512
其他区域为 0 黑色。由于 HDMI 的驱动还是 1280x1024, 那么我就在
640x512 有效区去输出像素。 就要在标准的 HDMI 驱动中修改;
Vs_add_pixel 是等于(1280-640)/2
Hs_add_pixel 是等于(1024-512)/2
显示有效像素水平方向起始位置就等于 1280x1024 的标准起始位置再偏移
hs_add_pixel 这么多像素。
显示有效像素的水平方向结束位置就等于 1280x1024 的标准起始位置
+hs_add_pixel + 有效区域宽度。
显示有效像素垂直方向起始位置就等于 1280x1024 的标准起始位置再偏移
vs_add_pixel 这么多像素。
显示有效像素的垂直方向结束位置就等于 1280x1024 的标准起始位置
+vs_add_pixel + 有效区域高度。

3.6.2 多元化比特位的选择

这里的bit选择主要是对16bit的红外图像选择哪几个连续的8bit数据作为显示目标

always @ (posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)begin
		po_vga_r <= 'd0;
	end
	else if(h_act_flag_for_valid == 1'b1 && v_act_flag_for_valid == 1'b1) begin
		case (r_sel_rgb_bit)
			0:po_vga_r <=tmp_rgb_pixel[7: 0];
			1:po_vga_r <=tmp_rgb_pixel[8: 1];
			2:po_vga_r <=tmp_rgb_pixel[9: 2];
			3:po_vga_r <=tmp_rgb_pixel[10:3];
			4:po_vga_r <=tmp_rgb_pixel[11:4];
			5:po_vga_r <=tmp_rgb_pixel[12:5];
			6:po_vga_r <=tmp_rgb_pixel[13:6];
			7:po_vga_r <=tmp_rgb_pixel[14:7];
			8:po_vga_r <=tmp_rgb_pixel[15:8];
			default: po_vga_r <=tmp_rgb_pixel[7:0];
		endcase
	end
	else begin
		po_vga_r <= 'd0;
	end

4.模块的验证

4.1 验证板卡是否通

在这里插入图片描述

打印信息正确
能够ping通
在这里插入图片描述

iperf测试一下带宽

iperf – c 192.168.1.10 – p 5001 – i 5 – t 30 – w 2M

-c 指的的是客户端连接服务端的 IP
-p 指的是服务端的端口
-i 打印带宽信息间隔 s 为单位
-t 测试带宽时间 30s
-w 使用的包窗口大小 2M
当 LWIP TCP 服务端启动完毕后, 执行此命令获得如下带宽测试信息。
在这里插入图片描述

这里的带宽是cpu没有跑别的负载,因此实际的带宽会小一些

4.2 利用matlab来测试

用matlab的tcp编程,来发送配置包,以及包头,灰度条图像数据

 clc;
clear all;
close all;
warning off;
%tcp object create
tcp = tcpip('192.168.1.10',10000);
%set rx tx buffer
set(tcp,'InputBufferSize',256*64);
set(tcp,'OutputBufferSize',256*64);
fopen(tcp);
width=1280;
high=1024;
psplconfig=[0,width,high,0];%hdmi sel  width high readback
framehead=[170,170,170,170,170,170,170,170];
conf_list=zeros(1,14,'uint8');
linebuf=zeros(1,width*2,'uint8');
recv_data=zeros(length(psplconfig),14,'uint8');
for p=1:length(psplconfig)
    for i=1:14
        if i<=8 
            conf_list(i) = 85;
        elseif i == 9 
            conf_list(i) = p-1;%reg addr;
        elseif i == 10
            conf_list(i) = 0;
        elseif i == 11
            conf_list(i) = uint8(bitand(bitshift( psplconfig(p),-24),255));
        elseif i == 12
            conf_list(i) = uint8(bitand(bitshift( psplconfig(p),-16),255));
        elseif i == 13
            conf_list(i) = uint8(bitand(bitshift( psplconfig(p),-8),255));
        elseif i == 14
            conf_list(i) = uint8(bitand(bitshift( psplconfig(p),0),255));
        end
    end
    fwrite(tcp,conf_list);
    pause(0.0001);
    recv_data(p,1:end)= fread(tcp,14,'uint8')';%转置
end
%send frame head
    fwrite(tcp,framehead);
    pause(0.0001);
    for r=1:high
        for c=1:width
            linebuf(2*c)=0;
            if r<high/4
                linebuf(2*c-1)=64;
            elseif r<(high*2)/4
                linebuf(2*c-1)=128;
            elseif r<(high*3)/4
                linebuf(2*c-1)=192;
            else
                linebuf(2*c-1)=255;
            end
        end
        fwrite(tcp,linebuf);
        pause(0.0001);
    end
    readreq = fread(tcp,8,'uint8');


1.matlab来发+pl端ila来抓检验,配置pl端的寄存器是否成功
2.检验axi_rd_HP2的是否成功,可以先用灰度条的图像来测试
3.ps端接收以太网数据的测试验证,用matlab来发包头看是否能命中vitis中的断点

5.遇到的问题

5.1 cache的一致性问题

arm接收到数据需要把cache里面的数据刷到ddr中,fpga来读ddr里面的数据的话要用Xil_DCacheFlushRange,将cache的数即使刷到ddr里面,不然读出来不一致

如果是fpga往ddr里面写,那么arm读ddr里面的数的时候要用Xil_DCacheInvalidateRange,使得cache重新加载ddr里面的数,从而解决一致性问题

5.2 bit反转的问题

这里要注意图像中像素的bit反转,不然显示出的图像是雪花

6.时序分析

因为都做了跨时钟域的处理,写代码的逻辑级数也不大,主要是axi的时钟有点大,在s和m之间加上slice切片就好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

帅癌晚期的彦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值