【单片机】14-I2C通信之EEPROM

【单片机】14-I2C通信之EEPROM_第1张图片

1.EEPROM概念

1.EEPROM

1.1 一些概念

(1)一些概念:ROM【只读存储器---硬盘】,RAM【随机访问存储器--内存】,PROM【可编程的ROM】,EPROM【可擦除ROM】,EEPROM【电可擦除ROM】

1.2 为什么需要EEPROM

单片机内部的ROM只能在程序下载时进行擦除和改写,但是程序运行本身是不能改写的。

单片机内部的RAM中的数据程序运行时可以改,但是掉电就丢失了。

有时候我们需要有一些数据存在系统中,要求掉电不丢失,而且程序还要能改。所以内部ROM和RAM都不行。【这时候系统需要一块EEPROM】

1.3 EEPROM和flash的区别与联系

单片机解密中Flash和EEPROM的区别-电子工程世界

【单片机】14-I2C通信之EEPROM_第2张图片

1.4 EEPROM存在系统中的2种形式

1:内置在单片机内部

2:外部扩展

2.EEPROM如何编程

1.I2C接口底层时序

底层:CPU和I2C的接口

2.器件定义的寄存器读写时序

上层:器件时序

2.原理图和数据手册

1.接线确定

【单片机】14-I2C通信之EEPROM_第3张图片

【单片机】14-I2C通信之EEPROM_第4张图片

查看SCL和SDA无其他接线影响

SCL对应P2.1 SDA对应P2.0

2.数据手册理论

立创商城_一站式电子元器件采购自营商城_现货元器件交易网-嘉立创电子商城

24c02中文官方资料手册pdf - 百度文库

1.芯片的基本信息

【单片机】14-I2C通信之EEPROM_第5张图片

【单片机】14-I2C通信之EEPROM_第6张图片

类似于一个主持人叫A说话,其他人就不可以说话,但是其他人可以听到主持人和A说话,但是不可以回应。 --广播式

【单片机】14-I2C通信之EEPROM_第7张图片

主设备:51单片机---发送器

从设备:24Cxxx---接收器

2.I2C从地址确定

每一个I2C都有从地址

3.I2C底层时序

起始信号:

发送字节:一般第一个是从设备的地址【因为我们在通话之前,要先发送要进行通话的地址,设备都与自己的地址是否相同,如果相同则响应;如果不同,则丢弃】

读取字节:

停止信号:

3.I2C总结

(1)主CPU和其附属芯片之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要

(2)三根线:SCL,SDA,GND,串行,电平式

(3)总线式结构:可以一对多,总线上可以挂上百个器件【一个主设备,多个从设备】,用【从地址】来区分--主设备不需要地址

【单片机】14-I2C通信之EEPROM_第8张图片

【单片机】14-I2C通信之EEPROM_第9张图片

(4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应

(5)通信速率一般(kbps级别),不合适语音,视频等信息类型

4.I2C总线协议定义

【单片机】14-I2C通信之EEPROM_第10张图片

起始信号

【单片机】14-I2C通信之EEPROM_第11张图片

终止信号

【单片机】14-I2C通信之EEPROM_第12张图片

应答信号

从设备回复主设备,判断从设备是否得到数据。

可以设置是否要进行”应答信号“【可有可无】

【单片机】14-I2C通信之EEPROM_第13张图片

3.I2C低层时序图和程序

1.起始信号和结束信号

【单片机】14-I2C通信之EEPROM_第14张图片

SCL和SDA交互进行判断

(1)起始信号:SCL保持高时,SDA有一个从高到低(下降沿)
(2)结束信号:SCL保持高时,SDA有一个从低到高(上升沿)

起始信号

/*******************************************************************************
* 函 数 名       : 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);			
}

2.I2C发送一个字节

【单片机】14-I2C通信之EEPROM_第15张图片

传输”0“或应答位(A)

【单片机】14-I2C通信之EEPROM_第16张图片

传输”1“或应答位(/A)

【单片机】14-I2C通信之EEPROM_第17张图片

/*******************************************************************************
* 函 数 名         : iic_write_byte
* 函数功能		   : IIC发送一个字节 
* 输    入         : dat:发送一个字节
* 输    出         : 无
*******************************************************************************/
void iic_write_byte(u8 dat)
{                        
    u8 i=0; 
	   	
    //为了保证时序正确,这里要加上    
    //当SDA将数据放好才可以将SCL置为高电平
    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);
    }	 
}

 【单片机】14-I2C通信之EEPROM_第18张图片

(1)I2C发送和接收字节时,都是从高位开始的

3.应答位处理

在接收完8位bit后,在第9个时间周期

应答处理:SDA变低【AT2402拉低】。

如果我们去检测,如果此时SDA为低电平,则表示已经被拉低,则表示已经响应到;如果SDA为高电平,则表示未能响应到。

【单片机】14-I2C通信之EEPROM_第19张图片

 产生ACK应答  

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

 产生NACK非应答 

/*******************************************************************************
* 函 数 名         : 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;	
}

4.I2C接收一个字节

【单片机】14-I2C通信之EEPROM_第20张图片

释放总线

在51单片机中,SDA=1就是释放总线【相当于主持人把话筒给嘉宾】;在其他更高级的单片机(比如STM32)这里的处理还会不一样。【因为拉高,则可以拉低(接地);但是拉低了,但是无法拉高】

为什么SDA=1就是释放总线??是因为当51单片机把引脚拉高时,从设备可以选择再把引脚拉高或者拉低;但是当51单片机把这个引脚拉低(接地)后,从设备也没有办法把这个引脚拉高了。

unsigned char IIC_ReadByte()
{
    unsigned char a=0,dat=0;
    //释放总线
    IIC_SDA=1;  //起始和发送一个字节之后IIC_SCL都是0
    IIC_delay();
    //按道理来说这里应该有一个SCL=0的
    for(a=0;a<8;a++){
        IIC_SCL=1;//通知从设备我要开始读了,可以放bit数据到SDA了
        IIC_delay();
        dat<<=1; //读取的时候高位再前
        dat|=IIC_SDA;
        IIC_delay();
        IIC_SCL=0;// 拉低为下一个bit周期做准备
        ICC_delay();
    }
    return dat;
}

4.EEPROM读写测试

1.  器件寻址

【单片机】14-I2C通信之EEPROM_第21张图片

(1)从器件的地址是由器件自身定义的,不同的从器件的地址的定义方式是不同的,要查具体的芯片数据手册来确定

(2)同一个I2C网络中只有一个主设备,但是从设备可以有多个。这多个从设备的地址不能相同。【硬件工程师必须保证这一点。因为从地址是不能通过软件设定的】

【单片机】14-I2C通信之EEPROM_第22张图片

(3)A0,A1,A2----2的三次方=8【表示最多只能接8个EEPROM】

【单片机】14-I2C通信之EEPROM_第23张图片

从CPU的角度来分析24C02的地址定义【如果不是从CPU角度看则得出结果不一样】

        从设备地址是:读地址:0xa1

                                 写地址:0xa0

【单片机】14-I2C通信之EEPROM_第24张图片

2.24C02写高层时序

写操作时序

【单片机】14-I2C通信之EEPROM_第25张图片

【单片机】14-I2C通信之EEPROM_第26张图片 

start-send_byte(从地址)--send_byte(字节地址)---send_byte(写入数据)

 字节写

/*******************************************************************************
* 函 数 名         : 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);	 
}

页写

【单片机】14-I2C通信之EEPROM_第27张图片

/*******************************************************************************
* 函 数 名         : at24c02_write_one_byte
* 函数功能		   : 在AT24CXX指定地址写入一个数据
* 输    入         : addr:写入数据的目的地址 
					 dat:要写入的数据
* 输    出         : 无
*******************************************************************************/
void at24c02_write_one_byte(u8 addr,u8 dat[],u8 i)
{				   	
    u8 j;  	    																 
    iic_start();  
	iic_write_byte(0XA0);	//发送写命令,发送写器件地址	    	  
	iic_wait_ack();	//表示要接收应答 
    iic_write_byte(addr);	//发送写地址   
	iic_wait_ack(); //表示要接收应答 	 										  		   
	for(j=0;j

3. 24C02读高层时序

【单片机】14-I2C通信之EEPROM_第28张图片

【单片机】14-I2C通信之EEPROM_第29张图片

【单片机】14-I2C通信之EEPROM_第30张图片

/*******************************************************************************
* 函 数 名         : 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;			//返回读取的数据
}

4.加入串口输出代码

/*******************************************************************************
* 实验名			  : EEPROM实验
* 使用的IO	    : 
* 实验效果      : 按K1保存显示的数据,按K2读取上次保存的数据,按K3显示数据加一,
*按K4显示数据清零。
*	注意					:由于P3.2口跟红外线共用,所以做按键实验时为了不让红外线影响实验效果,最好把红外线先
*取下来。
*
*********************************************************************************/
#include 
#include "at24c02.h"
#include "uart.h"



void delay20ms(void)   //误差 -0.000000000005us
{
    unsigned char a,b,c;
    for(c=1;c>0;c--)
        for(b=222;b>0;b--)
            for(a=40;a>0;a--);
}

/*******************************************************************************
* 函 数 名         : main
* 函数功能		   : 主函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void main()
{
	unsigned char i;
	unsigned char addr;	
	unsigned char src_data[] = "()ab#cde!fg1234567";	
	unsigned char buf[8] = "ABCDEFGH";

	uart_init();
/*
	for (i=0; i<128; i++)
	{
		uart_send_byte(i);
	}
	while (1);
*/
	// 先随便找一堆数据,譬如"abcdefg1234567-_-*&%@/\"
	// 把这些写入EEPROM的特定地址中
	// 然后读EEROM的这些地址,读出后通过串口打印出来看是不是我们写入的

	uart_send_byte('%');

	addr = 0;
	for (i=0; i<8; i++)
	{
		At24c02Write(addr, src_data[i]);
		delay20ms();
		addr++;
	}

	//先打印出buf
    //如果这里没有给buf初始化,则打印会出现问题
	for (i=0; i<8; i++)
	{
		uart_send_byte(buf[i]);
	}
	

	//分割
	for (i=0; i<20; i++)
	{
		uart_send_byte('-');
	}
	
	// 读出测试
	addr = 0;
	for (i=0; i<8; i++)
	{
		buf[i] = At24c02Read(addr);
		delay20ms();
		addr++;
	}

	//将数据打印出来
	for (i=0; i<8; i++)
	{
		uart_send_byte(buf[i]);
	}
 
	while (1);

	// 进一步测试
	// 先写入一些特定内容,然后关机断电;然后改代码为读出并打印显示看内容

}		

问题分析

(1)通过调试发现程序跑飞了,经检测发现uart中没有关中断

(2)读出内容不对,怀疑是EEPROM经不起快速的连续读写,所以在读和写之间加入20ms的delay,测试后发现读写正确了

(3)定义了局部变量没有初始化,程序中直接去通过串口输出,结果导致程序

你可能感兴趣的:(单片机,嵌入式硬件)