1.原理
IIC(Inter-Integrated Circuit)协议是一种串行通信协议,它具有以下特点:
- 双线通信:IIC协议使用两根信号线进行通信,分别是串行数据线SDA和串行时钟线SCL。
- 多主从架构:IIC支持多主多从的通信方式,允许多个设备连接到同一总线上,并且每个设备都有唯一的地址。
- 开漏输出:所有设备的SDA和SCL引脚都采用开漏输出结构,通过外接上拉电阻实现线与逻辑关系,防止数据冲突。
- 自动寻址:总线上的所有设备都可以通过软件寻址,具有唯一的7位或10位地址。
- 支持仲裁机制:在多主机系统中,通过冲突检测和仲裁机制防止多个主机同时发起数据传输时存在的冲突。
- 自动应答:IIC总线上所有器件都具有自动应答功能,保证数据传输的正确性。
- 传输速率:IIC支持不同的传输速率,包括标准模式(100kb/s)、快速模式(400kb/s)、增强快速模式(1Mb/s)、高速模式(3.4Mb/s)和极速模式(5Mb/s。
- 总线电容限制:IIC总线允许挂载的设备数量取决于总线上最大电容值,一般为400pf(高速模式100pf。
- 简单可靠:IIC协议结构简单,可靠性高,适用于低速外围设备的数据传输。
- 广泛应用:由于其简单性和可靠性,IIC协议被广泛应用于各种嵌入式系统中,如存储器、LED及LCD驱动器、A/D及D/A转换器等。
IIC协议的这些特点使其在嵌入式系统和集成电路间的通信中非常流行,特别是在对硬件资源要求较低、需要连接多个低速设备的应用场景中。
A2A1A3地址表示可以连接8个EEPROM设备,当读写控制位为0表示主机要对从机进行数据写入操作,1表示主机要对从机进行数据读出操作。当主机把器件地址发送到总线上,所有的从机都会收到,然后和自身的器件地址做对比,如果和总线上的地址相同,那么从机设备就会给主机发送一个应答信号。
内存地址只是一个编号,代表一个内存空间;内存地址所执行的内存单元大小就是1字节,跟内存地址位数无关
1.写操作
以上是单字节写
以上是页写
所有的i2c设备都支持单字节写入,但只有一部分I2C设备支持页写操作。并且支持页写操作的设备一次性写入的数据量不能超过这个设备单页所包含的存储单元数。而24c64单页的存储单元个数是32,也就是一次页写操作最多写入32个字节的数据。
2.读操作
以上是随机读操作
以上是顺序读
2.实践
任务
使用写控制按键向EEPROM写入1-10共10个字节的数据,再使用读控制按键读出数据并在数码管上显示出来。
64Kbit=8KB,EEPROM每个存储空间只能存储1字节的数据,24C64需要13位宽的地址。
以上是数据写的状态转移图
以上是读和写的状态转移图
系统时钟是50MHZ,i2c_clk是1mhz,所以i2c_clk信号经过系统时钟50分频。
时钟分频的时序图
i2c_scl的时钟频率是250K,250K相当于i2c_clk的4分频
以上是写操作的时序图
以上是读操作的时序图
2.1 i2c_ctrl.v
module i2c_ctrl
#(
parameter DEVICE_ADDR = 7'b1010_011,
parameter SYS_CLK_FREQ = 'd50_000_000,
parameter SCL_FREQ = 'd250_000
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire i2c_start ,
input wire wr_en ,
input wire [15:0] byte_addr ,
input wire [7:0] wr_data ,
input wire rd_en ,
input wire addr_num ,
output reg i2c_scl ,
inout wire i2c_sda ,
output reg [7:0] rd_data ,
output reg i2c_end ,
output reg i2c_clk
);
parameter CNT_CLK_MAX = (SYS_CLK_FREQ / SCL_FREQ) >> 3 ;
parameter IDLE = 4'd00,
START = 4'd01,
SEND_D_A = 4'd02,
ACK_1 = 4'd03,
SEND_B_H = 4'd04,
ACK_2 = 4'd05,
SEND_B_L = 4'd06,
ACK_3 = 4'd07,
WR_DATA = 4'd08,
ACK_4 = 4'd09,
START_2 = 4'd10,
SEND_R_A = 4'd11,
ACK_5 = 4'd12,
RD_DATA = 4'd13,
N_ACK = 4'd14,
STOP = 4'd15;
wire sda_en ;
wire sda_in ;
reg [7:0] cnt_clk ;
reg [3:0] state ;
reg [1:0] cnt_i2c_clk ;
reg cnt_i2c_clk_en ;
reg [2:0] cnt_bit ;
reg sda_out ;
reg ack ;
reg [7:0] rd_data_reg ;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 8'd0;
else if(cnt_clk == CNT_CLK_MAX - 1)
cnt_clk <= 8'd0;
else
cnt_clk <= cnt_clk + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i2c_clk <= 1'b1;
else if(cnt_clk == CNT_CLK_MAX - 1)
i2c_clk <= ~i2c_clk;
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE:
if(i2c_start == 1'b1)
state <= START;
else
state <= state;
START:
if(cnt_i2c_clk == 2'd3)
state <= SEND_D_A;
else
state <= state;
SEND_D_A:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
state <= ACK_1;
else
state <= state;
ACK_1:
if((cnt_i2c_clk == 2'd3) && (ack == 1'b0))
begin
if(addr_num == 1'b1)
state <= SEND_B_H;
else
state <= SEND_B_L;
end
else
state <= state;
SEND_B_H:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
state <= ACK_2;
else
state <= state;
ACK_2:
if((cnt_i2c_clk == 2'd3) && (ack == 1'b0))
state <= SEND_B_L;
else
state <= state;
SEND_B_L:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
state <= ACK_3;
else
state <= state;
ACK_3:
if((cnt_i2c_clk == 2'd3) && (ack == 1'b0))
begin
if(wr_en == 1'b1)
state <= WR_DATA;
else if(rd_en == 1'b1)
state <= START_2;
else
state <= state;
end
else
state <= state;
WR_DATA:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
state <= ACK_4;
else
state <= state;
ACK_4:
if((cnt_i2c_clk == 2'd3) && (ack == 1'b0))
state <= STOP;
else
state <= state;
START_2:
if(cnt_i2c_clk == 2'd3)
state <= SEND_R_A;
else
state <= state;
SEND_R_A:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
state <= ACK_5;
else
state <= state;
ACK_5:
if((cnt_i2c_clk == 2'd3) && (ack == 1'b0))
state <= RD_DATA;
else
state <= state;
RD_DATA:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
state <= N_ACK;
else
state <= state;
N_ACK:
if(cnt_i2c_clk == 2'd3)
state <= STOP;
else
s