在I2C发送对设备的地址和写操作后,就要向设备发送数据,设备接收到数据后,返回ack/nack信号给控制器。这个部分就是控制器对设备的写操作。在I2C PHY层 Verilog程序结构化设计 文章中的op_cmd[1:0] = 2’b01 来实现。如图1:

图1
1.I2C写数据部分控制器程序设计
-
接口要求:
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
- input rst,
- input sys_clk,
- input clk_en, clk_en_half,
- 从设备地址、启动信号、读/写标志(即该段程序之后的I2C 总线行为
- input [7:0] data_in, // controller send data to device
- input [1:0] op_cmd, // 2’b01 means controller send data to device command
- 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
- output reg dev_ack//从设备是否有应答
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
-
主体程序设计
该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b01),控制器发送数据进行串行化,等待从设备应答。串行化期间对SCL,SDA的要求是,在SCL为高电平期间设备接收控制器发送的数据,在SCL低电平后,等待半个时钟周期再变化数据,这样可以保证在pcb layout,或者FPGA 布局布线不会影响到 SCL 低电平内发送数据。
程序的流程图如下:

图2
-
程序代码如下:
`timescale 1ns / 1ps
module i2c_write(
input sys_clk,
input [1:0] op_cmd,
input op_cmd_en,
output reg op_cmd_done,
output reg dev_ack,
input [7:0] data_in,
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 [2: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;
dev_ack <= 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;
dev_ack <= 0;
scl_t <= scl_i;
sda_t <= sda_i;
i2c_wr_data <= data_in;
if({op_cmd_en,op_cmd[1:0]} == 3'b101)
i2c_st <= 1; // write data
end
1: //write
begin
if(clk_en)
begin
scl_t <= 1'b0;
i2c_st <= 2;
end
end
2:
begin
if(clk_en_half)
sda_t <= i2c_wr_data[sft_count];
if(clk_en)
begin
scl_t <= 1'b1;
sft_count <= sft_count - 1;
i2c_st <= 1;
if( sft_count == 0 )
i2c_st <= 3;
end
end
3:
begin
if(clk_en)
begin
scl_t <= 1'b0;
i2c_st <= 4;
end
end
4: //read ack/nack from device
begin
if( clk_en_half )
sda_t <= 1'b1;
if(clk_en)
begin
scl_t <= 1'b1;
dev_ack <= sda_i;
i2c_st <= 5;
end
end
5:
begin
if(clk_en)
begin
scl_t <= 1'b0;
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)
);
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_write();
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 [3:0] st = 0;
reg [31:0] cnt = 0;
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
i2c_error <= 0;
cnt <= 0;
st <= 0;
op_cmd_en <= 1'b0;
op_cmd <= 0;
end
else
begin
case ( st )
0:
begin
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;
op_cmd_en <= 1;
st <= 2;
end
2:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
i2c_error <= 1;
data_in <= 8'h00;
op_cmd <= 2'b01;
op_cmd_en <= 1;
cnt <= 0;
st <= 3;
end
end
3:
begin
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
i2c_error <= 1;
data_in <= data_in + 1;
op_cmd <= 2'b01;
op_cmd_en <= 1;
cnt <= cnt + 1;
if(cnt == 7)
st <= 4;
end
end
4:
begin
cnt <= 0;
op_cmd_en <= 0;
if(op_cmd_done)
begin
if(dev_ack == 1)
i2c_error <= 1;
st <= 15;
end
end
15:
begin
st <= 15;
#200;
$stop;
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可以看出在写阶段串行数据。24c02 iic设备应答。在I2C PHY层 Verilog程序结构化设计中介绍过op_cmd 定义:op_cmd[1:0] = 2’b01;代表 write + dev_ack。 op_cmd[1:0] 和op_cmd_en 同时由仿真程序发出,当iic_phy完成相关操作后,返回op_cmd_done 信号, 代表当前操作命令(op_cmd)完成。