总体概览
配置摄像头 | 将摄像头配置为RAW10输出格式 |
图像数据串转并 | 将从两个lane通道接收的串行数据转换为byte类型数据 |
字节对齐 | 由于应用层原始数据打包后是byte形式,在传输时又转换成bit形式,在进行图像的逆过程时,需要保证一个byte数据的各个bit在是原来的byte数据 |
通道对齐 | lane0和lane1数据到达FPGA的时刻存在偏差,要保证恢复后的数据相邻的两个byte是原始数据的相邻byte |
解析出原始图像数据 | 一个长包包含一行数据,但是还包含包头等非图像数据,需要去掉 |
字节转像素 | 将解析好的图像数据转换成能够通过hdmi显示的像素数据 |
摄像头配置
OV5640寄存器配置
时钟配置
首先,采用的是720P的图像模式,帧率是30帧,我们知道图像实际传输的时候分辨率会比我们用到的有效分辨率高,比如我们这里720P传输的时候并不是1280720,而是1892740,这样算下来每一帧图像总共有42002400个像素,图像帧率是30帧,这样每秒钟就有42002400*30=42002400
个像素需要传输,假如每个像素有两个Byte,也就是16bit,这样每秒钟就是42002400*16=672038400
个比特,而我们是两个通道传输,又是DDR模式,所以相应的时钟还要除以通道数目,再除以2(DDR模式),所以最终的时钟速率是168009600, 也就是168MHz。
Register | Name | Value | Description |
---|---|---|---|
0x3035 | PLL Control1 | 0x21 | System clock divided by 2, MIPI divided by 2 |
0x3036 | PLL Control2 | 0x54 | PLL multiplier by 0x54 = 84 |
0x3037 | PLL Control3 | 0x13 | PLL predivider is 31 |
这样我们根据实际参考时钟为24M来计算,首先24M经过3分频变为8M,然后经过84倍的倍频变为672M,在经过系统分频2变为336MHz,之后是MIPI的2分频,最终变为168MHz。
寄存器太复杂,太多了,对寄存器的配置理解的还是不够通透,时钟计算不太准确,自己的理解。根据手册配置能够满足时钟的要求。例如 1024×768×30帧/秒的图像采集,计算疫苗产生的像素点为1024×768×30=23,592,960,采用RGB565的格式 传输图像信息,一共16bit的数据,需要2个时钟周期才能完成一个像素点数据的传输,所以需要传输的数据23,592,960×2=47,185,920,约为47MHZ,个人认为大于该时钟就完全可以完成数据的传输
IIC时序
MIPI 串转并
OV5640配置 1080P 30Fps MIPI LANE DATA RATE = 420Mhz
mipi ddr mode mipiclk=210Mhz
mipi 数据传输 低位优先
1.数据传入包含MIPI_LANE[1:0] MIPI_CLK
使用IBUFDS_DPHY原语进行数据接入,并实现差分转单端操作
使用BUFG将转单端的时钟接入全局时钟,并使用BUFGCE_DIV原语生成串转并需要的4分频时钟信号
ISERDESE3原语,采用DDR模式,将传入时钟和分频时钟接入,输出并行8bit信号
MIPI_PHY数据接收和串转并操作
module mipi_phy_gen(
//
input resetn ,
input mipi_clk_p ,
input mipi_clk_n ,
input [1:0] mipi_data_p ,
input [1:0] mipi_data_n ,
//
output mipi_byte_clk ,
output [7:0] lane0_byte_data ,
output [7:0] lane1_byte_data
);
//clock
wire clk_in_int;
wire clk_div;
wire clk_in_int_bufg;
IBUFDS_DPHY #(
.DIFF_TERM("TRUE"), // Differential termination
.IOSTANDARD("MIPI_DPHY_DCI"), // I/O standard
.SIM_DEVICE("ULTRASCALE_PLUS") // Set the device version (ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1,
// ULTRASCALE_PLUS_ES2)
)
IBUFDS_DPHY_inst_uclk (
.HSRX_O(clk_in_int_bufg), // 1-bit output: HS RX output
.LPRX_O_N(), // 1-bit output: LP RX output (Slave)
.LPRX_O_P(), // 1-bit output: LP RX output (Master)
.HSRX_DISABLE(1'b0), // 1-bit input: Disable control for HS mode
.I(mipi_clk_p), // 1-bit input: Data input0 PAD
.IB(mipi_clk_n), // 1-bit input: Data input1 PAD
.LPRX_DISABLE(1'b1) // 1-bit input: Disable control for LP mode
);
BUFG BUFG_inst (
.O (clk_in_int ), // 1-bit output: Clock output
.I (clk_in_int_bufg ) // 1-bit input: Clock input
);
BUFGCE_DIV #(
.BUFGCE_DIVIDE(4), // 1-8
// Programmable Inversion Attributes: Specifies built-in programmable inversion on specific pins
.IS_CE_INVERTED(1'b0), // Optional inversion for CE
.IS_CLR_INVERTED(1'b0), // Optional inversion for CLR
.IS_I_INVERTED(1'b0) // Optional inversion for I
)
BUFGCE_DIV_inst (
.O(clk_div), // 1-bit output: Buffer
.CE(1'b1), // 1-bit input: Buffer enable
.CLR(1'b0), // 1-bit input: Asynchronous clear
.I(clk_in_int_bufg) // 1-bit input: Buffer
);
assign mipi_byte_clk = clk_div;
//data
wire [1:0] data_in_from_pins_int;
wire [7:0] lane_byte_data[0:1];
assign lane0_byte_data = lane_byte_data[0];
assign lane1_byte_data = lane_byte_data[1];
genvar i;
generate
for(i=0;i<2;i=i+1)begin:gen
IBUFDS_DPHY #(
.DIFF_TERM("TRUE"), // Differential termination
.IOSTANDARD("MIPI_DPHY_DCI"), // I/O standard
.SIM_DEVICE("ULTRASCALE_PLUS") // Set the device version (ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1,
// ULTRASCALE_PLUS_ES2)
)
IBUFDS_DPHY_inst_u0 (
.HSRX_O(data_in_from_pins_int[i]), // 1-bit output: HS RX output
.LPRX_O_N(), // 1-bit output: LP RX output (Slave)
.LPRX_O_P(), // 1-bit output: LP RX output (Master)
.HSRX_DISABLE(1'b0), // 1-bit input: Disable control for HS mode
.I(mipi_data_p[i]), // 1-bit input: Data input0 PAD
.IB(mipi_data_n[i]), // 1-bit input: Data input1 PAD
.LPRX_DISABLE(1'b1) // 1-bit input: Disable control for LP mode
);
ISERDESE3 #(
.DATA_WIDTH(8), // Parallel data width (4,8)
.FIFO_ENABLE("FALSE"), // Enables the use of the FIFO
.FIFO_SYNC_MODE("FALSE"), // Always set to FALSE. TRUE is reserved for later use.
.IS_CLK_B_INVERTED(1'b1), // Optional inversion for CLK_B
.IS_CLK_INVERTED(1'b0), // Optional inversion for CLK
.IS_RST_INVERTED(1'b0), // Optional inversion for RST
.SIM_DEVICE("ULTRASCALE_PLUS") // Set the device version for simulation functionality (ULTRASCALE,
// ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1, ULTRASCALE_PLUS_ES2)
)
ISERDESE3_u0 (
.FIFO_EMPTY(), // 1-bit output: FIFO empty flag
.INTERNAL_DIVCLK(), // 1-bit output: Internally divided down clock used when FIFO is
// disabled (do not connect)
.Q(lane_byte_data[i]), // 8-bit registered output
.CLK(clk_in_int), // 1-bit input: High-speed clock
.CLKDIV(clk_div), // 1-bit input: Divided Clock
.CLK_B(clk_in_int), // 1-bit input: Inversion of High-speed clock CLK
.D(data_in_from_pins_int[i]), // 1-bit input: Serial Data Input
.FIFO_RD_CLK(), // 1-bit input: FIFO read clock
.FIFO_RD_EN(), // 1-bit input: Enables reading the FIFO when asserted
.RST(1'b0) // 1-bit input: Asynchronous Reset
);//set_property DATA_RATE SDR|DDR [get_ports port_name]
end
endgenerate
endmodule
MIPI 通道对齐
SOT = 8'hB8; 帧头也是同步头
DI | SOT(B8) |
LPS(low power state) |
8bit | 10111000 | 0000 0000 |
{byte_data,byte_data_r1}; 16bit data,在其中寻找B8,确定数据偏移量;但是B8不一定是SOT,因为数据中也可能存在B8;所以,找到B8之后,计算ECC结果是否等于接收到的ECC,就能确定B8是否代表SOT;如果是SOT,利用偏移位置将拼接的16bit数据对应的数据进行输出;通道对齐模块只是进行寻找B8,验证是否是SOT在数据包解析模块进行
数据对齐
module byte_align(
input clk ,
input resetn ,
input [7:0] lane_data ,
output reg[7:0] mipi_byte_data ,
output reg mipi_byte_vld ,
input re_find
);
//================define==========
localparam SYNC = 8'hb8;
reg [2:0] offset;
reg [7:0] lane_data_r1;
wire [15:0] data_16bit;
reg [15:0] data_16bit_r1;
//===================main code=====
always@(posedge clk)begin
lane_data_r1 <= lane_data;
data_16bit_r1<= data_16bit;
end
assign data_16bit = {lane_data,lane_data_r1};
always@(posedge clk or negedge resetn)begin
if(!resetn)begin
offset <= 'd0;
mipi_byte_vld <= 1'b0;
end
else if(re_find)begin
offset <= 'd0;
mipi_byte_vld <= 1'b0;
end
else if(mipi_byte_vld == 1'b0)begin
if(data_16bit[7:0] == SYNC)begin
offset <= 'd0;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[8:1] == SYNC)begin
offset <= 'd1;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[9:2] == SYNC)begin
offset <= 'd2;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[10:3] == SYNC)begin
offset <= 'd3;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[11:4] == SYNC)begin
offset <= 'd4;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[12:5] == SYNC)begin
offset <= 'd5;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[13:6] == SYNC)begin
offset <= 'd6;
mipi_byte_vld <= 1'b1;
end
else if(data_16bit[14:7] == SYNC)begin
offset <= 'd7;
mipi_byte_vld <= 1'b1;
end
end
end
//assign mipi_byte_data data_16bit_r1[offset+7:offset];]
always@(*)begin
case(offset)
0: mipi_byte_data = data_16bit_r1[7:0];
1: mipi_byte_data = data_16bit_r1[8:1];
2: mipi_byte_data = data_16bit_r1[9:2];
3: mipi_byte_data = data_16bit_r1[10:3];
4: mipi_byte_data = data_16bit_r1[11:4];
5: mipi_byte_data = data_16bit_r1[12:5];
6: mipi_byte_data = data_16bit_r1[13:6];
7: mipi_byte_data = data_16bit_r1[14:7];
default: mipi_byte_data = data_16bit_r1[7:0];
endcase
end
endmodule
通道对齐
SOT在摄像头MIPI端是同时传输的,如果两个lane的同步头在FPGA内部传输相差超过1个时钟周期,就意味着源端发送2进制数据超过8bit,也就是超过MIPI传输数据的4个时钟周期(20ns),两个同步头的传输时不会发生这种情景,所以这种情况对应的一定是数据部分,不是同步头。
两个lane的byte_vld相差不超过一个时钟周期,谁快谁打拍,需做同步处理,谁快谁打拍,同时进入就同时输出。
如果在两个时钟周期以上,就一定不是SOT,反馈给byte_align,模块重新查找同步头。
通道对齐
module lane_align(
//sys
input clk ,
input rst_n ,
//input
input [7:0] lane0_byte_data ,
input [7:0] lane1_byte_data,
input lane0_byte_vld ,
input lane1_byte_vld ,
//output
output reg word_vld,
output reg [15:0] word_data,
input invalid_start
);
//=========================================
//================define===================
//=========================================
localparam LANE0_FIRST = 2'b01;
localparam LANE1_FIRST = 2'b10;
localparam NONE_FIRST = 2'b11;
wire lane_vld_or;
reg lane_vld_or_r1;
wire lane_vld_or_pos;
reg lane_vld_or_pos_r1;
reg [7:0] lane0_byte_data_r1;
reg [7:0] lane1_byte_data_r1;
reg lane0_byte_vld_r1;
reg lane1_byte_vld_r1;
reg [1:0] tap;
wire lane_vld_and;
//-----------------------------------------
assign lane_vld_or = lane0_byte_vld || lane1_byte_vld;
assign lane_vld_or_pos = lane_vld_or && !lane_vld_or_r1;
assign lane_vld_and = lane0_byte_vld && lane1_byte_vld;
always@(posedge clk)begin
lane0_byte_data_r1 <= lane0_byte_data;
lane1_byte_data_r1 <= lane1_byte_data;
lane_vld_or_r1 <= lane_vld_or;
lane_vld_or_pos_r1 <= lane_vld_or_pos;
lane0_byte_vld_r1 <= lane0_byte_vld;
lane1_byte_vld_r1 <= lane1_byte_vld;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
tap <= NONE_FIRST;
else if(lane_vld_or_pos)
tap <= {lane1_byte_vld,lane0_byte_vld};
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
invalid_start <= 1'b0;
else if(lane_vld_or_pos_r1 && lane_vld_and == 1'b0)
invalid_start <= 1'b1;
else
invalid_start <= 1'b0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
word_data <= 'd0;
word_vld <= 1'b0;
end
else begin
case(tap)
LANE0_FIRST:begin
word_data <= {lane1_byte_data,lane0_byte_data_r1};
word_vld <= lane1_byte_vld;
end
LANE1_FIRST:begin
word_data <= {lane1_byte_data_r1,lane0_byte_data};
word_vld <= lane0_byte_vld;
end
NONE_FIRST:begin
word_data <= {lane1_byte_data_r1,lane0_byte_data_r1};
word_vld <= lane1_byte_vld_r1;
end
default:begin
word_data <= {lane1_byte_data_r1,lane0_byte_data_r1};
word_vld <= lane1_byte_vld_r1;
end
endcase
end
end
endmodule