首先是比较好的博客与资料:
本次实践是I2C总线协议的硬件实现,其中本次实践只能用于
其核心思想:利用状态机的跳转来控制SDA、SCL产生符合I2C协议的时序,属于时序电路控制组合电路输出。
从参考资料2中,可知I2C总线工作的速度分为标准模式(100kbit/s)、快速模式(400kbit/s),因此SCL的速度受到限制。本次实践中,我采用了比I2C工作速度高2倍的时钟作为时钟输入,并且经过时钟二分频后,即可得到满足要求的SCL,具体实现如下(相当于时钟分频器):
/*产生SCL时钟:二分频*/
//下降沿时钟跳转:SCL中点检测SDA的电平
always@(negedge clk or negedge rst_n)
begin
if(!rst_n)
SCL <= 1;
else
SCL <= ~SCL;
end
重点: 采用clk下降沿进行时钟分频,由此后面模块使用clk上升沿判断时,可达到在SCL电平中点检测SDA的电平状态。
SDA是一个可输入/输出类型引脚,因此使用三态门的结构进行构建,同时硬件外部是将SDA线上拉的,所以具体实现方式如下:
/*组合逻辑控制产生电路*/
assign SDA = (Link_SDA)? SDA_reg:1'bz;
由参考资料1,可以得到I2C总线协议的基础状态为:起始信号、传输数据(控制字节/数据字节)、应答、停止信号。
以下是I2C单字节的读写操作顺序:
I2C写操作:
起始信号->传输写控制字节->从机应答->传输数据字节->从机应答->停止信号
I2C读操作:
起始信号->传输写控制字节->从机应答->传输器件存储字节->从机应答->起始信号->传输读控制字节->读数据->主机非应答->停止信号
由以上的读写顺序,结合I2C总线协议的基础状态,可得到如下的状态机跳转图:
其中ack状态内包含了判断I2C总线协议读/写的跳转,为此状态机的要点。
此处的状态机,采用三段式状态机书写。其中重点是:
具体硬件代码如下:
module I2C_Timing(
input clk, //时钟、复位
input rst_n,
input I2C_en, //I2C使能
input wr_en, //写使能
input rd_en, //读使能
inout SDA,
output reg SCL,
input [7:0] ctrl_in, //I2C控制字节输入
input [7:0] data_in, //I2C数据字节输入
output reg done, //测试信号输出
output reg [7:0] data_out //I2C数据输出
);
/*信号及寄存器定义*/
reg Link_SDA; //Link_SDA=1时,SDA为输出;Link_SDA=0时,SDA为输入
reg SDA_reg; //I2C的SDA输出寄存器
reg Finish; //从状态机完成标志位
reg wr_falg; //写标志位
reg [1:0]rd_falg; //读标志位
reg [7:0] write_buf; //数据写入缓冲寄存器
reg [7:0] read_buf; //数据读出缓冲寄存器
reg [4:0] current_state; //主状态机当前状态寄存器
reg [4:0] next_state; //主状态机下一状态寄存器
reg [4:0] start_state; //task:start状态寄存器
reg [4:0] stop_state; //task:stop状态寄存器
reg [5:0] P2S_state; //task:P2S状态寄存器
reg [5:0] S2P_state; //task:S2P状态寄存器
//-----主状态机状态定义-----
parameter
idle = 5'b0_0000, //空闲状态
gen_start = 5'b0_0001, //产生起始信号
trans_ctrl = 5'b0_0011, //传送控制字节
trans_data = 5'b0_0010, //传送数据字节
ack = 5'b0_0110, //应答
gen_stop = 5'b0_0111, //产生停止信号
rd_data = 5'b0_0101; //读取I2C数据
//-----从状态机状态定义-----
parameter
start_idle = 5'b1_0000, //起始状态
start_ready = 5'b1_0001, //拉高SDA
start_sda = 5'b1_0011, //拉低SDA,产生下降沿
start_stop = 5'b1_0010; //停止状态
parameter
stop_idle = 5'b1_1000, //起始状态
stop_ready = 5'b1_1001, //拉低SDA
stop_sda = 5'b1_1011, //拉高SDA,产生上升沿
stop_stop = 5'b1_1010; //停止状态
parameter
P2S_bit7 = 6'b10_0000,
P2S_bit6 = 6'b10_0001,
P2S_bit5 = 6'b10_0011,
P2S_bit4 = 6'b10_0010,
P2S_bit3 = 6'b10_0110,
P2S_bit2 = 6'b10_0101,
P2S_bit1 = 6'b10_0100,
P2S_bit0 = 6'b10_1100,
P2S_stop = 6'b10_1101;
parameter
S2P_bit7 = 6'b11_0000,
S2P_bit6 = 6'b11_0001,
S2P_bit5 = 6'b11_0011,
S2P_bit4 = 6'b11_0010,
S2P_bit3 = 6'b11_0110,
S2P_bit2 = 6'b11_0101,
S2P_bit1 = 6'b11_0100,
S2P_bit0 = 6'b11_1100,
S2P_stop = 6'b11_1101;
//-----常量定义-----
parameter YES = 1;
parameter NO = 0;
/*组合逻辑控制产生电路*/
assign SDA = (Link_SDA)? SDA_reg:1'bz;
/*产生SCL时钟:二分频*/
//下降沿时钟跳转:SCL中点检测SDA的电平
always@(negedge clk or negedge rst_n)
begin
if(!rst_n)
SCL <= 1;
else
SCL <= ~SCL;
end
/*主状态机:三段式*/
//-----第一段-----
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
current_state <= idle;
else
current_state <= next_state;
end
//-----第二段-----
always@(current_state or Finish)
begin
case(current_state)
idle: next_state <= gen_start;
gen_start:
begin
if(Finish==1)
next_state <= trans_ctrl; //产生开始信号完成,跳转传送控制字节
else
next_state <= gen_start;
end
trans_ctrl:
begin
if(Finish==1)
begin
if(rd_en && (rd_falg==2)) //I2C读操作,第二次产生开始信号和传送控制字节完成,
next_state <= rd_data; //开始接收SDA数据
else
next_state <= ack;
end
else
begin
next_state <= trans_ctrl;
end
end
ack:
begin
if(Finish==1)
begin
if(wr_en && wr_falg)
begin
next_state <= gen_stop; //I2C写操作完成
end
else if(rd_en && (rd_falg==1))
begin
next_state <= gen_start; //I2C读操作,完成控制字节和器件存储字节的传送,
end //开始产生开始信号,再写如I2C读操作的控制字节
else
next_state <= trans_data;
end
else
begin
next_state <= ack;
end
end
trans_data:
begin
if(Finish==1)
begin
next_state <= ack;
end
else
begin
next_state <= trans_data;
end
end
rd_data:
begin
if(Finish==1)
next_state <= gen_stop;
else
next_state <= rd_data;
end
gen_stop:
begin
if(Finish==1)
begin
next_state <= idle;
end
else
next_state <= gen_stop;
end
default: next_state <= idle;
endcase
end
//-----第三段-----
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
Link_SDA <= NO;
Finish <= 0;
SDA_reg <= 0;
done <= 0;
wr_falg <= 0;
rd_falg <= 0;
write_buf<= 0;
read_buf <= 0;
end
else
begin
case(current_state)
idle:
begin
Link_SDA <= YES;
SDA_reg <= 1;
Finish <= 0;
write_buf <= ctrl_in;
start_state <= start_sda; //为满足I2C时序,须下一个clk产生开始信号
end
gen_start:
begin
if(Finish==0)
start;
else
begin
Finish <= 0;
Link_SDA <= YES;
SDA_reg <= write_buf[7]; //为满足I2C时序,start信号后,必紧跟数据最高位
P2S_state <= P2S_bit6;
end
end
trans_ctrl:
begin
if(Finish==0)
P2S;
else
begin
done <= 1; //测试信号
Finish <= 0;
Link_SDA <= NO;
if(rd_en && (rd_falg==2)) //I2C读操作,第二次产生开始信号和传送控制字节完成
begin //开始接收SDA数据
rd_falg <= 0;
Link_SDA <= NO;
read_buf[7] <= SDA; //满足I2C时序,start信号后,必紧跟数据最高位
S2P_state <= S2P_bit6;
end
else
;
end
end
ack:
begin
if((SDA==0)&&(SCL==1))
begin
done <= 0; //测试信号
Finish <= 1;
write_buf <= data_in;
end
else
begin
Finish <= 0;
if(wr_en && wr_falg) //I2C写操作完成
begin
wr_falg <= 0;
Link_SDA <= YES;
SDA_reg <= 0;
stop_state <= stop_sda; //为满足I2C时序,须下一个clk产生停止信号
end
else if(rd_en && (rd_falg==1)) //I2C读操作,完成控制字节和器件存储字节的传送
begin
rd_falg <= rd_falg+1;
write_buf <= ctrl_in;
start_state <= start_sda; //为满足I2C时序,须下一个clk产生开始信号
Link_SDA <= YES;
end
else
begin
Link_SDA <= YES;
P2S_state <= P2S_bit6;
SDA_reg <= write_buf[7]; //为满足I2C时序,应答后须立即传送数据
end
end
end
trans_data:
begin
if(Finish==0)
P2S;
else
begin
done <= 1; //测试信号
if(wr_en)
wr_falg <= 1;
else if(rd_en)
rd_falg <= 1;
else
begin
wr_falg <= 0;
rd_falg <= 0;
end
Finish <= 0;
Link_SDA <= NO;
end
end
rd_data:
begin
if(Finish==0)
S2P;
else
begin
data_out <= read_buf; //I2C读一字节数据完成
Link_SDA <= YES;
Finish <= 0;
SDA_reg <= 1;
stop_state <= stop_idle; //I2C读一字节后,主机发送非应答(拉高SDA),并延拍
end
gen_stop:
begin
if(Finish==0)
stop;
else
begin
rd_falg <= 0;
wr_falg <= 0;
Finish <= 0;
Link_SDA <= NO;
end
end
default: Link_SDA <= NO;
endcase
end
end
/*任务定义*/
//-----产生起始信号-----
task start;
case(start_state)
start_idle:
begin
Finish <= 0;
Link_SDA <= YES;
SDA_reg <= 1;
start_state <= start_sda;
end
start_ready:
begin
Link_SDA <= YES;
start_state <= start_sda;
end
start_sda:
begin
if(SCL)
begin
Link_SDA <= YES;
SDA_reg <= 0;
Finish <= 1;
start_state <= 5'b1_1111; //起始信号产生完成,设置为无效态
end
else
begin
start_state<= start_sda;
end
end
default:
begin
start_state <= 5'b1_1111; //无效态
end
endcase
endtask
//-----产生停止信号-----
task stop;
case(stop_state)
stop_idle:
begin
Finish <= 0;
Link_SDA <= YES;
SDA_reg <= 1;
stop_state <= stop_ready;
end
stop_ready:
begin
Link_SDA <= YES;
SDA_reg <= 0;
stop_state <= stop_sda;
end
stop_sda:
begin
if(SCL)
begin
Link_SDA <= YES;
SDA_reg <= 1;
Finish <= 1;
stop_state <= 5'b1_1111;
end
else
begin
stop_state<= stop_sda;
end
end
default:
begin
stop_state <= 5'b1_1111; //无效态
end
endcase
endtask
//-----并行数据转串行数据-----
task P2S;
case(P2S_state)
P2S_bit6:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[6];
P2S_state <= P2S_bit5;
end
else
begin
P2S_state <= P2S_bit6;
end
end
P2S_bit5:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[5];
P2S_state <= P2S_bit4;
end
else
begin
P2S_state <= P2S_bit5;
end
end
P2S_bit4:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[4];
P2S_state <= P2S_bit3;
end
else
begin
P2S_state <= P2S_bit4;
end
end
P2S_bit3:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[3];
P2S_state <= P2S_bit2;
end
else
begin
P2S_state <= P2S_bit3;
end
end
P2S_bit2:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[2];
P2S_state <= P2S_bit1;
end
else
begin
P2S_state <= P2S_bit2;
end
end
P2S_bit1:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[1];
P2S_state <= P2S_bit0;
end
else
begin
P2S_state <= P2S_bit1;
end
end
P2S_bit0:
begin
if(!SCL)
begin
Link_SDA <= YES;
SDA_reg <= write_buf[0];
P2S_state <= P2S_stop;
end
else
begin
P2S_state <= P2S_bit0;
end
end
P2S_stop:
begin
Finish <= 1;
if(!SCL)
begin
Link_SDA <= NO;
P2S_state <= 6'b11_1111; //完成传输,设置为无效态
end
else
begin
P2S_state <= P2S_stop;
end
end
default:
begin
P2S_state <= 6'b11_1111; //设置为无效态
end
endcase
endtask
//-----串行数据转并行数据-----
task S2P;
case(S2P_state)
S2P_bit6:
begin
done <= 0;
if(!SCL)
begin
Link_SDA <= NO;
read_buf[6] <= SDA;
S2P_state <= S2P_bit5;
end
else
begin
S2P_state <= S2P_bit6;
end
end
S2P_bit5:
begin
if(!SCL)
begin
Link_SDA <= NO;
read_buf[5] <= SDA;
S2P_state <= S2P_bit4;
end
else
begin
S2P_state <= S2P_bit5;
end
end
S2P_bit4:
begin
if(!SCL)
begin
Link_SDA <= NO;
read_buf[4] <= SDA;
S2P_state <= S2P_bit3;
end
else
begin
S2P_state <= S2P_bit4;
end
end
S2P_bit3:
begin
if(!SCL)
begin
Link_SDA <= NO;
read_buf[3] <= SDA;
S2P_state <= S2P_bit2;
end
else
begin
S2P_state <= S2P_bit3;
end
end
S2P_bit2:
begin
if(!SCL)
begin
Link_SDA <= NO;
read_buf[2] <= SDA;
S2P_state <= S2P_bit1;
end
else
begin
S2P_state <= S2P_bit2;
end
end
S2P_bit1:
begin
if(!SCL)
begin
Link_SDA <= NO;
read_buf[1] <= SDA;
S2P_state <= S2P_bit0;
end
else
begin
S2P_state <= S2P_bit1;
end
end
S2P_bit0:
begin
if(!SCL)
begin
Link_SDA <= NO;
read_buf[0] <= SDA;
S2P_state <= S2P_stop;
end
else
begin
S2P_state <= S2P_bit0;
end
end
S2P_stop:
begin
Finish <= 1;
if(!SCL)
begin
Link_SDA <= NO;
S2P_state <= 6'b11_1111; //完成传输,设置为无效态
end
else
begin
S2P_state <= S2P_stop;
end
end
default:
begin
S2P_state <= 6'b11_1111; //设置为无效态
end
endcase
endtask
endmodule
为了方便仿真,我在硬件代码中加入了done这个测试信号,用于在仿真时,给予应答信号(拉低SDA)
module I2C_Timing_tb;
reg clk;
reg rst_n;
reg I2C_en;
reg wr_en;
reg rd_en;
reg uSDA;
wire SDA;
wire SCL;
reg [7:0] ctrl_in;
reg [7:0] data_in;
wire [7:0] data_out;
wire done;
pullup(SDA);
//inout型仿真要求
assign SDA = uSDA?1'b0:1'bz;
//reg define
reg uuSDA;
I2C_Timing uI2C_Timing(
.clk(clk), //时钟、复位
.rst_n(rst_n),
.I2C_en(I2C_en), //I2C使能
.wr_en(wr_en), //写使能
.rd_en(rd_en), //读使能
.SDA(SDA),
.SCL(SCL),
.ctrl_in(ctrl_in),
.data_in(data_in),
.done(done),
.data_out(data_out)
);
always #50 clk = ~clk;
always@(clk)
begin
if(done)
uSDA <= 1;
else
uSDA <= 0;
end
initial
begin
#100
clk = 0;
rst_n = 1;
uSDA = 0;
I2C_en = 0;
ctrl_in = 8'b1000_0001;
data_in = 8'b1000_1001;
#150
rst_n = 0;
#50
rst_n = 1;
#100
wr_en = 0;
rd_en = 1;
#100
I2C_en = 1;
end
endmodule