IIC-EEPROM

I^{2}C介绍

I2C(Inter-Integrated Circuit) 总线是由 PHILIPS 公司开发的两线式串行总线, 用于连接微控制器及其外围设备。 是微电子通信控制领域广泛采用的一种总线标准。 它是同步通信的一种特殊形式, 具有接口线少, 控制方式简单,器件封装形式小, 通信速率较高等优点。 I2C 总线只有两根双向信号线一根是数据线 SDA, 另一根是时钟线 SCL。 由于其管脚少, 硬件实现简单, 可扩展性强等特点, 因此被广泛的使用在各大集成芯片内。

I2C物理层

  1.  支持多设备的总线
  2. 一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL)。
  3. 每个连接到总线的设备都有一个独立的地址,主机可以通过这个地址来进行不同设备的访问。
  4. 总线通过上拉电阻连接到电源。当 I2C 设备空闲时, 会输出高阻态, 而当所有设备都空闲, 都输出高阻态时, 由上拉电阻把总线拉成高电平。
  5. 多个主机同时使用总线时, 为了防止数据冲突, 会利用仲裁方式决定由哪个设备占用总线。
  6. 具有三种传输模式: 标准模式传输速率为 100kbit/s, 快速模式为400kbit/s, 高速模式下可达 3.4Mbit/s, 但目前大多 I2C 设备尚不支持高速模式。
  7. 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。

I2C总线的一些术语

I2C协议层

(1)数据有效性规定

 I2C 总线进行数据传送时, 时钟信号为高电平期间, 数据线上的数据必须保持稳定, 只有在时钟线上的信号为低电平期间, 数据线上的高电平或低电平状态才允许变化。

 每次数据传输都以字节为单位, 每次传输的字节数不受限制。

(2)起始和停止信号

SCL 线为高电平期间, SDA 线由高电平向低电平的变化表示起始信号; SCL线为高电平期间, SDA 线由低电平向高电平的变化表示终止信号。
 

 起始和终止信号都是由主机发出的, 在起始信号产生后, 总线就处于被占用的状态; 在终止信号产生后, 总线就处于空闲状态。

(3)应答响应

每当发送器件传输完一个字节的数据后, 后面必须紧跟一个校验位, 这个校验位是接收端通过控制 SDA(数据线) 来实现的, 以提醒发送端数据我这边已经接收完成, 数据传送可以继续进行。 这个校验位其实就是数据或地址传输过程中的响应。 响应包括“应答(ACK)” 和“非应答(NACK)” 两种信号。 作为数据接收端时, 当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后, 若希望对方继续发送数据, 则需要向对方发送“应答(ACK)” 信号即特定的低电平脉冲,
发送方会继续发送下一个数据; 若接收端希望结束数据传输, 则向对方发送“非应答(NACK)” 信号即特定的高电平脉冲, 发送方接收到该信号后会产生一个停止信号, 结束信号传输。

 每一个字节必须保证是 8 位长度。 数据传送时, 先传送最高位(MSB) , 每一个被传送的字节后面都必须跟随一位应答位(即一帧共有 9 位)。

由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据) , 它必须将数据线置于高电平, 而由主机产生一个终止信号以结束总线的数据传送。

如果从机对主机进行了应答, 但在数据传送一段时间后无法继续接收更多的数据时, 从机可以通过对无法接收的第一个数据字节的“非应答” 通知主机, 主机则应发出终止信号以结束数据的继续传送。

当主机接收数据时, 它收到最后一个数据字节后, 必须向从机发出一个结束传送的信号。 这个信号是由对从机的“非应答” 来实现的。 然后, 从机释放 SDA线, 以允许主机产生终止信号。

这些信号中, 起始信号是必需的, 结束信号和应答信号都可以不要。

(4)总线的寻址方式

I2C 总线寻址按照从机地址位数可分为两种, 一种是 7 位, 另一种是 10位。 采用 7 位的寻址字节(寻址字节是起始信号后的第一个字节) 的位定义如下:

 D7~D1 位组成从机的地址。 D0 位是数据传送方向位, 为“ 0” 时表示主机向从机写数据, 为“1” 时表示主机由从机读数据。

10 位寻址和 7 位寻址兼容, 而且可以结合使用。 10 位寻址不会影响已有的 7 位寻址, 有 7 位和 10 位地址的器件可以连接到相同的 I2C 总线。

7位寻址:当主机发送了一个地址后, 总线上的每个器件都将头 7 位与它自己的地址比较, 如果一样, 器件会判定它被主机寻址, 其他地址不同的器件将被忽略后面的数据信号。 至于是从机接收器还是从机发送器, 都由 R/W 位决定的。 从机的地址由固定部分和可编程部分组成。 在一个系统中可能希望接入多个相同的从机, 从机地址中可编程部分决定了可接入总线该类器件的最大数目。 如一个从机的 7 位寻址位有 4 位是固定位, 3 位是可编程位, 这时仅能寻址 8 个同样的
器件, 即可以有 8 个同样的器件接入到该 I2C 总线系统中。

(5)数据传输

在起始信号后必须传送一个从机的地址(7 位) , 第 8 位是数据的传送方向位(R/W) , 用“ 0” 表示主机发送(写) 数据(W) , “ 1” 表示主机接收数据(R) 。 每次数据传送总是由主机产生的终止信号结束。 但是, 若主机希望继续占用总线进行新的数据传送, 则可以不产生终止信号, 马上再次发出起始信号对另一从机进行寻址。

a、 主机向从机发送数据, 数据传送方向在整个传送过程中不变

 注意: 有阴影部分表示数据由主机向从机传送, 无阴影部分则表示数据由从机向主机传送。 A 表示应答, A 非表示非应答(高电平) 。 S 表示起始信号, P 表示终止信号。

b、 主机在第一个字节后, 立即从从机读数据
 

 c、 在传送过程中, 当需要改变传送方向时, 起始信号和从机地址都被重复产生一次, 但两次读/写方向位正好相反。

 AT24C02介绍

AT24C01/02/04/08/16...是一个 1K/2K/4K/8K/16K 位串行 CMOS, 内部含有128/256/512/1024/2048 个 8 位字节, AT24C01 有一个 8 字节页写缓冲器,AT24C02/04/08/16 有一个 16 字节页写缓冲器。 该器件通过 I2C 总线接口进行操作, 它有一个专门的写保护功能。 我们开发板上使用的是 AT24C02(EEPROM)芯片, 此芯片具有 I2C 通信接口, 芯片内保存的数据在掉电情况下都不丢失,所以通常用于存放一些比较重要的数据等。

 

AT24C02 器件地址为 7 位, 高 4 位固定为 1010, 低 3 位由 A0/A1/A2 信PRECHIN号线的电平决定。 因为传输地址或数据是以字节为单位传送的, 当传送地址时,器件地址占 7 位, 还有最后一位(最低位 R/W) 用来选择读写方向, 它与地址无关。
 

 我们开发板已经将芯片的 A0/A1/A2 连接到 GND, 所以器件地址为1010000, 即 0x50(未计算最低位) 。 如果要对芯片进行写操作时, R/W 即为 0,写器件地址即为 0XA0; 如果要对芯片进行读操作时, R/W 即为 1, 此时读器件地址为 0XA1。 开发板上也将 WP 引脚直接接在 GND 上, 此时芯片允许数据正常读写。

I2C 总线时序如下图所示:
 

 

 硬件设计

开发板上 EEPROM 模块电路
 

 从图中可以看出, 芯片的 SCL 和 SDA 管脚是连接在单片机的 P2.1 和 P2.0 上,在介绍 IIC 总线的时候我们说过, 为了让 IIC 总线默认为高电平, 通常会在 IIC总线上接上拉电阻, 在图中并没有看到 SCL 和 SDA 管脚有上拉电阻, 这是因为开发板单片机 IO 都外接了 10K 上拉电阻, 当单片机 IO 口连接到芯片的 SCL 和 SDA脚时即相当于它们外接上拉电阻。

软件设计

创建多文件工程

在电脑上创建一个实验文件夹, 为了与教程配套, 这里命名为“I2C-EEPROM实验” , 然后在该文件夹内新建 App、 Obj、 Public、 User 四个文件夹, 如下所示:

  • App 文件夹: 用于存放外设驱动文件, 如 LED、 数码管、 定时器等。
  • Obj 文件夹: 用于存放编译产生的 c/汇编/链接的列表清单、 调试信息、 hex
  • 文件、 预览信息、 封装库等文件。
  • Public 文件夹: 用于存放 51 单片机公共的文件, 如延时、 51 头文件、 变量
  • 类型重定义等。
  • User 文件夹: 用于存放用户主函数文件, 如 main.c。

新建工程

 

向工程添加文件

 按照需要给工程分组并添加对应文件, 这里我们在工程中分 3 组, User、 App、Publi, 至于前面创建的 Obj 文件夹是在工程中无需体现, 因为只是编译器生成的一些中间文件和.hex 执行文件。 通常在工程组的命名与创建的文件夹名保持一致, 方便查找到源文件位置。

 

 然后就是给每个组添加对应的.c 源文件

 

 可以选择到要添加的.c 文件(红色标记 5) , 然后鼠标左键双击也可直接添加进去, 就免去了点击红色标记 6 这步。 添加好后, 在右侧就会显示对应组中已添加的文件。

 配置魔术棒选项卡

  1. Output 选项卡中把输出文件夹定位到我们实验目录下的 Obj 文件夹如果想在编译的过程中生成 hex 文件, 那么 Create HEX File 选项勾上。

  2. Listing 选项卡中把输出文件夹也定位到我们实验目录下的 Obj 文件夹。 其它设置默认, 配置如下:

  3. C51 选项卡配置, 此处目的是将我们前面添加到工程组中的文件路径包括进来, 否则程序中调用其他文件夹的头文件则会报错找不到头文件路径, 具体步骤如下:

    添加的头文件路径是指, 在 I2C-EEPROM 实验文件夹下里面, 哪些文件夹内含有.h 头文件, 并且需要被调用到的, 通常我们会把只要含有头文件的文件夹都选择进去。 比如本例程中 App 内含有很多子文件夹, 它们里面都含有头文件,因此要分别添加, Public 也含有头文件, 所以也要添加。 添加完成后如下:

 仿真器配置

有关 51 仿真器配置可参考前面章节内容, 此处不再重复。然后编译一下工程, 编译后结果 0 错误 0 警告, 表明我们创建的多文件工程没有问题。 如下:
 

 至此, 我们就成功创建好一个多文件工程模板。 在以后的实验中, 凡是涉及到多个外设资源模块的都可以使用该工程模板, 尤其是对重复利用已编写过的外设驱动。

防止头文件被重复包含,避免引起编译错误。

#ifndef _key_H
#define _key_H

//code 

#endif

./App/24c02

//24c02.h

#ifndef _24c02_H
#define _24c02_H

#include "public.h"


void at24c02_write_one_byte(u8 addr,u8 dat);//AT24C02指定地址写数据
u8 at24c02_read_one_byte(u8 addr);//AT24C02指定地址读数据
#endif
//24c02.c

#include "24c02.h"
#include "iic.h"


/*******************************************************************************
* 函 数 名         : at24c02_write_one_byte
* 函数功能		   : 在AT24CXX指定地址写入一个数据
* 输    入         : addr:写入数据的目的地址 
					 dat:要写入的数据
* 输    出         : 无
*******************************************************************************/
void at24c02_write_one_byte(u8 addr,u8 dat)
{				   	  	    																 
    iic_start();  
	iic_write_byte(0XA0);	//发送写命令	    	  
	iic_wait_ack();	   
    iic_write_byte(addr);	//发送写地址   
	iic_wait_ack(); 	 										  		   
	iic_write_byte(dat);	//发送字节    							   
	iic_wait_ack();  		    	   
    iic_stop();				//产生一个停止条件
	delay_ms(10);	 
}

/*******************************************************************************
* 函 数 名         : at24c02_read_one_byte
* 函数功能		   : 在AT24CXX指定地址读出一个数据
* 输    入         : addr:开始读数的地址 
* 输    出         : 读到的数据
*******************************************************************************/
u8 at24c02_read_one_byte(u8 addr)
{				  
	u8 temp=0;		  	    																 
    iic_start();  
	iic_write_byte(0XA0);	//发送写命令	   
	iic_wait_ack(); 
    iic_write_byte(addr); 	//发送写地址  
	iic_wait_ack();	    
	iic_start();  	 	   
	iic_write_byte(0XA1); 	//进入接收模式         			   
	iic_wait_ack();	 
    temp=iic_read_byte(0);	//读取字节		   
    iic_stop();				//产生一个停止条件    
	return temp;			//返回读取的数据
}

 ./App/iic

//iic.h
#ifndef _iic_H
#define _iic_H

#include "public.h"

//定义EEPROM控制脚
sbit IIC_SCL=P2^1;//SCL时钟线
sbit IIC_SDA=P2^0;//SDA数据线


//IIC所有操作函数				 
void iic_start(void);			//发送IIC开始信号
void iic_stop(void);	  		//发送IIC停止信号
void iic_write_byte(u8 txd);	//IIC发送一个字节
u8 iic_read_byte(u8 ack);		//IIC读取一个字节
u8 iic_wait_ack(void); 			//IIC等待ACK信号
void iic_ack(void);				//IIC发送ACK信号
void iic_nack(void);			//IIC不发送ACK信号

#endif
//iic.c
#include "iic.h"


/*******************************************************************************
* 函 数 名       : iic_start
* 函数功能		 : 产生IIC起始信号
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void iic_start(void)
{
	IIC_SDA=1;//如果把该条语句放在SCL后面,第二次读写会出现问题
	delay_10us(1);
	IIC_SCL=1;
	delay_10us(1);
	IIC_SDA=0;	//当SCL为高电平时,SDA由高变为低
	delay_10us(1);
	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
	delay_10us(1);
}

/*******************************************************************************
* 函 数 名         : iic_stop
* 函数功能		   : 产生IIC停止信号   
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void iic_stop(void)
{	
	IIC_SDA=0;//如果把该条语句放在SCL后面,第二次读写会出现问题
	delay_10us(1);
	IIC_SCL=1;
	delay_10us(1);
	IIC_SDA=1;	//当SCL为高电平时,SDA由低变为高
	delay_10us(1);			
}

/*******************************************************************************
* 函 数 名         : iic_ack
* 函数功能		   : 产生ACK应答  
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void iic_ack(void)
{
	IIC_SCL=0;
	IIC_SDA=0;	//SDA为低电平
	delay_10us(1);
   	IIC_SCL=1;
	delay_10us(1);
	IIC_SCL=0;
}

/*******************************************************************************
* 函 数 名         : iic_nack
* 函数功能		   : 产生NACK非应答  
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void iic_nack(void)
{
	IIC_SCL=0;
	IIC_SDA=1;	//SDA为高电平
	delay_10us(1);
   	IIC_SCL=1;
	delay_10us(1);
	IIC_SCL=0;	
}

/*******************************************************************************
* 函 数 名         : iic_wait_ack
* 函数功能		   : 等待应答信号到来   
* 输    入         : 无
* 输    出         : 1,接收应答失败
        			 0,接收应答成功
*******************************************************************************/
u8 iic_wait_ack(void)
{
	u8 time_temp=0;
	
	IIC_SCL=1;
	delay_10us(1);
	while(IIC_SDA)	//等待SDA为低电平
	{
		time_temp++;
		if(time_temp>100)//超时则强制结束IIC通信
		{	
			iic_stop();
			return 1;	
		}			
	}
	IIC_SCL=0;
	return 0;	
}

/*******************************************************************************
* 函 数 名         : iic_write_byte
* 函数功能		   : IIC发送一个字节 
* 输    入         : dat:发送一个字节
* 输    出         : 无
*******************************************************************************/
void iic_write_byte(u8 dat)
{                        
    u8 i=0; 
	   	    
    IIC_SCL=0;
    for(i=0;i<8;i++)	//循环8次将一个字节传出,先传高再传低位
    {              
        if((dat&0x80)>0) 
			IIC_SDA=1;
		else
			IIC_SDA=0;
        dat<<=1; 	  
		delay_10us(1);  
		IIC_SCL=1;
		delay_10us(1); 
		IIC_SCL=0;	
		delay_10us(1);
    }	 
}

/*******************************************************************************
* 函 数 名         : iic_read_byte
* 函数功能		   : IIC读一个字节 
* 输    入         : ack=1时,发送ACK,ack=0,发送nACK 
* 输    出         : 应答或非应答
*******************************************************************************/
u8 iic_read_byte(u8 ack)
{
	u8 i=0,receive=0;
   	
    for(i=0;i<8;i++ )	//循环8次将一个字节读出,先读高再传低位
	{
        IIC_SCL=0; 
        delay_10us(1);
		IIC_SCL=1;
        receive<<=1;
        if(IIC_SDA)receive++;   
		delay_10us(1); 
    }					 
    if (!ack)
        iic_nack();
    else
        iic_ack();  
		  
    return receive;
}

./App/key

//key.h
#ifndef _key_H
#define _key_H

#include "public.h"

//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;


//使用宏定义独立按键按下的键值
#define KEY1_PRESS	1
#define KEY2_PRESS	2
#define KEY3_PRESS	3
#define KEY4_PRESS	4
#define KEY_UNPRESS	0


u8 key_scan(u8 mode);

#endif
//key.c
#include "key.h"

/*******************************************************************************
* 函 数 名       : key_scan
* 函数功能		 : 检测独立按键是否按下,按下则返回对应键值
* 输    入       : mode=0:单次扫描按键
				   mode=1:连续扫描按键
* 输    出    	 : KEY1_PRESS:K1按下
				   KEY2_PRESS:K2按下
				   KEY3_PRESS:K3按下
				   KEY4_PRESS:K4按下
				   KEY_UNPRESS:未有按键按下
*******************************************************************************/
u8 key_scan(u8 mode)
{
	static u8 key=1;

	if(mode)key=1;//连续扫描按键
	if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
	{
		delay_10us(1000);//消抖
		key=0;
		if(KEY1==0)
			return KEY1_PRESS;
		else if(KEY2==0)
			return KEY2_PRESS;
		else if(KEY3==0)
			return KEY3_PRESS;
		else if(KEY4==0)
			return KEY4_PRESS;	
	}
	else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1)	//无按键按下
	{
		key=1;			
	}
	return KEY_UNPRESS;		
}

./App/smg

//smg.h
#ifndef _smg_H
#define _smg_H

#include "public.h"


#define SMG_A_DP_PORT	P0	//使用宏定义数码管段码口

//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;


void smg_display(u8 dat[],u8 pos);

#endif
//smg.c
#include "smg.h"

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
				0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

/*******************************************************************************
* 函 数 名       : smg_display
* 函数功能		 : 动态数码管显示
* 输    入       : dat:要显示的数据
				   pos:从左开始第几个位置开始显示,范围1-8
* 输    出    	 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
	u8 i=0;
	u8 pos_temp=pos-1;

	for(i=pos_temp;i<8;i++)
	{
	   	switch(i)//位选
		{
			case 0: LSC=1;LSB=1;LSA=1;break;
			case 1: LSC=1;LSB=1;LSA=0;break;
			case 2: LSC=1;LSB=0;LSA=1;break;
			case 3: LSC=1;LSB=0;LSA=0;break;
			case 4: LSC=0;LSB=1;LSA=1;break;
			case 5: LSC=0;LSB=1;LSA=0;break;
			case 6: LSC=0;LSB=0;LSA=1;break;
			case 7: LSC=0;LSB=0;LSA=0;break;
		}
		SMG_A_DP_PORT=gsmg_code[dat[i-pos_temp]];//传送段选数据
		delay_10us(100);//延时一段时间,等待显示稳定
		SMG_A_DP_PORT=0x00;//消音
	}
}

./Public

//public.h
#ifndef _public_H
#define _public_H

#include "reg52.h"

typedef unsigned int u16;	//对系统默认数据类型进行重定义
typedef unsigned char u8;


void delay_10us(u16 ten_us);
void delay_ms(u16 ms);

#endif
//public.c
#include "public.h"

/*******************************************************************************
* 函 数 名       : delay_10us
* 函数功能		 : 延时函数,ten_us=1时,大约延时10us
* 输    入       : ten_us
* 输    出    	 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
	while(ten_us--);	
}

/*******************************************************************************
* 函 数 名       : delay_ms
* 函数功能		 : ms延时函数,ms=1时,大约延时1ms
* 输    入       : ms:ms延时时间
* 输    出    	 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}

./User

//main.c
/**************************************************************************************
深圳市普中科技有限公司(PRECHIN 普中)
技术支持:www.prechin.net
PRECHIN
 普中

实验名称:I2C-EEPROM实验
接线说明:	
实验现象:下载程序后,数码管右4位显示0,按K1键将数据写入到EEPROM内保存,
		  按K2键读取EEPROM内保存的数据,按K3键显示数据加1,按K4键显示数据清零,
		  最大能写入的数据是255。
注意事项:																				  
***************************************************************************************/
#include "public.h"
#include "24c02.h"
#include "key.h"
#include "smg.h"


#define EEPROM_ADDRESS	0	//定义数据存入EEPROM的起始地址

/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	u8 key_temp=0;
   	u8 save_value=0;
	u8 save_buf[3];

	while(1)
	{			
		key_temp=key_scan(0);
		if(key_temp==KEY1_PRESS)
		{
			at24c02_write_one_byte(EEPROM_ADDRESS,save_value);
		}
		else if(key_temp==KEY2_PRESS)
		{
			save_value=at24c02_read_one_byte(EEPROM_ADDRESS);
		}
		else if(key_temp==KEY3_PRESS)
		{
			save_value++;
			if(save_value==255)save_value=255;
		}
		else if(key_temp==KEY4_PRESS)
		{
			save_value=0;	
		}
		save_buf[0]=save_value/100;
		save_buf[1]=save_value%100/10;
		save_buf[2]=save_value%100%10;
		smg_display(save_buf,6);
	}		
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值