上一节学习了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~3
reg [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;
else
state <= next_state;
end
/*
时钟基频为50MHz,但是我们IIC通信的频率为250KHZ,因此要进行分频
分频方案有PLL IP核或者使用计数器分频,为节约IP资源考虑,我们选择计数器分频
很多人有疑惑,为什么要先产生1MHz的时钟信号,这是为了更好地产生SDA和SCL考虑。
直接生成250KHz就不能进行每半个周期的操作了。
*/
always@(posedge sys_clk or negedge sys_reset_n)begin //计数0~24
if(sys_reset_n == 1'b0)
cnt_iic <= 5'd0;
else if(cnt_iic == 5'd24)
cnt_iic <= 5'd0;
else
cnt_iic <= cnt_iic + 5'd1;
end
always@(posedge sys_clk or negedge sys_reset_n)begin //0~24个基频为半个周期,翻转一次,一个周期50个基频。产生1MHZ的时钟i2c_clk
if(sys_reset_n == 1'b0)
i2c_clk <= 1'b1;
else if(cnt_iic == 5'd24)
i2c_clk <= ~i2c_clk;
else
i2c_clk <= i2c_clk;
end
always@(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;
else
i2c_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;
else
next_state = state;
START1:
if(i2c_clk_cnt == 3'd3)
next_state = TRAN1;
else
next_state = state;
TRAN1 : if(i2c_bit_cnt == 4'd9) //注意转移条件是计数值为9,如果到八就跳转那么第九个无关位会被跳过
next_state = STOP1;
else
next_state = state;
STOP1: if((i2c_bit_cnt==4'd9)&&(i2c_clk_cnt == 3'd3))//
next_state = START2;
else
next_state = state;
START2: if(i2c_clk_cnt == 3'd3)
next_state = TRAN2;
else
next_state = state;
TRAN2 : if(i2c_bit_cnt == 4'd9)
next_state = STOP2;
else
next_state = state;
STOP2: if((i2c_bit_cnt==4'd9)&&(i2c_clk_cnt == 3'd3))
next_state = START3;
else
next_state = state;
START3: if(i2c_clk_cnt == 3'd3)
next_state = TRAN3;
TRAN3 : if(i2c_bit_cnt == 4'd9)
next_state = STOP3;
else
next_state = state;
STOP3: if((i2c_bit_cnt==4'd9)&&(i2c_clk_cnt == 3'd3))
next_state = IDLE;
else
next_state = state;
endcase
end
always@(posedge sys_clk or negedge sys_reset_n)begin //核心,SDA和SCL数据波形的确定
if(sys_reset_n == 1'b0)begin
i2c_scl <= 1'b1;
i2c_sda <= 1'b1;
done <= 1'b0;
end
else
case(state)
IDLE:begin
if(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
end
START1: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
end
TRAN1: 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需要置0
if(i2c_clk_cnt<=1)begin
i2c_sda <= 1'b0;
i2c_scl <= 1'b0;
end else if(i2c_clk_cnt >1)begin
i2c_sda <= i2c_sda;
i2c_scl <= 1'b1;
end end
end
STOP1: //停止位,SCL为高,SDA拉高
begin
if(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
end
START2:begin
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
end
TRAN2: 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需要置0
if(i2c_clk_cnt<=1)begin
i2c_sda <= 1'b0;
i2c_scl <= 1'b0;
end else if(i2c_clk_cnt >1)begin
i2c_sda <= i2c_sda;
i2c_scl <= 1'b1;
end end
end
STOP2:
begin
if(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:begin
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
end
TRAN3: 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需要置0
if(i2c_clk_cnt<=1)begin
i2c_sda <= 1'b0;
i2c_scl <= 1'b0;
end else if(i2c_clk_cnt >1)begin
i2c_sda <= i2c_sda;
i2c_scl <= 1'b1;
end end
end
STOP3:
begin
if(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)begin
i2c_sda <= 1'b1;
done <= 1'b1; //完成标志置位
end
end
end
endcase
end
assign SDA = i2c_sda;
assign SCL = i2c_scl;
endmodule
TB文件
`timescale 1ns/1ns
module tb_sccb();
reg sys_reset_n;
reg sys_clk;
reg i2c_start;
reg [7:0] byte_addr;
reg [7:0] i2c_data;
initial begin
sys_reset_n = 1'b0;
sys_clk = 1'b1;
i2c_start = 1'b0;
byte_addr = 8'd0;
i2c_data = 8'd0;
#20
i2c_start = 1'b1;
sys_reset_n = 1'b1;
byte_addr = 8'b1111_1111;
i2c_data = 8'b1010_0100;
/*#2000
byte_addr = 8'b1000_1111;
i2c_data = 8'b1111_0100;*/
end
always #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之间的关系正确。(开始信号,数据传输八位+无效一位;结束信号)
通过仿真可以知道,目标功能成功实现(累死我了)。
下一节,我们将实现寄存器配置模块的功能!
本文含有隐藏内容,请 开通VIP 后查看