1.5.2设计思路
本工程使用的SCCB接口协议,只有两根线,时钟线sio_c和数据线sio_d,因此如果要使用这个接口,我们需要时钟线的频率要求、读数据的时序和写数据的时序。知道了需求,那么在查看数据手册的时候便容易很多。 下图为三线传输开始的标志,本工程中使用的是两线传输,因此可以将下图中的SCCB_E信号忽略,然后可以看出,在sio_c为高的时候,sio_d拉低,表示数据传输的开始,但是在图中看不出sio_d拉低到sio_c拉低的间隔时间是多少。 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)。 准备好接收指示信号rdy:该信号为0时,表示当前模块处于发送或者接收状态,不能接收上游模块发送的数据。该信号为1时,表示当前看模块处于空闲状态,可以接受上游模块发送的数据。因此该信号为1的条件是(wen || ren || flag_r|| flag_w)。注意rdy信号需要使用组合逻辑产生。 1.5.3参考代码- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- count_sck <= 0;
- end
- else if(add_count_sck)begin
- if(end_count_sck)begin
- count_sck <= 0;
- end
- else begin
- count_sck <= count_sck + 1;
- end
- end
- end
- assign add_count_sck = flag_r || flag_w;
- assign end_count_sck = add_count_sck && count_sck == SIO_C-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- count_bit <= 0;
- end
- else if(add_count_bit)begin
- if(end_count_bit)begin
- count_bit <= 0;
- end
- else begin
- count_bit <= count_bit + 1;
- end
- end
- end
- assign add_count_bit = end_count_sck;
- assign end_count_bit = add_count_bit && count_bit == bit_num+2-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- rdata_vld <= 0;
- end
- else if(flag_r && end_count_duan)begin
- rdata_vld <= 1;
- end
- else begin
- rdata_vld <= 0;
- end
- end
- always @(*)begin
- if(ren || wen || flag_r || flag_w)begin
- rdy = 0;
- end
- else begin
- rdy = 1;
- end
- end
- endmodule
[color=rgb(51, 102, 153) !important]复制代码
1.6 图像采集模块设计1.6.1接口信号
信号名 | | | | | | | | | | | | | | | 摄像头配置完成指示信号,当其为1时,表示摄像头完成了配置。 | | | | 摄像头帧同步信号,该信号拉高几个时钟周期,表示一帧图像传输的开始。 | | | | 摄像头行同步信号,该信号为高电平时,摄像头输出的数据有效。 | | | | | | | | | | | | | | | | | | | | |
1.6.2设计思路下面时序图是从OV7670摄像头数据手册中得到的,可以看出来摄像头输出的数据跟随像素时钟时钟,一个时钟周期输出一个字节的数据,而且数据只在行同步信号href为高时有效,我们可以将行同步信号href当作是摄像头输出数据有效指示信号来使用。输出图像数据有效指示信号dout_vld:一个像素数据为2个字节,摄像头每个时钟输出一个字节,因此是输出两次有效一次。初始状态为0,表示数据无效,当行计数器cnt_x的第0位为1的时候,该信号变为1,表示输出图像数据有效。 最后一个有效数据指示信号dout_eop:当行计数器cnt_x等于1280-1,并且列计数器等于480-1的时候,表示输出最后一个有效数据,该信号为高,其他时候为低电平。 1.6.3参考代码- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt_x <= 0;
- end
- else if(add_cnt_x)begin
- if(end_cnt_x)begin
- cnt_x <= 0;
- end
- else begin
- cnt_x <= cnt_x + 1;
- end
- end
- end
- assign add_cnt_x = flag_capture && din_vld;
- assign end_cnt_x = add_cnt_x && cnt_x == COL*2-1;
- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt_y <= 0;
- end
- else if(add_cnt_y)begin
- if(end_cnt_y)begin
- cnt_y <= 0;
- end
- else begin
- cnt_y <= cnt_y + 1;
- end
- end
- end
- assign add_cnt_y = end_cnt_x;
- assign end_cnt_y = add_cnt_y && cnt_y == ROW-1;
- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- dout <= 0;
- end
- else if(din_vld)begin
- dout <= {dout[7:0],din};
- end
- end
- assign din_vld = flag_capture && href;
- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- dout_vld <= 0;
- end
- else if(flag_dout_vld)begin
- dout_vld <= 1;
- end
- else begin
- dout_vld <= 0;
- end
- end
- assign flag_dout_vld = add_cnt_x && cnt_x[0] == 1;
- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- dout_sop <= 0;
- end
- else if(flag_dout_vld && cnt_x[10:1] == 0 && cnt_y == 0)begin
- dout_sop <= 1;
- end
- else begin
- dout_sop <= 0;
- end
- end
- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- dout_eop <= 0;
- end
- else if(flag_dout_vld && cnt_x[10:1] == COL-1 && cnt_y == ROW-1)begin
- dout_eop <= 1;
- end
- else begin
- dout_eop <= 0;
- end
- end
[color=rgb(51, 102, 153) !important]复制代码
1.7 存储控制模块设计
1.7.1接口信号1.7.2设计思路
本工程的主体时钟为25M,显示器的分辨率为640*480,相当于一个时钟周期显示一个像素。而摄像头的工作时钟也是25M,但是数据位宽为8bit,相当于两个时钟输出一个像素,如果直接将采集模块接收到的数据送给显示器显示,那么一个像素点会被显示两次,这肯定是不对的,因此需要先将摄像头输出的数据缓存起来,当存够一帧图像之后,再送给显示器进行显示,显示器本该1秒显示60帧图像的,降低到了30帧,但是显示的图像是正确而且连续的 读FIFO的读使能:当进入到有效显示区域的时候,便将FIFO内部数据读出。
读FIFO的写使能:当SDRAM输出有效数据的时候,写使能便拉高,将数据写入。
1.7.3参考代码
- always @(*)begin
- if(rd_wfifo_flag)
- bank=waddr_bank;
- else
- bank=raddr_bank;
- end
- always @(*)begin
- if(rd_wfifo_flag)
- addr=waddr;
- else
- addr=raddr;
- end
- fifo_16bwrite fifo_16bwrite_inst (
- .aclr ( ~rst_n ),
- .data (din ),
- .rdclk (clk_in ),
- .rdreq (wfifo_rdreq ),
- .wrclk (clk ),
- .wrreq (wfifo_wrreq ),
- .q (wdata ),
- .rdempty (wfifo_rdempty ),
- .rdusedw (wfifo_rdusedw )
- always @(posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- cnt_rd_wfifo <= 0;
- end
- else if(add_cnt_rd_wfifo)begin
- if(end_cnt_rd_wfifo)
- cnt_rd_wfifo <= 0;
- else
- cnt_rd_wfifo <= cnt_rd_wfifo + 1;
- end
- end
- assign add_cnt_rd_wfifo = rd_wfifo_flag&&((cnt_rd_wfifo==0&&wr_ack)||(cnt_rd_wfifo!=0));
- assign end_cnt_rd_wfifo = add_cnt_rd_wfifo && cnt_rd_wfifo== 512-1;
- always @(*)begin
- if(add_cnt_rd_wfifo&&wfifo_rdempty==0)
- wfifo_rdreq=1;
- else
- wfifo_rdreq=0;
- end
- assign add_cnt_bank = change_bank_instr;
- assign end_cnt_bank = add_cnt_bank && cnt_bank==8-1 ;
[color=rgb(51, 102, 153) !important]复制代码
1.8 SDRAM接口模块设计
本模块的设计跟上一个案例《SDRAM读写控制器》基本一样,这里就不再做介绍,想要了解的可以去明德扬论坛搜素相关**即可。 【每周FPGA案例】SDRAM读写控制器
1.9 VGA接口模块设计
本模块的设计可以参考明德扬书籍《FPGA至简设计原理与应用》中关于VGA部分的案例,也可以在明德扬论坛中搜索书籍名称。 【FPGA至简设计原理与应用】书籍连载17 第三篇FPGA至简设计项目 第八章VGA显示颜色
1.10 效果和总结
|