在I2C在完成相关的读写操作后,就要向设备发送STOP操作,STOP作为一个独立模块,是因为在I2c总线发生错误时,也需要发送STOP,不仅仅是正常的I2c读写结束时。STOP 信号是在SCL信号为高电平期间,SDA信号由低变高。这个部分就是控制器对设备的STOP操作。在I2C PHY层 Verilog程序结构化设计 文章中的op_cmd[1:0] = 2’b11 来实现。如图1:

图1
1.I2C读数据部分控制器程序设计
-
接口要求:
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
- input rst,
- input sys_clk,
- input clk_en, clk_en_half,
- 命令信号
- input [1:0] op_cmd, // 2’b11 means controller send STOP
- input op_cmd_en, // op_cmd enable , sys_clk single pulse
- I2C 总线的内部信号:由于inout信号一般只在端口使用,因此在FPGA的内部逻辑(内部模块)将会把inout(双向口)变换成input, output类型进行传递
- input sda_i,
- output sda_o,
- output sda_t,
- input scl_i,
- output scl_o,
- output scl_t,
- 握手信号:op_cmd_done 操作完成、从设备应答。当op_cmd_done产生,从设备应答后(dev_ack)同时送给上层模块。op_cmd_done 保持一个sys_clk 时钟宽度。
- output op_cmd_done, // operation finished
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
-
主体程序设计
该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b11),在SCL为高电平期间等待半个时钟周期再将SDA由低变高。在STOP之后,总线处于idle状态,即控制器和设备都不去控制SDA,SCL, SDA,SCL 都是处于高阻状态。
程序的流程图如下:

图2
-
程序代码如下:
`timescale 1ns / 1ps
module i2c_stop(
input sys_clk,
input [1:0] op_cmd,
input op_cmd_en,
output reg op_cmd_done,
input clk_en,
input clk_en_half,
output reg sda_t,
output sda_o,
input sda_i,
output reg scl_t,
output scl_o,
input scl_i,
input rst
);
/*
op_cmd[1:0] : 00 send start + address + wr/rd (data_in)
01 send data
10 receive data (data_out)
11 send stop flag
*/
assign scl_o = 1'b0;
assign sda_o = 1'b0;
reg [7:0] i2c_st;
reg [2:0] sft_count;
reg [7:0] i2c_wr_data; //将需要写的数据存入本地寄存器中
always@(posedge sys_clk or posedge rst)
if(rst)
begin
op_cmd_done <= 0;
scl_t <= 1'b0;
sda_t <= 1'b1;
sft_count <= 7;
i2c_wr_data <= 0;
i2c_st <= 0;
end
else
case(i2c_st)
0:
begin
sft_count <= 7;
op_cmd_done <= 1'b0;
scl_t <= scl_i;
sda_t <= sda_i;
if({op_cmd_en,op_cmd[1:0]} == 3'b111)
i2c_st <= 1; // stop flag
end
1: //stop flag
begin
sda_t <= 1'b0;
if(clk_en)
begin
scl_t <= 1'b1;
i2c_st <= 2;
end
end
2:
begin
if(clk_en)
begin
sda_t <= 1'b1;
i2c_st <= 3;
end
end
3:
begin
if(clk_en)
begin
sda_t <= 1'b1;
scl_t <= 1'b1;
op_cmd_done <= 1'b1;
i2c_st <= 0;
end
end
default:i2c_st <= 0;
endcase
endmodule
- iic_phy 模块代码
`timescale 1ns / 1ps
module iic_phy(
input sys_clk,
input [1:0] op_cmd,
input op_cmd_en,
output reg op_cmd_done,
output reg dev_ack,
input master_ack,
input [7:0] data_in,
output [7:0] data_out,
input clk_en,
input clk_en_half,
output reg sda_t,
output sda_o,
input sda_i,
output reg scl_t,
output scl_o,
input scl_i,
input rst
);
/*
op_cmd[1:0] : 00 send start + address + wr/rd (data_in)
01 send data
10 receive data (data_out)
11 send stop flag
*/
wire dev_ack_addr;
wire dev_ack_wr;
always @(*)
begin
case(op_cmd)
2'b00: dev_ack = dev_ack_addr;
2'b01: dev_ack = dev_ack_wr;
default: ;
endcase
end
wire op_cmd_done_addr;
wire op_cmd_done_wr;
wire op_cmd_done_read;
wire op_cmd_done_stop;
always @(*)
begin
case(op_cmd)
2'b00: op_cmd_done = op_cmd_done_addr;
2'b01: op_cmd_done = op_cmd_done_wr;
2'b10: op_cmd_done = op_cmd_done_read;
2'b11: op_cmd_done = op_cmd_done_stop;
endcase
end
wire sda_t_addr;
wire sda_t_wr;
wire sda_t_read;
wire sda_t_stop;
wire scl_t_addr;
wire scl_t_wr;
wire scl_t_read;
wire scl_t_stop;
always @(*)
begin
case(op_cmd)
2'b00: begin sda_t = sda_t_addr; scl_t = scl_t_addr; end
2'b01: begin sda_t = sda_t_wr; scl_t = scl_t_wr; end
2'b10: begin sda_t = sda_t_read; scl_t = scl_t_read; end
2'b11: begin sda_t = sda_t_stop; scl_t = scl_t_stop; end
endcase
end
i2c_addr i2c_addr_inst
(
.sys_clk (sys_clk),
.op_cmd (op_cmd),
.op_cmd_en (op_cmd_en),
.op_cmd_done (op_cmd_done_addr),
.dev_ack (dev_ack_addr),
.data_in (data_in),
.clk_en (clk_en),
.clk_en_half (clk_en_half),
.sda_t (sda_t_addr),
.sda_i (sda_i),
.scl_t (scl_t_addr),
.scl_i (scl_i),
.rst (rst)
);
i2c_write i2c_write_inst
(
.sys_clk (sys_clk),
.op_cmd (op_cmd),
.op_cmd_en (op_cmd_en),
.op_cmd_done (op_cmd_done_wr),
.dev_ack (dev_ack_wr),
.data_in (data_in),
.clk_en (clk_en),
.clk_en_half (clk_en_half),
.sda_t (sda_t_wr),
.sda_i (sda_i),
.scl_t (scl_t_wr),
.scl_i (scl_i),
.rst (rst)
);
i2c_read i2c_read_inst
(
.sys_clk (sys_clk),
.op_cmd (op_cmd),
.op_cmd_en (op_cmd_en),
.op_cmd_done (op_cmd_done_read),
.master_ack (master_ack),
.data_out (data_out),
.clk_en (clk_en),
.clk_en_half (clk_en_half),
.sda_t (sda_t_read),
.sda_i (sda_i),
.scl_t (scl_t_read),
.scl_i (scl_i),
.rst (rst)
);
i2c_stop i2c_stop_inst
(
.sys_clk (sys_clk),
.op_cmd (op_cmd),
.op_cmd_en (op_cmd_en),
.op_cmd_done (op_cmd_done_stop),
.clk_en (clk_en),
.clk_en_half (clk_en_half),
.sda_t (sda_t_stop),
.sda_i (sda_i),
.scl_t (scl_t_stop),
.scl_i (scl_i),
.rst (rst)
);
assign sda_o = 1'b0;
assign scl_o = 1'b0;
endmodule
- 将设计的模块例化,并组装到 iic_engine的模块中,这样就可以使用SCl_en模块,充分利用以设计的成果,逐步组装成程序代码及接口如下:
`timescale 1ns / 1ps
module iic_engine #
(
parameter DIV_ADDR = 32'hFFFF_0001,
parameter DEV_ADDR = 7'b101_0000,
parameter CLK_F = 100_000_000,
parameter SPEED = 100_000
)
(
input sys_clk,
inout SDA,
inout SCL,
input [31:0] div_addr,
input [31:0] div_in,
input div_en,
input div_rd,
output [31:0] div_dout,
input [1:0] op_cmd,
input op_cmd_en,
output op_cmd_done,
output dev_ack,
input master_ack,
input [7:0] data_in,
output [7:0] data_out,
output i2c_error,
input rst
);
wire sda_i;
wire sda_o;
wire sda_t;
wire scl_o;
wire scl_t;
wire scl_i;
assign SDA = sda_t ? 1'bz: sda_o;
assign sda_i = SDA;
assign SCL = scl_t ? 1'bz: scl_o;
assign scl_i = SCL;
wire clk_en;
wire clk_en_half;
//=========================== i2c clock baudtick
scl_en #
(
.DIV_ADDR ( DIV_ADDR ),
.CLK_F ( CLK_F ),
.SPEED ( SPEED )
)
scl_en_inst
(
.sys_clk ( sys_clk ),
.div_addr ( div_addr ),
.div_in ( div_in ),
.div_en ( div_en ),
.clk_en ( clk_en ),
.div_rd ( div_rd ),
.div_dout ( div_dout ),
.clk_en_half ( clk_en_half ),
.rst ( rst )
);
//===============================iic phy
iic_phy iic_phy_inst(
.sys_clk (sys_clk),
.op_cmd ( op_cmd ),
.op_cmd_en ( op_cmd_en ),
.op_cmd_done ( op_cmd_done ),
.dev_ack ( dev_ack ),
.master_ack ( master_ack ),
.data_in ( data_in ),
.data_out ( data_out ),
.clk_en ( clk_en ),
.clk_en_half ( clk_en_half ),
.sda_t ( sda_t ),
.sda_o ( sda_o ),
.sda_i ( sda_i ),
.scl_t ( scl_t ),
.scl_o ( scl_o ),
.scl_i ( scl_i ),
.rst ( rst )
);
endmodule
-
仿真程序设计:
`timescale 1ns / 1ps
module tb_dev_stop();
parameter PERIOD = 10 ;
parameter FREQUANCY = 100000000; //100M
parameter SPEED = 100000; // 100K
parameter SPEED1 = 400000; //400K
parameter DEV_ADDR = 7'b101_0001;
reg inclk;
always #( PERIOD / 2 ) inclk = ~inclk; //133M
wire sys_clk = inclk;
reg rst; //
reg [ 31: 0 ] div_in; //for baudrate register
wire [ 31: 0 ] div_addr = 32'hFFFF_0001;
reg div_en = 0;
reg [1:0] op_cmd = 0;
reg op_cmd_en = 0;
wire op_cmd_done;
wire dev_ack;
reg master_ack = 0;
reg [7:0] data_in = 0;
wire [7:0] data_out;
reg i2c_error = 0;
initial
begin
rst = 1'b1;
inclk = 0;
#50 rst = 1'b0;
end
reg [7:0] dev_data = 0;
reg dev_data_valid = 0;
reg [3:0] st = 0;
reg [31:0] cnt = 0;
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
master_ack <= 0;
i2c_error <= 0;
cnt <= 0;
st <= 0;
op_cmd_en <= 1'b0;
op_cmd <= 0;
end
else
begin
case ( st )
0:
begin
master_ack <= 0;
dev_data <= 0;
dev_data_valid <= 0;
i2c_error <= 0;
op_cmd <= 2'b00;
op_cmd_en <= 0;
if(cnt == 5000)
begin
cnt <= 0;
st <= 1;
end
else cnt <= cnt + 1;
end
1:
begin
data_in <= {DEV_ADDR, 1'b0};
op_cmd <= 2'b00; // send device_addr + write
op_cmd_en <= 1;
st <= 2;
end
2:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
begin
i2c_error <= 1;
st <= 13;
end
else
begin
data_in <= 8'h00; // write memory address 8'h00
op_cmd <= 2'b01;
op_cmd_en <= 1;
cnt <= 0;
st <= 3;
end
end
end
3:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
begin
i2c_error <= 1;
st <= 13;
end
else
begin
data_in <= data_in + 1; // wirte data to device
op_cmd <= 2'b01;
op_cmd_en <= 1;
cnt <= cnt + 1;
if(cnt == 7)
st <= 4;
end
end
end
4:
begin
cnt <= 0;
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
begin
i2c_error <= 1;
st <= 13;
end
else
begin
op_cmd <= 2'b11; //send STOP command
op_cmd_en <= 1;
st <= 5;
end
end
end
5:
begin
cnt <= 0;
op_cmd_en <= 0;
if(op_cmd_done)
st <= 6;
end
6: // delay 5ms according to EEPROM spec
begin
if(cnt == 5_000_00)
begin
cnt <= 0;
st <= 7;
end
else cnt <= cnt + 1;
end
7:
begin
cnt <= 0;
op_cmd_en <= 0;
data_in <= {DEV_ADDR, 1'b0};
op_cmd <= 2'b00; // send START + dev_addr + write
op_cmd_en <= 1;
st <= 8;
end
8:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
begin
i2c_error <= 1;
st <= 13;
end
else
begin
data_in <= 8'h00;
op_cmd <= 2'b01; // send memory address 8'h00
op_cmd_en <= 1;
st <= 9;
end
end
end
9:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
begin
i2c_error <= 1;
st <= 13;
end
else
begin
data_in <= {DEV_ADDR, 1'b1};
op_cmd <= 2'b00; // send RESTART + dev_addr + read
op_cmd_en <= 1;
st <= 10;
end
end
end
10:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
begin
i2c_error <= 1;
st <= 13;
end
else
begin
op_cmd <= 2'b10; // send read
op_cmd_en <= 1;
master_ack <= 0;
cnt <= cnt + 1;
st <= 11;
end
end
end
11:
begin
op_cmd_en <= 0;
dev_data_valid <= 0;
if(op_cmd_done)
begin
dev_data <= data_out;
dev_data_valid <= 1;
op_cmd <= 2'b10; //send read
op_cmd_en <= 1;
cnt <= cnt + 1;
if(cnt == 7)
begin
master_ack <= 1; // send NACK for the last data
st <= 12;
end
end
end
12:
begin
dev_data_valid <= 0;
cnt <= 0;
op_cmd_en <= 0;
if(op_cmd_done)
begin
dev_data <= data_out;
dev_data_valid <= 1;
st <= 13;
end
end
13: // STOP command
begin
dev_data_valid <= 0;
op_cmd <= 2'b11; // send STOP command
op_cmd_en <= 1;
st <= 14;
end
14:
begin
cnt <= 0;
op_cmd_en <= 0;
if(op_cmd_done)
begin
st <= 15;
end
end
15:
begin
if(cnt == 5000)
begin
st <= 15;
$stop;
end
else cnt <= cnt + 1;
end
default:
st <= 0;
endcase
end
wire SDA;
wire SCL;
iic_engine
#(
.CLK_F ( FREQUANCY ) , //default with 100Mhz
.SPEED ( SPEED ) //default speed 100k
)
iic_engine_inst
(
.sys_clk ( sys_clk ),
.div_addr ( div_addr ),
.div_in ( div_in ),
.div_en ( div_en ),
.op_cmd ( op_cmd ),
.op_cmd_en ( op_cmd_en ),
.op_cmd_done( op_cmd_done ),
.dev_ack ( dev_ack ),
.master_ack ( master_ack ),
.data_in ( data_in ),
.data_out ( data_out ),
.SDA ( SDA ),
.SCL ( SCL ),
.rst ( rst )
);
PULLUP SDA_inst ( .O(SDA) );
PULLUP SCL_inst ( .O(SCL) );
AT24C02D #
(.SLAVE_ADDRESS(DEV_ADDR))
AT24C02D_inst
(
.SDA (SDA),
.SCL (SCL),
.WP (0)
);
endmodule
-
仿真结果
仿真波形如图3

图3
从图3可以看出:
1)start + dev_addr +wr。设备回ack
2)send memory addr, 设备会ack
3)restart + dev_addr +rd, 设备回ack
4)receive data, 控制器回ack
5) 。。。。
6)receive the last data ,控制器回nack
7 )send STOP command
在等待设备回ack期间,如果设备回nack, I2c总线发生错误(设备无应答)。这时,控制器需要停止总线(发送STOP),将SDA,SCL 线变为高阻。之后其他的I2c 命令才能正常执行。