CMOS攝像頭(3):DVP端口輸出


  SCCB 配置好后,cmos 的 DVP 端口就有數據出來了,怎么設計時序獲取圖像呢?

一、DVP端口

cmos_capture u_cmos_capture
(
    .clk_24m                (clk_24m                ),
    .cmos_pclk              (cmos_pclk              ),
    .rst_n                  (rst_n & sccb_cfg_done  ),
    .cmos_vsync             (cmos_vsync             ),
    .cmos_href              (cmos_href              ),
    .cmos_data              (cmos_data              ),
    .frame_vsync            (                       ),
    .frame_hsync            (                       ),
    .rgb565_vld             (                       ), // 640x480
    .rgb565_data            (                       ),
    .rgb_vld                (rgb_vld                ), // 480x272
    .rgb_data               (rgb_data               ),
    .fps_rate               (fps_rate               )
);

  端口的輸出包括:圖像數據,圖像數據使能,fps數值。其中數據和使能又分為原版 640x480 和 裁剪后的 480x272。(如果是上一講配置的ov5640則為1024x768→480x272)

 

二、去除前10幀

  根據數據手冊說的,前10幀圖像數據不穩定,因此一般都是丟掉。代碼設計很簡單,找到 cmos_vsync 的上升沿計數,只計一次,計滿 10 幀后的數據才是有效數據,如下所示:

//==========================================================================
//==    打拍,以供后面程序使用
//==========================================================================
always @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        cmos_vsync_r1 <= 1'b0;
        cmos_vsync_r2 <= 1'b0;
        cmos_href_r1  <= 1'b0;
        cmos_href_r2  <= 1'b0;
    end
    else begin
        cmos_vsync_r1 <= cmos_vsync;
        cmos_vsync_r2 <= cmos_vsync_r1;
        cmos_href_r1  <= cmos_href;
        cmos_href_r2  <= cmos_href_r1;
    end
end
//==========================================================================
//==    前10幀圖像數據不穩定,丟棄掉
//==========================================================================
//vsync上升沿
//---------------------------------------------------
assign cmos_vsync_pos = (~cmos_vsync_r1 & cmos_vsync);

//幀有效信號,去除前10幀
//---------------------------------------------------
always @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        frame_cnt <= 'd0;
    end
    else if(cmos_vsync_pos && frame_vld==1'b0) begin
        frame_cnt <= frame_cnt + 1'b1;
    end
end

assign frame_vld = (frame_cnt >= WAIT) ? 1'b1 : 1'b0;

  其中,WAIT的數值為 10,frame_vld 為有效數據指示信號,前10幀時為低,之后一直為高。

 

三、數據拼接

  ov7725 的數據手冊中有如下一張圖,說明了配置成 RGB565 時的時序情況。攝像頭的數據為 8bit,是按照 RAW 像素設計的,當我們配置成 RGB565 格式輸出時,需要兩個 8bit 才能表示一個 16bit 的 RGB565 像素,因此需要對輸出的像素進行人為的拼接,典型的時序如下所示: 

  RGB565的排列順序也是通過寄存器設置的,也有別的排列順序,我這設置的就是如圖的格式。時序設計如下所示:

  我們設計一個指示信號 byte_flag,不斷的對原始數據 ov_5640_data 進行拼接,並設計 vld 信號表明輸出有效標志。這個時序圖是我之前學習開源騷客的教學視頻時畫的,和我下面貼出的代碼信號名字有一點點不一樣,但大體是相同的。代碼如下所示:

//==========================================================================
//==    兩個原始數據拼成一個RGB565像素
//==========================================================================
//字節指示
//---------------------------------------------------
always  @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        byte_flag <= 1'b0;
    end
    else if(cmos_href) begin
        byte_flag <= ~byte_flag;
    end
    else begin
        byte_flag <= 1'b0;
    end
end

//rgb_data
//---------------------------------------------------
always  @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        rgb565_data <= 'h0;
    end
    else if(byte_flag == 1'b0) begin                    //first byte
        rgb565_data <= {cmos_data, rgb565_data[7:0]};
    end
    else if(byte_flag == 1'b1) begin                    //second byte
        rgb565_data <= {rgb565_data[15:8], cmos_data};
    end
end

//rgb_vld
//---------------------------------------------------
always  @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n) begin
        rgb565_vld <= 1'b0;
    end
    else if(frame_vld && byte_flag) begin
        rgb565_vld <= 1'b1;
    end
    else begin
        rgb565_vld <= 1'b0;
    end
end

 

四、行場有效信號

  經過處理,行場信號需要改變才能和處理后的數據完美對齊,代碼如下所示:

//==========================================================================
//==    輸出行場有效信號
//==========================================================================
assign frame_vsync = frame_vld ? cmos_vsync_r2 : 1'b0;
assign frame_hsync = frame_vld ? cmos_href_r2  : 1'b0;

 

五、分辨率裁剪

  上一講博客中提到,可以通過寄存器的配置改變輸出的圖像分辨率,但寄存器的配置需要查詢 datasheet 再計算得結果,比較麻煩。我們可以通過簡單的計數器,實現分辨率裁剪的效果。以 640x480 裁剪為 480x272 為例,取中間的部分,邊上的舍去,代碼如下所示:

parameter H_START            = 12'd79                ; //裁剪后的寬度起始像素
parameter H_STOP             = 12'd559               ; //裁剪后的寬度結束像素
parameter V_START            = 12'd103               ; //裁剪后的高度起始像素
parameter V_STOP             = 12'd375               ; //裁剪后的高度結束像素 
//==========================================================================
//==    分辨率裁剪:640x480 -> 480x272
//==========================================================================
//行計數
//---------------------------------------------------
always @(posedge cmos_pclk or negedge rst_n) begin
    if(!rst_n)
        cnt_h <= 'd0;
    else if(add_cnt_h) begin
        if(end_cnt_h)
            cnt_h <= 'd0;
        else
            cnt_h <= cnt_h + 1'b1;
    end
end

assign add_cnt_h = rgb565_vld;
assign end_cnt_h = add_cnt_h && cnt_h== 640-1;

//場計數
//---------------------------------------------------
always @(posedge cmos_pclk or negedge rst_n) begin 
    if(!rst_n)
        cnt_v <= 'd0;
    else if(add_cnt_v) begin
        if(end_cnt_v)
            cnt_v <= 'd0;
        else
            cnt_v <= cnt_v + 1'b1;
    end
end

assign add_cnt_v = end_cnt_h;
assign end_cnt_v = add_cnt_v && cnt_v== 480-1;

//裁剪后的數據:適配TFT屏
//---------------------------------------------------
assign rgb_data = rgb565_data;
assign rgb_vld  = rgb565_vld && (cnt_h >= H_START) && (cnt_h < H_STOP)
                             && (cnt_v >= V_START) && (cnt_v < V_STOP);

  這樣得到的 rgb_data 和 rgb_vld 就是裁剪后的數據了,且只是裁剪,數據本身沒有移位,總體時序沒有變化。

 

六、幀率fps計算

  通過巧妙的設計,我們就能夠實時的得到當前 fps 的數值。其思想很簡單,即計算 1s 時間內,來了多少次 cmos_vsync 即可。具體設計思想如下所示:

(1)通過一個確定的時鍾對 cmos_vsync 進行打拍,求得其上升沿;

(2)通過該確定的時鍾再進行 1s 時間的計數,每當計滿 1s 則清0重新計數;

(3)對(1)求得的 cmos_vsync 上升沿進行計數,每當(2)的 1s 計滿時,清 0 重新計數;

(4)對(3)在 1s 計滿時那一刻的 cmos_vsync 上升沿數目寄存並輸出,得到 fps 值。

  這樣看文字比較麻煩,好像很難一樣,上代碼吧:

//==========================================================================
//==   幀率計算,不能用pclk時鍾,需重新捕捉vsync_pos
//==========================================================================
//vsync上升沿
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n)
        frame_vsync_r <= 1'b0;
    else
        frame_vsync_r <= frame_vsync;
end

assign frame_vsync_pos = (~frame_vsync_r & frame_vsync);

//1s時間
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n)
        cnt_1s <= 'd0;
    else if(add_cnt_1s) begin
        if(end_cnt_1s)
            cnt_1s <= 'd0;
        else
            cnt_1s <= cnt_1s + 1'b1;
    end
end

assign add_cnt_1s = frame_vld;
assign end_cnt_1s = add_cnt_1s && cnt_1s== TIME_1S-1;

//1s時間內的vsync次數
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n)
        cnt_fps <= 'd0;
    else if(end_cnt_1s) begin
        cnt_fps <= 'd0;
    end
    else if(frame_vld && frame_vsync_pos)begin
        cnt_fps <= cnt_fps + 'd1;
    end
end

//實時更新幀率值
//---------------------------------------------------
always @(posedge clk_24m or negedge rst_n) begin
    if(!rst_n) begin
        fps_rate <= 'd0;
    end
    else if(end_cnt_1s) begin
        fps_rate <= cnt_fps;
    end
end

  注釋中特別提到,不能用 Pclk 來計算,必須是外部引入的確定的時鍾。這點在 CrazyBingo 韓彬的代碼中沒有處理好,算是一個小 bug。最后我們將幀率值 fps_rate 引到端口,輸送給數碼管顯示模塊,就能夠實時的知道當前采集的圖像幀率值了。

 

  至此,攝像頭模塊的部分算是講解完了,講得很粗,大把的貼代碼。一是因為網上關於這方面的資料是在是太多了,講得都比我總結的好,二是這些代碼其實大部分都是我改的別家的,不算原創,也不盈利,所以貼出來。

  還沒有結束,下一講我再整理一下攝像頭顯示工程中,攝像頭以外的一些關鍵點。

 

參考資料:

[1]正點原子FPGA教程

[2]小梅哥《OV5640圖像采集從原理到應用》

[3]開源騷客《SDRAM那些事兒》

[4]韓彬, 於瀟宇, 張雷鳴. FPGA設計技巧與案例開發詳解[M]. 電子工業出版社, 2014.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM