23. 基于OV7725的以太网视频传输

在前面的章节,我们已经实现了以太网数据回环,以及基于VGA、HDMI、TFT_LCD的以太网传输图片显示。在本章节,我们将以太网、OV7725摄像头与野火上位机相结合,通过OV7725摄像头采集图像信息,使用以太网传输给PC机,通过PC机上的上位机实时显示。

23.1. 理论学习

本章节涉及到的以太网、SDRAM以及OV7725摄像头的相关知识在前面章节均有详细讲解,此处不再赘述。读者若有遗忘,请回顾相关章节进行学习巩固。

23.2. 实战演练

23.2.1. 实验目标

通过实验设计并实现基于OV7725的以太网视频传输。使用OV7725摄像头采集图像信息,将图像信息暂存到SDRAM中,读取暂存的图像信息,通过以太网将读取的图片信息发送给PC机,使用上位机读取图像信息并实时显示,图像分辨率位640*480。

23.2.2. 硬件资源

硬件资源部分,读者可参阅“SDRAM读写控制器的设计与验证”“OV7725摄像头VGA图像显示”和“以太网数据环回实验”章节的硬件资源小节。

23.3. 程序设计

23.3.1. 整体说明

23.4. 上板调试

23.4.1. 引脚约束

实验目标已经明确,硬件资源也已经介绍完毕,我们开始程序设计部分的讲解。我们先来对实验工程进行一个整体说明,让读者了解整个实验工程的框架结构。实验工程整体框图,具体见图 73‑1;子功能模块简介,具体见表格 73‑1。

OV7725002

图 73‑1 实验工程整体框图

表格 73‑1 子功能模块功能描述

模块名称

功能描述

clk_gen

时钟信号生成模块(IP核)

ov7725_top

OV7725摄像头驱动模块

image_format

图像格式配置模块

image_data

图像数据打包模块

eth_udp_rmii

以太网数据收发器(RMII)

sdram_top

SDRAM数据读写控制器

eth_ov7725_rgb

顶层模块

由上述图表可知,实验工程包含6个子功能模块:时钟信号生成模块(clk_gen(IP核))、OV7725摄像头驱动模块(ov7725_top)、图像格式配置模块(image_format)、图像数据打包模块(image_data)、以太网数据收发器 (eth_udp_rmii)、SDRAM数据读写控制器(sdram_top)和顶层模块(eth_ov7725_rgb)。

时钟信号生成模块(clk_gen),为实验工程各模块提供工作时钟,调用IP核生成;OV7725摄像头驱动模块(ov7725_top),实现OV7725摄像头的寄存器配置、初始化,经OV7725摄像头传输的图像数据进行拼接,并暂存到SDRAM中;图像格式配置模块(image_format),生成图像格 式配置包,将图像格式按照野火上位机数据包格式进行打包,通过以太网发送给上位机,配置上位机的显示格式;图像数据打包模块(image_data),读取暂存在SDRAM中的图像数据,按照野火上位机图像格式进行打包,将图像数据通过以太网发送给上位机,实现图像显示;以太网数据收发器 (eth_udp_rmii),实现以太网数据收发操作;SDRAM数据读写控制器(sdram_top),暂存OV7725摄像头采集的图像数据;顶层模块(eth_ov7725_rgb),例化子功能模块,连接各自对应信号。

这一部分我们对实验工程整体做了简要介绍,工程涉及的部分子功能模块在前面章节已有详细介绍,下面我们将会对实验工程涉及的未讲解的子功能模块进行解释和说明。

23.4.1.1. 图像格式配置模块

模块框图

图像格式配置模块image_format,功能是将待传输图片的图片格式按照野火上位机图像格式指令包进行打包,打包完成后,将格式指令包通过以太网传输给PC段的上位机。上位机接收到格式指令包后,提取图片格式信息,进行显示格式配置。模块框图,具体见图 73‑2;输入输出端口简介,具体见表格 69‑3。

OV7725003

图 73‑2 图像格式配置模块框图

表格 73‑2 输入输出端口简介

信号

位宽

类型

功能描述

sys_clk

1bit

Input

模块工作时钟

sys_rst_n

1bit

Input

复位信号,低有效

eth_tx_req

1bit

Input

以太网数据请求信号

eth_tx_done

1bit

Input

单包以太网数据发送完成信号

eth_tx_start

1bit

Output

单包以太网发送数据开始信号

eth_tx_data

32bit

Output

以太网发送数据

eth_tx_data_num

16bit

Output

以太网单包数据有效字节数

i_config_end

1bit

Output

图像格式指令包发送完成

由上述图表可知,本模块输入输出信号共8路,输入输出各4路。

输入信号中,时钟信号sys_clk,由外部PHY芯片传入的时钟信号(eth _clk)二分频得到,频率25MHz,作为模块工作时钟;复位信号sys_rst_n,低电平有效;以太网数据请求信号eth_tx_req、单包以太网数据发送完成信号eth_tx_done,由以太网数据收发器 (eth_udp_rmii)传入。

输出信号中,单包以太网发送数据开始信号eth_tx_start,传输到以太网数据收发器 (eth_udp_rmii),标志着单包以太网数据开始发送;以太网数据请求信号eth_tx_req有效时,输出有效以太网发送数据eth_tx_data,位宽32bit;为保证数据接收的准确性,同时向以太网数据收发器 (eth_udp_rmii)发送以太网单包数据有效字节数eth_tx_data_num,位宽16bit;待模块内所有数据包发送完毕后,图像格式指令包发送完成信号i_config_end拉高,表示图像格式指令包发送完成。

波形图绘制

在模块框图小节,我们已经对以太网数据接收模块的模块功能和输入输出端口做了详细介绍。接下来我们通过波形图的绘制,为读者说明各信号的设计与实现方法,以及模块的功能实现。图像格式配置模块整体波形图,具体见图 73‑3、图 73‑4。

OV7725004

图 73‑3 图像格式配置模块整体波形图(一)

OV7725005

图 73‑4 图像格式配置模块整体波形图(二)

模块整体波形图已经列出来了,波形图中的各信号波形是如何设计与实现的呢,下面我们会对各波形的设计与实现做详细说明。

第一部分:输入信号

输入信号在模块框图部分我们已经提到,输入信号共4路,时钟信号sys_clk,频率25MHz,由PHY芯片传入的时钟信号(eth _clk)二分频得到,作为模块工作时钟;复位信号sys_rst_n,低电平有效;以太网数据请求信号eth_tx_req、单包以太网数据发送完成信号eth_tx_done,由以太网数据收发器(eth_udp_rmii)传入,控制以太网数据的读取以及鸟事单包以太网数据发送完成。输入信号波形如下。

OV7725006

图 73‑5 输入各信号波形图

第二部分:待发送指令数据

为保证以太网传输的指令、图像数据的准确性,野火上位机定义了自己的通讯协议,格式指令协议入下图 73‑6所示。

OV7725007

图 73‑6 野火摄像头调试助手通讯协议(指令)

指令包的通讯协议由包头、设备地址、包长度、指令、图像格式、宽、高以及校验位构成。

包头为固定的4字节数据32’h59_48_5A_53;设备地址宽度1字节,可选8’h00、8’h01,我们使用默认值8’h00;包长度宽度4字节,表示从包头到校验的所有数据长度,单位为字节,格式指令包包长度17字节,表示为32’h00_00_00_11;指令字节表示数据包类型,8’h01表示指令包, 8’h02表示图像数据包;图像格式有多种选择,我们使用的是RGB565格式,对应指令为8’h04;宽、高表示图像横向和纵向像素点个数,就是分辨率,宽度为2字节,我们使用的是640*480,对应指令为16’h02_80、16’h01_E0;最后的两字节为校验字节,使用的是CRC-16校验,我们关闭了上 位机的校验部分,此处两字节可填写任意值。

根据上面的讲述,我们将格式指令包进行了参数化,如图 73‑7所示。

OV7725008

图 73‑7 参数化格式指令

为方便指令数据的传输,声明存储器data_mem对指令数据进行暂存,data_mem信号波形如图 73‑8所示。

OV7725009

图 73‑8 data_mem信号波形图

第三部分:状态机及相关信号波形的设计与实现

由整体波形图,我们可以看出,在模块功能实现中,我们使用了状态机,一共定义了状态机的4个状态:初始状态(IDLE)、指令发送状态(CMD_SEND)、循环状态(CYCLE)以及结束状态(END)。

系统上电后,本模块进入初始状态(IDLE),在初始状态会做一段时间的等待,等待的目是确保PHY启动成功。为确定等待时间,声明计数器cnt_start,初值为0,状态机位于初始状态时每个时钟周期自加1,其他时刻复位初值。当计数器cnt_start计数到最大值,状态机跳转到下一状态指令发送状态(CMD_ SEND),同时将单包以太网发送数据开始信号eth_tx_start拉高一个时钟周期,告知以太网数据收发器(eth_udp_rmii)可以开始单包数据的发送。相关信号波形图,如图 73‑9所示。

OV7725010

图 73‑9 cnt_start、eth_tx_start信号波形图

状态机跳转到指令发送状态(CMD_SEND)后,以太网数据收发器(eth_udp_rmii)会传入以太网数据请求信号,请求读取待传输数据,请求信号高电平有效,每次读取4字节数据,指令包共17字节,需要读取5次。

为保证数据的正确读取,以及实现状态机的正常跳转,声明读取数据计数器cnt_data对读取的指令数据进行计数,初值为0,请求信号每拉高一个时钟周期,输出对应的格式数据eth_tx_data,同时计数器加1,状态机回到初始状态时,计数器归0;同时以计数器为约束条件实现状态机的跳转,当计数器计数到最大值5 时,指令数据全部读取完成,以太网数据收发器(eth_udp_rmii)会回传eth_tx_done信号,表明单包数据发送完成,状态机跳转到下一状态循环状态(CYCLE)。各信号波形,如图 73‑10所示。

OV7725011

图 73‑10 数据读取信号波形图

状态机计入循环状态后,会进行循环次数判断,当循环次数小于设定值,跳回初始状态,将图像格式指令包重新发送一遍;当循环次数达到设定值,跳转到结束状态,完成图像格式数据包的发送。

这里为什么要将图像格式数据包循环发送呢?目的是保证上位机能够接收到数据包。

数据包发送的次数是由循环计数器cnt_cycle决定,循环计数器cnt_cycle初值为0,eth_tx_done信号每拉高一个时钟周期,计数器加1,当状态机跳转到结束状态,循环计数器归0。循环计数器波形图,如图 73‑11所示。

OV7725012

图 73‑11 循环计数器波形图

状态机跳转到结束状态(END)后,不再进行跳转,将格式配置完成信号i_config_end拉高并输出,表示图像格式包发送完成。i_config_end信号波形,如图 73‑12所示。

OV7725013

图 73‑12 i_config_end信号波形图

整合各部分信号波形,就能得到模块整体波形图,设计思路仅供参考,读者可按照自己理解进行波形图的设计。

代码编写

以上述绘制波形图为参照,编写模块参考代码。模块参考代码具体见代码清单 69‑1。

代码清单 73‑1 图像格式配置模块参考代码(image_format.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
module image_format
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //系统复位,低电平有效
input wire eth_tx_req , //以太网数据请求信号
input wire eth_tx_done , //单包以太网数据发送完成信号

output reg eth_tx_start, //单包以太网发送数据开始信号
output reg [31:0] eth_tx_data , //以太网发送数据
output reg i_config_end, //图像格式指令包发送完成
output reg [15:0] eth_tx_data_num //以太网单包数据有效字节数

);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter HEAD = 32'h59_48_5a_53 , //包头
ADDR = 8'h00 , //设备地址
DATA_NUM = 32'h00_00_00_11 , //包长
CMD = 8'h01 , //指令
FORMAT = 8'h04 , //图像格式(RGB565)
H_PIXEL = 16'h02_80 , //行像素个数
V_PIXEL = 16'h01_E0 , //场像素个数
CRC = 16'h0B_7C ; //CRC-16校验

parameter IDLE = 4'b0001, //初始状态
CMD_SEND = 4'b0010, //发送格式配置
CYCLE = 4'b0100, //循环配置
END = 4'b1000; //结束

parameter CNT_START_MAX = 32'd125_000_00; //初始状态等待时钟周期数

//wire define
wire [31:0] data_mem [4:0] ; //待发送指令

//reg define
reg [3:0] state ; //状态机状态
reg [31:0] cnt_start ; //初始状态等待计数器
reg [3:0] cnt_data ; //发送数据个数计数
reg [15:0] cnt_cycle ; //循环配置次数计数

////
//\* Main Code \//
////
//data_mem:待发送指令
assign data_mem[0] = HEAD;
assign data_mem[1] = {ADDR,DATA_NUM[31:8]};
assign data_mem[2] = {DATA_NUM[7:0],CMD,FORMAT,H_PIXEL[15:8]};
assign data_mem[3] = {H_PIXEL[7:0],V_PIXEL,CRC[15:8]};
assign data_mem[4] = {CRC[7:0],24'b0};

//state:状态机状态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE:
if(cnt_start == CNT_START_MAX)
state <= CMD_SEND;
else
state <= IDLE;
CMD_SEND:
if((cnt_data == 4'd5) && (eth_tx_done == 1'b1))
state <= CYCLE;
else
state <= CMD_SEND;
CYCLE:
if(cnt_cycle == 16'd10)
state <= END;
else
state <= IDLE;
END: state <= END;
default:state <= IDLE;
endcase

//cnt_start:初始状态等待计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_start <= 16'd0;
else if(state == IDLE)
if(cnt_start < CNT_START_MAX)
cnt_start <= cnt_start + 1'b1;
else
cnt_start <= 16'd0;
else
cnt_start <= 16'd0;

//eth_tx_start:以太网发送数据开始信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
eth_tx_start <= 1'b0;
else if(cnt_start == CNT_START_MAX)
eth_tx_start <= 1'b1;
else
eth_tx_start <= 1'b0;

//cnt_data:发送数据个数计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_data <= 4'd0;
else if(state == IDLE)
cnt_data <= 4'd0;
else if(eth_tx_req == 1'b1)
cnt_data <= cnt_data + 1'b1;
else
cnt_data <= cnt_data;

//eth_tx_data:以太网发送数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
eth_tx_data <= 32'h0;
else if(state == IDLE)
eth_tx_data <= 32'h0;
else if(eth_tx_req == 1'b1)
eth_tx_data <= data_mem[cnt_data];
else
eth_tx_data <= eth_tx_data;

//cnt_cycle:循环配置次数计数
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_cycle <= 16'd0;
else if(state == END)
cnt_cycle <= 16'd0;
else if((eth_tx_done == 1'b1) && (cnt_cycle < 16'd10))
cnt_cycle <= cnt_cycle + 1'b1;
else
cnt_cycle <= cnt_cycle;

//i_config_end:图像格式配置完成
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i_config_end <= 1'b0;
else if(state == END)
i_config_end <= 1'b1;
else
i_config_end <= 1'b0;

//eth_tx_data_num:以太网单包数据有效字节数
always @(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
eth_tx_data_num <= 16'd0;
else
eth_tx_data_num <= 16'd17;

endmodule

参考代码是参照绘制波形图编写,在前文各信号已经做了详细介绍,此处不再赘述。关于模块的仿真验证,此处不在单独进行,等到图像数据打包模块讲解完毕,一同进行模块的仿真验证。

23.4.1.2. 图像数据打包模块

模块框图

图像数据打包模块image_data,功能是读取暂存在SDRAM的图像数据,按照野火上位机的图像数据包格式进行打包,打包完成后,将图像数据包通过以太网传输给PC段的上位机。上位机接收到数据包后,提取图像数据信息,进行图像显示。模块框图,具体见;输入输出端口简介,具体见图 73‑13。

OV7725014

图 73‑13 图像数据打包模块框图

表格 73‑3 输入输出端口简介

信号

位宽

类型

功能描述

sys_clk

1bit

Input

模块工作时钟

sys_rst_n

1bit

Input

复位信号,低有效

image_data

16bit

Input

自SDRAM中读取的图像数据

eth_tx_req

1bit

Input

以太网数据请求信号

eth_tx_done

1bit

Input

单包以太网数据发送完成信号

data_rd_req

1bit

Output

图像数据请求信号

eth_tx_start

1bit

Output

单包以太网发送数据开始信号

eth_tx_data

32bit

Output

以太网发送数据

eth_tx_data_num

16bit

Output

以太网单包数据有效字节数

由上述图表可知,本模块输入输出信号共9路,5路输入、4路输出。

输入信号中,时钟信号sys_clk,由外部PHY芯片传入的时钟信号(eth _clk)二分频得到,频率25MHz,作为模块工作时钟;复位信号sys_rst_n,低电平有效;图像数据image_data,当输出的图像数据请求信号data_rd_req有效时,自SDRAM中读取的图像数据;以太网数据请 求信号eth_tx_req、单包以太网数据发送完成信号eth_tx_done,由以太网数据收发器 (eth_udp_rmii)传入。

输出信号中,图像数据请求信号data_rd_req作为读使能信号传入到SDRAM数据读写控制器,读取暂存在SDRAM的图像数据;单包以太网发送数据开始信号eth_tx_start,传输到以太网数据收发器 (eth_udp_rmii),标志着单包以太网数据开始发送;以太网数据请求信号eth_tx_req有效时,输出有效以太网发送数据eth_tx_data,位宽32bit;为保证数据接收的准确性,同时向以太网数据收发器 (eth_udp_rmii)发送以太网单包数据有效字节数eth_tx_data_num,位宽16bit。

波形图绘制

在模块框图小节,我们已经对以太网数据接收模块的模块功能和输入输出端口做了详细介绍。接下来我们通过波形图的绘制,为读者说明各信号的设计与实现方法,以及模块的功能实现。图像格式配置模块整体波形图,具体见图 73‑14、图 73‑15、图 73‑16。

OV7725015

图 73‑14 图像数据打包模块整体波形图(一)

OV7725016

图 73‑15 图像数据打包模块整体波形图(二)

OV7725017

图 73‑16 图像数据打包模块整体波形图(三)

模块整体波形图已经列出来了,波形图中的各信号波形是如何设计与实现的呢,下面我们会对各波形的设计与实现做详细说明。

输入信号在模块框图部分我们已经提到,输入信号共5路,时钟信号sys_clk,由外部PHY芯片传入的时钟信号(eth _clk)二分频得到,频率25MHz,作为模块工作时钟;复位信号sys_rst_n,低电平有效;图像数据image_data,当输出的图像数据请求信号data_rd_req有效时,自 SDRAM中读取的图像数据;以太网数据请求信号eth_tx_req、单包以太网数据发送完成信号eth_tx_done,由以太网数据收发器 (eth_udp_rmii)传入。输入信号波形如下。

OV7725018

图 73‑17 输入各信号波形图

为保证以太网传输图像数据的准确性,野火上位机定义了自己的通讯协议,图像数据包协议入下图 73‑18所示。

OV7725019

图 73‑18 野火摄像头调试助手通讯协议(图像数据)

图像数据包的通讯协议由包头、设备地址、包长度、指令、图像数据以及校验位构成。

包头为固定的4字节数据32’h59_48_5A_53;设备地址宽度1字节,可选8’h00、8’h01,我们使用默认值8’h00;包长度宽度4字节,表示从包头到校验的所有数据长度,单位为字节;指令字节表示数据包类型,8’h01表示指令包,8’h02表示图像数据包;图像数据表示一帧完整图像的字节数;最后 的两字节为校验字节,使用的是CRC-16校验,我们关闭了上位机的校验部分,此处两字节可填写任意值。

图像数据包协议是针对的完整的一帧图像,换句话说就是在完整一帧图像之前加上包头、设备地址、包长度、指令,在最后补上2字节校验位。以太网数据包最大为1500字节左右,所以,一个完整的图像数据包要使用多个以太网数据包进行发送。

在本实验工程中,我们发送的图像分辨率为640*480,图像格式为RGB565(2字节),每行图像数据为640*2 = 1280字节,共480行。我们可以将一帧完整的图像数据包分为480个以太网数据包进行发送,每次发送一行图像数据。在每帧图像的第一个以太网数据包中,图像数据之前添加包头、设备地址、包长 度、指令;在每帧图像的最后一个以太网数据包中,图像数据之后补充2字节校验位,实验中未打开上位机校验位,校验位字节可填任意值。

那么如何实现上述这一系列操作呢?我们选择使用状态机。

在状态机中,我们声明6个状态,分别为初始状态(IDLE)、首包数据发送状态(FIRST_BAG)、普通包数据发送状态(COM_BAG)、末包数据发送状态(LAST_BAG)、单包数据发送完成等待状态(BAG_WAIT)和帧图像发送完成等待状态(FRAME_END)。

系统上电后,状态机处于初始状态(IDLE),在初始状态等待一段时间后体跳转到下一状态,等待时间由计数器cnt_idle_wait控制,计数器初值为0,每个时钟周期自加1,计数到最大值后归0,同时状态机跳转到首包数据发送状态(FIRST_BAG)。计数器cnt_idle_wait信号波形如下图 73‑19所示。

OV7725020

图 73‑19 cnt_idle_wait信号波形图

在首包数据发送状态(FIRST_BAG)下,我们需要传输帧图像数据的第一行图像数据,同时要在最前端添加包头、设备地址、包长度、指令。

包头为固定的4字节数据32’h59_48_5A_53;设备地址宽度1字节,使用默认值8’h00;包长度宽度4字节,表示从包头到校验的所有数据长度,单位为字节,具体数值为(640 * 480 *2)(图像数据) + 4(包头) + 1(设备地址) + 4(包长度) + 1(指令字节);指令字节使用8’h02表示图像数据包。

在首包数据发送状态,我们首先要做的就是首行图像数据的读取,摄像头将采集到的图像数据暂存在SDRAM中,要实现数据的读取,使能信号必不可少,所以首先声明数据读使能信号data_rd_req,高电平有效,为了实现一次只进行单行图像数据的读取,声明计数器cnt_h对数据读使能信号data_rd_req的拉 高时钟周期进行控制。

计数器cnt_h初值为0,当计数器cnt_idle_wait计数到最大值时,将单行图像像素点个数幅值给cnt_h,计数器cnt_h大于0时,每个时钟周期自减1,直至为0;数据读使能信号data_rd_req只在计数器cnt_h不为0时保持高电平,其他时刻均为低电平,这样能够保证,使能信号每次只在SD RAM中读取一行图像数据。两信号波形如下图 73‑20所示。

OV7725021

图 73‑20 data_rd_req、cnt_h信号波形图

首行图像数据读取完成后,接下来就是包头、设备地址、包长度、指令的添加。

首先对数据读使能信号data_rd_req和读取的图像数据image_data进行多次打拍,目的时方便包头、设备地址、包长度、指令的添加。同时要声明数据有效信号data_valid,方便16bit的图像数据拼接为32bit的以太网数据。

包头、设备地址、包长度、指令的字节总长度为10字节,每个时钟周期读取的图形数据image_data为2字节,数据有效信号data_valid要在前面为包头、设备地址、包长度、指令预留5个时钟周期,然后继续保持一行图像数据的有效时间。上述各信号波形如下图 73‑21所示。

OV7725022

图 73‑21 data_valid等信号波形图

接下来进行数据的拼接,声明拼接使能信号wr_fifo_en对首包数据进行拼接,拼接后的数据为wr_fifo_data,为了保证包头、设备地址、包长度、指令的正确拼接,同时声明计数器cnt_wr_data。

拼接使能信号wr_fifo_en初为低电平,当数据有效信号data_valid有效时,拼接使能信号每个时钟周期自身取反一次,否则,保持低电平;当数据有效信号data_valid有效,且拼接使能信号wr_fifo_en为高电平时,计数器cnt_wr_data自加1,data_valid无效时计数器归0 ;当数据有效信号data_valid有效,且拼接使能信号wr_fifo_en为低电平时,对数据进行拼接。

拼接具体方法如下,当数据有效信号data_valid有效,拼接使能信号wr_fifo_en为低电平时开始数据拼接,计数器cnt_wr_data为0时,将4字节包头赋值给wr_fifo_data;计数器为1时,将设备地址和包长度的前3个字节赋值给wr_fifo_data;计数器为2时,将包长度的末字节 、指令字节以及首行第一个像素数据image_data5赋值给wr_fifo_data;接下来直接将后续的图像数据进行拼接赋值给wr_fifo_data = {image_data6, image_data5}。各信号波形如下所示。

OV7725023

图 73‑22 数据拼接相关信号波形图

拼接后的数据与拼接使能信号同步,以拼接后的使能信号wr_fifo_en为写使能,将拼接后的首包数据暂存在FIFO中,等待传输。

数据写入FIFO后,FIFO传出的空信号fifo_empty由高变低,以此下降沿为触发信号,拉高输出信号eth_tx_start,告知以太网模块可以开始数据的读取。eth_tx_start信号波形如下图 73‑23所示。

OV7725024

图 73‑23 eth_tx_start信号波形图

eth_tx_start信号传入以太网模块,首包数据开始打包传输,随后以太网模块传入数据请求信号作为读使能信号读取FIFO中暂存的首包数据,同时传出的还有数据字节数信号eth_tx_data_num。数据读取完成后,输入的单包数据发送完成信号eth_tx_done拉高一个时钟周期,表示首包数据发送完 成。各信号波形如下所示。

OV7725025

图 73‑24 FIFO数据读取相关信号波形图

首包数据发送完成后,状态机跳转到下一状态,同时数据包个数计数器cnt_v加1。各信号波形图如下所示。

OV7725026

图 73‑25 数据包个数计数器cnt_v波形图

状态机跳转到单包数据发送完成等待状态(BAG_WAIT),在这一状态会进行一段时间的等待,原因是以太网数据包传输过程中,包与包之间需要时间等待,百兆以太网的等待时间必须大于960ns。我们同样使用初始状态使用过的计数器cnt_idle_wait对等待时间进行计数。

计数完成后,对数据包个数计数器cnt_v进行判断,当计数器计数值表示倒数第二包数据时,状态机跳转到末包数据发送状态(LAST_BAG);否则状态机跳转到普通包数据发送状态(COM_BAG)。

若状态机跳转到普通包数据发送状态(COM_BAG),表示将要发送纯图像数据包,数据包内只包含图像数据,不再需要添加包头或CRC等协议字节。各信号波形变化与首包数据发送状态(FIRST_BAG)类似。

OV7725027

图 73‑26 data_rd_req信号波形图

首先,生成图像数据请求信号data_rd_req,读取暂存在SDRAM的图像数据;随后对数据请求信号和读取的图像数据image_data进行打拍处理,生成图像数据有效信号data_valid以及数据拼接使能信号wr_fifo_en,将数据拼接为wr_fifo_data暂存到FIFO中。

OV7725028

图 73‑27 数据拼接相关信号波形图

接下来,使用FIFO空信号fifo_empty下降沿拉高开始信号eth_tx_start,当有效的以太网数据请求信号eth_tx_req传入,开始读取FIFO中的数据;数据读取完成后,传入读取完成信号eth_tx_done,状态机跳回到BAG_WAIT状态。

OV7725029

图 73‑28 FIFO数据读取相关信号波形图

若状态机跳转到末包数据发送状态(LAST_BAG),表示将要发送包含CRC校验位图像数据包,需要在图像数据末尾添加CRC校验字节,我们关闭了上位机的CRC校验,CRC校验位可使用任意值。各信号波形变化与首包数据发送状态(FIRST_BAG)类似。

OV7725030

图 73‑29 data_rd_req信号波形图

首先,生成图像数据请求信号data_rd_req,读取暂存在SDRAM的图像数据;随后对数据请求信号和读取的图像数据image_data进行打拍处理,生成图像数据有效信号data_valid以及数据拼接使能信号wr_fifo_en,将数据拼接为wr_fifo_data暂存到FIFO中。

OV7725031

图 73‑30 数据拼接相关信号波形图

接下来,使用FIFO空信号fifo_empty下降沿拉高开始信号eth_tx_start,当有效的以太网数据请求信号eth_tx_req传入,开始读取FIFO中的数据;数据读取完成后,传入读取完成信号eth_tx_done,状态机跳转到帧图像发送完成等待状态(FRAME_END)。

OV7725032

图 73‑31 FIFO数据读取相关信号波形图

状态机跳转到帧图像发送完成等待状态(FRAME_END),要在此状态等待一段时间,因为上位机对图像的提取与拼接需要时间。等待时间长短由计数器cnt_frame_wait,等待完毕后,状态机跳回初始状态,开始新一帧图像数据的打包与传输。

整合各部分信号波形,就能得到模块整体波形图,设计思路仅供参考,读者可按照自己理解进行波形图的设计。

代码编写

以上述绘制波形图为参照,编写模块参考代码。模块参考代码具体见代码清单 73‑2。

代码清单 73‑2 图像数据打包模块参考代码(image_data.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
module image_data
#(
parameter H_PIXEL = 11'd640 , //图像水平方向像素个数
parameter V_PIXEL = 11'd480 , //图像竖直方向像素个数
parameter CNT_FRAME_WAIT = 24'h0E_FF_FF , //单帧图像等待时间计数
parameter CNT_IDLE_WAIT = 24'h00_01_99 //单包数据等待时间计数
)
(
input wire sys_clk , //系统时钟,频率25MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [15:0] image_data , //自SDRAM中读取的16位图像数据
input wire eth_tx_req , //以太网发送数据请求信号
input wire eth_tx_done , //以太网发送数据完成信号

output reg data_rd_req , //图像数据请求信号
output reg eth_tx_start, //以太网发送数据开始信号
output wire [31:0] eth_tx_data , //以太网发送数据
output reg [15:0] eth_tx_data_num //以太网单包数据有效字节数
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter IDLE = 6'b0000_01, //初始状态
FIRST_BAG = 6'b0000_10, //发送第一包数据(包含包头)
COM_BAG = 6'b0001_00, //发送普通包数据
LAST_BAG = 6'b0010_00, //发送最后一包数据(包含CRC-16)
BAG_WAIT = 6'b0100_00, //单包数据发送完成等待
FRAME_END = 6'b1000_00; //一帧图像发送完成等待

//wire define
wire fifo_empty ; //FIFO读空信号
wire fifo_empty_fall ; //FIFO读空信号下降沿

//reg define
reg [5:0] state ; //状态机状态
reg [23:0] cnt_idle_wait ; //初始状态即单包间隔等待时间计数
reg [10:0] cnt_h ; //单包数据包含像素个数计数(一行图像)
reg data_rd_req1 ;
reg data_rd_req2 ;
reg data_rd_req3 ;
reg data_rd_req4 ;
reg data_rd_req5 ;
reg data_rd_req6 ; //图像数据请求信号打拍(插入包头和CRC)
reg [15:0] image_data1 ;
reg [15:0] image_data2 ;
reg [15:0] image_data3 ;
reg [15:0] image_data4 ;
reg [15:0] image_data5 ;
reg [15:0] image_data6 ; //图像数据打拍(目的是插入包头和CRC)
reg data_valid ; //图像数据有效信号
reg wr_fifo_en ; //FIFO写使能
reg [15:0] cnt_wr_data ; //写入FIFO数据个数(单位2字节)
reg [31:0] wr_fifo_data ; //写入FIFO数据
reg fifo_empty_reg ; //fifo读空信号打一拍
reg [10:0] cnt_v ; //一帧图像发送包个数(一帧图像行数)
reg [23:0] cnt_frame_wait ; //单帧图像等待时间计数

////
//\* Main Code \//
////
//state:状态机状态变量
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE:
if(cnt_idle_wait == CNT_IDLE_WAIT)
state <= FIRST_BAG;
else
state <= IDLE;
FIRST_BAG:
if(eth_tx_done == 1'b1)
state <= BAG_WAIT;
else
state <= FIRST_BAG;
BAG_WAIT:
if((cnt_v < V_PIXEL - 11'd1) &&
(cnt_idle_wait == CNT_IDLE_WAIT))
state <= COM_BAG;
else if((cnt_v == V_PIXEL - 11'd1) &&
(cnt_idle_wait == CNT_IDLE_WAIT))
state <= LAST_BAG;
else
state <= BAG_WAIT;
COM_BAG:
if(eth_tx_done == 1'b1)
state <= BAG_WAIT;
else
state <= COM_BAG;
LAST_BAG:
if(eth_tx_done == 1'b1)
state <= FRAME_END;
else
state <= LAST_BAG;
FRAME_END:
if(cnt_frame_wait == CNT_FRAME_WAIT)
state <= IDLE;
else
state <= FRAME_END;
default:state <= IDLE;
endcase

//cnt_idle_wait:初始状态即单包间隔等待时间计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_idle_wait <= 24'd0;
else if(((state == IDLE) \|\| (state == BAG_WAIT)) && (cnt_idle_wait < CNT_IDLE_WAIT))
cnt_idle_wait <= cnt_idle_wait + 1'b1;
else
cnt_idle_wait <= 24'd0;

//cnt_h:单包数据包含像素个数计数(一行图像)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 11'd0;
else if(cnt_h == 11'd0)
if(cnt_idle_wait == CNT_IDLE_WAIT)
cnt_h <= H_PIXEL;
else
cnt_h <= cnt_h;
else
cnt_h <= cnt_h - 1'b1;

//data_rd_req:图像数据请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_rd_req <= 1'b0;
else if(cnt_h != 11'd0)
data_rd_req <= 1'b1;
else
data_rd_req <= 1'b0;

//图像数据请求信号打拍,插入包头和CRC
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
begin
data_rd_req1 <= 1'b0;
data_rd_req2 <= 1'b0;
data_rd_req3 <= 1'b0;
data_rd_req4 <= 1'b0;
data_rd_req5 <= 1'b0;
data_rd_req6 <= 1'b0;
end
else
begin
data_rd_req1 <= data_rd_req;
data_rd_req2 <= data_rd_req1;
data_rd_req3 <= data_rd_req2;
data_rd_req4 <= data_rd_req3;
data_rd_req5 <= data_rd_req4;
data_rd_req6 <= data_rd_req5;
end

//图像数据打拍,方便插入包头和CRC
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
begin
image_data1 <= 16'b0;
image_data2 <= 16'b0;
image_data3 <= 16'b0;
image_data4 <= 16'b0;
image_data5 <= 16'b0;
image_data6 <= 16'b0;
end
else
begin
image_data1 <= image_data;
image_data2 <= image_data1;
image_data3 <= image_data2;
image_data4 <= image_data3;
image_data5 <= image_data4;
image_data6 <= image_data5;
end

//data_valid:图像数据有效信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_valid <= 1'b0;
else if(state == FIRST_BAG)
data_valid <= (data_rd_req1 \|\| data_rd_req6);
else if(state == LAST_BAG)
data_valid <= (data_rd_req4 \|\| data_rd_req5);
else
data_valid <= data_rd_req1;

//wr_fifo_en:FIFO写使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_fifo_en <= 1'b0;
else if(data_valid == 1'b1)
wr_fifo_en <= ~wr_fifo_en;
else
wr_fifo_en <= 1'b0;

//cnt_wr_data:写入FIFO数据个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wr_data <= 16'd0;
else if(data_valid == 1'b1)
if(wr_fifo_en == 1'b1)
cnt_wr_data <= cnt_wr_data + 1'b1;
else
cnt_wr_data <= cnt_wr_data;
else
cnt_wr_data <= 16'd0;

//wr_fifo_data:写入FIFO数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_fifo_data <= 32'h0;
else if(wr_fifo_en == 1'b0)
if(state == FIRST_BAG)
if(cnt_wr_data == 16'd0)
wr_fifo_data <= 32'h59_48_5a_53;
else if(cnt_wr_data == 16'd1)
wr_fifo_data <= 32'h00_00_09_60;
else if(cnt_wr_data == 16'd2)
wr_fifo_data <= {16'h0C_02,image_data5};
else
wr_fifo_data <= {image_data6,image_data5};
else if(state == COM_BAG)
wr_fifo_data <= {image_data2,image_data1};
else if(state == LAST_BAG)
if(cnt_wr_data == 16'd320)
wr_fifo_data <= {16'h5A_A5,16'h00_00};
else
wr_fifo_data <= {image_data4,image_data3};
else
wr_fifo_data <= wr_fifo_data;

//fifo_empty:FIFO读空信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
fifo_empty_reg <= 1'b1;
else
fifo_empty_reg <= fifo_empty;

//fifo_empty_fall:FIFO读空信号下降沿
assign fifo_empty_fall = ((fifo_empty_reg == 1'b1) && (fifo_empty == 1'b0));

//eth_tx_start:以太网发送数据开始信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
eth_tx_start <= 1'b0;
else if(fifo_empty_fall == 1'b1)
eth_tx_start <= 1'b1;
else
eth_tx_start <= 1'b0;

//eth_tx_data_num:以太网单包数据有效字节数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
eth_tx_data_num <= 16'd0;
else if(state == FIRST_BAG)
eth_tx_data_num <= {H_PIXEL,1'b0} + 16'd10;
else if(state == COM_BAG)
eth_tx_data_num <= {H_PIXEL,1'b0};
else if(state == LAST_BAG)
eth_tx_data_num <= {H_PIXEL,1'b0} + 16'd2;
else
eth_tx_data_num <= eth_tx_data_num;

//cnt_v:一帧图像发送包个数(一帧图像行数)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 11'd0;
else if(state == IDLE)
cnt_v <= 11'd0;
else if(eth_tx_done == 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= cnt_v;

//cnt_frame_wait:单帧图像等待时间计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_frame_wait <= 24'd0;
else if((state == FRAME_END) && (cnt_frame_wait < CNT_FRAME_WAIT))
cnt_frame_wait <= cnt_frame_wait + 1'b1;
else
cnt_frame_wait <= 24'd0;

////
//\* Instantiation \//
////
//------------- fifo_image_inst -------------
fifo_image fifo_image_inst
(
.aclr (~sys_rst_n ),
.clock (sys_clk ),
.data (wr_fifo_data),
.rdreq (eth_tx_req ),
.wrreq (wr_fifo_en ),

.empty (fifo_empty ),
.q (eth_tx_data )
);

endmodule

参考代码是参照绘制波形图编写,在前文各信号已经做了详细介绍,此处不再赘述。

仿真文件编写

模块参考代码编写完成后,为验证参考代码是否能够实现预期功能,编写仿真参考代码对模块参考代码进行仿真验证。仿真参考代码,具体见代码清单 69‑2。

代码清单 73‑3 模块仿真参考代码(tb_image.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
module tb_image();
////
//\* Parameter and Internal Signal \//
////
//parameter define
//板卡MAC地址
parameter BOARD_MAC = 48'h12_34_56_78_9A_BC;
//板卡IP地址
parameter BOARD_IP = {8'd169,8'd254,8'd1,8'd23};
//板卡端口号
parameter BOARD_PORT = 16'd1234;
//PC机MAC地址
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
//PC机IP地址
parameter DES_IP = {8'd169,8'd254,8'd191,8'd31};
//PC机端口号
parameter DES_PORT = 16'd1234;

//wire define
wire send_end ; //单包数据发送完成标志信号
wire read_data_req ; //读FIFO使能信号
wire eth_tx_en ; //输出数据有效信号
wire [3:0] eth_tx_data ; //输出数据
wire crc_en ; //CRC开始校验使能
wire crc_clr ; //CRC复位信号
wire [31:0] crc_data ; //CRC校验数据
wire [31:0] crc_next ; //CRC下次校验完成数据
wire send_en ; //数据输入开始标志信号
wire [31:0] send_data ; //待发送数据
wire [15:0] send_data_num ;
wire rd_en ;
wire i_config_end ;
wire eth_tx_start_f ;
wire [31:0] eth_tx_data_f ;
wire [15:0] eth_tx_data_num_f;
wire eth_tx_start_i ;
wire [31:0] eth_tx_data_i ;
wire [15:0] eth_tx_data_num_i;

//reg define
reg eth_tx_clk ; //PHY芯片发送数据时钟信号
reg sys_rst_n ; //系统复位,低电平有效
reg [15:0] image_data ;

////
//\* Main Code \//
////
//时钟、复位信号
initial
begin
eth_tx_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end

always #20 eth_tx_clk = ~eth_tx_clk;

always@(posedge eth_tx_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
image_data <= 16'h0;
else if(rd_en == 1'b1)
image_data <= image_data + 3'd6;
else
image_data <= image_data;

assign send_en = (i_config_end == 1'b1) ? eth_tx_start_i : eth_tx_start_f;
assign send_data = (i_config_end == 1'b1) ? eth_tx_data_i : eth_tx_data_f;
assign send_data_num = (i_config_end == 1'b1) ? eth_tx_data_num_i : eth_tx_data_num_f;

defparam image_format_inst.CNT_START_MAX = 50 ;

////
//\* Instantiation \//
////
//------------ ip_send_inst -------------
ip_send
#(
.BOARD_MAC (BOARD_MAC ), //板卡MAC地址
.BOARD_IP (BOARD_IP ), //板卡IP地址
.BOARD_PORT (BOARD_PORT ), //板卡端口号
.DES_MAC (DES_MAC ), //PC机MAC地址
.DES_IP (DES_IP ), //PC机IP地址
.DES_PORT (DES_PORT ) //PC机端口号
)
ip_send_inst
(
.sys_clk (eth_tx_clk ), //时钟信号
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.send_en (send_en ), //数据发送开始信号
.send_data (send_data ), //发送数据
.send_data_num (send_data_num ), //发送数据有效字节数
.crc_data (crc_data ), //CRC校验数据
.crc_next (crc_next[31:28]), //CRC下次校验完成数据

.send_end (send_end ), //单包数据发送完成标志信号
.read_data_req (read_data_req ), //读FIFO使能信号
.eth_tx_en (eth_tx_en ), //输出数据有效信号
.eth_tx_data (eth_tx_data ), //输出数据
.crc_en (crc_en ), //CRC开始校验使能
.crc_clr (crc_clr ) //crc复位信号
);

//------------ crc32_d4_inst -------------
crc32_d4 crc32_d4_inst
(
.sys_clk (eth_tx_clk ), //时钟信号
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.data (eth_tx_data ), //待校验数据
.crc_en (crc_en ), //crc使能,校验开始标志
.crc_clr (crc_clr ), //crc数据复位信号

.crc_data (crc_data ), //CRC校验数据
.crc_next (crc_next ) //CRC下次校验完成数据
);

//------------- image_format_inst -------------
image_format image_format_inst
(
.sys_clk (eth_tx_clk ), //系统时钟
.sys_rst_n (sys_rst_n ), //系统复位,低电平有效
.eth_tx_req (read_data_req && (~i_config_end)),//以太网数据请求信号
.eth_tx_done (send_end && (~i_config_end)), //单包以太网数据发送完成信号

.eth_tx_start (eth_tx_start_f ), //以太网发送数据开始信号
.eth_tx_data (eth_tx_data_f ), //以太网发送数据
.i_config_end (i_config_end ), //图像格式包发送完成
.eth_tx_data_num (eth_tx_data_num_f ) //以太网单包数据有效字节数
);

//------------- image_data_inst -------------
image_data
#(
.H_PIXEL (10 ), //图像水平方向像素个数
.V_PIXEL (10 ), //图像竖直方向像素个数
.CNT_FRAME_WAIT (10000 ), //帧间隔时钟周期计数
.CNT_IDLE_WAIT (50 ) //数据包间隔时钟周期计数
)
image_data_inst
(
.sys_clk (eth_tx_clk ), //系统时钟,频率25MHz
.sys_rst_n (sys_rst_n && i_config_end), //复位信号,低电平有效eth_tx_end
.image_data (image_data ), //自SDRAM中读取的16位图像数据
.eth_tx_req (read_data_req && i_config_end), //以太网发送数据请求信号
.eth_tx_done (send_end && i_config_end), //以太网发送数据完成信号

.data_rd_req (rd_en ), //图像数据请求信号
.eth_tx_start (eth_tx_start_i ), //以太网发送数据开始信号
.eth_tx_data (eth_tx_data_i ), //以太网发送数据
.eth_tx_data_num (eth_tx_data_num_i ) //以太网单包数据有效字节数
);

endmodule

仿真波形分析

仿真参考代码编写完成后,对图像格式配置模块(image_format)和图像数据打包模块(image_data)进行仿真,仿真波形如下。

OV7725033

图 73‑32 图像格式配置模块整体仿真波形图

OV7725034

图 73‑33 图像格式配置模块局部仿真波形图

OV7725035

图 73‑34 图像数据打包模块整体仿真波形图

OV7725036

图 73‑35 图像数据打包模块单帧整体仿真波形图

OV7725037

图 73‑36 图像数据打包模块单帧首包仿真波形图(一)

OV7725038

图 73‑37 图像数据打包模块单帧首包仿真波形图(二)

OV7725039

图 73‑38 图像数据打包模块单帧普通包仿真波形图(一)

OV7725040

图 73‑39 图像数据打包模块单帧普通包仿真波形图(二)

OV7725041

图 73‑40 图像数据打包模块单帧末包仿真波形图(一)

OV7725042

图 73‑41 图像数据打包模块单帧末包仿真波形图(二)

对比仿真波形图与绘制波形图,各信号波形变化相同,仿真验证通过.

23.4.1.3. 顶层模块

模块框图

顶层模块作用较为简单,内部实例化各子功能模块,连接子模块对应信号;外部输入输出摄像头、SDRAM、以太网等相关数据。在此我们只对顶层模块的整体框图和参考代码做一下介绍,无需绘制波形图。模块框图和输入输出信号简介,具体见图 73‑42、表格 73‑4。

OV7725043

图 73‑42 顶层模块框图

表格 73‑4 输入输出信号功能描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

板卡晶振输入时钟,50MHz

sys_rst_n

1Bit

Input

系统复位,低电平有效

ov7725_pclk

1Bit

Input

摄像头传入工作时钟,频率24MHz

ov7725_vsync

1Bit

Input

传入图像场同步信号

ov7725_href

1Bit

Input

传入图像行有效区域

ov7725_data

8Bit

Input

传入图像信息

eth_clk

1Bit

Input

PHY芯片传入以太网时钟,频率50MHz

eth_rxdv_r

1Bit

Input

输入数据有效信号

eth_rx_data_r

2Bit

Input

输入数据

ov7725_rst_n

1Bit

Output

摄像头复位信号

ov7725_pwdn

1Bit

Output

摄像头时钟选择信号

sccb_scl

1Bit

Output

摄像头SCCB_SCL线

sccb_sda

1Bit

Inout

摄像头SCCB_SDA线

sdram_clk

1Bit

Output

SDRAM芯片时钟

sdram_cke

1Bit

Output

SDRAM时钟有效信号

sdram_cs_n

1Bit

Output

SDRAM片选信号

sdram_ras_n

1Bit

Output

SDRAM行地址选通信号

sdram_cas_n

1Bit

Output

SDRAM列地址选通信号

sdram_we_n

1Bit

Output

SDRAM写允许信号

sdram_ba

2Bit

Output

SDRAM的L-Bank地址线

sdram_addr

13Bit

Output

SDRAM地址总线

sdram_dqm

2Bit

Output

SDRAM数据掩码

sdram_dq

16Bit

Inout

SDRAM数据总线

eth_tx_en_r

1Bit

Output

以太网输出数据有效信号

eth_tx_data_r

2Bit

Output

以太网输出数据

eth_rst_n

1Bit

Output

PHY芯片复位信号,低电平有效

代码编写

顶层模块参考代码,具体见代码清单 73‑4。

代码清单 73‑4 顶层模块参考代码(eth_ov7725_rgb.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
module eth_ov7725_rgb
(
input wire sys_clk , //系统时钟
input wire sys_rst_n , //系统复位,低电平有效
//摄像头
input wire ov7725_pclk , //摄像头数据像素时钟
input wire ov7725_vsync, //摄像头场同步信号
input wire ov7725_href , //摄像头行同步信号
input wire [7:0] ov7725_data , //摄像头数据
output wire ov7725_rst_n, //摄像头复位信号,低电平有效
output wire ov7725_pwdn , //摄像头时钟选择信号
output wire sccb_scl , //摄像头SCCB_SCL线
inout wire sccb_sda , //摄像头SCCB_SDA线
//SDRAM
output wire sdram_clk , //SDRAM 时钟
output wire sdram_cke , //SDRAM 时钟使能
output wire sdram_cs_n , //SDRAM 片选
output wire sdram_ras_n , //SDRAM 行有效
output wire sdram_cas_n , //SDRAM 列有效
output wire sdram_we_n , //SDRAM 写有效
output wire [1:0] sdram_ba , //SDRAM Bank地址
output wire [1:0] sdram_dqm , //SDRAM 数据掩码
output wire [12:0] sdram_addr , //SDRAM 地址
inout wire [15:0] sdram_dq , //SDRAM 数据
//以太网
input wire eth_clk , //PHY芯片接收数据时钟
input wire eth_rxdv_r , //PHY芯片输入数据有效信号
input wire [1:0] eth_rx_data_r, //PHY芯片输入数据
output wire eth_tx_en_r , //PHY芯片输出数据有效信号
output wire [1:0] eth_tx_data_r, //PHY芯片输出数据
output wire eth_rst_n //PHY芯片复位信号,低电平有效
);

////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter H_PIXEL = 24'd640 ; //CMOS水平方向像素个数,用于设置SDRAM缓存大小
parameter V_PIXEL = 24'd48 ; //CMOS垂直方向像素个数,用于设置SDRAM缓存大小
parameter CNT_FRAME_WAIT = 24'h0E_FF_FF; //单帧图像等待时间计数
parameter CNT_IDLE_WAIT = 24'h00_01_99; //单包数据等待时间计数
//板卡MAC地址
parameter BOARD_MAC = 48'h12_34_56_78_9A_BC;
//板卡IP地址
parameter BOARD_IP = {8'd169,8'd254,8'd255,8'd255};
//板卡端口号
parameter BOARD_PORT = 16'd1234;
//PC机MAC地址
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
//PC机IP地址
//parameter DES_IP = {8'd255,8'd255,8'd255,8'd255};
parameter DES_IP = {8'd192,8'd168,8'd0,8'd245};
//PC机端口号
parameter DES_PORT = 16'd1234;

//wire define
wire clk_25m ; //25MHz时钟,提供给摄像头驱动时钟
wire clk_125m ; //125MHz时钟,SDRAM操作时钟
wire clk_125m_shift ; //125MHz时钟,SDRAM相位偏移时钟
wire locked ; //时钟输出有效
wire rst_n ; //系统复位信号
wire cfg_done ; //摄像头初始化完成
wire wr_en ; //sdram写使能
wire [15:0] wr_data ; //sdram写数据
wire rd_en ; //sdram读使能
wire [15:0] rd_data ; //sdram读数据
wire sdram_init_done ; //SDRAM初始化完成
wire sys_init_done ; //系统初始化完成(SDRAM初始化+摄像头初始化)
wire eth_tx_req ; //以太网发送数据请求信号
wire eth_tx_done ; //以太网发送数据完成
wire eth_tx_start ; //以太网开始发送信号
wire [31:0] eth_tx_data ; //以太网发送的数据
wire [15:0] eth_tx_data_num ; //以太网单包发送的有效字节数
wire eth_tx_start_f ; //以太网开始发送信号(格式)
wire [31:0] eth_tx_data_f ; //以太网发送的数据(格式)
wire [15:0] eth_tx_data_num_f; //以太网单包发送的有效字节数(格式)
wire i_config_end ; //图像格式包发送完成
wire eth_tx_start_i ; //以太网开始发送信号(图像)
wire [31:0] eth_tx_data_i ; //以太网发送的数据(图像)
wire [15:0] eth_tx_data_num_i; //以太网单包发送的有效字节数(图像)
wire eth_rxdv ; //输入数据有效信号(mii)
wire [3:0] eth_rx_data ; //输入数据(mii)
wire eth_tx_en ; //输出数据有效信号(mii)
wire [3:0] eth_tx_data_m ; //输出数据(mii)

//reg define
reg mii_clk ; //mii时钟

////
//\* Main Code \//
////
assign eth_tx_start = (i_config_end == 1'b1) ? eth_tx_start_i : eth_tx_start_f;
assign eth_tx_data = (i_config_end == 1'b1) ? eth_tx_data_i : eth_tx_data_f;
assign eth_tx_data_num = (i_config_end == 1'b1) ? eth_tx_data_num_i : eth_tx_data_num_f;

//rst_n:复位信号(sys_rst_n & locked)
assign rst_n = sys_rst_n && locked;

//sys_init_done:系统初始化完成(SDRAM初始化+摄像头初始化)
assign sys_init_done = sdram_init_done && cfg_done;

//ov7725_rst_n:摄像头复位,固定高电平
assign ov7725_rst_n = 1'b1;

//ov7725_pwdn:摄像头时钟选择信号,0:使用引脚XCLK提供的时钟 1:使用摄像头自带的晶振
assign ov7725_pwdn = 1'b0;

//mii_clk:mii时钟
always@(negedge eth_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
mii_clk <= 1'b0;
else
mii_clk <= ~mii_clk;

////
//\* Instantiation \//
////
//------------- clk_gen_inst -------------
clk_gen clk_gen_inst
(
.areset (~sys_rst_n ),
.inclk0 (sys_clk ),
.c0 (clk_125m ),
.c1 (clk_125m_shift ),
.c2 (clk_25m ),
.locked (locked )
);

//------------- ov7725_top_inst -------------
ov7725_top ov7725_top_inst
(
.sys_clk (clk_25m ), //系统时钟
.sys_rst_n (rst_n ), //复位信号
.sys_init_done (sys_init_done ), //系统初始化完成(SDRAM + 摄像头)

.ov7725_pclk (ov7725_pclk ), //摄像头像素时钟
.ov7725_href (ov7725_href ), //摄像头行同步信号
.ov7725_vsync (ov7725_vsync ), //摄像头场同步信号
.ov7725_data (ov7725_data ), //摄像头图像数据

.cfg_done (cfg_done ), //寄存器配置完成
.sccb_scl (sccb_scl ), //SCL
.sccb_sda (sccb_sda ), //SDA
.ov7725_wr_en (wr_en ), //图像数据有效使能信号
.ov7725_data_out (wr_data ) //图像数据
);

//------------- sdram_top_inst -------------
sdram_top sdram_top_inst
(
.sys_clk (clk_125m ), //sdram 控制器参考时钟
.clk_out (clk_125m_shift ), //用于输出的相位偏移时钟
.sys_rst_n (rst_n ), //系统复位
//用户写端口
.wr_fifo_wr_clk (ov7725_pclk ), //写端口FIFO: 写时钟
.wr_fifo_wr_req (wr_en ), //写端口FIFO: 写使能
.wr_fifo_wr_data (wr_data ), //写端口FIFO: 写数据
.sdram_wr_b_addr (24'd0 ), //写SDRAM的起始地址
.sdram_wr_e_addr (H_PIXEL*V_PIXEL), //写SDRAM的结束地址
.wr_burst_len (10'd512 ), //写SDRAM时的数据突发长度
.wr_rst (~rst_n ), //写复位信号
//用户读端口
.rd_fifo_rd_clk (mii_clk ), //读端口FIFO: 读时钟
.rd_fifo_rd_req (rd_en ), //读端口FIFO: 读使能
.rd_fifo_rd_data (rd_data ), //读端口FIFO: 读数据
.sdram_rd_b_addr (24'd0 ), //读SDRAM的起始地址
.sdram_rd_e_addr (H_PIXEL*V_PIXEL), //读SDRAM的结束地址
.rd_burst_len (10'd512 ), //从SDRAM中读数据时的突发长度
.rd_fifo_num ( ), //读fifo中的数据量
.rd_rst (~rst_n ), //读复位信号
//用户控制端口
.read_valid (1'b1 ), //SDRAM 读使能
.pingpang_en (1'b1 ), //SDRAM 乒乓操作使能
.init_end (sdram_init_done), //SDRAM 初始化完成标志
//SDRAM 芯片接口
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码

);

//------------- image_format_inst -------------
image_format image_format_inst
(
.sys_clk (mii_clk ), //系统时钟
.sys_rst_n (rst_n && sys_init_done ), //系统复位,低电平有效
.eth_tx_req (eth_tx_req && (~i_config_end)),//以太网数据请求信号
.eth_tx_done (eth_tx_done && (~i_config_end)),//单包以太网数据发送完成信号

.eth_tx_start (eth_tx_start_f ), //以太网发送数据开始信号
.eth_tx_data (eth_tx_data_f ), //以太网发送数据
.i_config_end (i_config_end ), //图像格式包发送完成
.eth_tx_data_num (eth_tx_data_num_f ) //以太网单包数据有效字节数
);

//------------- image_data_inst -------------
image_data
#(
.H_PIXEL (H_PIXEL ), //图像水平方向像素个数
.V_PIXEL (V_PIXEL ), //图像竖直方向像素个数
.CNT_FRAME_WAIT (CNT_FRAME_WAIT ), //帧间隔时钟周期计数
.CNT_IDLE_WAIT (CNT_IDLE_WAIT ) //数据包间隔时钟周期计数
)
image_data_inst
(
.sys_clk (mii_clk ), //系统时钟,频率25MHz
.sys_rst_n (rst_n&sys_init_done&i_config_end),//复位信号,低电平有效
.image_data (rd_data ), //自SDRAM中读取的16位图像数据
.eth_tx_req (eth_tx_req && i_config_end), //以太网发送数据请求信号
.eth_tx_done (eth_tx_done && i_config_end), //以太网发送数据完成信号

.data_rd_req (rd_en ), //图像数据请求信号
.eth_tx_start (eth_tx_start_i ), //以太网发送数据开始信号
.eth_tx_data (eth_tx_data_i ), //以太网发送数据
.eth_tx_data_num (eth_tx_data_num_i ) //以太网单包数据有效字节数
);

//------------- eth_udp_rmii_inst -------------
eth_udp_rmii
#(
.BOARD_MAC (BOARD_MAC ), //板卡MAC地址
.BOARD_IP (BOARD_IP ), //板卡IP地址
.BOARD_PORT (BOARD_PORT ), //板卡端口号
.DES_MAC (DES_MAC ), //PC机MAC地址
.DES_IP (DES_IP ), //PC机IP地址
.DES_PORT (DES_PORT ) //PC机端口号
)
eth_udp_rmii_inst
(
.eth_rmii_clk (eth_clk ), //rmii时钟
.eth_mii_clk (mii_clk ), //mii时钟
.sys_rst_n (rst_n ), //复位信号,低电平有效
.rx_dv (eth_rxdv_r ), //输入数据有效信号(rmii)
.rx_data (eth_rx_data_r ), //输入数据(rmii)
.send_en (eth_tx_start ), //开始发送信号
.send_data (eth_tx_data ), //发送数据
.send_data_num (eth_tx_data_num), //发送有效数据字节数

.send_end (eth_tx_done ), //单包数据发送完成信号
.read_data_req (eth_tx_req ), //读数据请求信号
.rec_end ( ), //单包数据接收完成信号
.rec_en ( ), //接收数据使能信号
.rec_data ( ), //接收数据
.rec_data_num ( ), //接收有效数据字节数
.eth_tx_dv (eth_tx_en_r ), //输出数据有效信号(rmii)
.eth_tx_data (eth_tx_data_r ), //输出数据(rmii)
.eth_rst_n (eth_rst_n ) //PHY芯片复位信号,低电平有效
);

endmodule

23.5. 上板调试

23.5.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 73‑5所示。

表格 73‑5 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

Input

E1

板卡晶振传入时钟,频率50MHz

sys_rst_n

Input

M15

复位信号,低有效

ov7725_pclk

Input

M16

摄像头数据像素时钟

ov7725_vsync

Input

C16

摄像头场同步信号

ov7725_href

Input

J13

摄像头行同步信号

ov7725_data[7]

Input

L13

OV7725摄像头采集图像数据

ov7725_data[6]

Input

F13

OV7725摄像头采集图像数据

ov7725_data[5]

Input

B16

OV7725摄像头采集图像数据

ov7725_data[4]

Input

C15

OV7725摄像头采集图像数据

ov7725_data[3]

Input

D16

OV7725摄像头采集图像数据

ov7725_data[2]

Input

F14

OV7725摄像头采集图像数据

ov7725_data[1]

Input

F15

OV7725摄像头采集图像数据

ov7725_data[0]

Input

G15

OV7725摄像头采集图像数据

eth_clk

Input

C3

PHY芯片传入以太网时钟,频率50MHz

eth_rxdv_r

Input

D3

输入数据有效信号

eth_rx_data_r[1]

Input

D8

输入数据

eth_rx_data_r[0]

Input

A4

输入数据

sccb_scl

Output

P15

摄像头SCCB_SCL

sccb_sda

Inout

N14

摄像头SCCB_SDA

sdram_clk

Output

R4

SDRAM芯片时钟

sdram_cke

Output

R9

SDRAM时钟有效信号

sdram_cs_n

Output

R12

SDRAM片选信号

sdram_cas_n

Output

R10

SDRAM列地址选通脉冲

sdram_ras_n

Output

R11

SDRAM行地址选通脉冲

sdram_we_n

Output

L9

SDRAM写允许位

sdram_ba[0]

Output

R13

SDRAM的L-Bank地址线

sdram_ba[1]

Output

R14

SDRAM的L-Bank地址线

sdram_addr[0]

Output

P11

SDRAM地址总线

sdram_addr[1]

Output

P14

SDRAM地址总线

sdram_addr[2]

Output

N9

SDRAM地址总线

sdram_addr[3]

Output

N11

SDRAM地址总线

sdram_addr[4]

Output

T14

SDRAM地址总线

sdram_addr[5]

Output

T13

SDRAM地址总线

sdram_addr[6]

Output

T12

SDRAM地址总线

sdram_addr[7]

Output

T11

SDRAM地址总线

sdram_addr[8]

Output

T10

SDRAM地址总线

sdram_addr[9]

Output

P9

SDRAM地址总线

sdram_addr[10]

Output

T15

SDRAM地址总线

sdram_addr[11]

Output

N12

SDRAM地址总线

sdram_addr[12]

Output

M11

SDRAM地址总线

sdram_dqm[0]

Output

M10

SDRAM数据掩码

sdram_dqm[1]

Output

M9

SDRAM数据掩码

sdram_dq[0]

Output

R3

SDRAM数据总线

sdram_dq[1]

Output

T9

SDRAM数据总线

sdram_dq[2]

Output

R5

SDRAM数据总线

sdram_dq[3]

Output

R6

SDRAM数据总线

sdram_dq[4]

Output

R7

SDRAM数据总线

sdram_dq[5]

Output

M8

SDRAM数据总线

sdram_dq[6]

Output

R8

SDRAM数据总线

sdram_dq[7]

Output

N8

SDRAM数据总线

sdram_dq[8]

Output

P8

SDRAM数据总线

sdram_dq[9]

Output

T8

SDRAM数据总线

sdram_dq[10]

Output

T7

SDRAM数据总线

sdram_dq[11]

Output

T6

SDRAM数据总线

sdram_dq[12]

Output

T5

SDRAM数据总线

sdram_dq[13]

Output

T4

SDRAM数据总线

sdram_dq[14]

Output

T3

SDRAM数据总线

sdram_dq[15]

Output

T2

SDRAM数据总线

eth_tx_en_r

Output

D5

输出数据使能信号

eth_tx_data_r[1]

Output

D6

输出数据

eth_tx_data_r[0]

Output

C8

输出数据

eth_rst_n

Output

B3

PHY复位信号

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 73‑43、图 73‑44所示。

OV7725044

图 73‑43 管脚分配

OV7725045

图 73‑44 管脚分配

23.5.1.1. 结果验证

如图 73‑45所示,开发板连接12V直流电源、USB-Blaster下载器JTAG端口、网线以及OV7725摄像头,网线另一端连接路由器;PC机也使用另一条网线与路由器相连。线路正确连接后,打开开关为板卡上电。

OV7725046

图 73‑45 程序下载连线图

如图 73‑46所示,打开网络调试助手,按照如所示进行参数配置,随后启动监听。

OV7725047

图 73‑46 配置上位机参数

如图 73‑47所示,使用“Programmer”为开发板下载程序。

OV7725048

图 73‑47 程序下载

如图 73‑48所示,上位机左下角滚动显示格式配置成功。

OV7725049

图 73‑48 上位机图像格式配置成功

随后,实时显示ov7725摄像头实时采集的图像,如图 73‑49所示。

OV7725050

图 73‑49 上位机显示图像

上位机能够正确的显示摄像头采集图像,验证通过。读者也可使用与“以太网数据回环”章节相同的验证方法进行验证。

23.6. 章末总结

在本章节中,我们实现了基于以太网的摄像头采集图像上位机实时显示实验,实验工程中的诸多模块是复用的前面实验的功能模块,本实验中只编写了上位机图像格式配置模块和图像数据打包模块,以保证上位机图像的正确显示。

23.7. 章末总结

本实验中对传输到上位机的数据未进行CRC-16校验,各位学员可在原实验的基础上自行添加CRC-16校验模块,并上板验证。