02.基于OV5640视频传输(未完)
02.基于OV5640视频传输(未完)
1.硬件设计

1.2 程序设计
整体模块:

1.2.1 IIC驱动模块
信号名 | 位宽 | 方向 | 端口 | 作用 |
---|---|---|---|---|
clk | 1 | 输入 | 系统时钟, 50MHz | |
rst_n | 1 | 输入 | 系统复位按键,低电平有效 | |
i2c_exec | 1 | 输入 | I2C 触发执行信号 | I2C触发执行信号 |
bit_ctrl | 1 | 输入 | 字地址位控制(1: 16b/ 0: 8b) | |
i2c_rh_wl | 1 | 输入 | I2C 读写控制信号 | 1:读操作;0:写操作 |
i2c_addr[15:0] | 16 | 输入 | I2C 器件字地址(双字节) | 同i2c_exec 一并传入 |
i2c_data_w[7:0] | 8 | 输入 | I2C 要写的数据 | 同i2c_exec 一并传入 |
dri_clk | 1 | 输出 | 驱动 I2C 操作的驱动时钟 | |
i2c_data_r[7:0] | 8 | 输出 | I2C 读出的数据 | |
i2c_done | 1 | 输出 | I2C 一次操作完成 | |
i2c_ack | 1 | 输出 | I2C 应答标志 0:应答 1:未应答 | |
scl | 1 | 输出 | I2C 的串行时钟信号 SCL | |
sda | 1 | 输入/输出 | I2C 的串行数据信号 SDA |
单次写操作:当 I2C 触发执行信号有效, 并且 i2c_rh_wl 信号为0,按照 I2C 器件字地址 i2c_addr,向 对应地址写入数据 i2c_data_w ;
随机数据读操作:,当 I2C 触发执行信号有效,且当i2c_rh_wl 为 1,模块执行随机数据读操作,按照 I2C 器件字地址 i2c_addr 读取 对应地址中的数据;
器件字地址的控制:
信号名 值 表示 作用 bit_ctrl 1 双字节字地址 在进行数据读写操作时要写入数据字地址 i2c_addr 的全部 16 位 bit_ctrl 0 单节字地址 在进行数据读写操作时只写入数据字地址 i2c_addr 的低 8 位 工作时钟 dri_clk : 由系统时钟sys_clk分频而来,其时钟频率为串行时钟scl的四倍((保证正确产生I2C起始信号和停止信号)。
状态转移图
wr_flag: 由 i2c_rh_wl 在 i2c_exec 为高时寄存得到

涉及信号及细节
串行时钟scl的频率为250KHz,且只在数据读写操作时时钟信号才有效,其他时刻scl始终保持高电平,dri_clk为其四倍 1MHz
dri_clk: 利用计数器配合系统时钟产生
串行时钟scl:
利用计数器2配合dri_clk 产生
I2C协议:在 scl 的上升沿采集数据,在 scl 为高电平时数据保持, sda 可以在 scl 为低电平时进行改变。
scl 低电平的中间是 i2c 驱动模块时钟(dri_clk)的上升沿,所以主机在写数据的时候在 scl 低电平的中间更新数据,读数据的时候可以在 scl 为高电平的时候寄存数据 。
串行时钟scl与串行数据sda在进行数据读写操作时有效, 其他时刻始终保持高电平
单次写状态机信号波形设计与实现:对时钟i2c_clk时钟信号进行计数。单次写操作的每个状态初始时, cnt的值为0, 每计数一个周期的i2c_clk时钟, cnt自加1
根据I2C单次写时序,随着cnt计数,依次对串行时钟与串行数据赋值,既传输的指令、地址以及数据,位宽为固定的8位数据。并且申明一个该状态完成信号st_done, st_done高有效,作为状态机状态跳转的触发信号
声明信号sda_in作为串行数据sda缓存,声明i2c_ack信号作为应答信号:
如果在主机发送完成后,从机发送非应答信号 (sda_in =1)
此时给i2c_ack信号赋值一个高电平,代表数据传输错误,会使led灯闪烁,提示I2C通信错误,接下来清空cnt计数跳转到下一个状态开始工作
i2c_ack信号只在状态机处于各应答状态时根据sda_in信号进行赋值,此时为从机回传的应答信号,其他状态时钟保持低电平
I2C: 避免主机、从机同时操作数据线
可以在 FPGA 内部可以使用三态门结构避免此事件发生。 sda_dir 表示 I2C 数据方向,为 1 时表示主机(FPGA)输出信号,为 0时 FPGA 输出高阻态,表示释放控制权。
image-20240822155648638
在 I2C 单次写操作,既每次 FPGA 输出数据时,在进行数据传输之前都需要先将 sda_dir 信号拉高,在数据传输完成后再将 sda_dir 信号拉低,将 SDA 总线的控制权交给从机发送响应数据。
//SDA 控制
assign sda = sda_dir ? sda_out : 1'bz ; //SDA 数据输出或高阻
assign sda_in = sda ; //SDA 数据输入
- 从机一直应答
因为我们设计从机一直处于应答状态,只有传输发生错误是从机回发出一个非应答信号,提示数据传输错误,数据重新传输;应答完成后开始切换状态机的状态,所以之后滞后一个周期,状态的状态由上一个状态切换到当前状态,计数器 cnt 清零,开始下一数据传输状态。
具体时序
见文档
代码相关
1、有符号数,符号位为1,使用>>>,高位补1;
2、有符号数,符号位为0,使用>>>,高位补0(和>>相同);
3、无符号数,无论最高位是什么,使用>>>,高位补0
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2 ; //模块驱动时钟的分频系数
//生成 I2C 的 SCL 的四倍频率的驱动时钟用于驱动 i2c 的操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt ==(clk_divide[8:1] - 9'd1)) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 10'b1;
end
主机发送写命令状态的代码(仅 st_sladdr 状态),如下所示:
st_sladdr: begin //写命令(器件地址和写控制位)
case(cnt)
7'd1 : sda_out <= 1'b0; //开始 I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; //0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin //主机释放 SDA 以使从机应答
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 7'b0; //清空计数
end
default : ;
endcase
end
1.2.2 IIC配置模块
信号名 | 位宽 | 方向 | 端口 | 作用 |
---|---|---|---|---|
clk | 1 | 输入 | IIC驱动时钟信号 | |
rst_n | 1 | 输入 | 系统复位按键,低电平有效 | |
cmos_h_pixel | 13 | 输入 | CMOS分辨率--行 | |
cmos_v_pixel | 13 | 输入 | CMOS分辨率—场 | |
total_h_pixel | 13 | 输入 | 水平总像素大小 | |
total_v_pixel | 13 | 输入 | 垂直总像素大小 | |
i2c_data_r | 8 | 输入 | I2C读出的数据 | |
i2c_done | 1 | 输入 | I2C寄存器配置完成信号 | |
i2c_exec | 1 | 输出 | I2C触发执行信号 | |
i2c_data | 24 | 输出 | I2C要配置的地址与数据(高16位地址,低8位数据) | |
i2c_rh_wl | 1 | 输出 | I2C读写控制信号 | |
init_done | 1 | 输入/输出 | 初始化完成信号 | 当需要配置的寄存器配置完成后,该信号拉高表示初始化完成 |

I2C 配置模块寄存需要配置的寄存器地址、 数据以及控制初始化的开始与结束。
由OV5640 的数据手册可知, 图像传感器上电后到开始配置寄存器需要延时 20ms,所以程序中定义了一个延时计数器( start_init_cnt), 用于延时 20ms。
当计数器计数到预设值之后, 开始第一次配置传感器即软件复位,目的是让所有的寄存器复位到默认的状态。
代码
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: i2c_ov5640_rgb565_cfg
// Last modified Date: 2020/05/04 9:19:08
// Last Version: V1.0
// Descriptions: iic配置
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/05/04 9:19:08
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module i2c_ov5640_rgb565_cfg
(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
input [7:0] i2c_data_r , //I2C读出的数据
input i2c_done , //I2C寄存器配置完成信号
input [12:0] cmos_h_pixel ,
input [12:0] cmos_v_pixel ,
input [12:0] total_h_pixel , //水平总像素大小
input [12:0] total_v_pixel , //垂直总像素大小
output reg i2c_exec , //I2C触发执行信号
output reg [23:0] i2c_data , //I2C要配置的地址与数据(高16位地址,低8位数据)
output reg i2c_rh_wl , //I2C读写控制信号
output reg init_done //初始化完成信号
);
//parameter define
localparam REG_NUM = 8'd250 ; //总共需要配置的寄存器个数
//reg define
reg [14:0] start_init_cnt; //等待延时计数器
reg [7:0] init_reg_cnt ; //寄存器配置个数计数器
//*****************************************************
//** main code
//*****************************************************
////SCL配置成250KHz,输入的clk时钟频率为1Mhz,周期为1us 20000*1us = 20ms
//OV5640上电到开始配置SCCB至少等待20ms
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
start_init_cnt <= 1'b0;
else if(start_init_cnt < 15'd20000) begin
start_init_cnt <= start_init_cnt + 1'b1;
end
end
//寄存器配置个数计数
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
init_reg_cnt <= 8'd0;
else if(i2c_exec)
init_reg_cnt <= init_reg_cnt + 8'b1;
end
//i2c触发执行信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_exec <= 1'b0;
else if(start_init_cnt == 15'd20000 - 1'b1)
i2c_exec <= 1'b1;
else if(i2c_done && (init_reg_cnt < REG_NUM))
i2c_exec <= 1'b1;
else
i2c_exec <= 1'b0;
end
//配置I2C读写控制信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_rh_wl <= 1'b1;
else if(init_reg_cnt == 8'd2)
i2c_rh_wl <= 1'b0;
end
//初始化完成信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
init_done <= 1'b0;
else if((init_reg_cnt == REG_NUM) && i2c_done)
init_done <= 1'b1;
end
//配置寄存器地址与数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_data <= 24'b0;
else begin
case(init_reg_cnt)
//先读OV5640 ID
8'd0 : i2c_data <= {16'h300a,8'h0}; //
8'd1 : i2c_data <= {16'h300b,8'h0}; //
8'd2 : i2c_data <= {16'h3008,8'h82}; //Bit[7]:复位 Bit[6]:电源休眠
8'd3 : i2c_data <= {16'h3008,8'h02}; //正常工作模式
8'd4 : i2c_data <= {16'h3103,8'h02}; //Bit[1]:1 PLL Clock
//引脚输入/输出控制 FREX/VSYNC/HREF/PCLK/D[9:6]
8'd5 : i2c_data <= {8'h30,8'h17,8'hff};
//引脚输入/输出控制 D[5:0]/GPIO1/GPIO0
// ...
//LENC(镜头校正)控制 16'h5800~16'h583d
// ...
//AWB(自动白平衡控制) 16'h5180~16'h519e
// ...
//Gamma(伽马)控制 16'h5480~16'h5490
// ...
//CMX(彩色矩阵控制) 16'h5381~16'h538b
// ...
//SDE(特殊数码效果)控制 16'h5580~16'h558b
// ...
//CIP(颜色插值)控制 (16'h5300~16'h530c)
// ...
//系统时钟分频 Bit[7:4]:系统时钟分频 input clock =24Mhz, PCLK = 48Mhz
// ...
//时序控制 16'h3800~16'h3821
// ...
//设置输出像素个数
//DVP 输出水平像素点数高4位
8'd220: i2c_data <= {16'h3808,{4'd0,cmos_h_pixel[11:8]}};
//DVP 输出水平像素点数低8位
8'd221: i2c_data <= {16'h3809,cmos_h_pixel[7:0]};
//DVP 输出垂直像素点数高3位
8'd222: i2c_data <= {16'h380a,{5'd0,cmos_v_pixel[10:8]}};
//DVP 输出垂直像素点数低8位
8'd223: i2c_data <= {16'h380b,cmos_v_pixel[7:0]};
//水平总像素大小高5位
8'd224: i2c_data <= {16'h380c,{3'd0,total_h_pixel[12:8]}};
//水平总像素大小低8位
8'd225: i2c_data <= {16'h380d,total_h_pixel[7:0]};
//垂直总像素大小高5位
8'd226: i2c_data <= {16'h380e,{3'd0,total_v_pixel[12:8]}};
//垂直总像素大小低8位
8'd227: i2c_data <= {16'h380f,total_v_pixel[7:0]};
// ...
//彩条测试使能
8'd245: i2c_data <= {16'h503d,8'h00}; //8'h00:正常模式 8'h80:彩条显示
//测试闪光灯功能
8'd246: i2c_data <= {16'h3016,8'h02};
8'd247: i2c_data <= {16'h301c,8'h02};
8'd248: i2c_data <= {16'h3019,8'h02}; //打开闪光灯
8'd249: i2c_data <= {16'h3019,8'h00}; //关闭闪光灯
//只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
default : i2c_data <= {16'h300a,8'h00}; //器件ID高8位
endcase
end
end
endmodule
1.2.3 开始传输控制模块
信号名 | 位宽 | 方向 | 端口 | 作用 |
---|---|---|---|---|
clk | 1 | 输入 | rxc 时钟信号 | |
rst_n | 1 | 输入 | 系统复位按键,低电平有效 | |
udp_rec_en | 1 | 输入 | UDP 接收的数据使能信号 | |
upd_rec_data | 8 | 输入 | UDP 接收的数据 | |
upda_rec_pkt_done | 1 | 输入 | UDP 单包数据接收完成信号 | |
udp_rec_byte_num | 16 | 输入 | UDP 接收到的字节数 | |
transfer_flag | 1 | 输出 | 图像开始传输标信号 |
UDP 单包数据接收完成后接收的字节数不为 0 且接收的数据不为 0 时, 图像开始传输标信号transfer_flag拉高,图像开始传输;否则图像开始传输标志位拉低,图像停止传输。

module start_transfer_ctrl(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
input udp_rec_pkt_done , //UDP 单包数据接收完成信号
input udp_rec_en , //UDP 接收的数据使能信号
input [7 :0] udp_rec_data , //UDP 接收的数据
input [15:0] udp_rec_byte_num , //UDP 接收到的字节数
output reg transfer_flag //图像开始传输标志,1:开始传输 0:停止传输
);
//parameter define
parameter START = "1"; //开始命令
parameter STOP = "0"; //停止命令
//*****************************************************
//** main code
//*****************************************************
//解析接收到的数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
transfer_flag <= 1'b0;
else if(udp_rec_pkt_done && udp_rec_byte_num == 1'b1) begin
if(udp_rec_data == START) //开始传输
transfer_flag <= 1'b1;
else if(udp_rec_data == STOP) //停止传输
transfer_flag <= 1'b0;
else ;
end
end
endmodule
1.2.4 图像数据封装模块(异步FIFO)
信号名 | 位宽 | 方向 | 端口 | 作用 |
---|---|---|---|---|
cam_pclk | 1 | 输入 | 像素时钟 | |
rst_n | 1 | 输入 | 系统复位按键,低电平有效 | |
img_vsync | 1 | 输入 | 帧同步信号 | |
img_data_en | 1 | 输入 | 数据有效使能信号 | |
img_data | 8 | 输入 | cmos数据 | |
eth_tx_clk | 1 | 输入 | 以太网发送时钟 | |
transfer_flag | 1 | 输入 | 图像开始传输标志 | 1:开始传输 / 0:停止传输 |
udp_tx_req | 1 | 输入 | udp发送数据请求信号 | |
udp_tx_done | 1 | 输入 | udp发送数据完成信号 | |
udp_tx_start_en | 1 | 输出 | udp开始发送信号 | |
udp_tx_data | 8 | 输出 | udp发送的数据 | |
udp_tx_byte_num | 16 | 输出 | udp单包发送的有效字节数 |
- 在一帧图像的第一行图像需要添加帧头和图像行场分辨率,而其余行的图像是不需要添加的,这因为我们需要根据 img_vsync(帧同步信号)来判断什么时候输入的数据是第一行的图像。
- 摄像头输出的数据我们可以使用异步 fifo 进行数据的缓存,将缓存好的数据传递给 UDP的发送接口。
- 当 fifo 缓存的个数满足 udp_tx_byte_num 的个数之后, udp_tx_start_en 会拉高,开始发送一包数据,而 udp_tx_data 会通过异步 fifo 将 img_data 缓存的数据发送到上位机。


我们在用网口传输图像数据时,一次发送一行图像数据
在发送一帧图像的第一行数据时,在一行数据的开头添加图像的帧头和图像的行场分辨率
在使用 UDP 传输数据时,有时会出现数据丢包的现象、
为了降低丢包对视频显示带来的影响,我们为每帧图像添加一个帧头,上位机解析到帧头之后,接下来将接收到的数据重新放到图像显示区域的起始位置,保证了在视频传输过程中,即使出现丢包的现象,视频也能恢复到正常显示的画面。
图像帧头的值要尽量选择图像数据不容易出现的值,否则很容易把图像数据当成帧头,程序中把图像帧头设置为{32'hf0_5a_a5_0f},图像中出现的像素数据和帧头相同的概率极低。
除此之外,上位机软件按照帧头为{32'hf0_5a_a5_0f}来解析数据,所以帧头的值不可以随意修改。
只在一帧的第一行添加了帧头和行场分辨率,因此只有在发送第一行图像数据时,发送的 UDP 字节数为 1288(640*2+8),而其余行单包发送的字节数为 1280
当以太网发送空闲时,且 FIFO 中的字节数达到待发送的字节数时,此时拉高 udp_tx_start_en 信号,开始控制以太网顶层模块读取 FIFO,并将 FIFO 中的数据发送给上位机
帧同步的上升沿和 transfer_flag 信号作为 FIFO 的复位信号,避免上位机在传输图像中途发送停止命令, FIFO 没有被清空的情况
代码
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: img_data_pkt
// Last modified Date: 2020/2/18 9:20:14
// Last Version: V1.0
// Descriptions: 图像封装模块(添加帧头)
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/2/18 9:20:14
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module img_data_pkt(
input rst_n , //复位信号,低电平有效
//图像相关信号
input cam_pclk , //像素时钟
input img_vsync , //帧同步信号
input img_data_en , //数据有效使能信号
input [7 :0] img_data , //有效数据
input transfer_flag , //图像开始传输标志,1:开始传输 0:停止传输
//以太网相关信号
input eth_tx_clk , //以太网发送时钟
input udp_tx_req , //udp发送数据请求信号
input udp_tx_done , //udp发送数据完成信号
output reg udp_tx_start_en, //udp开始发送信号
output [7 :0] udp_tx_data , //udp发送的数据
output reg [15:0] udp_tx_byte_num //udp单包发送的有效字节数
);
//parameter define
parameter CMOS_H_PIXEL = 16'd640; //图像水平方向分辨率
parameter CMOS_V_PIXEL = 16'd480; //图像垂直方向分辨率
//图像帧头,用于标志一帧数据的开始
parameter IMG_FRAME_HEAD = {32'hf0_5a_a5_0f};
reg img_vsync_d0 ; //帧有效信号打拍
reg img_vsync_d1 ; //帧有效信号打拍
reg img_vsync_d2 ; //帧有效信号打拍
reg img_data_en_d0 ; //数据有效使能信号
reg [7 :0] img_data_d0 ; //有效图像数据打拍
reg [3 :0] head_cnt ; //帧头计数器
reg head_flag ; //帧头标志位
reg wr_fifo_en ; //写fifo使能
reg [7 :0] wr_fifo_data ; //写fifo数据
reg img_vsync_txc_d0; //以太网发送时钟域下,帧有效信号打拍
reg img_vsync_txc_d1; //以太网发送时钟域下,帧有效信号打拍
reg img_vsync_txc_d2; //以太网发送时钟域下,帧有效信号打拍
reg tx_busy_flag ; //发送忙信号标志
reg [1:0] bit_sel ;
//wire define
wire pos_vsync ; //帧有效信号上升沿
wire neg_vsync ; //帧有效信号下降沿
wire neg_vsynt_txc ; //以太网发送时钟域下,帧有效信号下降沿
wire [10:0] fifo_rdusedw ; //当前FIFO缓存的个数
//*****************************************************
//** main code
//*****************************************************
//信号采沿
assign neg_vsync = img_vsync_d2 & (~img_vsync_d1);
assign pos_vsync = ~img_vsync_d2 & img_vsync_d1;
assign neg_vsynt_txc = ~img_vsync_txc_d2 & img_vsync_txc_d1;
//对img_vsync信号延时三个时钟周期,用于采沿
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n) begin
img_vsync_d0 <= 1'b0;
img_vsync_d1 <= 1'b0;
img_vsync_d2 <= 1'b0;
end
else begin
img_vsync_d0 <= img_vsync;
img_vsync_d1 <= img_vsync_d0;
img_vsync_d2 <= img_vsync_d1;
end
end
//帧头计数器
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n)
head_cnt <= 4'd0;
else if(neg_vsync)
head_cnt <= 4'd0;
else if(head_cnt == 4'd8)
head_cnt <= head_cnt;
else
head_cnt <= head_cnt + 4'd1;
end
//帧头标志位
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n)
head_flag <= 1'd0;
else if(neg_vsync)
head_flag <= 1'd1;
else if(head_cnt == 4'd7)
head_flag <= 1'd0;
else ;
end
//同步数据有效信号和有效数据
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n) begin
img_data_en_d0 <= 1'd0;
img_data_d0 <= 8'd0;
end
else begin
img_data_en_d0 <= img_data_en;
img_data_d0 <= img_data;
end
end
//将帧头和图像数据写入FIFO
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n) begin
wr_fifo_en <= 1'b0;
wr_fifo_data <= 8'd0;
end
else begin
wr_fifo_en <= 1'b1;
if(head_flag) begin
wr_fifo_en <= 1'b1;
if(head_cnt == 4'd0)
wr_fifo_data <= IMG_FRAME_HEAD[31:24]; //帧头
else if(head_cnt == 4'd1)
wr_fifo_data <= IMG_FRAME_HEAD[23:16]; //帧头
else if(head_cnt == 4'd2)
wr_fifo_data <= IMG_FRAME_HEAD[15: 8]; //帧头
else if(head_cnt == 4'd3)
wr_fifo_data <= IMG_FRAME_HEAD[ 7: 0]; //帧头
else if(head_cnt == 4'd4)
wr_fifo_data <= CMOS_H_PIXEL[15: 8]; //水平方向分辨率
else if(head_cnt == 4'd5)
wr_fifo_data <= CMOS_H_PIXEL[ 7: 0]; //水平方向分辨率
else if(head_cnt == 4'd6)
wr_fifo_data <= CMOS_V_PIXEL[15: 8]; //垂直方向分辨率
else if(head_cnt == 4'd7)
wr_fifo_data <= CMOS_V_PIXEL[ 7: 0]; //垂直方向分辨率
else ;
end
else if(img_data_en_d0) begin
wr_fifo_en <= 1'b1;
wr_fifo_data <= img_data_d0;
end
else begin
wr_fifo_en <= 1'b0;
wr_fifo_data <= 8'd0;
end
end
end
//以太网发送时钟域下,对img_vsync信号延时三个时钟周期,用于采沿
always @(posedge eth_tx_clk or negedge rst_n) begin
if(!rst_n) begin
img_vsync_txc_d0 <= 1'b0;
img_vsync_txc_d1 <= 1'b0;
img_vsync_txc_d2 <= 1'b0;
end
else begin
img_vsync_txc_d0 <= img_vsync;
img_vsync_txc_d1 <= img_vsync_txc_d0;
img_vsync_txc_d2 <= img_vsync_txc_d1;
end
end
//控制以太网发送的字节数
always @(posedge eth_tx_clk or negedge rst_n) begin
if(!rst_n)
udp_tx_byte_num <= 1'b0;
else if(neg_vsynt_txc)
udp_tx_byte_num <= {CMOS_H_PIXEL,1'b0} + 16'd8;
else if(udp_tx_done)
udp_tx_byte_num <= {CMOS_H_PIXEL,1'b0};
end
//控制以太网发送开始信号
always @(posedge eth_tx_clk or negedge rst_n) begin
if(!rst_n) begin
udp_tx_start_en <= 1'b0;
tx_busy_flag <= 1'b0;
end
//上位机未发送"开始"命令时,以太网不发送图像数据
else if(transfer_flag == 1'b0) begin
udp_tx_start_en <= 1'b0;
tx_busy_flag <= 1'b0;
end
else begin
udp_tx_start_en <= 1'b0;
//当FIFO中的个数满足需要发送的字节数时
if(tx_busy_flag == 1'b0 && fifo_rdusedw >= udp_tx_byte_num[15:0]) begin
udp_tx_start_en <= 1'b1; //开始控制发送一包数据
tx_busy_flag <= 1'b1;
end
else if(udp_tx_done || neg_vsynt_txc)
tx_busy_flag <= 1'b0;
end
end
//异步FIFO
async_fifo_2048x8b async_fifo_2048x8b_inst (
.rst(pos_vsync | (~transfer_flag)), // input wire rst
.wr_clk(cam_pclk), // input wire wr_clk
.rd_clk(eth_tx_clk), // input wire rd_clk
.din(wr_fifo_data), // input wire [7 : 0] din
.wr_en(wr_fifo_en), // input wire wr_en
.rd_en(udp_tx_req), // input wire rd_en
.dout(udp_tx_data), // output wire [7 : 0] dout
.full(), // output wire full
.empty(), // output wire empty
.rd_data_count(fifo_rdusedw), // output wire [10 : 0] rd_data_count
.wr_rst_busy(), // output wire wr_rst_busy
.rd_rst_busy() // output wire rd_rst_busy
);
endmodule