I2C读写EEPROM(AT24C02)

一、I2C总线介绍

IIC(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C数据传输速率有标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps),另外一些实现了低速模式(10Kbps)和快速+模式(1Mbps)。
I2C总线的特点:
(1)简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量。降低了互连成本。
(2)支持多主控,其中任何能够进行发送和接收的设备都可以成为主总线,一个主控能够控制信号的传输和时钟频率。当然在任何时间点上只能有一个主控占用I2C总线。
I2C总线接口是一个标准的的双向传输接口,一次数据传输需要主机和从机按照I2C协议的标准进行。I2C总线是由数据线SDA和时钟线SCL构成的串行总线,可以发送和接收数据,并且在硬件上都需要接一个上拉电阻到Vcc。

I2C读写EEPROM(AT24C02)_第1张图片
I2C主机往从机写数据:
(1)主机发送一个起始信号和从机设备地址给从机
(2)主机发送数据给从机
(3)主机发送一个停止信号结束发送过程
I2C主机从从机里读出数据:
(1)主机发送一个起始信号和从机的设备地址给从机
(2)主机发送一个要读取的地址给从机
(3)主机从从机接收数据
(4)主机发送一个停止信号给从机,结束整个接收过程。

I2C在通信中一共有以下几种状态:
1、空闲状态
I2C总线的SDA和SCL两条信号线同时处于高电平的时候,规定总线为空闲状态。此时各个期间的输出级场效应管均处于截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2、起始状态和截止状态
在时钟线SCL保持高电平的时候,数据线SDA上的电平被拉低,定义为I2C总线的起始信号,它标志着一次数据传输的开始。起始信号是由主控器主动建立的,在建立信号之前I2C总线必须处于空闲状态。
在时钟线SCL保持高电期间,数据线SDA被释放,使得SDA返回高电平,称为I2C总线的停止信号,它标志着一次数据传输的终止。停止信号也是由主控器主动建立的,建立信号之后,I2C将返回空闲状态。
I2C读写EEPROM(AT24C02)_第2张图片
3、有效的数据位传输
在I2C总线上穿送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,数据在SDA上从高位向低位依次串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的数据必须保持稳定,低电平为数据0,高电平为数据1,。只有在SCL为低电平期间才允许SDA上的数据进行电平状态改变。
I2C读写EEPROM(AT24C02)_第3张图片
4、应答和非应答
I2C总线上的所有数据都是以8位字节进行传送的,发送器(主机)每发送一个字节,就在第九个时钟脉冲期间释放数据总线,由接收器(从机)反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK),表示接收器已经成功接收了该字节,;应答信号为高电平时,规定为非应答(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位(ACK)的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA信号线拉低,并且确保在该时钟的高电平期间为稳定的低电平。

对非应答位(NACK)还要特别说明的是,还有以下四种情况IIC通信过程中会产生非应答位:
(1)、接收器(从机)正在处理某些实时的操作无法与主机实现IIC通信的时候,接收器(从机)会给主机反馈一个非应答位(NACK)
(2)、主机发送数据的过程中,从机无法解析发送的数据,接收器(从机)也会给主机反馈一个非应答位(NACK)
(3)、主机发送数据的过程中,从机无法再继续接收数据,接收器(从机)也会给主机反馈一个非应答位(NACK)
(4)、主机从从机中读取数据的过程中,主机不想再接收数据,主机会给从机反馈一个非应答位(NACK),注意,这种情况是主机给从机反馈一个非应答位(NACK)
I2C读写EEPROM(AT24C02)_第4张图片

二、主机通过I2C总线往从机里面写数据

机通过IIC总线往从机中写数据的时候,主机首先会发送一个起始信号,接着把IIC从机的7位设备地址后面添一个0(设备地址后面的0表示主机向从机写数据,1表示主机从从机中读数据)组成一个8位的数据,把这个8位的数据发给从机,发完这8位的数据以后主机马上释放SDA信号线等待从机的应答,如果从机正确收到这个数据,从机就会发送一个有效应答位0给主机告诉主机自己已经收到了数据,主机收到从机的有效应答位以后 ,接下来主机会发送想要写入的寄存器地址,寄存器发送完毕以后主机同样会释放SDA信号线等待从机的应答,从机如果正确收到了主机发过来的寄存器地址,从机会再次发送一个有效应答位给主机,主机收到从机的有效应答位0以后,接下来主机就会给从机发送想要写入从机的数据,从机正确收到这个数据以后仍然像之前两次一样会给主机发送一个有效应答位,主机收到这个有效应答位以后给从机发送一个停止信号,整个传输过程就结束了。下图是整个传输过程的示意图:
I2C读写EEPROM(AT24C02)_第5张图片
特别注意:上图中灰色的地方表示主机正在控制SDA信号线,白色的地方表示从机正在控制SDA信号线。

三、主机通过I2C总线从从机里面读取数据

主机通过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给从机,主机发送一个停止信号给从机结束整个读数据的过程。下图是整个读数据过程的示意图
I2C读写EEPROM(AT24C02)_第6张图片
 特别注意:上图中灰色的地方表示主机正在控制SDA信号线,白色的地方表示从机正在控制SDA信号线。

四、AT24C02介绍

I2C读写EEPROM(AT24C02)_第7张图片
含有256*8个字节
有一个16字节页写缓冲器
该器件通过I2C总线接口进行操作。
在这里插入图片描述
I2C读写EEPROM(AT24C02)_第8张图片
功能描述:
CAT24WC01/02/04/06/16支持I2C总线数据传送协议。I2C总线协议规定,任何数据传送到总线的器件作为发送器。任何从总线接收数据的器件作为接收器哦。数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件和从器件都可以作为发送器或接收器。但主器件控制传送数据(发送或接收)模式、通过器件地址输入端,A0、A1、A2可以实现将最多8个24WC01和24WC02器件,4个242C04,2个24WC08和去个24WC16器件连接到总线上。
WP:写保护
如果WP管脚连接到VCC,所有的内容都被写保护(只能读)。悬空或者连接到Vss,允许器件的正常读写。
写保护操作特性可使用户避免由于不当操作而造成对存储区内的数据进行改写,当WP管脚接高时,整个寄存器区全部被保护起来而变为只读取。CAT24WC01…可以接收从器件地址和字节地址,但是装置在接收到第一个数据字节后不发送应答信号,从而避免寄存器区域被编程改写。
器件地址
I2C读写EEPROM(AT24C02)_第9张图片
I2C读写EEPROM(AT24C02)_第10张图片
I2C读写EEPROM(AT24C02)_第11张图片
I2C读写EEPROM(AT24C02)_第12张图片
I2C读写EEPROM(AT24C02)_第13张图片
I2C读写EEPROM(AT24C02)_第14张图片
I2C读写EEPROM(AT24C02)_第15张图片
I2C读写EEPROM(AT24C02)_第16张图片
I2C读写EEPROM(AT24C02)_第17张图片
I2C读写EEPROM(AT24C02)_第18张图片
I2C读写EEPROM(AT24C02)_第19张图片

/*
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

你可能感兴趣的:(I2C读写EEPROM(AT24C02))