说在前头
前两天老师布置了一个I2C协议的手绘图,我心想这么简单的协议我就用verilog写一个呗。
初步设想是双机,能够实现主从机相互转换,可以相互读写,但是现在只实现了主机写从机读从机,手头上目前还没有FPGA,主从任意转换就没有写了,今天就把目前做到的写下来,以后有机会就填坑。
I2C协议
瞌睡的洋葱博客
这位博主说的十分清楚了,再结合一下百度百科基本上就能搞清楚了。
时序图:
写寄存器的标准流程为:
- Master发起START
- Master发送I2C addr(7bit)和w操作0(1bit),等待ACK
- Slave发送ACK
- Master发送reg addr(8bit),等待ACK
- Slave发送ACK
- Master发送data(8bit),即要写入寄存器中的数据,等待ACK
- Slave发送ACK
- 第6步和第7步可以重复多次,即顺序写多个寄存器
- Master发起STOP
读寄存器的标准流程为:
- Master发送I2C addr(7bit)和w操作1(1bit),等待ACK
- Slave发送ACK
- Master发送reg addr(8bit),等待ACK
- Slave发送ACK
- Master发起START
- Master发送I2C addr(7bit)和r操作1(1bit),等待ACK
- Slave发送ACK
- Slave发送data(8bit),即寄存器里的值
- Master发送ACK
- 第8步和第9步可以重复多次,即顺序读多个寄存器
大家不懂的可以看看上面那一篇博客。
编程思路
既然了解了I2C协议的过程下面就是要解决问题了。
I2C协议的步骤如此清晰,我们自然而然想到使用状态机,状态机可以用FPGA系统时钟,这里为了方便我使用的是1Mhz的FPGA时钟,I2C_SCL时钟为系统时钟的10分频.
状态机我分为6个状态:
- wait状态,完成部分初始化,SDA与SCL不工作时都为高;
- start状态,主机在SCL为高时将SDA拉低
- adder状态,主机输出7位设备地址和读写控制符
- adder2状态,主机输出数据存放地址
- doing状态,主机读写数据
- ask状态,每写完地址或者数据从机都需要应答
- stop状态,结束,回到等待状态
parameter s_wait=3'b000,s_start=3'b001,s_adder1=3'b010,s_adder2=3'b011,s_doing=3'b100,s_ask=3'b101,s_stop=3'b110;
完整代码如下
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/05/13 17:34:09
// Design Name:
// Module Name: I2Cpro_top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module I2Cpro_top(
input clk,
input rst_n,
input enable, //使能I2C模块
input wr_cont,//0写,1读
input [6:0]Device_addr,//设备地址
input [7:0]Store_addr,//储存地址
input [7:0]data_in,
output [7:0]data_out,
inout SDA,
inout SCL);
parameter s_wait=3'b000,s_start=3'b001,s_adder1=3'b010,s_adder2=3'b011,s_doing=3'b100,s_ask=3'b101,s_stop=3'b110;
parameter data_size=8;
reg doing=1'b0; //SDA数据有效,开始到结束
reg ready=1'b0; //进行数据读写
reg [7:0]data_reg[7:0]; //暂存输入的所有数据
reg [7:0]data_wr=8'h00,data_rd=8'h00; //暂存收发数据
reg [7:0]rDevice_wr_addr=8'b000; //设备地址和读写控制
reg [7:0]rStore_addr=8'h00; //读写地址
reg rSDA=1'b1; //SDA
reg [2:0]rState=s_wait; //I2C控制状态机
reg [2:0]bit_cnt=3'b000; //读写bit计数器
reg [7:0]data_cnt=data_size+2; //读写byte计数器
reg [4:0]buffer=5'b00001; //在clk驱动的状态机中与SCL时钟规则匹配
reg s_cnt=1'b0; //用于控制两次数据传输间隙
//调用偶分频div模块
//SCL时钟由clk系统时钟10分频产生
sysclk2I2CSCL #(
.N(10) //分频数为10
)sysclk2I2CSCL2(
.clk(clk),
.rst_n(rst_n),
.SCL(SCL)
);
// 如果SCL高电平的时候检测到了SDA的高转低说明开始
always@(negedge SDA)begin
if(SCL==1)
doing<=1'b1;
end
// 如果SCL高电平的时候检测到SDA的低转高说明结束
always@(posedge SDA)begin
if(SCL==1)
doing<=1'b0;
end
always@(posedge clk)begin
if(enable)begin
if(!rst_n)begin
rSDA<=1;
rState<=s_wait;
buffer<=5'b00001;
data_cnt<=data_size+2;
bit_cnt<=3'b000;
end
else begin
case(rState)
s_wait:begin
if(SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])begin
if(s_cnt==1)begin
rSDA<=1'b1;
rState<=s_start;
rDevice_wr_addr<={Device_addr,wr_cont};
rStore_addr<=Store_addr;
data_wr<=data_in;
data_cnt<=data_size+2;
bit_cnt<=3'b000;
end
buffer<=5'b00001;
s_cnt<=s_cnt+1;
end
end
end
s_start:begin
if(SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])
begin
rSDA<=1'b0;
rState<=s_adder1;
buffer<=5'b00001;
end
end
end
s_adder1:begin
if(!SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])
begin
rSDA<=rDevice_wr_addr[0];
rDevice_wr_addr<={rDevice_wr_addr[0],rDevice_wr_addr[7:1]};
bit_cnt<=bit_cnt+1'b1;
if(bit_cnt==7)
begin
data_cnt<=data_cnt-1;
rState<=s_ask;
buffer<=5'b00001;
end
end
end
end
s_adder2:begin
if(!SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])
begin
rSDA<=rStore_addr[0];
rStore_addr<={rStore_addr[0],rStore_addr[7:1]};
bit_cnt<=bit_cnt+1'b1;
if(bit_cnt==7)
begin
data_cnt<=data_cnt-1;
rState<=s_ask;
buffer<=5'b00001;
end
end
end
end
s_doing:begin
if(wr_cont==0)begin//如果是写,写入数据改变SDA
if(!SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])begin
rSDA<=data_wr[0];
data_wr<={data_wr[0],data_wr[7:1]};
bit_cnt<=bit_cnt+1'b1;
if(bit_cnt==7)begin
data_cnt<=data_cnt-1;
rState<=s_ask;
buffer<=5'b00001;
end
end
end
end
else begin
if(SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])begin
data_rd[0]<=SDA;
data_rd<={data_rd[0],data_rd[7:1]};
bit_cnt<=bit_cnt+1;
if(bit_cnt==7)begin
data_reg[data_cnt]<=data_rd;
data_cnt<=data_cnt+1;
end
end
end
end
end
s_ask:begin
if(wr_cont==0)begin //等待回复ask
if(SCL && !SDA)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])begin
if(data_cnt==data_size+1)
rState<=s_adder2;
else if(data_cnt==0)
rState<=s_stop;
else
rState<=s_doing;
end
end
end/*
else begin //主动回复ask 从机时候
if(!SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])begin
rSDA<=1'b0;
buffer<=5'b00001;
if(data_cnt==data_size+1)
rState<=s_adder2;
else if(data_cnt==0)
rState<=s_stop;
else
rState<=s_doing;
end
end
end */
end
s_stop:begin //结束
if(SCL)begin
buffer<={buffer[3:0],buffer[4]};
if(buffer[2])
begin
if(s_cnt==1)begin
rSDA<=1'b1;
rState<=s_wait;
end
s_cnt<=s_cnt+1;
buffer<=5'b00001;
end
end
end
endcase
end
end
end
// 读数据
assign SDA=rSDA;
assign data_out=data_rd;
endmodule
tsetbench
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/05/11 15:07:11
// Design Name:
// Module Name: I2Ctb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module I2Ctb();
reg clk;
reg rst_n;
reg enable;
reg wr_cont;
reg[6:0]Device_addr;//设备地址
reg[7:0]Store_addr;//储存地址
reg[7:0]data_in;
wire [7:0]data_out;
wire SDA;
wire SCL;
I2Cpro_top I2C_top1(
.clk(clk),
.rst_n(rst_n),
.enable(enable),
.wr_cont(wr_cont),
.Device_addr(Device_addr),
.Store_addr(Store_addr),
.data_in(data_in),
.data_out(data_out),
.SDA(SDA),
.SCL(SCL)
);
always #5 clk=~clk;
initial
begin
clk=0;
rst_n=0;
enable=0;
wr_cont=0;
Device_addr=7'h78;
Store_addr=8'haa;
data_in=8'hff;
#10
enable=1;
#10
rst_n=1;
#20000
wr_cont=1;
end
endmodule
有人读的话再继续完善