Menu Close

图像采集与传输系统(采集图像DDR数据存储)

ddr 数据存储在摄像头采样工程中用来处理存储ov5640 采样来的图像数据, 为avd7511 hdmi 输出芯片提供图像数据。这主要是利用了ddr 芯片存储数据容量比较大, 批量读取速度比较快, 实现ov5640 边采集, adv7511 hdmi 图像边输出。 同时利用ddr 存储, 还可以实现采样帧率 和输出帧率的转换。

参考目录:图像采集与传输系统的设计

摄像头采样工程ddr 存储框图:

%title插图%num

 

图1

图1中主要分三个区域:1) ddr 数据读写区域,使用ddr 时钟域; 2)hdmi 时钟域, 负责将ddr 读出的数据发送到显示器上; 3) 摄像头时钟域, 主要负责将ov5640采样的图像数据写入到ddr memory 中去。

图像采集系统数据存储架构:

ddr_intf: ddr IP 核 例化接口,包括地址总线, 写数据总线, 读数据总线

ddr_ctrl: 读取fifo数据(读fifo),将这些数据写入到指定的ddr 存储空间上, 根据用户指定的ddr 地址, 将ddr存储空间里的数据读出, 写入到fifo 中去 (写fifo)

pic_storage: 接收camera 来的数据,将这些数据写入到fifo中去(ddr 读fifo);根据camera 的href信号,确定当前数据时哪一行(写入ddr 存储空间);根据camera 的vsync 信号, 确定那一场的数据(ddr page 空间)

display:根据hdmi_end信号, 确定要读取那一场(page); 根据hdmi_rd_en信号,确定从ddr存储空间中读取哪一行的数据

 

ddr 时钟域和摄像头时钟域, hdmi 时钟域的数据转换:

%title插图%num

图像数据在不同时钟域的转换, 主要是通过fifo IP 来进行的, fifo IP 本身就具有不同时钟域转换的能力, 同时,我们也利用了fifo 输入,输出口径不同的特点, 可以实现16bit 数据 写入fifo , 128bit 数据读出fifo (camera side); 当然,也可以实现 128bit 数据写入, 16 bit 数据读出的功能(hdmi side)。

区分时钟域

  • camera 时钟域(从camera 硬件模块而来,包括时钟, 数据,数据enable,v信号等)
  • ddr 时钟域 (控制ddr 芯片,包括地址总线,写数据总线,读数据总线等)
  • hdmi 时钟域 (fpga 发送数据到adv7511, 包括 数据,h,v,de,clk 等)

camera 数据输入fifo: 16bit in, 128bit out fifo, 但camera 来的数据是8bit,需要每两个数据写入fifo一次。 ddr时钟域端读取fifo数据,存储到ddr 芯片中去

hdmi 数据输出fifo: 16bit out,128bit in fifo, ddr 写入数据到这个fifo, hdmi 时钟域端读取数据,并且发送给adv7511 芯片

%title插图%num

除了使用fifo 作为图像数据在不同的时钟域之间交互, 这里还用到了几个变量,也需要在不同的时钟域之间交互。

摄像头时钟域:

发送app_wr_start 信号 和 app_wr_line_num 通知ddr模块 将camera的数据写入到ddr存储空间中的什么位置(ddr 地址空间);由于时异步时钟域,所以app_wr_start, app_wr_line_num 都是持续多个camera_clk的时钟周期,用于ddr时钟域采集这个信号

当ddr 将fifo 里的数据写入到app_wr_line_num 指定的ddr 地址空间后,发送ddr_we_ack,通知应用程序,ddr 操作完成。 由于是异步时钟域,ddr_we_ack 也是多个时钟周期(ddr_clk)的信号,用于摄像头时钟域来采集。

hmdi 时钟域:

发送app_rd_start 信号 和 app_rd_line_num 通知ddr模块, 通知ddr,从app_rd_line_num 指定的ddr地址空间中,将数据读出,写入到fifo中去;由于时异步时钟域,所以app_rd_start, app_rd_line_num 都是持续多个camera_clk的时钟周期,用于ddr时钟域采集这个信号

当ddr 将数据写入fifo后,发送ddr_rd_ack,通知应用程序,ddr 操作完成。应用程序这时就可以读取fifo 里的数据了。 由于是异步时钟域,ddr_we_ack 也是多个时钟周期(ddr_clk)的信号,用于摄像头时钟域来采集。

ddr 地址选择接口(异步信号同步方案)

camera_clk时钟和ddr_clk时钟分数不同的时钟域

app_wr_start, app_wr_line_num 被ddr_clk 时钟域采样

ddr_we_ack 由ddr_clk 时钟域产生, 可以被camera_clk 时钟域采样

%title插图%num

 

vga_clk时钟和ddr_clk时钟分数不同的时钟域

app_rd_start, app_rd_line_num 被ddr_clk 时钟域采样

ddr_rd_ack 由ddr_clk 时钟域产生, 可以被vga_clk 时钟域采样

%title插图%num

 

ov5640 写入ddr 参考代码:

localparam TEST_PIXEL = 0; // 测试pixel , 正常情况下 TEST_PIXEL = 0

(* mark_debug = "true" *)reg [15:0] wr_cnt = 0; // 写fifo 计数器, 用于状态机中的多个应用
(* mark_debug = "true" *)reg [2:0] wr_st = 0; // 写fifo 状态机
reg app_wr_start_flag = 0; // 写line number 启动信号(单个时钟周期),之后会利用这个边沿,产生一定宽度的信号, 以便ddr 时钟域抓取
reg [8:0] timer_cnt = 0; // 照相功能 延迟信号, 当按下 OK 键后, 当前照片会在屏幕上保持10s 
always @ (posedge camera_clk)
if(cam_reset)
begin
    app_we <= 0;
    app_wr_rst <= 0;
    app_wr_start_flag <= 0;
    app_wr_line_num <= 0;
    wr_st <= 0;
end
else case (wr_st)
0:
begin
    timer_cnt <= 0;
    wr_cnt <= 0;
    app_we <= 0;
    app_din <= 0;
    app_wr_rst <= 1; // reset 写fifo ip 
    if(cam_ready)
        wr_st <= 1;
end
1:
begin
    wr_cnt <= wr_cnt + 1;

    if(wr_cnt == 3)
        app_wr_rst <= 0; // 持续 3 个 clock , 释放 fifo reset 信号, 具体信息, 可查看fifo iP 手册

    if(&wr_cnt[5:0]) // 等待 64 个时钟周期后, 可以正常使用fifo ,具体看fifo IP 手册
    begin
        wr_cnt <= 0;
        wr_st <= 2;
    end
end
2:
begin
    app_we <= 0;

    if(camera_v == 2'b01) // 等待 camera 输出 V 信号, 同时提取 V 信号的上升沿
    begin
        if(snapshot_r) // 如果这个时候,是拍照模式, 进入 拍照 , 当前图像在屏幕上持续 10s
            wr_st <= 3;
        else // 如果不是拍照模式, 将行地址 清零, 同时 更换 一页存储空间, camera 的数据 ,将循环使用 4页的ddr 存储空间。
        begin
            app_wr_line_num[10:0] <= 0; // 行地址
            app_wr_line_num[12:11] <= app_wr_line_num[12:11] + 1; // 页 地址
        end
    end

    if(camera_de) // 如果 camera 数据enable 信号出现, 开始存储数据到fifo 中
    begin
        app_din <= TEST_PIXEL ? 0 : {app_din[7:0] , camera_data_r};
        wr_cnt <= TEST_PIXEL ? 0 : wr_cnt + 1;
        wr_st <= 4;
    end
end
3:
begin
    if(camera_v == 2'b01) timer_cnt <= timer_cnt + 1; // 拍照模式下, 计数器开始启动, 每一场,计数器加一, 300场是10秒钟时间 (30HZ)

    if(timer_cnt == 300) // 计数器 到300 场 (10s), 返回到状态机2
    begin
        timer_cnt <= 0;
        wr_st <= 2;
    end
end
4:
begin
    if(wr_cnt == BYTE_WR_NUM) // 当数据 等于 1280 = 640pixel *2 bytes( 一行数据写入完成), 状态机跳转到5
    begin
        app_we <= 0;
        wr_st <= 5;
    end
    else
    begin
        if(TEST_PIXEL == 1) // fifo, ddr 测试模式, 正常情况下, TEST_PIXEL = 0,这段代码不会被执行
        begin
            app_din <= app_din + 1;
            wr_cnt <= wr_cnt + 2;
            app_we <= 1;
        end
        else // 正常camera 采集模式 (正常工作模式)
        begin
            app_din <= {app_din[7:0] , camera_data_r}; // ov5640 配置模式为RGB565 即:R[4:0],G[5:0],B[4:0]
            wr_cnt <= wr_cnt + 1;
            if(wr_cnt[0]) app_we <= 1; // 每两个 bytes 写入fifo 一次
            else app_we <= 0;
        end
    end
end
5:
begin
    wr_cnt <= 0;
    app_wr_start_flag <= 1; // 通知 fifo 另外一段 (ddr) , 数据写入完成。
    wr_st <= 6;
end
6:
begin
    app_wr_start_flag <= 0;
    if(ddr_wr_ack_pos) // 等待ddr 端 将fifo 中的数据全部读走
        wr_st <= 7;
end
7:
begin
    app_wr_line_num[10:0] <= app_wr_line_num[10:0] + 1; // ddr 的行地址 加一
    wr_st <= 0;
end
default: wr_st <= 0;
endcase

 

其中,TEST_PIXEL 参数 在正常操作时,TEST_PIXEL = 0, 在测试模式时,利用这个参数,可以测试fifo,ddr是否正确。这在项目开发中是十分必要的。可以用来测试写入ddr 的数据和读出ddr 的数据是否正确。 同时 ,也可以利用TEST_PIXEL 来实现其他的视频操作, 例如: 测试图片。(利用状态机4,填写视频内容)

%title插图%num

同时, 也可以利用TEST_PIXEL 检测ddr 的读写过程:

%title插图%num

利用TEST_PIXEL 查看数据写入ddr 的过程。

%title插图%num

利用TEST_PIXEL 查看数据读出ddr 的过程。

 

 

ov5640 图像采集数据写入ddr 过程:

由于ov5640数据接口camera_data 是8bit, 而fifo 却是16bit的, 所以,每两个数据,写入fifo一次。 由于fifo在例化是,没有办法选择 8bin 128out; 所以,我们使用的fifo是 16in 128out。 这样每得到2个 ov5640 的数据, 写入一次fifo, 就可以实现 8bit 数据 写入 16 bit fifo 了。

%title插图%num

 

ddr 芯片内部 图像数据存储结构:

%title插图%num

ddr 芯片中的存储空间

page0 地址: 0x00_0000 – 0x3f_ffff

page1 地址: 0x40_0000 – 0x7f_ffff

page2 地址: 0x80_0000 – 0xbf_ffff

page3 地址: 0xc0_0000 – 0xff_ffff

 

在每一页地址中:

第1行地址起始地址:0x0000

第2行地址起始地址:0x0800 = 上一行起始地址 + 2048 bytes

第3行地址起始地址:0x1000 = 上一行起始地址 + 2048 bytes

第4行地址起始地址:0x1800 = 上一行起始地址 + 2048 bytes

…..

 

ddr 数据读出参考代码:

reg app_rd_start_flag = 0;
always @ (posedge vga_clk)
if(vga_reset | hdmi_end_pos)
begin
    app_rd <= 0;
    app_rd_rst <= 0;
    app_rd_start_flag <= 0;
    app_rd_line_num <= 0;
    rd_st <= 0;
end
else case (rd_st)
0:
begin
    rd_cnt <= 0;
    app_rd <= 0;

    if(vga_ready) // 等待 所有外部信号稳定: ddr稳定,camera 稳定, hdmi 稳定 (初始化成功)
    begin
        app_rd_rst <= 1; // reset fifo
        rd_st <= 1;
    end
end
1:
begin
    rd_cnt <= rd_cnt + 1;
    if(rd_cnt == 3) // 等待 3 clock 后 , reset fifo 信号 拉低
        app_rd_rst <= 0;

    if(&rd_cnt[5:0]) // 等待 64 个时钟周期后 ,可以正常使用fifo, 具体可以查看fifo 手册
    begin
        rd_cnt <= 0;
        rd_st <= 2;
    end
end
2:
begin
    if(hdmi_end) // hmdi V (场信号) 时
    begin
        app_rd_line_num[10:0] <= 0; // ddr 行地址 清零
        app_rd_line_num[12:11] <= hdmi_page_id; // ddr 页地址 指向 比写ddr的 页地址 - 2 页
    end
    rd_st <= 3;
end
3:
begin
    app_rd_start_flag <= 1; // 通知ddr , ddr 可以读去数据, 并且把数据写入到fifo
    rd_st <= 4;
end
4:
begin
    app_rd_start_flag <= 0;
    if(ddr_rd_ack_pos) 
        rd_st <= 5;
end
5:
begin
    if(hdmi_rd_en) // 当hdmi 需要数据时, 开始从fifo 中读取数据
        rd_st <= 6;
end
6:
begin
    if(rd_cnt == BYTE_RD_NUM)
    begin
        app_rd <= 0;
        rd_st <= 7;
    end
    else
    begin
        rd_cnt <= rd_cnt + 2;
        app_rd <= 1;
    end
end
7:
begin
    app_rd_line_num[10:0] <= app_rd_line_num[10:0] + 1; // 当读取完一行后, ddr 读取行的计数 加一
    rd_st <= 0;
end

endcase

 

通过读ddr 状态机, 实现了数据从ddr 读取, 送到 hdmi 显示模块的操作。

 

 

Posted in FPGA, FPGA 教材教案, IC 教材教案, Verilog, 教材与教案, 文章

7 Comments

  1. 6班李红梅

    老师,我看代码里面的pic_storage模块里面的cam_reset ,是不是可以直接在pll模块里面 用always@(posedge camera_pic_clk) rst_n_cam <= locked & PB

    • William

      // 将外部输入的reset 信号 同步到 camera_clk 上
      reg cam_reset = 1;
      always @ (posedge camera_clk )
      cam_reset <= app_reset; cam_reset 是app_reset 信号 在camera_clk下的同步信号。 .app_reset (!rst_n_50m) // 整个模块的reset 信号输入 app_reset 信号就是由系统时钟生成出来的

  2. 6班李红梅

    老师,ddr_ctl里面的app_wr_line_num_r 是不是应该拼接为{app_wr_line_num[12:11],2’b00,app_wr_line_num[10:0],10’b0} 因为bank只需要使用低2bit,行只需要低11bit,但是高位是不是需要补0,不然控制器的ip 的地址映射就不对了,是这样的么?这块没想明白

  3. William

    ddr 内存地址,可以理解为 一片连续的存储空间, 这个空间比较大, 可以根据用户的需要,自由的配置,使用。
    app_wr_line_num 定义为:
    app_wr_line_num[10:0] <= 0; // 行地址 app_wr_line_num[12:11] <= app_wr_line_num[12:11] + 1; // 页 地址 app_wr_line_num[10:0] 0 -- 2047 行 ,一般显示的分辨率不会大于这个值 ,例如1920x1080 只有1080 行。 app_wr_line_num[12:11] 是用户自己分配的页地址, 这个页地址,完全是用户定义的, 和ddr 芯片的bank 没有直接的关系。 写入ddr的真实地址(起始地址 ) app_wr_line_num always @ (posedge ddr_clk ) if (!rst_n) ddr_wr_start_flag = 0; else begin if(app_wr_start_r == 2'b01) begin ddr_wr_start_flag <= 1; app_wr_line_num_r <= {app_wr_line_num,11'b000_0000_0000}; end else if(cstate == 1) ddr_wr_start_flag <= 0; end app_wr_line_num_r <= {app_wr_line_num,11'b000_0000_0000}; 指定了真实地址的页 + 行地址 + 11'b0 bytes 地址。 可以根据 “ddr 芯片内部 图像数据存储结构” 这个图仔细看看。

发表回复

相关链接