IIC(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C数据传输速率有标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps),另外一些实现了低速模式(10Kbps)和快速+模式(1Mbps)。
I2C总线的特点:
(1)简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量。降低了互连成本。
(2)支持多主控,其中任何能够进行发送和接收的设备都可以成为主总线,一个主控能够控制信号的传输和时钟频率。当然在任何时间点上只能有一个主控占用I2C总线。
I2C总线接口是一个标准的的双向传输接口,一次数据传输需要主机和从机按照I2C协议的标准进行。I2C总线是由数据线SDA和时钟线SCL构成的串行总线,可以发送和接收数据,并且在硬件上都需要接一个上拉电阻到Vcc。
I2C主机往从机写数据:
(1)主机发送一个起始信号和从机设备地址给从机
(2)主机发送数据给从机
(3)主机发送一个停止信号结束发送过程
I2C主机从从机里读出数据:
(1)主机发送一个起始信号和从机的设备地址给从机
(2)主机发送一个要读取的地址给从机
(3)主机从从机接收数据
(4)主机发送一个停止信号给从机,结束整个接收过程。
I2C在通信中一共有以下几种状态:
1、空闲状态
I2C总线的SDA和SCL两条信号线同时处于高电平的时候,规定总线为空闲状态。此时各个期间的输出级场效应管均处于截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2、起始状态和截止状态
在时钟线SCL保持高电平的时候,数据线SDA上的电平被拉低,定义为I2C总线的起始信号,它标志着一次数据传输的开始。起始信号是由主控器主动建立的,在建立信号之前I2C总线必须处于空闲状态。
在时钟线SCL保持高电期间,数据线SDA被释放,使得SDA返回高电平,称为I2C总线的停止信号,它标志着一次数据传输的终止。停止信号也是由主控器主动建立的,建立信号之后,I2C将返回空闲状态。
3、有效的数据位传输
在I2C总线上穿送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,数据在SDA上从高位向低位依次串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的数据必须保持稳定,低电平为数据0,高电平为数据1,。只有在SCL为低电平期间才允许SDA上的数据进行电平状态改变。
4、应答和非应答
I2C总线上的所有数据都是以8位字节进行传送的,发送器(主机)每发送一个字节,就在第九个时钟脉冲期间释放数据总线,由接收器(从机)反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK),表示接收器已经成功接收了该字节,;应答信号为高电平时,规定为非应答(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位(ACK)的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA信号线拉低,并且确保在该时钟的高电平期间为稳定的低电平。
对非应答位(NACK)还要特别说明的是,还有以下四种情况IIC通信过程中会产生非应答位:
(1)、接收器(从机)正在处理某些实时的操作无法与主机实现IIC通信的时候,接收器(从机)会给主机反馈一个非应答位(NACK)
(2)、主机发送数据的过程中,从机无法解析发送的数据,接收器(从机)也会给主机反馈一个非应答位(NACK)
(3)、主机发送数据的过程中,从机无法再继续接收数据,接收器(从机)也会给主机反馈一个非应答位(NACK)
(4)、主机从从机中读取数据的过程中,主机不想再接收数据,主机会给从机反馈一个非应答位(NACK),注意,这种情况是主机给从机反馈一个非应答位(NACK)
机通过IIC总线往从机中写数据的时候,主机首先会发送一个起始信号,接着把IIC从机的7位设备地址后面添一个0(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据)组成一个8位的数据,把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,主机收到从机的有效应答位以后 ,接下来主机会发送想要写入的寄存器地址,寄存器发送完毕以后主机同样会释放SDA信号线等待从机的应答,从机如果正确收到了主机发过来的寄存器地址,从机会再次发送一个有效应答位给主机,主机收到从机的有效应答位0以后,接下来主机就会给从机发送想要写入从机的数据,从机正确收到这个数据以后仍然像之前两次一样会给主机发送一个有效应答位,主机收到这个有效应答位以后给从机发送一个停止信号,整个传输过程就结束了。下图是整个传输过程的示意图:
特别注意:上图中灰色的地方表示主机正在控制SDA信号线,白色的地方表示从机正在控制SDA信号线。
主机通过IIC总线从从机中读数据的过程与写数据的过程有相似之处,但是读数据的过程还多了一些额外的步骤。主机从从机读数据时主机首先会发送一个起始信号,接着把IIC从机的7位设备地址后面添一个0(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据),把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,主机收到从机的有效应答位以后 ,接下来主机会发送想要读的寄存器地址,寄存器发送完毕以后主机同样会释放SDA信号线等待从机的应答,从机如果正确收到了主机发过来的寄存器地址,从机会再次发送一个有效应答位给主机,主机收到从机的有效应答位0以后,主机会给从机再次发送一次起始信号,接着把IIC从机的7位设备地址后面添一个1(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据),注意,第一次是在设备地址后面添0,这一次是在设备地址后面添1,把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,接着从机继续占用SDA信号线给主机发送寄存器中的数据,发送完毕以后,主机再次占用SDA信号线发送一个非应答信号1给从机,主机发送一个停止信号给从机结束整个读数据的过程。下图是整个读数据过程的示意图
特别注意:上图中灰色的地方表示主机正在控制SDA信号线,白色的地方表示从机正在控制SDA信号线。
含有256*8个字节
有一个16字节页写缓冲器
该器件通过I2C总线接口进行操作。
功能描述:
CAT24WC01/02/04/06/16支持I2C总线数据传送协议。I2C总线协议规定,任何数据传送到总线的器件作为发送器。任何从总线接收数据的器件作为接收器哦。数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件和从器件都可以作为发送器或接收器。但主器件控制传送数据(发送或接收)模式、通过器件地址输入端,A0、A1、A2可以实现将最多8个24WC01和24WC02器件,4个242C04,2个24WC08和去个24WC16器件连接到总线上。
WP:写保护
如果WP管脚连接到VCC,所有的内容都被写保护(只能读)。悬空或者连接到Vss,允许器件的正常读写。
写保护操作特性可使用户避免由于不当操作而造成对存储区内的数据进行改写,当WP管脚接高时,整个寄存器区全部被保护起来而变为只读取。CAT24WC01…可以接收从器件地址和字节地址,但是装置在接收到第一个数据字节后不发送应答信号,从而避免寄存器区域被编程改写。
器件地址
/*
module key_debounce(
clk,rst_n,
key1_n,key2_n,key3_n,
led1_n,led2_n,led3_n
);
input clk;
input rst_n;
input key1_n,key2_n,key3_n;
output led1_n,led2_n,led3_n;
reg [2:0] key_rst;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_rst <= 3'b111;
else
key_rst <= {key3_n,key2_n,key1_n}; // 读取当前时刻的按键值
end
reg [2:0] key_rst_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_rst_r <= 3'b111;
else
key_rst_r <= key_rst; // 将上一时刻的按键值进行存储
end
wire [2:0]key_an = key_rst_r & (~key_rst); // 当键值从0到1时key_an改变
//wire [2:0]key_an = key_rst_r ^ key_rst; // 注:也可以这样写
reg [19:0] cnt; // 延时用计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 20'd0;
else if(key_an)
cnt <= 20'd0;
else
cnt <= cnt + 20'd1;
end
reg [2:0] key_value;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_value <= 3'b111;
else if(cnt == 20'hfffff) // 2^20*1/(50MHZ)=20ms
key_value <= {key3_n,key2_n,key1_n}; // 去抖20ms后读取当前时刻的按键值
end
reg [2:0] key_value_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_value_r <= 3'b111;
else
key_value_r <= key_value; // 将去抖前一时刻的按键值进行存储
end
wire [2:0] key_ctrl = key_value_r & (~key_value); // 当键值从0到1时key_ctrl改变
reg d1;
reg d2;
reg d3;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin // 一个if内有多条语句时不要忘了begin end
d1 <= 0;
d2 <= 0;
d3 <= 0;
end
else
begin
if(key_ctrl[0]) d1 <= ~d1;
if(key_ctrl[1]) d2 <= ~d2;
if(key_ctrl[2]) d3 <= ~d3;
end
end
assign led1_n = d1? 1'b1:1'b0; // 此处只是为了将LED输出进行翻转,RTL级与下面注释代码无差别
assign led2_n = d2? 1'b1:1'b0;
assign led3_n = d3? 1'b1:1'b0;
endmodule
*/
module i2c_ctl(
input clk,
input rst_n,
input sw1, //按键写
input sw2, //按键读
output reg scl,
inout sda,
output [7:0] dis_data
);
//按键消抖
reg [1:0] sw;
reg [19:0] cnt_20ms;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
sw <= 2'b11;
end
else begin
sw <= {sw1,sw2};
end
end
reg [1:0] sw_r;
always @(posedge clk) begin
sw_r <= sw;
end
wire [1:0] sw_an = ~sw & (sw_r); //检测按键下降沿
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_20ms <= 'd0;
end
else if(sw_an) begin
cnt_20ms <= 'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
reg [1:0] sw_value;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
sw_value <= 2'b11;
end
else if(cnt_20ms==20'hfffff) begin
sw_value <= {sw1,sw2};
end
end
//-----scl时钟
//分频
reg [2:0] cnt; //cnt=0,scl上升沿 cnt=1,scl高电平中间值 cnt=2,scl下降沿 cnt=3,scl低电平中间沿
reg [8:0] cnt_delay; //500循环计数,产生I2C所需的时钟
reg scl_r;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_delay <= 'd0;
end
else if(cnt_delay==9'd499) begin
cnt_delay <= 9'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 3'd5;
end
else begin
case(cnt_dealy)
9'd124 : cnt <= 3'd1; //scl高电平中间值
9'd249 : cnt <= 3'd2; //scl下降沿
9'd374 : cnt <= 3'd3; //scl低电平中间值
9'd499 : cnt <= 3'd0; //scl上升沿
default : cnt <= 3'd5;
endcase
end
end
`define SCL_POS (cnt==3'd0) //cnt=0:scl上升沿
`define SCL_HIG (cnt==3'd1) //cnt=1:scl高电平中间,用于数据采样
`define SCL_NEG (cnt==3'd2) //cnt=2:scl下降沿
`define SCL_LOW (cnt==3'd3) //cnt=3:scl低电平中间,用于数据变化
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
scl_r <= 1'b0;
end
else if(cnt==3'd0) begin
scl_r <= 1'b1; //scl信号上升沿
end
else if(cnt==3'd2) begin
scl_r <= 1'b0;
end
end
assign scl = scl_r;
//---------SDA
//需要写入24c02的数据
`define DEVICE_READ 8'b1010_0001 //从设备地址(读)
`define DEVICE_WRITE 8'b1010_0000 //从设备地址(写)
`define WRITE_DATA 8'b0001_0001 //写入EEPROM中的数据
`define BYTE_ADDR 8'b0000_0001 //写入或读出EEPROM的寄存器地址
reg [7:0] db_r; //I2C传输的数据寄存器
reg [7:0] read_data; //读出EEPROM的数据寄存器
//读写时序
parameter IDLE = 4'b0;
parameter START1 = 4'd1;
parameter ADD1 = 4'd2;
parameter ACK1 = 4'd3;
parameter ADD2 = 4'd4;
parameter ACK2 = 4'd5;
parameter START2 = 4'd6;
parameter ADDR3 = 4'd7;
parameter ACK3 = 4'd8;
parameter DATA = 4'd9;
parameter ACK4 = 4'd10;
parameter STOP1 = 4'd11;
parameter STOP2 = 4'd12;
reg [3:0] cstate;
reg sda_r; //输出数据寄存器
reg sda_link; //数据数据sda信号inout方向控制位
reg [3:0] num;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
sctate <= IDLE;
sda_r <= 1'b1;
sda_link <= 1'b0;
num <= 4'd0;
read_data <= 8'b0;
end
else begin
case(cstate)
IDLE : begin
sda_link <= 1'b1;
sda_r <= 1'b1;
if(sw_value != 2'b11) begin //sw1、sw2有一个被按下
db_r <=`DEVICE_WRITE; //送器件地址(写操作)
cstate <= START1;
end
else begin
cstate <= IDLE;
end
end
START1 : begin
if(`SCL_HIG) begin
sda_link <= 1'b1; //数据线sda为输出
sda_r <= 1'b0;
cstate <= ADD1;
num <= 4'd0;
end
else begin
cstate <= START1;
end
end
ADD1 : begin
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK1;
end
else begin
cstate <= ADD1;
num <= num + 1'b1;
case(num)
4'd0 : sda_r <= db_r[7];
4'd1 : sda_r <= db_r[6];
4'd2 : sda_r <= db_r[5];
4'd3 : sda_r <= db_r[4];
4'd4 : sda_r <= db_r[3];
4'd5 : sda_r <= db_r[4];
4'd6 : sda_r <= db_r[1];
4'd7 : sda_r <= db_r[0];
default : ;
endcase
end
end
else begin
cstate <= ADD1;
end
end
ACK1 : begin
if(`SCL_NEG) begin
cstate <= ADD2;
db_r <= `BYTE_ADDR;
end
else begin
cstate <= ACK1;
end
end
ADD2 : begin
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0;
cstate <= ACK2;
end
else begin
sda_link <= 1'b1;
num <= num + 1'b1;
case(num)
4'd0 : sda_r <= db_r[7];
4'd1 : sda_r <= db_r[6];
4'd2 : sda_r <= db_r[5];
4'd3 : sda_r <= db_r[4];
4'd4 : sda_r <= db_r[3];
4'd5 : sda_r <= db_r[4];
4'd6 : sda_r <= db_r[1];
4'd7 : sda_r <= db_r[0];
default : ;
endcase
end
else begin
cstate <= ADD2;
end
end
ACK2 : begin
if(`SCL_NEG) begin //从机响应信号
if(!sw_value[1]) begin
cstate <= DATA;
db_r <= `WRITE_DATA;
end
else if(!sw_value[0]) begin
cstate <= START2;
db_r <= `DEVOCE_READ;
end
end
else begin
cstate <= ACK2;
end
end
START2 : begin
if(`SCL_LOW) begin
sda_lin <= 1'b1;
sda_r = 1'b1;
cstate <= STATE2;
end
else if(`SCL_HIG) begin
sda_r <= 1'b0;
cstate <= ADD3;
end
else begin
cstate <= START2;
end
end
ADD3 : begin
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0;
cstate <= ACK3;
end
else begin
num <= num + 1'b1;
case(num)
4'd0 : sda_r <= db_r[7];
4'd1 : sda_r <= db_r[6];
4'd2 : sda_r <= db_r[5];
4'd3 : sda_r <= db_r[4];
4'd4 : sda_r <= db_r[3];
4'd5 : sda_r <= db_r[2];
4'd6 : sda_r <= db_r[1];
4'd7 : sda_r <= db_r[0];
default :;
endcase
cstate <= ADD3;
end
end
else begin
cstate <= ADD3;
end
end
ACK3 : begin
if(`SCL_NEG) begin
cstate <= DATA;
sda_link <= 1'b0;
end
else begin
cstate <= ACK3;
end
end
DATA : begin
if(!sw_value[0]) begin //read
if(num<=4'd7) begin
cstate <= DATA;
if(`SCL_HIG) begin
num <= num + 1'b1;
case(num)
4'd0: read_data[7] <= sda;
4'd1: read_data[6] <= sda;
4'd2: read_data[5] <= sda;
4'd3: read_data[4] <= sda;
4'd4: read_data[3] <= sda;
4'd5: read_data[2] <= sda;
4'd6: read_data[1] <= sda;
4'd7: read_data[0] <= sda;
default: ;
endcase
end
end
end
else if(!sw_value[1]) begin//write
sda_link <= 1'b1;
if(num<=4'd7) begin
cstate <= DATA;
if(`SCL_LOW) begin
sda_link <= 1'b1;
num <= num + 1'b1;
case(num)
4'd0 : sda_r <= db_r[7];
4'd1 : sda_r <= db_r[6];
4'd2 : sda_r <= db_r[5];
4'd3 : sda_r <= db_r[4];
4'd4 : sda_r <= db_r[3];
4'd5 : sda_r <= db_r[2];
4'd6 : sda_r <= db_r[1];
4'd7 : sda_r <= db_r[0];
default :;
endcase
end
end
end
else if((`SCL_LOW)&&(num==4'd8)) begin
num ,= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0;
cstate <= ACK4;
end
else begin
cstate <= DATA;
end
end
ACK4 : begin
if(`SCL_NEG) begin
cstate <= STOP1;
end
else begin
cstate <= ACK4;
end
end
STOP1 : begin
if(`SCL_LOW) begin
sda_link <= 1'b1;
sda_r <= 1'b0;
cstate <= STOP1;
end
else if(`SCL_HIG) begin
sda_r <= 1'b1;
cstate <= STOP2;
end
end
STOP2 : begin
if(`SCL_LOW) sda_r <= 1'b1;
else if(cnt_20ms==20'hffff0) cstate <= IDLE;
else cstate <= STOP2;
end
default :cstate <= IDLE;
endcase
end
end
//io
assign sda = sda_link ? sda_r : 1'bz;
assign dis_data = read_data;
endmodule