上一节学习了OV7725的配置协议SCCB,且该协议几乎与一致,大家可能会疑惑应答位的问题,实际上SCCB协议虽说不关心,但是还是会把SDA拉低;这一节我们将讲解OV7725配置模块中SCCB发送器的Verilog实现。

学习目标

  • ​​​SCCB协议Verilog实现

系统框图

可以看到OV7725配置模块包括两个部分,其中ov7725_cfg模块负责存储配置信息,i2c_ctrl模块,也就是本篇文章所讨论的SCCB发送器,负责发送协议控制。

SCCB发送模块实现

接口列表

sys_reset_n 复位信号、低电平有效
sys_clk 输入时钟信号
i2c_start i2c启动信号
byte_addr[7:0] 寄存器地址信号
i2c_data[7:0] 配置数据
i2c_clk 输出时钟
i2c_scl SCL信号
i2c_sda SDA信号
done 传输完成标志

状态转移图

由于只考虑了“三步写”的实现,本模块只有九个状态

IDLE -->START1 -->TRAN1(发送器件ID)-->STOP1 -->START2-->TRAN2(发送寄存器地址)-->STOP2 -->START3-->TRAN3(发送配置信息) -->STOP3 -->IDLE

实例代码

/*
SCCB transmitter By WWD 2022/9/2实现SCCB协议“三步写”操作1、7位地址码+1位读写控制码+1位无关位
2、8位寄存器地址码+1位无关位
3、8位数据+1位无关位思路:
先将50MHz的时钟进行计数器分频得到1MHz的 i2c_clk,(25次基频翻转一次,一个周期为50个基频,1MHz)
再以四个i2c_clk为一个信号周期,进行数据传送,这样做的好处是可以控制SCL和SDA的高低电平占比。
*/module SCCB(//输入
input wire sys_reset_n, //复位信号,低电平有效
input wire sys_clk, //时钟信号,频率为50MHz
input wire i2c_start, //使能信号,高电平有效,开始传输数据
input wire [7:0] byte_addr, //八位地址信号
input wire [7:0] i2c_data, //八位数据信号
//输出
output wire  SCL, //SCL信号线
output wire  SDA, //SDA信号线
output reg i2c_clk, //I2C时钟线,频率1MHz
output reg done //数据传输完成标志信号,高电平有效
);parameter
IDLE = 4'd0,START1 = 4'd1,TRAN1 = 4'd2,
STOP1 = 4'd3,START2=4'd4,TRAN2 = 4'd5,STOP2 = 4'd6,
START3=4'd7,TRAN3 = 4'd8,STOP3 = 4'd9; //状态声明reg [3:0] state,next_state; reg [4:0] cnt_iic; //初步分频1MHz,reg [3:0] i2c_clk_cnt; //计数0~3reg [3:0] i2c_bit_cnt;//计算传输了多少位数据reg i2c_sda,i2c_scl; //数据暂存reg [7:0] id_addr=8'b1010_0000;//设备地址always@(posedge i2c_clk or negedge sys_reset_n)begin //状态转移及复位,注意我们这里使用分频之后的时钟进行触发,如果采用基频状态会快速转移。if(sys_reset_n == 1'b0)state <= IDLE;elsestate <= next_state;
end/*
时钟基频为50MHz,但是我们IIC通信的频率为250KHZ,因此要进行分频
分频方案有PLL IP核或者使用计数器分频,为节约IP资源考虑,我们选择计数器分频
很多人有疑惑,为什么要先产生1MHz的时钟信号,这是为了更好地产生SDA和SCL考虑。
直接生成250KHz就不能进行每半个周期的操作了。
*/always@(posedge sys_clk or negedge sys_reset_n)begin //计数0~24if(sys_reset_n == 1'b0)cnt_iic <= 5'd0;
else if(cnt_iic == 5'd24)cnt_iic <= 5'd0;
elsecnt_iic <= cnt_iic + 5'd1;
endalways@(posedge sys_clk or negedge sys_reset_n)begin //0~24个基频为半个周期,翻转一次,一个周期50个基频。产生1MHZ的时钟i2c_clkif(sys_reset_n == 1'b0)i2c_clk <= 1'b1;else if(cnt_iic == 5'd24)i2c_clk <= ~i2c_clk;elsei2c_clk <= i2c_clk;
endalways@(posedge i2c_clk or negedge sys_reset_n)begin //四分频,这个的有效信号是i2c_clk_cnt,范围0—3这个信号特别关键,决定了每个状态持续的时间
if(sys_reset_n == 1'b0)i2c_clk_cnt <= 3'd0;
else if(i2c_clk_cnt == 3'd3)i2c_clk_cnt <= 3'd0;
else if(i2c_start == 1'b1) //防止四分频计数器错乱i2c_clk_cnt <= i2c_clk_cnt+3'd1;
end//对数据位进行计数,i2c_bit_cnt范围0-9,虽然有效数据只有八个,再加上一个无效位也才九位,但是由于停止状态还需要占一个数据周期,因此需要计数10位。always@(posedge i2c_clk or negedge sys_reset_n)begin if(sys_reset_n == 1'b0)i2c_bit_cnt <= 4'd0;else if((i2c_bit_cnt == 4'd9)&&(i2c_clk_cnt == 3'd3))i2c_bit_cnt <= 4'd0;else if((i2c_clk_cnt == 3'd3)&&((state == TRAN1)||(state == TRAN2)||(state == TRAN3))) //防止无效状态干扰计数i2c_bit_cnt <= i2c_bit_cnt +4'd1;elsei2c_bit_cnt <= i2c_bit_cnt;
end//这里要理解的就是状态跳转的时间。我们知道状态是每次 i2c_clk上升沿转移的,因此如果我们想要每个状态都维持4个i2c_clk周期,那么判断的条件要加上(i2c_clk_cnt == 3'd3)
//如果不加,那么一到这个数字就会跳转,从而缩短状态的时间。
/*
计数器时间段 :数据序号
0-1 0
1-2 1
2-3 2
3-4 3
4-5 4
5-6 5
6-7 6
7-8 7
8-9 8 //无关位周期
9-0 9 //停止周期
*/always@(*)begin //状态转移条件:持续时间的确定case(state)IDLE: if((i2c_start == 1'b1)&&(i2c_clk_cnt == 3'd3))next_state = START1;elsenext_state = state;START1: if(i2c_clk_cnt == 3'd3)next_state = TRAN1;elsenext_state = state;TRAN1 : if(i2c_bit_cnt == 4'd9) //注意转移条件是计数值为9,如果到八就跳转那么第九个无关位会被跳过next_state = STOP1;elsenext_state = state;STOP1: if((i2c_bit_cnt==4'd9)&&(i2c_clk_cnt == 3'd3))//next_state = START2;elsenext_state = state;START2: if(i2c_clk_cnt == 3'd3)next_state = TRAN2;elsenext_state = state;TRAN2 : if(i2c_bit_cnt == 4'd9)next_state = STOP2;elsenext_state = state;STOP2: if((i2c_bit_cnt==4'd9)&&(i2c_clk_cnt == 3'd3))next_state = START3;elsenext_state = state;START3: if(i2c_clk_cnt == 3'd3)next_state = TRAN3;TRAN3 : if(i2c_bit_cnt == 4'd9)next_state = STOP3;elsenext_state = state;STOP3: if((i2c_bit_cnt==4'd9)&&(i2c_clk_cnt == 3'd3))next_state = IDLE;elsenext_state = state;endcase
endalways@(posedge sys_clk or negedge sys_reset_n)begin //核心,SDA和SCL数据波形的确定if(sys_reset_n == 1'b0)begini2c_scl <= 1'b1;i2c_sda <= 1'b1;done <= 1'b0;end
elsecase(state)IDLE:beginif(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b1;i2c_scl <= 1'b1;done <= 1'b0;//完成标志复位end else if(i2c_clk_cnt>1)begin //后半个周期i2c_sda <= 1'b1;i2c_scl <= 1'b1;end endSTART1:begin //起始位,SCL为高,SDA拉低if(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b1;i2c_scl <= 1'b1;end else if(i2c_clk_cnt>1)begin //后半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b1;end endTRAN1: begin if(i2c_bit_cnt<8)begin //前八位是数据位if(i2c_clk_cnt<=1)begin //前半个周期,SCL为低,SDA传递数据i2c_sda <= id_addr[7-i2c_bit_cnt];i2c_scl <= 1'b0;end else if(i2c_clk_cnt>1)begin //后半个周期,SCL为高,SDA保持i2c_sda <= i2c_sda;i2c_scl <= 1'b1;end end else begin //第九位,应答位,此位SDA需要置0if(i2c_clk_cnt<=1)begini2c_sda <= 1'b0;i2c_scl <= 1'b0;end else if(i2c_clk_cnt >1)begini2c_sda <= i2c_sda;i2c_scl <= 1'b1;end endend STOP1: //停止位,SCL为高,SDA拉高beginif(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b0;end else if(i2c_clk_cnt>1)begin //后半个周期i2c_scl <= 1'b1;i2c_sda <= 1'b0;if(i2c_clk_cnt == 3)i2c_sda <= 1'b1;end endSTART2:beginif(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b1;i2c_scl <= 1'b1;end else if(i2c_clk_cnt>1)begin //后半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b1;end endTRAN2: begin if(i2c_bit_cnt<8)begin //前八位是数据位if(i2c_clk_cnt<=1)begin //前半个周期,SCL为低,SDA传递数据i2c_sda <= byte_addr[7-i2c_bit_cnt];i2c_scl <= 1'b0;end else if(i2c_clk_cnt>1)begin //后半个周期,SCL为高,SDA保持i2c_sda <= i2c_sda;i2c_scl <= 1'b1;end end else begin //第九位,应答位,此位SDA需要置0if(i2c_clk_cnt<=1)begini2c_sda <= 1'b0;i2c_scl <= 1'b0;end else if(i2c_clk_cnt >1)begini2c_sda <= i2c_sda;i2c_scl <= 1'b1;end endend STOP2:beginif(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b0;end else if(i2c_clk_cnt>1)begin //后半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b1;if(i2c_clk_cnt == 3)i2c_sda <= 1'b1;end end   START3:beginif(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b1;i2c_scl <= 1'b1;end else if(i2c_clk_cnt>1)begin //后半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b1;end endTRAN3: begin if(i2c_bit_cnt<8)begin //前八位是数据位if(i2c_clk_cnt<=1)begin //前半个周期,SCL为低,SDA传递数据i2c_sda <= i2c_data[7-i2c_bit_cnt];i2c_scl <= 1'b0;end else if(i2c_clk_cnt>1)begin //后半个周期,SCL为高,SDA保持i2c_sda <= i2c_sda;i2c_scl <= 1'b1;end end else begin //第九位,应答位,此位SDA需要置0if(i2c_clk_cnt<=1)begini2c_sda <= 1'b0;i2c_scl <= 1'b0;end else if(i2c_clk_cnt >1)begini2c_sda <= i2c_sda;i2c_scl <= 1'b1;end endend STOP3:beginif(i2c_clk_cnt<=1)begin //前半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b0;end else if(i2c_clk_cnt>1)begin //后半个周期i2c_sda <= 1'b0;i2c_scl <= 1'b1;if(i2c_clk_cnt == 3)begini2c_sda <= 1'b1;done <= 1'b1; //完成标志置位endend end
endcase
endassign SDA = i2c_sda;
assign SCL = i2c_scl;endmodule

TB文件

`timescale 1ns/1nsmodule tb_sccb();reg sys_reset_n;
reg sys_clk;
reg i2c_start;
reg [7:0] byte_addr;
reg [7:0] i2c_data;initial beginsys_reset_n = 1'b0;
sys_clk = 1'b1;
i2c_start = 1'b0;
byte_addr = 8'd0;
i2c_data = 8'd0;#20i2c_start = 1'b1;sys_reset_n = 1'b1;byte_addr = 8'b1111_1111;i2c_data = 8'b1010_0100;/*#2000byte_addr = 8'b1000_1111;i2c_data = 8'b1111_0100;*/
endalways #10 sys_clk = ~sys_clk; //50MHz时钟SCCB u1(
.sys_reset_n(sys_reset_n),
.sys_clk(sys_clk),
.i2c_start(i2c_start),
.byte_addr(byte_addr),
.i2c_data(i2c_data)
);endmodule

Modelsim仿真波形图

cnt_iic 计数sys_clk,i2c_clk是1MHz时钟,i2c_clk_cnt计数i2c_clk,关系正确。

i2c_bit_cnt 的计数值和state之间的关系正确(只有TRAN状态才开始计数)。

SDA和SCL之间的关系正确。(开始信号,数据传输八位+无效一位;结束信号)

通过仿真可以知道,目标功能成功实现(累死我了)。

下一节,我们将实现寄存器配置模块的功能!

FPGA之OV7725摄像头采集与VGA显示实验--2--SCCB协议发送器实现(Verilog代码)相关推荐

  1. FPGA之OV7725摄像头采集与VGA显示实验--4--摄像头数据输出VAG协议分析

    大家好,前面几节给大家介绍了OV7725摄像头通过SCCB协议进行配置的内容,这一节我们来聊一下关于OV7725摄像头的VAG协议的知识,为后面的数据采集模块做铺垫. 学习目标 理解VGA协议传输数据 ...

  2. FPGA--(基于Quartus的FPAG程序下载与固化教程)VGA显示实验之上板测试

    本节内容旨在教会大家如何下载程序进入FPGA并且验证我们前几节所做的VGA显示实验. 材料 EP4CE10F17C8N FPGA .USB_Blaster 下载器 完整Verilog代码.Quartu ...

  3. 【接口协议】FPGA 驱动 VGA 显示实验(二)实验设计部分

    目录 实验任务 实验环境 实验设计 程序设计 VGA 时序模块 模块框图 仿真波形 顶层模块 约束文件 实验任务 利用FPGA驱动VGA实现彩条显示,分辨率为800 × 600@60Hz,分别显示三种 ...

  4. 【Verilog】基于FPGA的五子棋小游戏(VGA显示、双人对战、胜负判别、附完整代码)

    基于FPGA的五子棋小游戏 有一些说明: 1.本文是基于VGA的显示小游戏,主要为VGA显示的拓展应用: 2.为适应不同显示屏的分辨率,棋盘确定为10X10的黑线白底的方格: 3.下棋主要用棋格颜色变 ...

  5. 基于FPGA的VGA显示实验

    VGA驱动原理 信号线 定义 HS 行同步信号(3.3V 电平) VS 场同步信号(3.3V 电平) R 红基色 (0~0.714V 模拟信号) G 绿基色 (0~0.714V 模拟信号) B 蓝基色 ...

  6. 基于 FPGA 的 RISC CPU 设计(2)详细的模块设计思路及其 Verilog 代码

    引言         其实,一个 CPU 的设计中,各个子模块都是比较基本的.比较简单的,只是组合起来的一个整体架构会比较复杂而已,无论是时序路径,还是数据通路和控制通路,这里,主要详细介绍整个微架构 ...

  7. OV7725学习之SCCB协议(一)

    OV7725摄像头只能作为从机,通过SCCB协议配置内置的172个寄存器.因此首先要了解的就是SCCB总线 1.SCCB协议简述 SCCB协议有两线也有三线,两线为SIO_C与SIO_D,三线为SIO ...

  8. 【正点原子FPGA连载】第三十九章OV7725摄像头RGB-LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  9. 【正点原子FPGA连载】第四十三章MT9V034摄像头RGB-LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

最新文章

  1. Kong APIGW — v.s. Apache APISIX
  2. 【LDPC/STBC】基于LDPC/STBC编译码的图像传输系统的MATLAB仿真
  3. Python中最长公共前缀
  4. 10.9 ping:测试主机之间网络的连通性
  5. jwt用户注销 PHP,flask_jwt 如何实现用户注销
  6. Spring实战第七章
  7. 计算机组成与设计英文版在线,计算机组成与设计(硬件软件接口英文版原书第5版RISC-V版)/经典原版书库...
  8. avs3 ts格式封装 标准_超能课堂(204):多媒体容器格式变迁录
  9. eeepc linux 软件管理,Linux_为EeePC增添活力 EeePC安装Ubuntu预览,虽然Eee PC运行基于Xandros的Linux - phpStudy...
  10. eplan 电箱布局_Eplan D布局步骤
  11. Win10超详细 JavaJDK的安装(D盘)及环境配置
  12. End-to-End(端到端)的理解
  13. 使用supervisor实现进程管理
  14. FPGA中模为60的BCD码加法计数器
  15. android 实时渲染模糊
  16. 如何利用XMLSpy实现从多个XML实例生成架构
  17. redis watch使用场景_redis使用watch完成秒杀抢购
  18. 华为鸿蒙的追逼,慌了的谷歌让新系统兼容安卓应用以加速推广
  19. las文件matlab,基于Matlab的LAS格式数据解析与显示.pdf
  20. WordPress插件:微信公众号涨粉插件

热门文章

  1. 中国银行软件中心2023校园招聘
  2. 【Pygame合集】滴~穿越童年游戏指南 请查收:这里面有你玩过的游戏嘛?(附五款源码自取)
  3. 2013年CISA考试知识点变化总结讲义
  4. 如何学习MATLAB
  5. SSVEP干电极介绍及其分类算法研究
  6. 优思学院|2020年10月美质协六西格玛黑带ASQ CSSBB考试经历……
  7. c语言实现补码转换成原码,(转)C语言之原码、反码和补码(示例代码)
  8. 微型计算机点火系统有分电器,汽车发动机点火系统,微机控制点火系统的类型与组织,你知道多少...
  9. 彩虹代shua最新6.6版本源码/修改版/后台同步官方版本升级
  10. 这几款好用超赞的 Google Chrome插件送给你!