51单片机-使用IIC通信协议实现EEPROM模块教程

本章概述思维导图:

51单片机使用IIC通信协议实现EEPROM模块

IIC通信简介


IIC总线简介

IIC总线是Philips飞利浦公司在八十年代初推出的一种串行、半双工总线。主要用于近距离、低速的芯片之间的通信;IIC总线有两根双向的信号线,一根数据线SDA用于收发数据、一根时钟线SCL用于通信双方时钟的同步;IIC总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用。

IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机。主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生;每个连接到IIC总线的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其它器件正常工作;IIC总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器;

小结:

IIC总线是一种串行、半双工总线;

IIC总线主要用于近距离、低速的芯片之间的通信;

IIC总线有两个双向的信号号线,数据线SDA和时钟线SCL

IIC总线是多主机总线,每个器件都可以为主机和从机,但是同一时刻只能有一个主机。

IIC总线具备冲突检测和仲裁功能防止错误的产生

连接IIC总线的器件都具备一个唯一的7bit地址


IIC总线通信过程

  1. 主机发送起始信号启用总线
  2. 主机发送一个字节数据指明从机地址和后续字节的传送方向
  3. 被寻址的从机发送应答信号回应主机
  4. 发送器发送一个字节数据
  5. 接收器发送应答信号回应发送器
  6. ......(循环步骤4、5)
  7. 通信完成后主机发送停止信号释放总线

详细讲解:

步骤1:因为IIC是多主机总线,当某一个主机发送起始信号后,其它主机就知道总线被占用了 ;

步骤2:主机发送一个字节数据也就是八位数据,高七位(1~7位)为:从机地址,低(0位)位为传输数据方向,如果为:0传输方向为主机->从机,如果为:1传输方向为从机->主机。这一步也就确定了谁为发送器谁为接收器。这一步,开始阶段主机发送一字节数据后,所有从机都接收的到就好比广播。所有从机把高七位数据都提取出来,与自己地址号比较。不相同,从机就不应答闭嘴嘿嘿,相同从机就把第0位提前出来,来确定数据传输方向。

步骤3:主机发送完一字节后数据,从机匹配的上从机地址号后给主机发送一个应答信号。

步骤4:就是正式开始传输数据了,发送器发送一个字节数据给接收器。注意点是发送器并不是主机或者从机,具体要观察第二步骤主机发送一字节数据第0位来确定谁是发送器谁是接收器。

步骤5:接收器接收完一字节数据传输后,要发送一个应答信号给发送器。

步骤6:一直重复步骤4和步骤5来传输数据。

步骤7:当数据传输完成后,主机向从机发送一个停止信号,告诉其它器件总线空闲下来了。

使用IIC总线发送数据第一个字节数据一定是主机发送给从机的,后续不确定,要看第一个字节的数据的第0位来确定,第0位为0:发送器为主机,接收器为从机;第0位为1,发送器为从机,接收器为主机;最后数据传输完成后,主机发送停止信号给从机表述一次通讯的结束;


IIC总线寻址方式

IIC总线上传送的数据是广义的,即包括地址,又包括真正的数据。

主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,'0'表示主机发送数据,'1'表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第0位将自己定为发送器或接收器


起始信号和停止信号

SCL为高电平,SDA由高变低表示起始信号(下降沿)。

SCL为高电平,SDA由低变高表示停止信号(上升沿)。

起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态,停止信号产生后总线处于空闲状态

IIC总线在空闲的时候SCL和SDA都处于高电平


字节传送与应答信号

IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送一位应答位来回应发送器即一帧共有9位;SDA应答信号是低电平,SDA非应答信号是高电平;

同步信号

IIC总线在进行数据传送时,时钟线SCL为低电平期间发送器向数据线上发送一位数据,在此期间数据线上的信号允许发生变化,时钟线SCL为高电平期间接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发生变化,必须保持稳定;

SCL为高电平,SDA读取数据;

SCL为低电平,SDA发送数据;


典型IIC时序

1、主机向从机发送数据

S 从机地址(7位)0(1位) A 数据(1字节) A 数据(1字节) A/!A P

详细介绍:主机先发送一个起始S信号,在发送7位从机地址和1位传输方向信号,从机接收完成后回应一个应答信号A,主机开始发送1字节数据,从机回应一个应答信号A,主机在接着发送1字节数据。当从机不想接收数据了发送非应答信号!A主机发送停止信号P或者主机不想发送了,当接收到从机发送的应答信号主机立马发送停止信号;


2、从机向主机发送数据

S 从机地址(7位)1(1位) A 数据(1字节) A 数据(1字节) !A P

详细介绍:主机发送一个起始S信号,在发送7位从机地址和1位传输方向信号1,从机接收完成后回应一个应答信号A,从机发送一字节数据,主机回应一个应答信号。当主机不想接收数据了,回应一个非应答信号。在发送一个停止信号接收传输;


3、主机先向从机发送数据,然后从机再向主机发送数据

S 从机地址(7位)0(1位) A 数据 A/!A S 从机地址(7位)1(1位) A 数据 !A P

注意这里有是一种特殊情况:中途传输数据时主机向从机发送数据,当主机不想发了或者从机不想接了,是回应非应答信号。但是后面并没有停止信号,这种情况是允许的,因为传输信号并没有完成,如果发送了停止信号,总线可能被其它主机占用。所以是直接再次发送了一个起始信号开始由从机向主机发送数据;

注释:红色字体部分表示数据由主机向从机传送数据,黑色字体部分则表示数据由从机向主机传送;A表示应答,!A表示非应答;S表示起始信号,P表示停止信号


AT24C02芯片简介

AT24C02芯片概述

AT24C02是一款由Microchip(原Atmel)生产的低功耗、串行电可擦可编程只读存储器(EEPROM),采用I²C总线接口,存储容量为2Kbit(256字节)。其核心特点包括:

非易失性存储:断电后数据可保留10年以上(部分资料显示可达100年)。

低功耗设计:工作电流1mA,待机电流仅1μA,适合电池供电设备。

宽电压范围:支持1.8V至5.5V供电,兼容多种电源环境。

高可靠性:擦写次数达100万次(部分资料为10万次,差异可能源于批次或测试条件)。

灵活寻址:通过A0、A1、A2引脚设置器件地址,支持同一总线上连接最多8个设备。

同款芯片有:AT24C01、AT24C04、AT24C08、AT24C16等后面的数字表示存储的位数;

AT24C01存储容量为1Kbit(128字节);

AT24C04存储容量为4Kbit(512字节);

AT24C08存储容量为8Kbit(1024字节);

AT24C16存储容量为16Kbit(2048字节);

AT24C01芯片封装图和地址数据图、时序图如下:


AT24C02芯片引脚介绍

AT24C02采用8引脚封装(如DIP-8、SOP-8),关键引脚如下:

|-------------|-------|--------------------------------------------------|
| 引脚 | 名称 | 功能描述 |
| 1-3 | A0-A2 | 地址引脚,用于设置器件在I²C总线上的唯一地址。接地时默认地址为0xA0(写)或0xA1(读)。 |
| 4 | GND | 电源地,连接系统地线。 |
| 5 | SDA | 串行数据线,双向I/O,需通过上拉电阻(通常4.7kΩ)接至VCC。 |
| 6 | SCL | 串行时钟线,由主设备提供时钟信号,需上拉电阻。 |
| 7 | WP | 写保护引脚,接地时允许读写操作,接高电平时禁止写入(仅允许读取)。 |
| 8 | VCC | 电源正极,连接1.8V至5.5V电源。 |

A0-A2:设备地址引脚,用于设置芯片在IIC总线为唯一地址。三个引脚组合可生成8种不同 地址(000-111),支持同一总线上连接最多8个AT24C02设备

GND:电源脚,提供芯片的零电位参考

SDA:串行数据线,负责IIC总线的双向数据传输。开漏输出结构,需外接上拉电阻(通常 4.7KΏ)至VCC,以确保信号稳定性。

SCL:串行时钟线,由主设备提供时钟信号,同步数据传输。

WP:写保护引脚,用于控制写入权限。接地:允许正常的读写操作;接高电平:禁止写入, 仅允许读取,防止数据被意外修改。

VCC:电源正极,为芯片提供工作电压


EEPROM模块硬件电路原理分析

观察EEPROM模块硬件电路原理图可分析出

AT24C02芯片的WE引脚默认接入GND信号,说明芯片保持一直保持一个可读可写状态;

AT24C02芯片的SCL时钟引脚接入MCU的P21IO引脚,实现时钟信号的产生;

AT24C02芯片的SDA数据引脚接入MCU的P20IO引脚,实现串行数据传输功能;


EEPROM模块软件编程设计

实现的任务是:系统运行时,数码管右三位显示0,按K1键将数据写入到EEPROM内保存,按K2键读取EEPROM内保存的数据,按K2键显示数据加1,按K4键将数据清零,最大能写入的数据是255;

实现EEPROM模块步骤!

步骤1:实现IIC软件模拟信号的产生

起始信号代码示例!

cpp 复制代码
void IIC_start(void)//IIC起始信号函数
{
	IIC_SDA=1;		//IIC数据线为高电平
	Delay_10us(1);	
	IIC_SCLK=1;		//IIC时钟线为高电平
	Delay_10us(1);	//下降沿
	IIC_SDA=0;		//IIC数据线为低电平	
	Delay_10us(1);	
	IIC_SCLK=0;		//占用总线以进行数据传输
	Delay_10us(1);	//
}

代码讲解:在空闲阶段数据线为高电平,时钟线为高电平;这时想数据线变为低电平。产生一个下降沿延时一点时间,将时钟线拉低占用总线延时一点时间,不让其他外设使用IIC总线。在下降沿的时候就是一个起始信号的产生;

停止信号代码示例:

cpp 复制代码
void IIC_stop(void)	//IIC停止信号函数
{
	IIC_SDA=0;		//IIC数据线为低电平
	Delay_10us(1);	
	IIC_SCLK=1;		//IIC时钟线为高电平
	Delay_10us(1);	//上升沿
	IIC_SDA=1;		//IIC数据线为高电平
	Delay_10us(1);
}

代码讲解:观察时序图得知时钟线为高电平,数据线为低电平,延时一点时间。数据线转变为高电平产生一个上升沿。这就是停止信号的产生


应答信号代码示例:

cpp 复制代码
void IIC_ack(void)	//IIC应答信号函数
{
	IIC_SCLK=0;		//IIC时钟线为低电平,运行数据线传输数据
	IIC_SDA=0;		//数据线发送应答信号
	Delay_10us(1);	//延时
	IIC_SCLK=1;		//IIC时钟线为高电平
	Delay_10us(1);
	IIC_SCLK=0;
}

代码讲解:观察时序图可了解为:时钟线为低电平时,数据线转变为低电平,延时一点时间,时钟线为高电平,数据线保持低电平,延时一点时间,时钟线为低电平;当数据线为低电平时就是发送应答信号

非应答信号代码示例;

cpp 复制代码
void IIC_nack(void)	//IIC非应答信号函数
{
	IIC_SCLK=0;		//IIC时钟线为低电平,运行数据线传输数据
	IIC_SDA=1;		//数据线发送非应答信号
	Delay_10us(1);	//延时
	IIC_SCLK=1;		//IIC时钟线为高电平
	Delay_10us(1);
	IIC_SCLK=0;
}

代码讲解:观察时序图可了解为:时钟线为低电平时,数据线转变为高电平,延时一点时间,时钟线为高电平,数据线保持高电平,延时一点时间,时钟线为低电平;当数据线为高电平时就是发送非应答信号;


IIC总线等待应答或者非应答信号代码示例:

cpp 复制代码
u8 IIC_wait_ack(void)	//IIC等待应答信号
{
	u8 time_cnt=0;
	IIC_SCLK=1;			//IIC时钟线为高电平,读取数据线传输的数据
   	Delay_10us(1);		//延时
   	while(IIC_SDA)		//SDA为高电平进入循环
	{  	
		time_cnt++;
		if(time_cnt>100)
		{
			IIC_stop();
			return 1;	//非应答信号返回1
		}
	}
	IIC_SCLK=0;	
	return 0;			//应答信号返回0
}

代码讲解:主设备在发送数据(如设备地址、寄存器地址等)后,将时钟线(SCL)拉高并保持短暂延时,随后持续检测数据线(SDA)的电平状态------若SDA保持高电平(非应答NACK),则通过循环计数实现超时保护(约1ms后触发停止条件并返回错误码1),避免程序卡死;若检测到SDA被从设备拉低(应答ACK),则立即退出循环并拉低时钟线,返回成功状态0。


IIC写入一字节数据代码示例:

cpp 复制代码
void IIC_wriat_byte(u8 dat)		//IIC写入一字节函数,形参为传输的字节
{
	u8 i=0;
	IIC_SCLK=0;				//低电平,IIC开始写入数据
	for(i=0;i<8;i++)
	{
		if((dat&0x80)>0)
		{
			IIC_SDA=1;			//高位为1数据线为1
		}
		else 
		{
			IIC_SDA=0;			//高位为0数据线为0
		}
		dat<<=1;				//左移一位;传输下一位
		IIC_SCLK=1;				//时钟线为高电平,IIC开始读取数据
		Delay_10us(1);			//延时一点时间
		IIC_SCLK=0;				//时钟线为高电平,IIC开始读取数据
		Delay_10us(1);			//延时一点时间
	}
}

代码讲解:首先拉低时钟线(SCL)进入数据传输准备状态,随后循环8次处理8位数据------每次通过位掩码(dat&0x80)检测当前最高位电平,对应设置数据线(SDA)为高/低电平后左移一位处理下一位;在时钟线拉高期间(SCL=1)保持数据稳定约10μs供从设备读取,随后拉低时钟线并再次延时,完成单比特传输。


IIC读取一字节数据代码示例:

cpp 复制代码
u8 IIC_read_byte(u8 ack)		//IIC读取一字节数据,形参为1应答/0非应答;
{
	u8 i=0;
	u8 read_data=0;
	for(i=0;i<8;i++)
	{
		IIC_SCLK=0;				//IIC发送一位数据
		Delay_10us(1);			//延时一点时间
		IIC_SCLK=1;				//IIC读取一位数据
		read_data<<=1;				//必须要先左移,如果先接收数据的话,最高位会溢出
		if(IIC_SDA)
		{
			read_data++;
		}
		Delay_10us(1);			//延时一点时间
	}
	if(ack==1)
	{
		IIC_ack();				//发送非应答	
	}
	else if(ack==0)
	{
		IIC_nack();				//发送应答
	}
	return read_data;
}

代码讲解:通过8次循环逐位读取数据(MSB优先),每次循环先拉低时钟线(SCL)准备读取,延时后拉高时钟线触发从设备输出当前数据位;主机通过检测SDA电平并左移累加到read_data变量(如检测到高电平则+1),完成单比特采集。循环结束后根据ack参数值执行应答控制------ack=1时调用IIC_ack()发送非应答(NACK,停止后续传输),ack=0时调用IIC_nack()发送应答(ACK,允许继续读取),最终返回读取的字节数据。


步骤2:实现AT24C02芯片写入和读取一字节函数封装

AT24C02芯片写入一字节函数代码示例:

cpp 复制代码
void AT24C02_wirat_byte(u8 address,u8 dat)		//AT24C02芯片写入1字节函数封装:形参address为写入的AT24C02的内存空间地址值;dat为传输的数据
{
	IIC_start();				//第一步发送一个起始信号
	IIC_wriat_byte(0xA0);		//第二步主机发送一个从机地址和传输方向
	IIC_wait_ack();				//第三步等待从机发送应答信号
	IIC_wriat_byte(address);	//第四步主机发送需要在AR24C02芯片中写入数据的内除空间地址
	IIC_wait_ack();				//第五步等待从机发送应答信号
	IIC_wriat_byte(dat);		//第六步AT24c02写入主机发送的信号
	IIC_wait_ack();				//第七步等待接收器发送非应答信号
	IIC_stop();					//第八步主机发送停止信号,结束传输
}

代码讲解:首先发送起始信号,接着发送从机地址(0xA0,含7位设备地址+写标志),三次等待并验证从机应答(确保地址、数据写入成功),期间依次发送目标内存地址和待写入数据,最终发送停止信号结束传输,完整遵循"起始-地址-应答-地址-应答-数据-应答-停止"的I²C时序规范,确保非易失存储的可靠操作。


AT24C02芯片读取一字节函数代码示例:

cpp 复制代码
u8 AT24C02_read_byte(u8 address)				//读取AT24C02芯片1字节函数封装:形参address为读取的AT24C02的内存空间地址值;
{
	u8 temp_read=0;
	IIC_start();				//第一步主机发送一个起始信号
	IIC_wriat_byte(0xA0);		//第二步主机发送一个从机地址和传输方向
	IIC_wait_ack();				//第三步等待从机发送应答信号
	IIC_wriat_byte(address);	//第四步主机发送需要在AR24C02芯片中写入数据的内除空间地址
	IIC_wait_ack();				//第五步等待从机发送非应答信号
	IIC_start();				//第六步主机发送一个起始信号
	IIC_wriat_byte(0xA1);		//第七步主机发送一个从机地址和传输方向
	IIC_wait_ack();				//第八步等待从机发送应答信号
	temp_read=IIC_read_byte(0);	//第九步从机发送数据,主机读取
	IIC_stop();					//第十步主机发送停止信号
	return temp_read;
}

代码讲解:首先发送起始信号,写入从机地址(0xA0,含7位设备地址+写标志)并验证应答;接着发送目标内存地址(address)并再次应答确认;随后发送重复起始信号切换至读模式,写入从机地址(0xA1,含读标志)并等待应答;最后调用IIC_read_byte读取数据(传入ack=0发送应答),发送停止信号后返回读取值。


步骤3:将独立按键检查函数和数码管显示函数、AT24C02写入/读取一字节函数相结合;

主函数代码示例:

cpp 复制代码
#define EEPROM_ADDRESS 0	//定义数据存入EEPROM的起始地址
int main()
{
	u8 KEY_temp=0;
	u8 save_value=0;
	u8 save_buf[3];
	while(1)
	{
		KEY_temp=KEY_detect(0);					//获取按键值
		if(KEY_temp == 1)				//将数据写入到EEPROM
		{
			AT24C02_wirat_byte(EEPROM_ADDRESS,save_value);	
		}
		else if(KEY_temp == 2)			//将从EEPROM中读取数据
		{
			save_value=AT24C02_read_byte(EEPROM_ADDRESS);
		}
		else if(KEY_temp == 3) 			//数据加1
		{
			save_value++;
			if(save_value == 255)save_value=255;
		}
		else if(KEY_temp == 4)			//数据清0
		{
			save_value=0;
		}
		save_buf[0]=save_value/100;
		save_buf[1]=save_value%100/10;
		save_buf[2]=save_value%100%10;
		DIGITALTUBE_EEPROM_Display2(save_buf,6);
	}
}

代码讲解:主循环通过检测按键输入执行不同操作------按键1将当前数值save_value写入EEPROM起始地址,按键2从EEPROM读取数据到save_value,按键3实现数值自增(255封顶),按键4清零数值;每次操作后,程序将数值拆分为百、十、个位存入缓冲区,并通过数码管驱动函数实时显示,完整实现了"按键触发-数据操作-非易失存储-数码反馈"的嵌入式系统交互逻辑

EEPROM模块实现展示:

EEPROM模块实现展示


制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!

相关推荐
工大一只猿2 小时前
51单片机学习
嵌入式硬件·学习·51单片机
小莞尔2 小时前
【51单片机】【protues仿真】 基于51单片机八路抢答器系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
风_峰2 小时前
Ubuntu Linux SD卡分区操作
嵌入式硬件·ubuntu·fpga开发
bing_feilong2 小时前
STM32精准控制水流
单片机·嵌入式硬件
Hello_Embed9 小时前
STM32HAL 快速入门(二十):UART 中断改进 —— 环形缓冲区解决数据丢失
笔记·stm32·单片机·学习·嵌入式软件
一起搞IT吧11 小时前
嵌入式ARM SOC开发中文专题分享一:ARM SOC外围资源介绍
arm开发·嵌入式硬件
研华嵌入式11 小时前
如何在高通跃龙QCS6490 Arm架构上使用Windows 11 IoT企业版?
arm开发·windows·嵌入式硬件
矢志不移79212 小时前
裸机开发 时钟配置,EPIT
单片机·嵌入式硬件
清风66666612 小时前
基于STM32的APP遥控视频水泵小车设计
stm32·单片机·mongodb·毕业设计·音视频·课程设计