文章目录
- 前言
- 一、图像合并模块的设计
- 二、仿真文件
前言
image_stitche_x 模块:将串口接收的尺寸为 400480 大小的彩色图像与灰度化处理后的 400480 大小的图像数据以左右形式合并成一张 800*480 的图像。
提示:以下是本篇文章正文内容,下面案例可供参考
一、图像合并模块的设计
要实现图像以左右形式合并,首先要分析下“基于 DDR3 的串口传图帧缓存系统”是如何实现在 TFT 上显示一张图片的过程。这个过程是外部输入(串口传过来)图像数据经过数据位宽的处理后写入 DDR3 固定地址区间中,TFT 显示驱动模块根据 TFT 屏驱动时序从这个固定的地址区间取出数据实现一个个图像像素点数据在 TFT 屏上的显示,目前这个DDR3 中固定的地址区间的范围是存储 TFT 显示一帧数据的内存空间。可以看作是一个深度为 384000(5 寸 TFT 的长宽为 800480),数据位宽为 16bit 的“RAM”(假想的一个RAM)。写入和读出端口的地址范围均为 0~383999。每次写入(或读取)一个数据,写入端口地址(或读端口地址)加 1,地址达到最大值 383999 后又重新回到 0 开始往上加 1,循环往复。也就说 TFT 显示控制模块总是从读端的地址 0 开始读取数据,每次地址加 1,在读地址达到最大值 383999 后就完成了一帧图像的显示,然后读地址又重新回到 0 开始下一帧图像的显示。地址总是顺序的,这样在TFT屏显示的图像就取决于这个“RAM”地址从0~383999内存存储的数据。那么这样 TFT 显示的图像数据取决于写入端写入的图像数据,如果写入的图像数据是一张完整的 800480 图像数据,那么显示的就是一张 800480 的图像,如果写入的图像数据是两张 400480 的图像数据,那么显示的就是一张混合后拼接成的 800480 的图像。
那么如何实现左右形式的图像拼接显示呢?先写入第一张 400480 的图像数据,然后再写入第二张 400480 的图像数据?并不是,要知道如何去写入这两张图片数据,需要知道TFT 显示图片的扫描方式,这个在前面 TFT 显示驱动章节已经讲过,扫描方式是一行一行的,也就是这个缓存图片数据的“RAM”存储图片数据的内容结构如下图所示,地址 0799存储的是第1行图像数据,地址8001599存储的是第2行图像数据……,地址383200~383999存储的是第 480 行图像数据。
如果要实现左右形式的图片拼接显示,就需要往“RAM”中交错写的写入存储两张400480 的图片数据,左右形式拼接的“RAM”中存储的数据与 TFT 屏显示的对应关系如下图所示。从图中可以看出,存储两张 400480 图片的方式是,先存储左边图像(图片 2)的一行数据,然后再存储右边图像(图片 2)的一行数据,直到两张图像的 480 行数据均存储完成。
想要实现上面存储方式,考虑到两张图片可能是同时从其他模块进入,但由于图片 1 和图片 2 的存储到“RAM”有先后顺序,避免在存储其中一张图片时,另一张图片数据的丢失,需要在存储到“RAM”前为图片 1 和图片 2 的数据各加一个 fifo 就行少量数据的缓存。图像合并模块的整体设计框架如下图所示。图像合并模块两个图片输入端口,分别作为为图片 1 和图片 2 数据流的输入。通过逻辑控制交替输出两张图片的行数据给到下一级存储模块,到达实现图片合并的显示效果。
所谓的交替,就是先读取图片 1 的 FIFO 的 400 个数据进行输出,读取完之后,切换到读取图片 2 的 FIFO 的 400 个数据数据进行输出,同样读取完之后,切换到读取图片 1 的FIFO 的 400 个数据数据进行输出,如此反复进行这样的切换操作。(这里需要注意的是每次读取的数据个数是图片 1/图片 2 的一行数据的像素点个数,这里图片 1/图片 2 均为 400*480大小图片,所以每次读取 400 个数据)根据上面的设计思路,采用状态机进行设计,画出状态转移图如下图所示。
IDLE:初始状态,上电或复位后处于该状态;
ARB:仲裁状态,根据当前的情况决定下一状态跳转到哪个状态。如果跳转到 ARB 的上一状态是读取图片 1 的 FIFO 数据状态 RD_IMG1,则下一状态跳转到读取图片 2 的FIFO 数据状态RD_IMG2;否则如果跳转到 ARB 的上一状态是读取图片 2 的 FIFO 数据状态 RD_IMG2,则下一状态跳转到读取图片 1 的 FIFO 数据状态 RD_IMG1,读取FIFO 的交替切换就是在本状态实现。
RD_IMG1:读取图片 1 的 FIFO 数据状态,在一行图像数据(400 个数据)读取完成后,状态跳转到 ARB 状态。
RD_IMG2:读取图片 2 的 FIFO 数据状态,在一行图像数据(400 个数据)读取完成后,状态跳转到 ARB 状态。
状态机具体实现代码如下。
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
curr_state <= S_IDLE;
else
curr_state <= next_state;
end
always@(*)
begin
case(curr_state)
S_IDLE:
begin
if(rst_busy_o == 1'b0)
next_state = S_ARB;
else
next_state = S_IDLE;
end
S_ARB:
begin
if((rd_image_sel == 1'b0) && (image1_buf_empty == 1'b0))
next_state = S_RD_IMG1;
else if((rd_image_sel == 1'b1) && (image2_buf_empty == 1'b0))
next_state = S_RD_IMG2;
else
next_state = S_ARB;
end
S_RD_IMG1:
begin
if(image1_buf_rden && (rd_data_cnt == IMAGE1_WIDTH_IN - 1'b1))
next_state = S_ARB;
else
next_state = S_RD_IMG1;
end
S_RD_IMG2:
begin
if(image2_buf_rden && (rd_data_cnt == IMAGE2_WIDTH_IN - 1'b1))
next_state = S_ARB;
else
next_state = S_RD_IMG2;
end
default: next_state = S_IDLE;
endcase
end
IDLE 状态中判断条件信号 rst_busy_o 为复位情况下,FIFO 的复位忙信号,FIFO 的复位需要一段时间,需要在 FIFO 复位完全结束,也就是 FIFO 的复位忙信号均变为 0 后才跳转到 ARB 状态。
RD_IMG1 和 RD_IMG2 状态中的 rd_image_sel 为读 FIFO 的切换选择标识信号,每读完图片 1(或图片 2)FIFO 的一行图片数据(400 个),信号 rd_image_sel 电平就变化一次。具体代码如下。
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
rd_image_sel <= 1'b0;
else if((curr_state == S_RD_IMG1) && (rd_data_cnt == IMAGE1_WIDTH_IN - 1'b1))
rd_image_sel <= 1'b1;
else if((curr_state == S_RD_IMG2) && (rd_data_cnt == IMAGE2_WIDTH_IN - 1'b1))
rd_image_sel <= 1'b0;
else
rd_image_sel <= rd_image_sel;
end
不同的 rd_image_sel 信号电平表示的是需要读取哪个 FIFO 的图片数据,rd_image_sel为 0 表示需要读取图片 1 的 FIFO 的数据,rd_image_sel 为 1 表示需要读取图片 2 的 FIFO 的数据。代码中 rd_data_cnt 表示读取 FIFO 的数据个数计数,在读取 FIFO 状态(RD_IMG1 或RD_IMG2 状态),每读取一个 FIFO 数据,计数器加 1,具体代码如下。
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
rd_data_cnt <= 'd0;
else if(curr_state == S_RD_IMG1)
begin
if(image1_buf_rden == 1'b1)
rd_data_cnt <= rd_data_cnt + 1'b1;
else
rd_data_cnt <= rd_data_cnt;
end
else if(curr_state == S_RD_IMG2)
begin
if(image2_buf_rden == 1'b1)
rd_data_cnt <= rd_data_cnt + 1'b1;
else
rd_data_cnt <= rd_data_cnt;
end
else
rd_data_cnt <= 'd0;
end
读 FIFO 信号 image1_buf_rden 和 image2_buf_rden 分别根据当前所处状态和 FIFO 的空满标识控制产生,直接使用组合逻辑产生。具体代码如下。
assign image1_buf_rden = (curr_state == S_RD_IMG1) & (image1_buf_empty == 1'b0) & (data_out_ready_i == 1'b0);
assign image2_buf_rden = (curr_state == S_RD_IMG2) & (image2_buf_empty == 1'b0) & (data_out_ready_i == 1'b0);
data_out_ready_i 信号表示的是下游其他模块的入口 FIFO 的将满信号,主要是考虑到下游模块的入口 FIFO 如果要满的情况下,是不去读本模块的 FIFO 数据的,因为读取的本模块的 FIFO 数据是要输出给下游的,如果下游 FIFO 满了,还往下游传数据会导致数据的丢失的。合并后的输出信号就相对简单,将读出的 FIFO 的数据直接输出即可,考虑到读 FIFO 的读使能与读出的数据有效之间有一个周期的延时,所以读出的数据需要在读使能延迟一拍后获取数据,具体代码如下。
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
begin
image1_buf_rden_dly1 <= 1'b0;
image2_buf_rden_dly1 <= 1'b0;
end
else
begin
image1_buf_rden_dly1 <= image1_buf_rden;
image2_buf_rden_dly1 <= image2_buf_rden;
end
end
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_valid_o <= 1'b0;
else if(image1_buf_rden_dly1 | image2_buf_rden_dly1)
data_valid_o <= 1'b1;
else
data_valid_o <= 1'b0;
end
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_pixel_o <= 'd0;
else if(image1_buf_rden_dly1)
data_pixel_o <= image1_buf_dout;
else if(image2_buf_rden_dly1)
data_pixel_o <= image2_buf_dout;
else
data_pixel_o <= 'd0;
end
考虑到模块的可移植性和可扩展性将图片的尺寸或数据位宽使用参数化表示,输入的两张图片的大小和合并输出的图片的大小尺寸均用参数化表示,具体代码如下。
module image_stitche_x
#(
parameter DATA_WIDTH = 16, //16 or 24
//image1_in: 400*480
parameter IMAGE1_WIDTH_IN = 400,
parameter IMAGE1_HEIGHT_IN = 480,
//image2_in: 400*480
parameter IMAGE2_WIDTH_IN = 400,
parameter IMAGE2_HEIGHT_IN = 480,
//image_out: 800*480
parameter IMAGE_WIDTH_OUT = 800,
parameter IMAGE_HEIGHT_OUT = 480
)
(
input clk_image1_in ,
input clk_image2_in ,
input clk_image_out ,
input reset_p ,
output rst_busy_o ,
output image_in_ready_o ,
input [DATA_WIDTH-1:0] image1_data_pixel_i,
input image1_data_valid_i,
input [DATA_WIDTH-1:0] image2_data_pixel_i,
input image2_data_valid_i,
input data_out_ready_i ,
output reg[DATA_WIDTH-1:0] data_pixel_o ,
output reg data_valid_o
);
考虑到用户在例化使用模块时对参数进行重定义时可能对出现由于笔误或其他不小心的错误使得图片的尺寸设置出现,输入的两张图片大小的尺寸参数的宽度之和不等于输出图片大小尺寸,或者输入图片的高度与输出图片的高度不一致问题,导致最终无法达到模块的功能而不便于定位错误。本模块与“彩色图像灰度化处理模块”设计采用类似的方式,使用generate-if 的形式对错误参数的设置输出固定的数据,或者采取他某种固定的输出,这样便于根据实际仿真和上板的现象快速定位是否为参数配置错误导致。具体代码如下。
generate
if ((IMAGE1_WIDTH_IN + IMAGE2_WIDTH_IN != IMAGE_WIDTH_OUT) ||
(IMAGE1_HEIGHT_IN != IMAGE_HEIGHT_OUT) || (IMAGE2_HEIGHT_IN !=
IMAGE_HEIGHT_OUT))
begin: error_set_pro //错误设置输入/输出图片参数情况的处理
//---------------------------------------------------------
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_pixel_o <= 'd0;
else
data_pixel_o <= {DATA_WIDTH{1'b1}};
end
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_valid_o <= 'd0;
else
data_valid_o <= 1'b1;
end
//---------------------------------------------------------
end
else
begin: correct_set_pro //正确设置输入/输出图片参数情况的处理
//---------------------------------------------------------
........//正确参数配置情况下的代码,也就文档前面分析设计的代码
//---------------------------------------------------------
end
endgenerate
完整设计图例如下:
完整代码设计如下:
/
// Module Name : image_stitche_x
// Description : 输入的两张图片左右拼接后输出,
// 设置输入的两张图片尺寸需与设置拼接后输出图片尺寸满足如下要求:
// 输入图片1的宽度 + 输入图片2的宽度 == 输出图片的宽度;
// 输入图片1的高度 == 输入图片2的高度 == 输出图片的高度;
// 不满足要求的做错误参数设置处理,以输出设置尺寸输出一张纯白色图片
/
module image_stitche_x
#(
parameter DATA_WIDTH = 16, //16 or 24
//image1_in: 400*480
parameter IMAGE1_WIDTH_IN = 400,
parameter IMAGE1_HEIGHT_IN = 480,
//image2_in: 400*480
parameter IMAGE2_WIDTH_IN = 400,
parameter IMAGE2_HEIGHT_IN = 480,
//image_out: 800*480
parameter IMAGE_WIDTH_OUT = 800,
parameter IMAGE_HEIGHT_OUT = 480
)
(
input clk_image1_in ,
input clk_image2_in ,
input clk_image_out ,
input reset_p ,
output rst_busy_o ,
output image_in_ready_o ,
input [DATA_WIDTH-1:0] image1_data_pixel_i,
input image1_data_valid_i,
input [DATA_WIDTH-1:0] image2_data_pixel_i,
input image2_data_valid_i,
input data_out_ready_i ,
output reg[DATA_WIDTH-1:0] data_pixel_o ,
output reg data_valid_o
);
localparam S_IDLE = 4'b0001,
S_ARB = 4'b0010,
S_RD_IMG1 = 4'b0100,
S_RD_IMG2 = 4'b1000;
wire image1_buf_rden;
reg image1_buf_rden_dly1;
wire[DATA_WIDTH-1:0]image1_buf_dout;
wire image1_buf_alfull;
wire image1_buf_empty;
wire image1_buf_wr_rst_busy;
wire image1_buf_rd_rst_busy;
wire image2_buf_rden;
reg image2_buf_rden_dly1;
wire[DATA_WIDTH-1:0]image2_buf_dout;
wire image2_buf_alfull;
wire image2_buf_empty;
wire image2_buf_wr_rst_busy;
wire image2_buf_rd_rst_busy;
reg rd_image_sel;//0:image1,1:image2
reg [11:0] rd_data_cnt;
reg [3:0] curr_state;
reg [3:0] next_state;
assign rst_busy_o = image1_buf_wr_rst_busy | image1_buf_rd_rst_busy | image2_buf_wr_rst_busy | image2_buf_rd_rst_busy;
assign image_in_ready_o = (~image1_buf_alfull) && (~image2_buf_alfull) && (~rst_busy_o);
generate
if ((IMAGE1_WIDTH_IN + IMAGE2_WIDTH_IN != IMAGE_WIDTH_OUT) || (IMAGE1_HEIGHT_IN != IMAGE_HEIGHT_OUT) || (IMAGE2_HEIGHT_IN != IMAGE_HEIGHT_OUT))
begin: error_set_pro //错误设置输入/输出图片参数情况的处理
//---------------------------------------------------------
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_pixel_o <= 'd0;
else
data_pixel_o <= {DATA_WIDTH{1'b1}};
end
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_valid_o <= 'd0;
else
data_valid_o <= 1'b1;
end
//---------------------------------------------------------
end
else
begin: correct_set_pro //正确设置输入/输出图片参数情况的处理
//---------------------------------------------------------
image_buffer image1_buffer (
.rst (reset_p ), // input wire rst
.wr_clk (clk_image1_in ), // input wire wr_clk
.rd_clk (clk_image_out ), // input wire rd_clk
.din (image1_data_pixel_i ), // input wire [15 : 0] din
.wr_en (image1_data_valid_i ), // input wire wr_en
.rd_en (image1_buf_rden ), // input wire rd_en
.dout (image1_buf_dout ), // output wire [15 : 0] dout
.almost_full (image1_buf_alfull ), // output wire almost_full
.full ( ), // output wire full
.empty (image1_buf_empty ), // output wire empty
.wr_rst_busy (image1_buf_wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (image1_buf_rd_rst_busy ) // output wire rd_rst_busy
);
image_buffer image2_buffer (
.rst (reset_p ), // input wire rst
.wr_clk (clk_image2_in ), // input wire wr_clk
.rd_clk (clk_image_out ), // input wire rd_clk
.din (image2_data_pixel_i ), // input wire [15 : 0] din
.wr_en (image2_data_valid_i ), // input wire wr_en
.rd_en (image2_buf_rden ), // input wire rd_en
.dout (image2_buf_dout ), // output wire [15 : 0] dout
.almost_full (image2_buf_alfull ), // output wire almost_full
.full ( ), // output wire full
.empty (image2_buf_empty ), // output wire empty
.wr_rst_busy (image2_buf_wr_rst_busy ), // output wire wr_rst_busy
.rd_rst_busy (image2_buf_rd_rst_busy ) // output wire rd_rst_busy
);
//-----------------------------------------------------------------------------------------
//data_out_ready_i 信号表示的是下游其他模块的入口 FIFO 的将满信号,主要是考虑到下
//游模块的入口 FIFO 如果要满的情况下,是不去读本模块的 FIFO 数据的,因为读取的本模
//块的 FIFO 数据是要输出给下游的,如果下游 FIFO 满了,还往下游传数据会导致数据的丢失的
//-----------------------------------------------------------------------------------------
assign image1_buf_rden = (curr_state == S_RD_IMG1) & (image1_buf_empty == 1'b0) & (data_out_ready_i == 1'b0);
assign image2_buf_rden = (curr_state == S_RD_IMG2) & (image2_buf_empty == 1'b0) & (data_out_ready_i == 1'b0);
//考虑到读 FIFO 的读使能与读出的数据有效之间有一个周期的延时,所以读出的数据需要在读使能延迟一拍后获取数据
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
begin
image1_buf_rden_dly1 <= 1'b0;
image2_buf_rden_dly1 <= 1'b0;
end
else
begin
image1_buf_rden_dly1 <= image1_buf_rden;
image2_buf_rden_dly1 <= image2_buf_rden;
end
end
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_valid_o <= 1'b0;
else if(image1_buf_rden_dly1 | image2_buf_rden_dly1)
data_valid_o <= 1'b1;
else
data_valid_o <= 1'b0;
end
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
data_pixel_o <= 'd0;
else if(image1_buf_rden_dly1)
data_pixel_o <= image1_buf_dout;
else if(image2_buf_rden_dly1)
data_pixel_o <= image2_buf_dout;
else
data_pixel_o <= 'd0;
end
//rd_data_cnt
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
rd_data_cnt <= 'd0;
else if(curr_state == S_RD_IMG1)
begin
if(image1_buf_rden == 1'b1)
rd_data_cnt <= rd_data_cnt + 1'b1;
else
rd_data_cnt <= rd_data_cnt;
end
else if(curr_state == S_RD_IMG2)
begin
if(image2_buf_rden == 1'b1)
rd_data_cnt <= rd_data_cnt + 1'b1;
else
rd_data_cnt <= rd_data_cnt;
end
else
rd_data_cnt <= 'd0;
end
//rd_image_sel
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
rd_image_sel <= 1'b0;
else if((curr_state == S_RD_IMG1) && (rd_data_cnt == IMAGE1_WIDTH_IN - 1'b1))
rd_image_sel <= 1'b1;
else if((curr_state == S_RD_IMG2) && (rd_data_cnt == IMAGE2_WIDTH_IN - 1'b1))
rd_image_sel <= 1'b0;
else
rd_image_sel <= rd_image_sel;
end
//*********************************
//State Machine
//*********************************
always@(posedge clk_image_out or posedge reset_p)
begin
if(reset_p)
curr_state <= S_IDLE;
else
curr_state <= next_state;
end
always@(*)
begin
case(curr_state)
S_IDLE:
begin
if(rst_busy_o == 1'b0) // FIFO 的复位忙信号均变为 0 后才跳转到 ARB 状态
next_state = S_ARB;
else
next_state = S_IDLE;
end
S_ARB:
begin
if((rd_image_sel == 1'b0) && (image1_buf_empty == 1'b0))
next_state = S_RD_IMG1;
else if((rd_image_sel == 1'b1) && (image2_buf_empty == 1'b0))
next_state = S_RD_IMG2;
else
next_state = S_ARB;
end
S_RD_IMG1:
begin
if(image1_buf_rden && (rd_data_cnt == IMAGE1_WIDTH_IN - 1'b1))
next_state = S_ARB;
else
next_state = S_RD_IMG1;
end
S_RD_IMG2:
begin
if(image2_buf_rden && (rd_data_cnt == IMAGE2_WIDTH_IN - 1'b1))
next_state = S_ARB;
else
next_state = S_RD_IMG2;
end
default: next_state = S_IDLE;
endcase
end
//---------------------------------------------------------
end
endgenerate
endmodule
至此,图像左右合并模块的设计基本完成。
二、仿真文件
完整代码和仿真的 tb 文件见基于 FPGA 的彩色图像灰度化的设计实现。
仿真波形如下,图片1通道写入了两行(每行50个)数据,数据分别为049和100149。
之后,图片 2 通道写入了两行(每行 50 个)数据,数据分别为 50~99 和 150~199;
理论上,合并之后的输出数据应该为 0~199 的 200 个数据,可以观察仿真波形,仿真结果与期望一致。