第1节
–作者:小黑同学
本文为明德扬原创及录用文章,转载请注明出处!

1.1 总体设计

1.1.1 概述

OV7670是一种图像传感器,图像传感器,体积小,工作电压低,提供单片VGA摄像头和影像处理器的所有功能。通过SCCB总线控制,可以输入整帧、子采样、取窗口等方式的各种分辨率8位影像数据。该产品VGA图像最高达到30帧/秒。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、饱和度、色度等都可以通过SCCB接口编程。OmmiVision图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、托尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。

1.1.2 设计目标

本工程使用ov7670摄像头、SDRAM、VGA显示器、按键等,实现摄像头显示的功能,具体要求如下:
1、 通过SCCB接口对摄像头中的寄存器进行配置。
2、 通过按键/矩阵键盘控制是否对摄像头进行配置。
3、 对摄像头输出的图像数据进行采集之后,通过SDRAM进行缓存
4、 通过VGA显示器显示摄像头采集到的图像。

1.1.3 系统结构框图

系统结构框图如下图一所示:

图一

1.1.4 模块功能

 按键检测模块实现功能
1、 将外来异步信号打两拍处理,将异步信号同步化。
2、 实现20ms按键消抖功能。
3、 实现矩阵键盘或者普通案件的检测功能,并输出有效按键信号。
 锁相环PLL_1
1、 将摄像头输出的像素时钟转化为同频同相的25M时钟。
 锁相环PLL_2
1、产生摄像头需要的25M时钟xlclk。
2、 产生SDRAM工作需要的100M时钟。
 配置模块模块实现功能
1、 根据按键信息,将配置表中的寄存器信息读出。
2、 根据SCCB接口模块的指示,将配置信息输出到SCCB模块。
 SCCB接口模块实现功能
1、 根据上游模块的读写使能命令、地址以及数据,产生对应的SCCB时序,从而将数据写入摄像头指定的寄存器中,或者从指定的摄像头寄存器中读取数据。
 图像采集模块实现功能
1、 根据摄像头输出格式,对像素数据进行接收。
2、 对摄像头输出数据进行串并转换。
 存储控制模块实现功能
1、 通过乒乓操作的方法,控制SDRAM的读写以及Bank地址,以保证图像数据连续显示到显示器上。
 SDRAM接口模块实现功能
1、 接收上游模块发送的读/写请求、Bank地址、行地址和写数据,产生SDRAM的控制时序。
 VGA接口模块实现功能
1、 产生VGA时序,在有效显示区域,将图像数据送入显示器进行显示。

1.1.5 顶层信号

信号名 I/O 位宽 定义
clk I 1 系统工作时钟 50M
rst_n I 1 系统复位信号,低电平有效
Key_in I 4 4位按键信号,开发板按键为矩阵键盘时,不需要该信号
Key_col I 4 4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号
Key_row O 4 4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号
dq I/O 16 SDRAM数据总线,既能作为数据输出,也能作为数据输入。
cke O 1 SDRAM时钟使能信号,决定是否启用clk输入,为高电平时,时钟有效。
cs O 1 SDRAM片选信号,决定设备内是否启用命令输入,当cs为低时启用,当cs为高时禁用命令输入。
ras O 1 行地址选通信号,低电平有效
cas O 1 列地址选通信号,低电平有效
we O 1 写使能信号,低电平有效
dqm O 2 数据掩码,控制I/O的高低字节,低电平有效。例如:2’b10,表示数据高字节无效,低字节有效。
sd_addr O 13 SDRAM地址信号。
sd_bank O 2 Bank地址选择信号,通过该信号决定哪个Bank正处于激活、读、写、预充电等命令期间。
sd_clk O 1 SDRAM输入时钟,除cke外,SDRAM的所有输入与该引脚的上升沿同步获得。
Pclk I 1 摄像头输出像素时钟
vsync I 1 图像场同步信号
href I 1 图像行同步信号
din I 8 摄像头输出的数据
Xclk O 1 摄像头驱动时钟,25M
Pwdn O 1 摄像头待机状态指示信号,该信号为1表示,摄像头处于待机状态,不工作,该信号为0,摄像头处于唤醒状态。
Sio_c O 1 SCCB时钟
Sio_d I/O 1 SCCB数据线,既能作为输入也能作为输出
Vga_hys O 1 VGA行同步信号
Vga_vys O 1 VGA场同步信号
Vga_rgb O 16 VGA图像数据

1.1.6 参考代码

下面是使用工程的顶层代码:

1.   module sobel_edge_dection(
2.      clk         ,
3.      rst_n       ,
4.      key_in      ,
5.      pclk        ,
6.      vsync       ,
7.      href        ,
8.      din         ,
9.      xclk        ,
10.     pwdn        ,
11.     sio_c       ,
12.     sio_d       ,
13.     vga_hys     ,
14.     vga_vys     ,
15.     vga_rgb     ,
16.     cke         ,
17.     cs          ,
18.     ras         ,
19.     cas         ,
20.     we          ,
21.     dqm         ,
22.     sd_addr     ,
23.     sd_bank     ,
24.     sd_clk      ,
25.     input               clk           ;
26.     input               rst_n         ;
27.     input               pclk          ;
28.     input  [3:0]        key_in        ;
29.     input               vsync         ;
30.     input               href          ;
31.     input  [7:0]        din           ;
32.
33.     output              xclk          ;
34.     output              pwdn          ;
35.     output              vga_hys       ;
36.     output              vga_vys       ;
37.     output [15:0]       vga_rgb       ;
38.     output              sio_c         ;
39.     output              cs            ;
40.     output              ras           ;
41.     output              cas           ;
42.     output              we            ;
43.     output [1 :0]       dqm           ;
44.     output [12:0]       sd_addr       ;
45.     output [1 :0]       sd_bank       ;
46.     output              sd_clk        ;
47.     output              cke           ;
48.
49.
50.
51.     inout  [15:0]       dq            ;
52.     inout               sio_d         ;
53.     wire   [15:0]       dq_in         ;
54.     wire   [15:0]       dq_out        ;
55.     wire                dq_out_en     ;
56.     wire                en_sio_d_w    ;
57.     wire                sio_d_w       ;
58.     wire                sio_d_r       ;
59.     wire                xclk_         ;
60.     wire                clk_100m      ;
61.     wire                locked        ;
62.     wire   [3:0]        key_num       ;
63.     wire                en_coms       ;
64.     wire   [7:0]        value_gray    ;
65.     wire                rdy           ;
66.     wire                wen           ;
67.     wire                ren           ;
68.     wire   [7:0]        wdata         ;
69.     wire                capture_en    ;
70.     wire   [7:0]        rdata         ;
71.     wire                rdata_vld     ;
72.     wire   [15:0]       cmos_dout     ;
73.     wire                cmos_dout_vld ;
74.     wire                cmos_dout_sop ;
75.     wire                cmos_dout_eop ;
76.     wire   [15:0]       rd_addr       ;
77.     wire                rd_en         ;
78.     wire   [15:0]       vga_data      ;
79.     wire                rd_end        ;
80.     wire                wr_end        ;
81.     wire                rd_addr_sel   ;
82.     wire   [3:0]        key_vld       ;
83.     wire                display_area  ;
84.     wire   [7:0]        sub_addr      ;
85.     wire                cke           ;
86.     wire                cs            ;
87.     wire                ras           ;
88.     wire                cas           ;
89.     wire                we            ;
90.     wire   [1 :0]       dqm           ;
91.     wire   [12:0]       sd_addr       ;
92.     wire   [1 :0]       sd_bank       ;
93.     wire                sd_clk        ;
94.     wire   [15:0]       sd_rdata      ;
95.     wire                sd_rdata_vld  ;
96.     wire   [15:0]       fifo2sd_wdata ;
97.     wire                wr_ack        ;
98.     wire                rd_ack        ;
99.     wire                wr_req        ;
100.        wire                rd_req        ;
101.        wire   [1 :0]       bank          ;
102.        wire   [12:0]       addr          ;
103.
104.        assign  dq_in = dq;
105.        assign  dq    = dq_out_en?dq_out:16'hzzzz;
106.
107.
108.        assign sio_d = en_sio_d_w ? sio_d_w : 1'dz;
109.        assign sio_d_r = sio_d;
110.
111.
112.
113.
114.
115.        pll_sd  pll_sd_inst2 (
116.            .inclk0       (clk           ),
117.            .c0           (xclk          ),
118.            .c1           (clk_100m      )
119.        );
120.
121.
122.        cmos_pll  u_cmos_pll(
123.
124.            .inclk0       (pclk         ),
125.            .c0           (clk_25M       )
126.        );
127.
128.
129.
130.        key_module#(.KEY_W(4)) u_key_module(
131.            .clk          (xclk         ),
132.            .rst_n        (rst_n        ),
133.            .key_in       (key_in       ),
134.            .key_vld      (key_vld      )
135.        );
136.
137.        ov7670_config u4(
138.            .clk          (xclk         ),
139.            .rst_n        (rst_n        ),
140.            .config_en    (key_vld[1]   ),
141.            .rdy          (rdy          ),
142.            .rdata        (rdata        ),
143.            .rdata_vld    (rdata_vld    ),
144.            .wdata        (wdata        ),
145.            .addr         (sub_addr     ),
146.            .wr_en        (wen          ),
147.            .rd_en        (ren          ),
148.            .cmos_en      (en_capture   ),
149.            .pwdn         (pwdn         )
150.        );
151.
152.        sccb u5(
153.            .clk          (xclk         ),
154.            .rst_n        (rst_n        ),
155.            .ren          (ren          ),
156.            .wen          (wen          ),
157.            .sub_addr     (sub_addr     ),
158.            .rdata        (rdata        ),
159.            .rdata_vld    (rdata_vld    ),
160.            .wdata        (wdata        ),
161.            .rdy          (rdy          ),
162.            .sio_c        (sio_c        ),
163.            .sio_d_r      (sio_d_r      ),
164.            .en_sio_d_w   (en_sio_d_w   ),
165.            .sio_d_w      (sio_d_w      )
166.        );
167.
168.        cmos_capture u6(
169.            .clk          (clk_25M       ),
170.            .rst_n        (rst_n        ),
171.            .en_capture   (en_capture   ),
172.            .vsync        (vsync        ),
173.            .href         (href         ),
174.            .din          (din          ),
175.            .dout         (cmos_dout    ),
176.            .dout_vld     (cmos_dout_vld),
177.            .dout_sop     (cmos_dout_sop),
178.            .dout_eop     (cmos_dout_eop)
179.        );
180.
181.
182.        vga_config u11(
183.            .clk          (clk_25M       ),
184.            .clk_in       (clk_100m     ),
185.            .rst_n        (rst_n        ),
186.            .din          (cmos_dout    ),
187.            .din_vld      (cmos_dout_vld),
188.            .din_sop      (cmos_dout_sop),
189.            .din_eop      (cmos_dout_eop),
190.            .dout         (vga_data     ),
191.            .wr_req       (wr_req       ),
192.            .rd_req       (rd_req       ),
193.            .wr_ack       (wr_ack       ),
194.            .rd_ack       (rd_ack       ),
195.            .wdata        (fifo2sd_wdata),
196.            .sd_rdata     (sd_rdata     ),
197.            .sd_rdata_vld (sd_rdata_vld ),
198.            .display_area (display_area ),
199.            .bank         (bank         ),
200.            .addr         (addr         )
201.        );
202.
203.
204.         sdram_intf u20   (
205.            .clk          (clk_100m     ),
206.            .rst_n        (rst_n        ),
207.            .wr_req       (wr_req       ),
208.            .rd_req       (rd_req       ),
209.            .dq_in        (dq_in        ),
210.            .dq_out       (dq_out       ),
211.            .dq_out_en    (dq_out_en    ),
212.            .wr_ack       (wr_ack       ),
213.            .rd_ack       (rd_ack       ),
214.            .rdata        (sd_rdata     ),
215.            .rdata_vld    (sd_rdata_vld ),
216.            .cke          (cke          ),
217.            .cs           (cs           ),
218.            .ras          (ras          ),
219.            .cas          (cas          ),
220.            .we           (we           ),
221.            .dqm          (dqm          ),
222.            .sd_addr      (sd_addr      ),
223.            .sd_bank      (sd_bank      ),
224.            .sd_clk       (sd_clk       ),
225.            .wdata        (fifo2sd_wdata),
226.            .bank         (bank         ),
227.            .addr         (addr         )
228.
229.         );
230.
231.
232.        vga_driver u12(
233.            .clk         (clk_25M        ),
234.            .rst_n       (rst_n         ),
235.            .din         (vga_data      ),
236.            .vga_hys     (vga_hys       ),
237.            .vga_vys     (vga_vys       ),
238.            .vga_rgb     (vga_rgb       ),
239.            .display_area(display_area  )
240.        );
241.
242.
243.    endmodule

1.2 按键检测模块设计

1.2.1 接口信号

下面为使用矩阵键盘时的接口信号:

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
key_col 输入 矩阵键盘列输入信号
Key_row 输出 矩阵键盘行输出信号
Key_en 输出 按键按下位置指示信号

下面是使用普通按键时的接口信号:

信号 接口方向 定义
clk 输入 系统时钟
rst_n 输入 低电平复位信号
Key_in 输入 按键输入信号
Key_vld 输出 按键按下指示信号

1.2.2 设计思路

在前面的案例中已经有按键检测的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
【每周FPGA案例】至简设计系列_按键控制数字时钟

1.2.3 参考代码

1.   //矩阵键盘
2.  always  @(posedge clk or negedge rst_n)begin
3.      if(rst_n==1'b0)begin
4.          key_col_ff0 <= 4'b1111;
5.          key_col_ff1 <= 4'b1111;
6.      end
7.      else begin
8.          key_col_ff0 <= key_col    ;
9.          key_col_ff1 <= key_col_ff0;
10.     end
11. end
12.
13.
14. always @(posedge clk or negedge rst_n) begin
15.     if (rst_n==0) begin
16.         shake_cnt <= 0;
17.     end
18.     else if(add_shake_cnt) begin
19.         if(end_shake_cnt)
20.             shake_cnt <= 0;
21.         else
22.             shake_cnt <= shake_cnt+1 ;
23.    end
24. end
25. assign add_shake_cnt = key_col_ff1!=4'hf;
26. assign end_shake_cnt = add_shake_cnt  && shake_cnt == TIME_20MS-1 ;
27.
28.
29. always  @(posedge clk or negedge rst_n)begin
30.     if(rst_n==1'b0)begin
31.         state_c <= CHK_COL;
32.     end
33.     else begin
34.         state_c <= state_n;
35.     end
36. end
37.
1.  always  @(*)begin
38.     case(state_c)
39.         CHK_COL: begin
40.                      if(col2row_start )begin
41.                          state_n = CHK_ROW;
42.                      end
43.                      else begin
44.                          state_n = CHK_COL;
45.                      end
46.                  end
47.         CHK_ROW: begin
48.                      if(row2del_start)begin
49.                          state_n = DELAY;
50.                      end
51.                      else begin
52.                          state_n = CHK_ROW;
53.                      end
54.                  end
55.         DELAY :  begin
56.                      if(del2wait_start)begin
57.                          state_n = WAIT_END;
58.                      end
59.                      else begin
60.                          state_n = DELAY;
61.                      end
62.                  end
63.         WAIT_END: begin
64.                      if(wait2col_start)begin
65.                          state_n = CHK_COL;
66.                      end
67.                      else begin
68.                          state_n = WAIT_END;
69.                      end
70.                   end
71.        default: state_n = CHK_COL;
72.     endcase
73. end
74. assign col2row_start = state_c==CHK_COL  && end_shake_cnt;
75. assign row2del_start = state_c==CHK_ROW  && row_index==3 && end_row_cnt;
76. assign del2wait_start= state_c==DELAY    && end_row_cnt;
77. assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;
78.
79. always  @(posedge clk or negedge rst_n)begin
80.     if(rst_n==1'b0)begin
81.         key_row <= 4'b0;
82.     end
83.     else if(state_c==CHK_ROW)begin
84.         key_row <= ~(1'b1 << row_index);
85.     end
86.     else begin
87.         key_row <= 4'b0;
88.     end
89. end
90.
91.
92. always @(posedge clk or negedge rst_n) begin
93.     if (rst_n==0) begin
94.         row_index <= 0;
95.     end
96.     else if(add_row_index) begin
97.         if(end_row_index)
98.             row_index <= 0;
99.         else
100.                row_index <= row_index+1 ;
101.       end
102.       else if(state_c!=CHK_ROW)begin
103.           row_index <= 0;
104.       end
105.    end
106.    assign add_row_index = state_c==CHK_ROW && end_row_cnt;
107.    assign end_row_index = add_row_index  && row_index == 4-1 ;
108.
109.
110.    always @(posedge clk or negedge rst_n) begin
111.        if (rst_n==0) begin
112.            row_cnt <= 0;
113.        end
114.        else if(add_row_cnt) begin
115.            if(end_row_cnt)
116.                row_cnt <= 0;
117.            else
118.                row_cnt <= row_cnt+1 ;
119.       end
120.    end
121.    assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
122.    assign end_row_cnt = add_row_cnt  && row_cnt == 16-1 ;
123.
124.
125.    always  @(posedge clk or negedge rst_n)begin
126.        if(rst_n==1'b0)begin
127.            key_col_get <= 0;
128.        end
129.        else if(state_c==CHK_COL && end_shake_cnt ) begin
130.            if(key_col_ff1==4'b1110)
131.                key_col_get <= 0;
132.            else if(key_col_ff1==4'b1101)
133.                key_col_get <= 1;
134.            else if(key_col_ff1==4'b1011)
135.                key_col_get <= 2;
136.            else
137.                key_col_get <= 3;
138.        end
139.    end
140.
141.
142.    always  @(posedge clk or negedge rst_n)begin
143.        if(rst_n==1'b0)begin
144.            key_out <= 0;
145.        end
146.        else if(state_c==CHK_ROW && end_row_cnt)begin
147.            key_out <= {row_index,key_col_get};
148.        end
149.        else begin
150.            key_out <= 0;
151.        end
152.    end
153.
154.    always  @(posedge clk or negedge rst_n)begin
155.        if(rst_n==1'b0)begin
156.            key_vld <= 1'b0;
157.        end
158.        else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin
159.            key_vld <= 1'b1;
160.        end
161.        else begin
162.            key_vld <= 1'b0;
163.        end
164.    end
165.
166.
167.    always  @(*)begin
168.        if(rst_n==1'b0)begin
169.            key_en = 0;
170.        end
171.        else if(key_vld && key_out==0)begin
172.            key_en = 4'b0001;
173.        end
174.        else if(key_vld && key_out==1)begin
175.            key_en = 4'b0010;
176.        end
177.        else if(key_vld && key_out==2)begin
178.            key_en = 4'b0100;
179.        end
180.        else begin
181.            key_en = 0;
182.        end
183.    End
184.
185.    /*********************
186.    普通按键
187.    *********************/
188.    always  @(posedge clk or negedge rst_n)begin
189.        if(rst_n==1'b0)begin
190.            cnt <= 20'b0;
191.        end
192.        else if(add_cnt)begin
193.            if(end_cnt)
194.                cnt <= 20'b0;
195.            else
196.                cnt <= cnt + 1'b1;
197.        end
198.        else begin
199.            cnt <= 0;
200.        end
201.    end
202.
203.    assign add_cnt = flag_add==1'b0 && (&key_in_ff1==0);
204.    assign end_cnt = add_cnt && cnt == TIME_20MS - 1;
205.
206.
207.    always  @(posedge clk or negedge rst_n)begin
208.        if(rst_n==1'b0)begin
209.            flag_add <= 1'b0;
210.        end
211.        else if(end_cnt)begin
212.            flag_add <= 1'b1;
213.        end
214.        else if(&key_in_ff1==1)begin
215.            flag_add <= 1'b0;
216.        end
217.    end
218.
219.
220.    always  @(posedge clk or negedge rst_n)begin
221.        if(rst_n==1'b0)begin
222.            key_in_ff0 <= {{KEY_W}{1'b1}};
223.            key_in_ff1 <= {{KEY_W}{1'b1}};
224.        end
225.        else begin
226.            key_in_ff0 <= key_in    ;
227.            key_in_ff1 <= key_in_ff0;
228.        end
229.    end
230.
231.
232.    always  @(posedge clk or negedge rst_n)begin
233.        if(rst_n==1'b0)begin
234.            key_vld <= 0;
235.        end
236.        else if(end_cnt)begin
237.            key_vld <= ~key_in_ff1;
238.        end
239.        else begin
240.            key_vld <= 0;
241.        end
242.    end

1.3 锁相环

1.3.1 接口信号

PLL_1:pll_sd

信号名 I/O 位宽 定义
Inclk0 I 1 输入时钟50M
C0 O 1 输出时钟25M
C1 O 1 输出时钟100M

PLL_2:cmos_pll

信号名 I/O 位宽 定义
Inclk0 I 1 输入时钟25M
C0 O 1 输出时钟25M

1.3.2 设计思路

此模块是使用Quartus生成的PLL IP核,相关的生成步骤、功能原理等可以看明德扬论坛中关于PLL的介绍。
IP核设计(PLL)

1.3.3 时钟网络

本工程共有5个时钟,硬件上的晶振提供的50M时钟。由于摄像头的驱动时钟为25M,因此需要由锁相环产生一个25M的时钟xclk,摄像头输出图像数据的同时,会输出一个像素时钟pclk,该时钟是和图像数据对齐的,由于摄像头属于外设,是临时插在开发板上的,因此,插得不牢、晃动、碰撞等,都有可能造成摄像头输出的时钟pclk的不稳定,因此这里将此时钟经过锁相环,产生一个同频同相的稳定时钟clk_25M,作为之后图像处理模块的工作时钟。而SDRAM的工作时钟是100M,因此需要通过锁相环产生100M的时钟。通过架构图中模块的颜色来进行判别,按键检测模块、配置模块和SCCB模块都是采用的xclk;图像采集模块、存储控制模块和VGA接口模块都是采用的clk_25M;SDRAM接口模块的工作时钟为100M。

1.4 配置模块设计

1.4.1 接口信号

信号名 I/O 位宽 定义
clk I 1 工作时钟 100M
rst_n I 1 系统复位信号,低电平有效
Config_en I 1 开始配置指示脉冲
rdy I 1 下游模块准备好指示信号
rdata I 8 从摄像头中读取的数据
rdata_vld I 1 读取数据有效指示信号
Wdata O 8 将要写到摄像头中的数据
Wr_en O 2 写使能
Addr O 8 摄像头寄存器地址信号
rd_en O 1 读使能
Cmos_en O 1 配置完成指示信号
Pwdn O 1 摄像头待机状态指示信号

1.4.2 配置表

需要配置的寄存器信息都在配置表文件中保存,通过寄存器计数器reg_cnt进行读取,18bit位宽的信号add_wdata为保存配置信息的信号,其中add_wdata[17]表示读属性,当其为1时,表示该寄存器可读;daa_wdata[16]表示写属性,当其为1时,表示该寄存器可写;add_wdata[17:8]表示寄存器地址;add_wdata[7:0]表示要往寄存器中写入的值。
寄存器地址表示的寄存器以及数据每一位代表的意思,可以参考ov7670_中文版数据手册
OV7670摄像头模块资料及参考学习资料
举例说明:add_wdata={2’b11,16’h1e31},其中2’b11表示该寄存器可读可写,寄存器地址为8’h1e,查数据手册可知,寄存器名为MVFP,表示水平镜像/竖直翻转使能。数据的位[7:8]作为保留位,没有作用;位[5]表示水平镜像使能,0为正常,1为镜像;位[4]表示竖直翻转使能,0为正常,1为翻转;位[3]保留;位[2]表示消除黑太阳使能;位[1:0]保留。写数据为8’h31,表示开启水平镜像和竖直翻转。
注意,该文件不可综合,不要添加到编译软件中综合,否则会报错,只需要将此文件放到工程目录下即可。

1.4.3 参考代码

1.   parameter      REG_NUM =       164;
2.  always@(*) begin
3.          case(reg_cnt)
4.              0   : add_wdata = {2'b11,16'h1204};
5.              1   : add_wdata = {2'b11,16'h40d0};
6.              2   : add_wdata = {2'b11,16'h3a04};
7.              3   : add_wdata = {2'b11,16'h3dc8};
8.              4   : add_wdata = {2'b11,16'h1e31};
9.              5   : add_wdata = {2'b11,16'h6b00};
10.             6   : add_wdata = {2'b11,16'h32b6};
11.             7   : add_wdata = {2'b11,16'h1713};
12.             8   : add_wdata = {2'b11,16'h1801};
13.             9   : add_wdata = {2'b11,16'h1902};
14.             10  : add_wdata = {2'b11,16'h1a7a};
15.             11  : add_wdata = {2'b11,16'h030a};
16.             12  : add_wdata = {2'b11,16'h0c00};
17.             13  : add_wdata = {2'b11,16'h3e10};
18.             14  : add_wdata = {2'b11,16'h7000};
19.             15  : add_wdata = {2'b11,16'h7100};
20.             16  : add_wdata = {2'b11,16'h7211};
21.             17  : add_wdata = {2'b11,16'h7300};
22.             18  : add_wdata = {2'b11,16'ha202};
23.             19  : add_wdata = {2'b11,16'h1180};
24.             20  : add_wdata = {2'b11,16'h7a20};
25.             21  : add_wdata = {2'b11,16'h7b1c};
26.             22  : add_wdata = {2'b11,16'h7c28};
27.             23  : add_wdata = {2'b11,16'h7d3c};
28.             24  : add_wdata = {2'b11,16'h7e55};
29.             25  : add_wdata = {2'b11,16'h7f68};
30.             26  : add_wdata = {2'b11,16'h8076};
31.             27  : add_wdata = {2'b11,16'h8180};
32.             28  : add_wdata = {2'b11,16'h8288};
33.             29  : add_wdata = {2'b11,16'h838f};
34.             30  : add_wdata = {2'b11,16'h8496};
35.             31  : add_wdata = {2'b11,16'h85a3};
36.             32  : add_wdata = {2'b11,16'h86af};
37.             33  : add_wdata = {2'b11,16'h87c4};
38.             34  : add_wdata = {2'b11,16'h88d7};
39.             35  : add_wdata = {2'b11,16'h89e8};
40.             36  : add_wdata = {2'b11,16'h13e0};
41.             37  : add_wdata = {2'b11,16'h0010};
42.             38  : add_wdata = {2'b11,16'h1000};
43.             39  : add_wdata = {2'b11,16'h0d00};
44.             40  : add_wdata = {2'b11,16'h1428};
45.             41  : add_wdata = {2'b11,16'ha505};
46.             42  : add_wdata = {2'b11,16'hab07};
47.             43  : add_wdata = {2'b11,16'h2475};
48.             44  : add_wdata = {2'b11,16'h2563};
49.             45  : add_wdata = {2'b11,16'h26a5};
50.             46  : add_wdata = {2'b11,16'h9f78};
51.             47  : add_wdata = {2'b11,16'ha068};
52.             48  : add_wdata = {2'b11,16'ha103};
53.             49  : add_wdata = {2'b11,16'ha6df};
54.             50  : add_wdata = {2'b11,16'ha7df};
55.             51  : add_wdata = {2'b11,16'ha8f0};
56.             52  : add_wdata = {2'b11,16'ha990};
57.             53  : add_wdata = {2'b11,16'haa94};
58.             54  : add_wdata = {2'b11,16'h13ef};
59.             55  : add_wdata = {2'b11,16'h0e61};
60.             56  : add_wdata = {2'b11,16'h0f4b};
61.             57  : add_wdata = {2'b11,16'h1602};
62.             58  : add_wdata = {2'b11,16'h2102};
63.             59  : add_wdata = {2'b11,16'h2291};
64.             60  : add_wdata = {2'b11,16'h2907};
65.             61  : add_wdata = {2'b11,16'h330b};
66.             62  : add_wdata = {2'b11,16'h350b};
67.             63  : add_wdata = {2'b11,16'h371d};
68.             64  : add_wdata = {2'b11,16'h3871};
69.             65  : add_wdata = {2'b11,16'h392a};
70.             66  : add_wdata = {2'b11,16'h3c78};
71.             67  : add_wdata = {2'b11,16'h4d40};
72.             68  : add_wdata = {2'b11,16'h4e20};
73.             69  : add_wdata = {2'b11,16'h6900};
74.
75.             70  : add_wdata = {2'b11,16'h7419};
76.             71  : add_wdata = {2'b11,16'h8d4f};
77.             72  : add_wdata = {2'b11,16'h8e00};
78.             73  : add_wdata = {2'b11,16'h8f00};
79.             74  : add_wdata = {2'b11,16'h9000};
80.             75  : add_wdata = {2'b11,16'h9100};
81.             76  : add_wdata = {2'b11,16'h9200};
82.             77  : add_wdata = {2'b11,16'h9600};
83.             78  : add_wdata = {2'b11,16'h9a80};
84.             79  : add_wdata = {2'b11,16'hb084};
85.             80  : add_wdata = {2'b11,16'hb10c};
86.             81  : add_wdata = {2'b11,16'hb20e};
87.             82  : add_wdata = {2'b11,16'hb382};
88.             83  : add_wdata = {2'b11,16'hb80a};
89.
90.             84  : add_wdata = {2'b11,16'h4314};
91.             85  : add_wdata = {2'b11,16'h44f0};
92.             86  : add_wdata = {2'b11,16'h4534};
93.             87  : add_wdata = {2'b11,16'h4658};
94.             88  : add_wdata = {2'b11,16'h4728};
95.             89  : add_wdata = {2'b11,16'h483a};
96.             90  : add_wdata = {2'b11,16'h5988};
97.             91  : add_wdata = {2'b11,16'h5a88};
98.             92  : add_wdata = {2'b11,16'h5b44};
99.             93  : add_wdata = {2'b11,16'h5c67};
100.                94  : add_wdata = {2'b11,16'h5d49};
101.                95  : add_wdata = {2'b11,16'h5e0e};
102.                96  : add_wdata = {2'b11,16'h6404};
103.                97  : add_wdata = {2'b11,16'h6520};
104.                98  : add_wdata = {2'b11,16'h6605};
105.                99  : add_wdata = {2'b11,16'h9404};
106.                100 : add_wdata = {2'b11,16'h9508};
107.                101 : add_wdata = {2'b11,16'h6c0a};
108.                102 : add_wdata = {2'b11,16'h6d55};
109.                103 : add_wdata = {2'b11,16'h6e11};
110.                104 : add_wdata = {2'b11,16'h6f9f};
111.                105 : add_wdata = {2'b11,16'h6a40};
112.                106 : add_wdata = {2'b11,16'h0140};
113.                107 : add_wdata = {2'b11,16'h0240};
114.                108 : add_wdata = {2'b11,16'h13e7};
115.                109 : add_wdata = {2'b11,16'h1500};
116.
117.                110 : add_wdata = {2'b11,16'h4f80};
118.                111 : add_wdata = {2'b11,16'h5080};
119.                112 : add_wdata = {2'b11,16'h5100};
120.                113 : add_wdata = {2'b11,16'h5222};
121.                114 : add_wdata = {2'b11,16'h535e};
122.                115 : add_wdata = {2'b11,16'h5480};
123.                116 : add_wdata = {2'b11,16'h589e};
124.
125.                117 : add_wdata = {2'b11,16'h4108};
126.                118 : add_wdata = {2'b11,16'h3f00};
127.                119 : add_wdata = {2'b11,16'h7505};
128.                120 : add_wdata = {2'b11,16'h76e1};
129.                121 : add_wdata = {2'b11,16'h4c00};
130.                122 : add_wdata = {2'b11,16'h7701};
131.
132.                123 : add_wdata = {2'b11,16'h4b09};
133.                124 : add_wdata = {2'b11,16'hc9F0};//16'hc960;
134.                125 : add_wdata = {2'b11,16'h4138};
135.                126 : add_wdata = {2'b11,16'h5640};
136.
137.
138.                127 : add_wdata = {2'b11,16'h3411};
139.                128 : add_wdata = {2'b11,16'h3b02};
140.                129 : add_wdata = {2'b11,16'ha489};
141.                130 : add_wdata = {2'b11,16'h9600};
142.                131 : add_wdata = {2'b11,16'h9730};
143.                132 : add_wdata = {2'b11,16'h9820};
144.                133 : add_wdata = {2'b11,16'h9930};
145.                134 : add_wdata = {2'b11,16'h9a84};
146.                135 : add_wdata = {2'b11,16'h9b29};
147.                136 : add_wdata = {2'b11,16'h9c03};
148.                137 : add_wdata = {2'b11,16'h9d4c};
149.                138 : add_wdata = {2'b11,16'h9e3f};
150.                139 : add_wdata = {2'b11,16'h7804};
151.
152.
153.                 140 :add_wdata =  {2'b11,16'h7901};
154.                 141 :add_wdata =  {2'b11,16'hc8f0};
155.                 142 :add_wdata =  {2'b11,16'h790f};
156.                 143 :add_wdata =  {2'b11,16'hc800};
157.                 144 :add_wdata =  {2'b11,16'h7910};
158.                 145 :add_wdata =  {2'b11,16'hc87e};
159.                 146 :add_wdata =  {2'b11,16'h790a};
160.                 147 :add_wdata =  {2'b11,16'hc880};
161.                 148 :add_wdata =  {2'b11,16'h790b};
162.                 149 :add_wdata =  {2'b11,16'hc801};
163.                 150 :add_wdata =  {2'b11,16'h790c};
164.                 151 :add_wdata =  {2'b11,16'hc80f};
165.                 152 :add_wdata =  {2'b11,16'h790d};
166.                 153 :add_wdata =  {2'b11,16'hc820};
167.                 154 :add_wdata =  {2'b11,16'h7909};
168.                 155 :add_wdata =  {2'b11,16'hc880};
169.                 156 :add_wdata =  {2'b11,16'h7902};
170.                 157 :add_wdata =  {2'b11,16'hc8c0};
171.                 158 :add_wdata =  {2'b11,16'h7903};
172.                 159 :add_wdata =  {2'b11,16'hc840};
173.                 160 :add_wdata =  {2'b11,16'h7905};
174.                 161 :add_wdata =  {2'b11,16'hc830};
175.                 162 :add_wdata =  {2'b11,16'h7926};
176.
177.                 163 : add_wdata = {2'b11,16'h0903};
178.                 164 : add_wdata = {2'b11,16'h3b42};
179.
180.            default : add_wdata = 0;
181.            endcase
182.        end

1.4.4 配置模块设计思路

该模块主要功能是将配置表中的数据读出,并对读出的数据进行解析,识别读写属性,根据读写属性,产生读写使能;识别寄存器地址和写数据,并将其分离。
由于配置表文件不可综合,因此无法通过例化获取,可以通过以下语句进行调用:

该模块产生的读使能、写使能、寄存器地址和写数据,都会送给下游SCCB接口模块,而SCCB接口属于单总线接口。不能同时读写,因此,该模块在对配置表进行读取的时候,需要读取两次,先进行写,然后在进行读,该操作由计数器控制,如下图所示:

读写计数器rw_cnt:该计数器表示读写阶段,数第一个表示处于写数据阶段,数第二个表示处于读数据阶段。加一条件为(flag && rdy),表示按下配置开始按键,并且下游模块准备好的时候就加一;结束条件为数两个,只需要区分读阶段和写阶段,因此数两个即可,数完就清零。
待配置的寄存器共有164个,因此需要一个寄存器计数器来对它进行计数,如下图所示

寄存器计数器reg_cnt:该计数器主要对需要配置的寄存器个数进行计数。加一条件问end_rw_cnt,由于一个寄存器需要读写两个阶段,所以为读写计数器的结束条件;结束条件为数164个,需要配置的寄存器共164个,数完就清零。
下面介绍一下该模块的其他信号设计思路:
配置指示信号flag:该信号初始状态为0,表示还没有开始配置;当接收到开始配置使能信号config_en的时候,变为1,表示开始进入配置状态;当配置完成之后,也就是寄存器计数器数完了,便退出配置状态,因此该信号从高变低的条件为end_reg_cnt。
配置完成指示信号cmos_en:当该信号为高电平时,表示摄像头的寄存器已经经过最少一次配置了。初始状态为0,表示没有配置过;变1的条件为end_reg_cnt,也就是寄存器全部配置完一次之后,便置为高电平。
寄存器写数据wdata:初始状态为0,之后,直接取从配置表读出的add_wdata的低8位。
寄存器地址addr:初始状态位0,之后,直接取add_wdata的第8到第15位即可。
写使能wr_en:一个寄存器需要读和写两个阶段,先进行写,然后在读,add_wdata的第16位表示写属性。所以该信号从0变1的条件为(add_rw_cnt && rw_cnt0 && add_wdata[16])。
读使能rd_en:写之后,在进行读,add_wdata的第17位指示读属性,因此读使能拉高的条件位(add_rw_cnt && rw_cnt1 && add_wdata[17])。

1.4.5 参考代码

243. assign              pwdn = 0;
244.    `include "ov7670_para.v"
245.    always  @(posedge clk or negedge rst_n)begin
246.        if(rst_n==1'b0)begin
247.            reg_cnt <= 0;
248.        end
249.        else if(add_reg_cnt)begin
250.            if(end_reg_cnt)
251.                reg_cnt <= 0;
252.            else
253.                reg_cnt <= reg_cnt + 1;
254.        end
255.    end
256.
257.    assign add_reg_cnt = end_rw_cnt;
258.    assign end_reg_cnt = add_reg_cnt && reg_cnt==REG_NUM-1;
259.
260.    always  @(posedge clk or negedge rst_n)begin
261.        if(rst_n==1'b0)begin
262.            rw_cnt <= 0;
263.        end
264.        else if(add_rw_cnt) begin
265.            if(end_rw_cnt)
266.                rw_cnt <= 0;
267.            else
268.                rw_cnt <= rw_cnt + 1;
269.        end
270.    end
271.
272.    assign  add_rw_cnt = flag && rdy;
273.    assign  end_rw_cnt = add_rw_cnt && rw_cnt==RW_NUM-1;
274.
275.
276.    always  @(posedge clk or negedge rst_n)begin
277.        if(rst_n==1'b0)begin
278.            flag <= 1'b0;
279.        end
280.        else if(config_en)begin
281.            flag <= 1'b1;
282.        end
283.        else if(end_reg_cnt)begin
284.            flag <= 1'b0;
285.        end
286.    end
287.
288.    always  @(posedge clk or negedge rst_n)begin
289.        if(rst_n==1'b0)begin
290.            cmos_en <= 1'b0;
291.        end
292.        else if(end_reg_cnt)begin
293.            cmos_en <= 1'b1;
294.        end
295.    end
296.
297.
298.    always  @(posedge clk or negedge rst_n)begin
299.        if(rst_n==1'b0)begin
300.            wdata <= 8'b0;
301.        end
302.        else begin
303.            wdata <= add_wdata[7:0];
304.        end
305.    end
306.
307.    always  @(posedge clk or negedge rst_n)begin
308.        if(rst_n==1'b0)begin
309.            addr <= 8'b0;
310.        end
311.        else begin
312.            addr <= add_wdata[15:8];
313.        end
314.    end
315.
316.
317.    always  @(posedge clk or negedge rst_n)begin
318.        if(rst_n==1'b0)begin
319.            wr_en <= 1'b0;
320.        end
321.        else if(add_rw_cnt && rw_cnt==0 && add_wdata[16])begin
322.            wr_en <= 1'b1;
323.        end
324.        else begin
325.            wr_en <= 1'b0;
326.        end
327.    end
328.
329.
330.    always  @(posedge clk or negedge rst_n)begin
331.        if(rst_n==1'b0)begin
332.            rd_en <= 1'b0;
333.        end
334.        else if(add_rw_cnt && rw_cnt==1 && add_wdata[17])begin
335.            rd_en <= 1'b1;
336.        end
337.        else begin
338.            rd_en <= 1'b0;
339.        end
340.    end

1.5 SCCB接口模块设计

1.5.1 接口信号

信号名 I/O 位宽 定义
clk I 1 工作时钟 25M
rst_n I 1 系统复位信号,低电平有效
wren I 1 写使能
rden I 1 读使能
Sub_addr I 8 寄存器地址
rdata O 8 读寄存器数据
rdata_vld O 1 读寄存器数据有效指示信号
Wdata I 8 写寄存器数据
rdy O 1 准备好接收指示信号
Sio_c O 1 SCCB接口时钟信号
Sio_d_r I 1 SCCB接口读数据线
en_sio_d_w O 1 三态门使能信号
Sio_d_w O 1 SCCB接口写数据线

1.5.2 设计思路

本工程使用的SCCB接口协议,只有两根线,时钟线sio_c和数据线sio_d,因此如果要使用这个接口,我们需要时钟线的频率要求、读数据的时序和写数据的时序。知道了需求,那么在查看数据手册的时候便容易很多。
下图为三线传输开始的标志,本工程中使用的是两线传输,因此可以将下图中的SCCB_E信号忽略,然后可以看出,在sio_c为高的时候,sio_d拉低,表示数据传输的开始,但是在图中看不出sio_d拉低到sio_c拉低的间隔时间是多少。

下图是三线传输停止的标志,本工程使用的是两线传输,因此还是将SCCB_E信号忽略即可,由此得出,在sio_c为高的时候,将sio_d拉高,表示传输的结束,但是下图仍然无法看出从sio_c拉高到sio_d拉高间隔的时间

下图为OV7670数据手册中关于SCCB接口时序的描述,开始条件保持时间为tHD:STA,开始条件建立时间为tSU:STO。

下表为ov7670摄像头数据手册中关于SCCB时序的一些时间参数,查表得到,tHD:STA和tSU:STO最小值为600ns,因此对于这两个参数们可以根据我们使用的方便进行设置。而SCCB的时钟频率,最大值为400K,也可以根据我们的方便进行设置,只要不超过400K就可以了。

经过上面的时序图以及表格的参考,我们基本上可以将sio_c这个信号设计出来了,下面我们在关注sio_d,该信号我们主要关注读数据时序和写数据时序,下图为3相写传输周期,也就是写时序共有三段组成,第一段ID Address为读写属性,第二段Sub-address为寄存器地址,第三段Write Date为写数据,每段都是由9bit数据组成,数据从高到低排列,最低位x为不关心为,下面再使用的时候统一设置为1即可。

下图为读时序,读的时序比较特别,由两相写传输周期和两相读传输周期组成,首先执行写传输周期,该周期会发送读写属性与寄存器地址,目的是为了先识别要读取数据的寄存器,之后执行读传输周期,该周期会发送读写属性,之后开始接收返回的数据即可,8bit的数据接收完成之后,FPGA需要驱动NA位为1,之后读周期结束。

下面开始本模块的设计,首先确定sio_c的频率,这里我将它设置为120个系统时钟周期为1个sio_c的时钟周期,时钟频率约等于208K。为了方便计算,将开始条件保持时间和开始条件建立时间都设置为1个sio_c周期。每个周期发送1bit数据,因此还需要一个计数器来计数每阶段共有多少bit的数据。而读时序的话,由两段组成,因此还需要一个计数器来数写数据时序和读数据时序需要的段数,最终的到下面计数器的架构图:

下面开始对本模块各个信号进行设计:
Sio_c周期计数器count_sck:该计数器表示sio_c一个周期需要的时钟周期个数。加一条件为(flag_r || flag_w),表示接收到读使能或者写使能之后便开始计数;结束条件为数120个,跟工程设置的SCCB时钟频率为208Khz,一个周期约为4800ns,因此需要数120个,数完就清零。
位计数器count_bit:该计数器表示每个阶段需要传输的数据位数。加一条件为end_count_sck,每传输一位需要一个sio_c的周期;结束条件为数bit_num个,阶段不同,传输的数据数也不同,写数据阶段bit_num=30,读数据阶段bit_num=21。
阶段计数器count_duan:该计数器表示传输数据所需要的阶段。加一条件为end_count_bit,每个阶段的数据传输完成之后,表示当前阶段结束;结束条件为数duan_num个,写数据阶段,duan_num=1,读数据阶段duan_num=2。
SCCB时钟信号sio_c:初始状态为高电平,由于频率为208K,因此高电平和低电平各持续60个时钟周期,又由于开始条件保持时间和开始条件建立时间的存在,sio_c在每个阶段的第一个数据和最后一个数据部分不能变为低电平,所以变低的条件为(count_bit>=0 && count_bit < (bit_num-2) && add_count_sck && count_sck == SIO_C-1);从低变高的条件为(count_bit>=1 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/2-1)。
读状态指示信号flag_r:初始状态为0,表示不处于读状态,当接收到读使能ren有效的时候,变为高电平,表示进入读状态;当段计数器count_duan数完之后,该信号变为低电平,表示读阶段结束。
写状态指示信号flag_w:初始状态为0,表示不处于写状态,当接收到写使能wen有效的时候,变为高电平,表示进入写状态;当段计数器count_duan数完之后,该信号变为低电平,表示写阶段结束。
要输出的数据out_data:读数据阶段out_data= {1’h0,rd_com,1’h1,addr_ff,1’h1,1’h0,1’h1,9’h0},其中从左往右依次是起始位0、读写属性rd_com、1bit的x、寄存器地址、1bit的x、1个间隔为0、结束位1和9bit的0。间隔位0的存在是由于数据传输结束是一个拉高的操作,因此需要先将数据线拉低,在发送结束位。后面9bit0仅仅是为了将out_data的位宽占满,如果不补的话,会在高位自动补零,这样就不对了。读写属性rd_com会因为读写的不同而变化,写的时候为8’h42,读的时候为 8’ h43;写数据阶段out_data={1’h0,8’h42,1’h1,addr_ff,1’h1,wdata_ff,1’h1,1’h0,1’h1},其中从左往右依次是起始位0、写属性8’h42、1bit的x、寄存器地址、1bit的x、写数据wdata_ff、1个间隔为0、结束位1。
三态门使能en_sio_d_w:当该信号为0,此时只允许从机往FPGA发送数据。当该信号为1时,只允许FPGA往从机发送数据。因此该信号初始状态为0,当需要进行写数据的时候,将该信号拉高即可,拉高的条件为(ren || wen)和(flag_r && count_duan1 && count_bit18 && add_count_sck && count_sck1-1),这两个条件分别是在接收到读使能或者写使能的时候,都需要先往从机里面写入数据,再读数据的第二阶段,将数据读出之后,还要发送间隔位、停止位等。再读数据得时候,将三态门使能信号拉低,也就是在读的第二阶段,所以拉低的条件为(flag_r && count_duan1 && count_bit10 && add_count_sck && count_sck1-1)。
写数据线sio_d_w:在写数据区域,将out_data的值按照顺序赋值即可,注意在赋值的时候,要在SCCB时钟一个时钟周期开始的时候进行,因此条件为(count_bit >= 0 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/4-1)。
读数据rdata:读出数据的时间为读时序的第二阶段,注意在取值的时候,需要SCCB时钟的高电平的中间时刻进行(flag_r && count_duan1 && count_bit>=10 && count_bit < 18 && add_count_sck && count_sckSIO_C/4*3-1)。
读数据有效指示信号rdata_vld:初始状态为0,表示此时读数据信号无效。当读数据阶段全部完成之后,读数据有效,将该信号拉高一个时钟周期,因此条件为(flag_r && end_count_duan)。
准备好接收指示信号rdy:该信号为0时,表示当前模块处于发送或者接收状态,不能接收上游模块发送的数据。该信号为1时,表示当前看模块处于空闲状态,可以接受上游模块发送的数据。因此该信号为1的条件是(wen || ren || flag_r || flag_w)。注意rdy信号需要使用组合逻辑产生。

1.5.3 参考代码

1.       always  @(posedge clk or negedge rst_n)begin
2.          if(rst_n==1'b0)begin
3.              count_sck <= 0;
4.          end
5.          else if(add_count_sck)begin
6.              if(end_count_sck)begin
7.                  count_sck <= 0;
8.              end
9.              else begin
10.                 count_sck <= count_sck + 1;
11.             end
12.         end
13.     end
14.
15.     assign add_count_sck = flag_r || flag_w;
16.     assign end_count_sck = add_count_sck && count_sck == SIO_C-1;
17.
18.     always  @(posedge clk or negedge rst_n)begin
19.         if(rst_n==1'b0)begin
20.             count_bit <= 0;
21.         end
22.         else if(add_count_bit)begin
23.             if(end_count_bit)begin
24.                 count_bit <= 0;
25.             end
26.             else begin
27.                 count_bit <= count_bit + 1;
28.             end
29.         end
30.     end
31.
32.     assign add_count_bit = end_count_sck;
33.     assign end_count_bit = add_count_bit && count_bit == bit_num+2-1;
34.
35.     always  @(posedge clk or negedge rst_n)begin
36.         if(rst_n==1'b0)begin
37.             count_duan <= 0;
38.         end
39.         else if(add_count_duan)begin
40.             if(end_count_duan)begin
41.                 count_duan <= 0;
42.             end
43.             else begin
44.                 count_duan <= count_duan + 1;
45.             end
46.         end
47.     end
48.
49.     assign add_count_duan = end_count_bit;
50.     assign end_count_duan = add_count_duan && count_duan == duan_num-1;
51.
52.     always  @(posedge clk or negedge rst_n)begin
53.         if(rst_n==1'b0)begin
54.             flag_r <= 0;
55.         end
56.         else if(ren)begin
57.             flag_r <= 1;
58.         end
59.         else if(end_count_duan)begin
60.             flag_r <= 0;
61.         end
62.     end
63.
64.     always  @(posedge clk or negedge rst_n)begin
65.         if(rst_n==1'b0)begin
66.             flag_w <= 0;
67.         end
68.         else if(wen)begin
69.             flag_w <= 1;
70.         end
71.         else if(end_count_duan)begin
72.             flag_w <= 0;
73.         end
74.     end
75.
76.     always  @(*)begin
77.         if(flag_r)begin
78.             bit_num = 21;
79.             duan_num = 2;
80.         end
81.         else if(flag_w)begin
82.             bit_num = 30;
83.             duan_num = 1;
84.         end
85.         else begin
86.             bit_num = 1;
87.             duan_num = 1;
88.         end
89.     end
90.
91.     always  @(posedge clk or negedge rst_n)begin
92.         if(rst_n==1'b0)begin
93.             sio_c <= 1;
94.         end
95.         else if(sio_c_h2l)begin
96.             sio_c <= 0;
97.         end
98.         else if(sio_c_l2h)begin
99.             sio_c <= 1;
100.            end
101.        end
102.
103.        assign sio_c_h2l = count_bit >= 0 && count_bit < (bit_num-2) && add_count_sck && count_sck == SIO_C-1;
104.        assign sio_c_l2h = count_bit >= 1 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/2-1;
105.
106.
107.       always  @(posedge clk or negedge rst_n)begin
108.           if(rst_n==1'b0)begin
109.               wdata_ff  <= 0;
110.               addr_ff   <= 0;
111.           end
112.           else if(wen ||ren) begin
113.               wdata_ff  <= wdata    ;
114.               addr_ff   <= sub_addr ;
115.           end
116.       end
117.
118.
119.        always @ (*)begin
120.            if(flag_r)begin
121.                out_data = {1'h0,rd_com,1'h1,addr_ff,1'h1,1'h0,1'h1,9'h0};
122.            end
123.            else if(flag_w)begin
124.                out_data = {1'h0,8'h42,1'h1,addr_ff,1'h1,wdata_ff,1'h1,1'h0,1'h1};
125.            end
126.            else begin
127.                out_data = 0;
128.            end
129.        end
130.
131.        assign rd_com = (flag_r && count_duan == 0)? 8'h42 : 8'h43;
132.
133.        always  @(posedge clk or negedge rst_n)begin
134.            if(rst_n==1'b0)begin
135.                en_sio_d_w <= 0;
136.            end
137.            else if(ren || wen)begin
138.                en_sio_d_w <= 1;
139.            end
140.            else if(end_count_duan)begin
141.                en_sio_d_w <= 0;
142.            end
143.            else if(en_sio_d_w_h2l)begin
144.                en_sio_d_w <= 0;
145.            end
146.            else if(en_sio_d_w_l2h)begin
147.                en_sio_d_w <= 1;
148.            end
149.        end
150.
151.        assign en_sio_d_w_h2l = flag_r && count_duan == 1 && count_bit == 10 && add_count_sck && count_sck == 1-1;
152.        assign en_sio_d_w_l2h = flag_r && count_duan == 1 && count_bit == 18 && add_count_sck && count_sck == 1-1;
153.
154.        always  @(posedge clk or negedge rst_n)begin
155.            if(rst_n==1'b0)begin
156.                sio_d_w <= 1;
157.            end
158.            else if(out_data_time)begin
159.                sio_d_w <= out_data[30-count_bit-1];
160.            end
161.        end
162.
163.        assign out_data_time = count_bit >= 0 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/4-1;
164.
165.        always  @(posedge clk or negedge rst_n)begin
166.            if(rst_n==1'b0)begin
167.                rdata <= 0;
168.            end
169.            else if(rdata_time)begin
170.                rdata[17-count_bit] <= sio_d_r;
171.            end
172.        end
173.
174.        assign rdata_time = flag_r && count_duan==1 && count_bit>=10 && count_bit<18 && add_count_sck && count_sck==SIO_C/4*3-1;
175.
176.        always  @(posedge clk or negedge rst_n)begin
177.            if(rst_n==1'b0)begin
178.                rdata_vld <= 0;
179.            end
180.            else if(flag_r && end_count_duan)begin
181.                rdata_vld <= 1;
182.            end
183.            else begin
184.                rdata_vld <= 0;
185.            end
186.        end
187.
188.        always  @(*)begin
189.            if(ren || wen || flag_r || flag_w)begin
190.                rdy = 0;
191.            end
192.            else begin
193.                rdy = 1;
194.            end
195.        end

1.6 图像采集模块设计

1.6.1 接口信号

信号名 I/O 位宽 定义
clk I 1 工作时钟 25M
rst_n I 1 系统复位信号,低电平有效
en_capture I 1 摄像头配置完成指示信号,当其为1时,表示摄像头完成了配置。
vsync I 1 摄像头帧同步信号,该信号拉高几个时钟周期,表示一帧图像传输的开始。
href I I 摄像头行同步信号,该信号为高电平时,摄像头输出的数据有效。
din I 8 摄像头输出数据
dout O 16 输出图像数据
dout_vld O 1 输出图像数据有效指示信号
dout_sop O 1 输出图像第一个有效数据指示信号
dout_eop O 1 输出图像最后一个有效数据指示信号

1.6.2 设计思路

下面时序图是从OV7670摄像头数据手册中得到的,可以看出来摄像头输出的数据跟随像素时钟时钟,一个时钟周期输出一个字节的数据,而且数据只在行同步信号href为高时有效,我们可以将行同步信号href当作是摄像头输出数据有效指示信号来使用。

从下面时序图中可以看出,帧同步信号拉高三个时钟周期,表示将要开始一帧图像的传输,在vsync拉高之后,到图像开始传输,还有一段时间,这段时间不用管是多少,可以看行同步信号href是否为高就可以了。

我们配置摄像头输出格式为RGB565,因此一个像素是由两个字节组成,而输出数据接口的位宽是一个字节,因此摄像头两个时钟周期才能输出一个完整的图像数据,由下图可以看出先输出的是高8位数据,然后再输出低8位数据。

从上面几个时序图中,已经知道了摄像头输出图像的时刻以及方式,便可以开始对数据进行接收,这里采用行、场两个计数的架构,如下图所示:

行计数器cnt_x:该计数器用来表示摄像头输出图像一行数据的个数。加一条件为din_vld,表示输入的数据有效,便开始计数。结束条件为数1280个,一行有640个像素数据,每个像素都是两个字节,摄像头每次输出一个字节,因此共输出1280个。
列计数器cnt_y:该计数器用来计数一帧图像的行数。加一条件为end_cnt_x,表示每数完1行就加一。结束条件为数480个,一帧图像就是480行,数完就清零。
下面再看一下该模块其他信号的设计:
采集状态指示信号flag_capture:初始状态为0,表示不处于采集状态,当检测到帧同步信号的上升沿和配置完成指示信号有效的时候,便进入采集状态,因此该信号变为1的条件为(flag_capture==0 && vsync_12h && en_capture)。当一帧图像传输完成之后,就退出采集状态,因此该信号变0的条件为end_cnt_y。
输入数据有效指示信号din_vld:当进入到采集状态,并且检测到行同步信号为1的时候,输入数据就是有效的,该信号由组合逻辑产生,即flag_capture && href。
输出图像数据dout:初始状态为0,当输入数据有效时,按照数据的排列顺序,进行串并转换。
输出图像数据有效指示信号dout_vld:一个像素数据为2个字节,摄像头每个时钟输出一个字节,因此是输出两次有效一次。初始状态为0,表示数据无效,当行计数器cnt_x的第0位为1的时候,该信号变为1,表示输出图像数据有效。
第一个有效数据指示信号dout_sop:初始状态为0。当行计数器cnt_x等于1,并且列计数器等于0的时候,表示输出第一个有效数据,该信号拉高,其他时候为0。
最后一个有效数据指示信号dout_eop:当行计数器cnt_x等于1280-1,并且列计数器等于480-1的时候,表示输出最后一个有效数据,该信号为高,其他时候为低电平。

1.6.3 参考代码

1.       always @ (posedge clk or negedge rst_n)begin
2.          if(!rst_n)begin
3.              cnt_x <= 0;
4.          end
5.          else if(add_cnt_x)begin
6.              if(end_cnt_x)begin
7.                  cnt_x <= 0;
8.              end
9.              else begin
10.                 cnt_x <= cnt_x + 1;
11.             end
12.         end
13.     end
14.
15.     assign add_cnt_x = flag_capture && din_vld;
16.     assign end_cnt_x = add_cnt_x && cnt_x == COL*2-1;
17.
18.     always @ (posedge clk or negedge rst_n)begin
19.         if(!rst_n)begin
20.             cnt_y <= 0;
21.         end
22.         else if(add_cnt_y)begin
23.             if(end_cnt_y)begin
24.                 cnt_y <= 0;
25.             end
26.             else begin
27.                 cnt_y <= cnt_y + 1;
28.             end
29.         end
30.     end
31.
32.     assign add_cnt_y = end_cnt_x;
33.     assign end_cnt_y = add_cnt_y && cnt_y == ROW-1;
34.
35.     always @ (posedge clk or negedge rst_n)begin
36.         if(!rst_n)begin
37.             flag_capture <= 0;
38.         end
39.         else if(flag_capture == 0 && vsync_l2h && en_capture)begin
40.             flag_capture <= 1;
41.         end
42.         else if(end_cnt_y)begin
43.             flag_capture <= 0;
44.         end
45.     end
46.
47.     assign vsync_l2h = vsync_ff0 == 0 && vsync == 1;
48.
49.     always @ (posedge clk or negedge rst_n)begin
50.         if(!rst_n)begin
51.             vsync_ff0 <= 0;
52.         end
53.         else begin
54.             vsync_ff0 <= vsync;
55.         end
56.     end
57.
58.     always @ (posedge clk or negedge rst_n)begin
59.         if(!rst_n)begin
60.             dout <= 0;
61.         end
62.         else if(din_vld)begin
63.             dout <= {dout[7:0],din};
64.         end
65.     end
66.
67.     assign din_vld = flag_capture && href;
68.
69.     always @ (posedge clk or negedge rst_n)begin
70.         if(!rst_n)begin
71.             dout_vld <= 0;
72.         end
73.         else if(flag_dout_vld)begin
74.             dout_vld <= 1;
75.         end
76.         else begin
77.             dout_vld <= 0;
78.         end
79.     end
80.
81.     assign flag_dout_vld = add_cnt_x && cnt_x[0] == 1;
82.
83.     always @ (posedge clk or negedge rst_n)begin
84.         if(!rst_n)begin
85.             dout_sop <= 0;
86.         end
87.         else if(flag_dout_vld && cnt_x[10:1] == 0 && cnt_y == 0)begin
88.             dout_sop <= 1;
89.         end
90.         else begin
91.             dout_sop <= 0;
92.         end
93.     end
94.
95.     always @ (posedge clk or negedge rst_n)begin
96.         if(!rst_n)begin
97.             dout_eop <= 0;
98.         end
99.         else if(flag_dout_vld && cnt_x[10:1] == COL-1 && cnt_y == ROW-1)begin
100.                dout_eop <= 1;
101.            end
102.            else begin
103.                dout_eop <= 0;
104.            end
105.        end

1.7 存储控制模块设计

1.7.1 接口信号

信号名 I/O 位宽 定义
clk I 1 工作时钟 25M
rst_n I 1 系统复位信号,低电平有效
clk_in I 1 SDRAM时钟,100M
din I 16 输入图像数据
din_vld I 1 输入图像数据有效指示信号
din_sop I 1 第一个有效图像数据指示信号
din_eop I 1 最后一个有效图像数据指示信号
dout O 16 输出图像数据
wr_req O 1 写SDRAM请求信号
rd_req O 1 读SDRAM请求信号
wdata O 16 写SDRAM数据
wr_ack I 1 写SDRAM响应
rd_ack I 1 读SDRAM响应
sd_rdata I 16 读SDRAM数据
sd_rdata_vld I 1 读SDRAM数据有效指示信号
display_area I 1 显示器显示区域指示信号
bank O 2 SDRAM的Bank地址
addr O 13 SDRAM的地址

1.7.2 设计思路

本工程的主体时钟为25M,显示器的分辨率为640*480,相当于一个时钟周期显示一个像素。而摄像头的工作时钟也是25M,但是数据位宽为8bit,相当于两个时钟输出一个像素,如果直接将采集模块接收到的数据送给显示器显示,那么一个像素点会被显示两次,这肯定是不对的,因此需要先将摄像头输出的数据缓存起来,当存够一帧图像之后,再送给显示器进行显示,显示器本该1秒显示60帧图像的,降低到了30帧,但是显示的图像是正确而且连续的。
所以本模块的作用就是存储控制,再SDRAM中划分两个区域,通过乒乓操作的方式进行存储的控制,该操作的主要的难点在于摄像头是不断输出数据的,而显示器也需要不断的进行显示才可以,两边都不能等待,但是SDRAM的读写都是使用的同一组数据总线,这也就造成了它注定无法进行同时读写。但是对于本工程来说,由于工程主体时钟25M和SDRAM工作时钟100M相比有较大差距,因此可以使用一种“伪同时读写”的操作,达到“同时读写”的效果。此方法的介绍请在明德扬论坛中搜索“需要同时读写SDRAM的解决办法”。

本模块内部包含两个FIFO(fifo原理以及使用,可以在明德扬论坛搜索学习),fifo_16bwrite和fifo_16bread,前者为写FIFO,后者为读FIFO,两个FIFO的作用主要是跨时钟域处理和数据缓存。下面介绍一下该模块中各个信号的设计。
写FIFO写使能wfifo_wrreq:输入图像数据从第一个有效数据开始一直到最后一个有效数据期间,写使能信号一直为高电平,即(din_vld && (din_sop || get_data_flag))。
数据写入指示信号get_data_flag:初始状态为0,表示没有数据需要写入;拉高的条件为(din_vld && din_sop),表示接收到第一个有效数据时,便开始写入;有高变低的条件为(din_vld && din_eop),表示当检测到最后一个有效数据之后,将该信号拉低,停止写入。
写FIFO数据读出指示信号rd_wfifo_flag:初始状态为0,表示不需要读出数据;变高的条件为wfifo_rdusedw>=512,表示当写FIFO内部数据量达到512个的时候,便准备将数据读出
由于一帧图像的像素点共640*480=307200个,而SDRAM一行有512个存储空间,要将图像存储进SDRAM中,共需要600行。因此提出两个计数器的架构:

写数据行计数器cnt_rd_wfifo:该信号表示SDRAM中每一行中要写入的数据量。加一条件为(rd_wfifo_flag && ((cnt_rd_wfifo==0 && wr_ack) || (cnt_rd_wfifo!=0)),表示在写数据准备读出状态下,如果检测到写响应信号有效,便开始计数;结束条件为数512个,SDRAM每行内存容量为512个数据,数完就清零。
写SDRAM列地址计数器cnt_waddr:该计数器用于计数SDRAM的列地址。加一条件为end_cnt_rd_wfifo,表示每行写完就加一;结束条件为数600个,总需要600行。
读FIFO数据写入指示信号wr_rfifo_flag:初始状态为0,表示不需要写入数据;变高的条件为rfifo_rdusedw<550,表示当读FIFO内部数据量小于550个的时候,便请求写入数据。
读使能rd_req:再读FIFO不处于请求数据写入状态的时候,检测到读FIFO内部的数据量少于550个的时候,读使能信号有效,直到接收到读响应。
写使能wr_req:在写FIFO不处于请求输出的状态的时候,检测到写FIFO内部的数据量至少有512个的时候,写使能信号有效,直到接收到写响应。
写FIFO读使能wfifo_rdreq:当写FIFO内部不为空,并且行计数器开始计数的时候,该信号有效。
读数据行地址计数器cnt_wr_rfifo:该计数器表示读SDRAM时候的行地址;加一条件为sd_rdata_vld,表示SDRAM读出数据有效就加一;结束条件为数512个。
读数据列地址计数器cnt_raddr:该计数器表示读SDRAM时候的列地址;加一条件为end_cnt_wr_rfifo,表示读出一行数据,就加一;结束条件为数600个。
写Bank地址waddr_bank:初始状态为2’b0,当一帧图像的数据全部写进去之后,写Bank地址取反,变为2’b11。
读Bank地址raddr_bank:初始状态为2’b11,当一帧图像全部读出来之后,并且与写Bank地址相等,则取反,变为2’b00。
Bank切换计数器cnt_bank:由于读写Bank的切换需要时间,因此需要此计数器来决定间隔的时间,当开始切换Bank的时候开始计数,经过调试,共间隔8个时钟周期。
读FIFO的读使能:当进入到有效显示区域的时候,便将FIFO内部数据读出。
读FIFO的写使能:当SDRAM输出有效数据的时候,写使能便拉高,将数据写入。

1.7.3 参考代码

1.     always  @(*)begin
2.              if(rd_wfifo_flag)
3.                  bank=waddr_bank;
4.              else
5.                  bank=raddr_bank;
6.      end
7.       always  @(*)begin
8.              if(rd_wfifo_flag)
9.                  addr=waddr;
10.             else
11.                 addr=raddr;
12.     end
13.
14.
15. fifo_16bwrite   fifo_16bwrite_inst (
16.     .aclr        ( ~rst_n        ),
17.     .data        (din             ),
18.     .rdclk       (clk_in          ),
19.     .rdreq       (wfifo_rdreq     ),
20.     .wrclk       (clk             ),
21.     .wrreq       (wfifo_wrreq     ),
22.     .q           (wdata           ),
23.     .rdempty     (wfifo_rdempty   ),
24.     .rdusedw     (wfifo_rdusedw   )
25.
26.     );
27. fifo_16bread    fifo_16bread_inst (
28.     .aclr        ( ~rst_n         ) ,
29.     .data        (sd_rdata        ),
30.     .rdclk       (clk             ),
31.     .rdreq       (display_area    ),
32.     .wrclk       (clk_in          ),
33.     .wrreq       (sd_rdata_vld    ),
34.     .q           (dout            ),
35.     .rdempty     (rfifo_rdempty   ),
36.     .rdusedw     (rfifo_rdusedw   )
37.
38.     );
39.
40.     assign wfifo_wrreq= din_vld &&(din_sop || get_data_flag);
41.     assign waddr  = cnt_waddr ;
42.     assign wbank  = waddr_bank ;
43.
44.
45.       always @ (posedge clk or negedge rst_n)begin
46.         if(!rst_n)begin
47.             get_data_flag <= 0;
48.         end
49.         else if(din_vld && din_sop)begin
50.             get_data_flag <= 1;
51.                 end
52.         else if(din_vld && din_eop) begin
53.             get_data_flag <= 0;
54.         end
55.     end
56.
57.
58. assign rd_wfifo_start = rd_wfifo_flag==0&&wfifo_rdusedw>=512;
59.
60.       always @ (posedge clk_in or negedge rst_n)begin
61.         if(!rst_n)begin
62.             rd_wfifo_flag <= 0;
63.         end
64.         else if(wfifo_rdusedw>=512)begin
65.             rd_wfifo_flag <= 1;
66.                 end
67.         else if(end_cnt_rd_wfifo) begin
68.             rd_wfifo_flag <= 0;
69.         end
70.     end
71.
72.
73.       always @ (posedge clk_in or negedge rst_n)begin
74.         if(!rst_n)begin
75.             wr_req <= 0;
76.         end
77.         else if(rd_wfifo_flag==0&&wfifo_rdusedw>=512)begin
78.            wr_req <= 1 ;
79.                 end
80.         else if(wr_ack) begin
81.              wr_req <= 0;
82.         end
83.     end
84.
85.
86.     always @(posedge clk_in or negedge rst_n)begin
87.         if(!rst_n)begin
88.             cnt_rd_wfifo <= 0;
89.         end
90.         else if(add_cnt_rd_wfifo)begin
91.             if(end_cnt_rd_wfifo)
92.                 cnt_rd_wfifo <= 0;
93.             else
94.                 cnt_rd_wfifo <= cnt_rd_wfifo + 1;
95.         end
96.     end
97.
98.     assign add_cnt_rd_wfifo = rd_wfifo_flag&&((cnt_rd_wfifo==0&&wr_ack)||(cnt_rd_wfifo!=0));
99.     assign end_cnt_rd_wfifo = add_cnt_rd_wfifo && cnt_rd_wfifo== 512-1;
100.
101.
102.        always  @(*)begin
103.                if(add_cnt_rd_wfifo&&wfifo_rdempty==0)
104.                    wfifo_rdreq=1;
105.                else
106.                    wfifo_rdreq=0;
107.        end
108.
109.
110.        always @(posedge clk_in or negedge rst_n)begin
111.            if(!rst_n)begin
112.                cnt_waddr <= 0;
113.            end
114.            else if(add_cnt_waddr)begin
115.                if(end_cnt_waddr)
116.                    cnt_waddr <= 0;
117.                else
118.                    cnt_waddr <= cnt_waddr + 1;
119.            end
120.        end
121.
122.        assign add_cnt_waddr = end_cnt_rd_wfifo;
123.        assign end_cnt_waddr = add_cnt_waddr && cnt_waddr==600-1 ;
124.
125.
126.        assign  raddr=  cnt_raddr;
127.        assign  rbank=  raddr_bank;
128.
129.    assign wr_rfifo_star= wr_rfifo_flag==0&&rfifo_rdusedw<550;
130.
131.    always  @(posedge clk_in or negedge rst_n)begin
132.        if(rst_n==1'b0)begin
133.            wr_rfifo_flag <= 0;
134.        end
135.        else if(wr_rfifo_star)begin
136.              wr_rfifo_flag <= 1;
137.        end
138.        else if(end_cnt_wr_rfifo)begin
139.             wr_rfifo_flag <= 0;
140.        end
141.    end
142.
143.    always  @(posedge clk_in or negedge rst_n)begin
144.        if(rst_n==1'b0)begin
145.            rd_req <= 0;
146.        end
147.        else if(wr_rfifo_star)begin
148.            rd_req <= 1;
149.        end
150.        else if(rd_ack)begin
151.             rd_req <= 0;
152.        end
153.    end
154.
155.      always @(posedge clk_in or negedge rst_n)begin
156.            if(!rst_n)begin
157.                cnt_wr_rfifo <= 0;
158.            end
159.            else if(add_cnt_wr_rfifo)begin
160.                if(end_cnt_wr_rfifo)
161.                    cnt_wr_rfifo <= 0;
162.                else
163.                    cnt_wr_rfifo <= cnt_wr_rfifo + 1;
164.            end
165.        end
166.
167.        assign add_cnt_wr_rfifo = sd_rdata_vld ;
168.        assign end_cnt_wr_rfifo = add_cnt_wr_rfifo && cnt_wr_rfifo== 512-1;
169.
170.
171.
172.        always @(posedge clk_in or negedge rst_n)begin
173.            if(!rst_n)begin
174.                cnt_raddr <= 0;
175.            end
176.            else if(add_cnt_raddr)begin
177.                if(end_cnt_raddr)
178.                    cnt_raddr <= 0;
179.                else
180.                    cnt_raddr <= cnt_raddr + 1;
181.            end
182.        end
183.
184.        assign add_cnt_raddr = end_cnt_wr_rfifo;
185.        assign end_cnt_raddr = add_cnt_raddr && cnt_raddr==600-1 ;
186.
187.
188.
189.        always  @(posedge clk_in or negedge rst_n)begin
190.            if(rst_n==1'b0)begin
191.                waddr_bank <= 2'b00;
192.            end
193.            else if(end_cnt_waddr)begin
194.                waddr_bank <= ~waddr_bank;
195.            end
196.        end
197.
198.    assign change_bank = end_cnt_raddr&&raddr_bank==waddr_bank;
199.
200.    always  @(posedge clk_in or negedge rst_n)begin
201.        if(rst_n==1'b0)begin
202.            raddr_bank <= 2'b11;
203.        end
204.        else if(end_cnt_raddr&&raddr_bank==waddr_bank)begin
205.              raddr_bank <= ~raddr_bank;
206.        end
207.
208.    end
209.
210.
211.    always  @(posedge clk_in or negedge rst_n)begin
212.        if(rst_n==1'b0)begin
213.            change_bank_instr <= 0 ;
214.        end
215.        else if(change_bank)begin
216.            change_bank_instr<=1;
217.        end
218.        else if(end_cnt_bank)begin
219.             change_bank_instr <= 0 ;
220.        end
221.    end
222.
223.
224.
225.    always @(posedge clk_in or negedge rst_n)begin
226.        if(!rst_n)begin
227.            cnt_bank <= 0;
228.        end
229.        else if(add_cnt_bank)begin
230.            if(end_cnt_bank)
231.                cnt_bank <= 0;
232.            else
233.                cnt_bank <= cnt_bank + 1;
234.        end
235.    end
236.
237.    assign add_cnt_bank = change_bank_instr;
238.    assign end_cnt_bank = add_cnt_bank && cnt_bank==8-1 ;

1.8 SDRAM接口模块设计

本模块的设计跟上一个案例《SDRAM读写控制器》基本一样,这里就不再做介绍,想要了解的可以去明德扬论坛搜素相关文章即可。
【每周FPGA案例】 SDRAM读写控制器

1.9 VGA接口模块设计

本模块的设计可以参考明德扬书籍《FPGA至简设计原理与应用》中关于VGA部分的案例,也可以在明德扬论坛中搜索书籍名称。
【FPGA至简设计原理与应用】书籍连载17 第三篇 FPGA至简设计项目 第八章 VGA显示颜色

1.10 效果和总结

该工程无论在哪一个板子上都是使用的显示器观察现象,因此我们只看一个就可以了

感兴趣的朋友也可以访问mdy论坛进行FPGA相关工程设计学习,也可以看一下我们往期的文章。

OV7670摄像头显示相关推荐

  1. 总:基于FPGA的OV7670摄像头显示

    目录 前言: 一.整体系统设计 二.各部分模块设计 1.时钟模块 2.OV7670初始化模块 3.DVP协议数据流模块 4.写FIFO模块 5.读FIFO模块 6.写FIFO控制模块 7.读FIFO控 ...

  2. STM32F407获取OV7670摄像头图像及上位机解码(一维码二维码)

    STM32F407获取OV7670摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32F407对30万像素OV7670摄像头进行图像捕获,并通过串口将数据 ...

  3. 基于FPGA的OV7670摄像头实时检测

    目录 前言:整体系统框图 一.OV7670摄像头简介 二.OV7670 SCCB协议简介 三.OV7670初始化寄存器配置 四.OV7670初始化代码编写 五.什么是DVP? 六.摄像头写数据请求 七 ...

  4. 基于FPGA OV7670摄像头初始化配置

    目录 一.OV7670摄像头简介 二.ov7670 SCCB协议简介 三.OV7670初始化寄存器配置 四.OV7670初始化代码编写 1.初始化过程 2.RTL设计 五.仿真测试 一.OV7670摄 ...

  5. STM32H750获取OV7670摄像头图像及上位机解码(一维码二维码)

    STM32H750获取OV7670摄像头图像及上位机解码(一维码&二维码) 1. 目的 针对静止拍摄图像场景,实现STM32H750对30万像素OV7670摄像头进行图像捕获,并通过串口将数据 ...

  6. 基于人脸识别的课堂签到管理系统(二)---摄像头显示

    一.前言概述 摄像头显示 创建摄像头操作类:完成摄像头的采集功能 当需要摄像头完成一个功能时,就调用摄像头对象的一个函数去完成 实现一个摄像头类: 添加 打开摄像头 获取摄像头的实时数据 数据进行转换 ...

  7. 怎么解决视频时摄像头显示的画面显示绿色人影的问题

    怎么解决视频时摄像头显示的画面显示绿色人影的问题 怎么解决视频时摄像头显示的画面显示绿色人影的问题,如下图所示: 解决办法: 打开qq设置 设置之后 ,就ok了,如下图所示:

  8. 【OV7670】基于FPGA的OV7670摄像头介绍和使用

    1.软件版本 quartusii12.1 2.本算法理论知识 OV7670摄像头模块 带384Kb FIFO  数字摄像头  手动变焦 OV7670总共有656*488个像素,其中640*480个有效 ...

  9. PyQt+Opencv-python多线程显示摄像头信息至QLabel,摄像头显示区域自由拉伸尺寸

    文章目录 前言 效果 资源下载 核心代码 编程思路 Camera.py Widget.py 前言   通过PyQt与Opencv-python实现多线程显示摄像头信息至QLabel,可以同时拉伸窗口, ...

最新文章

  1. 如何免费(轻成本)在网上做推广宣传
  2. MySQL中有哪些锁?
  3. 【Java】Java_05 标识符与字符集
  4. 实时通信RTC技术栈之:视频编解码
  5. java mongo api_MONGODB的javaAPI简单应用
  6. OpenGL入门程序一:绘制简单的矩形
  7. LTR学习排序 Learning to Rank 小结
  8. 通过一个Thinkphp完成多个项目
  9. java中读取配置文件的内容
  10. LOL_2D局域网小游戏(Qt)
  11. 通过JS获取手机浏览器的类型
  12. 中秋之际献上【中秋快乐】藏头诗
  13. 合肥二手房房价分析(多元线性回归)
  14. DNS记录类型介绍(A记录、MX记录、NS记录等)
  15. 适配器模式--香港代购苹果(电子商品)
  16. 如何把标签输出为PDF文件
  17. QT学习串口编程之串口软件的UI设计
  18. mysql的安装与初始化
  19. 如何把 Excel 数据按分类拆分为多个
  20. docker使用文档1

热门文章

  1. 用 Python 实现带音乐的雪花飘落雪景图
  2. springmvc中数据绑定,controler开发细节,以及与struts2对比
  3. 戴尔携手VMware:让混合办公“梦想照进现实”
  4. ipconfig/flushdns(转)
  5. 新版标准日本语初级_第二十九课
  6. 换了编译环境/电脑 keil提示编译错误—>修改编译路径
  7. android invoke 参数,如何使用Invoke传递接口回调参数
  8. ORACLE EBS 介绍
  9. 计算机专业中哪些色盲不能报,色盲高考不能报哪些专业?受影响最大的是美术生,志愿填报需谨慎...
  10. 怀旧服服务器维护重置稀有,怀旧服修复:副本每日30次重置的限制,改为针对每一个角色...