IIC通信(STM32)与EEPROM(AT24C02)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

此篇文章是我在学习STM32编程的IIC通信时候的个人所得,若是大佬们看出小弟本篇内容有误,请大佬指点一下我,谢谢!

一、IIC(STM32)

IIC(Inter-Integrated Circuit),中文叫集成电路总线,是一种串行通信总线,使用一主多重架构。
在这里插入图片描述

基本特点

①双线制:SCL时钟同步线和SDA数据传输线。
②半双工:数据的传输是由SDA线来完成的,虽然能双向通信,但是发送和接收不能同时进行。
③同步通信:IIC的通讯协议要求严格根据SCL时钟线来完成,SDA必须在SCL的时序下按位传输数据。
④通信为主从机模式:支持一主多从模式,SCL时序线的电平一直由主机控制,SDA数据线则不固定。
⑤从属设备地址:由于支持多从机模式,所以需要依据从机的地址来准确地找到从机,从机设备的地址都是由厂家设置的。

△注意:IIC的通信永远都是由主机发起的!

设备地址

设备地址一般都是由7位(bit)数据组成,在IIC通信开始时将其左移一位然后将第8位作为读写位,0位写模式,1为读模式。

设备地址的作用
①让主机准确找到从机
②确定读写模式
③某些IIC支持在通信过程中通过从机的地址附带一些信息给从机

IIC通信协议分析

在这里插入图片描述
1、通信前:
SDA和SCL都设置为高电平,并且保持高电平一段时间。

void MyIIC_Init(void)//此处为模拟IIC初始化,需要将模拟SDA和SCL电平拉高
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
		
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8|GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_OUT;//输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
	GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;//浮空
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	SCL = 1//PBout(8)
	SDA_W = 1; //PBout(9)
}

由于通信进行读写操作,或者读写应答操作的时候需要经常改变主机引脚的输入输出模式,所以我参考老师的代码模式写了个换引脚模式的函数,如下:

static void MyIIC_SelectMode(GPIOMode_TypeDef Mode)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8|GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Mode  = Mode;//输出模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
	GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP;//此处设置成上拉
	GPIO_Init(GPIOB,&GPIO_InitStruct);
}

2、通信开始
通信的开始需要将引脚模式设置成输出模式,并且将SDA和SCL线拉高,延时5us,先将SDA_W拉低,等待5us再讲SCL拉低,完成模拟信号如下图步骤。
在这里插入图片描述

void MyIIC_Start(void)
{
	MyIIC_SelectMode(GPIO_Mode_OUT);
	SCL = 1;
	SDA_W = 1;
	delay_us(5);
	SDA_W = 0;
	delay_us(5);
	SCL = 0;
}

3、写入从机设备地址
在通信开始之后就要写入从机设备的地址,发送的内容为从机的7位地址加上末尾表示为“写”的0,由于IIC通信每次都是传8位数据,所以此程序段也是作为数据的发送程序。
在这里插入图片描述
程序如下所示:

void MyIIC_WriteByte(u8 WByte)
{
	MyIIC_SelectMode(GPIO_Mode_OUT);
	int i;
	for(i=0; i<8 ;i++)
	{
		SCL = 0;//SCL拉低之后SDA才能改变电平
		delay_us(2);
		if(WByte &=(0x80)>>i) SDA_W = 1;//在SCL拉低期间按照传参进来的地址改
		else SDA_W = 0;                 //变SDA的电平,拉低一次SCL传输一位数据
		delay_us(3);//此处拉低的总共5us
		SCL = 1;
	}
	SCL = 0;
}

4、等待从机应答
在主机发送从机地址到SDA线之后,需要接收从机的应答,此时SDA的电平是由从机控制的,每次主机发送数据给从机之后都要接收从机的应答,SDA电平为“1”则是无应答,“0”则是有应答,程序如下所示:
在这里插入图片描述

char MyIIC_ReadACK(void)
{
	MyIIC_SelectMode(GPIO_Mode_IN);	
	char ACK;
	SCL = 0;
	delay_us(5);
	
	SCL = 1;
	delay_us(2);
	if(SDA_R ==1) ACK = 1;
	else ACK = 0;
	delay_us(3);
	
	SCL = 0;
	return ACK;	
}

5、读取从机数据
主机可以写入数据给从机,那么也需要读取从机发送的数据,从机发送的数据也同样是8个位,和主机发送数据不同的是从机是在SCL高电平的时候发送数据,程序如下:

u8 MyIIC_ReadByte(void)
{
	MyIIC_SelectMode(GPIO_Mode_IN);
	u8 tem = 0;
	int i;
	for(i=0; i<8; i++)
	{
		SCL = 0;
		delay_us(5);
		SCL = 1;
		delay_us(2);
		if(SDA_R = 1) tem |=(0x80>>i);
		delay_us(3);	
	}
	SCL = 0;	
	return tem;
}

6、主机写应答
每次主机接收完从机发送的8位数据后也需要向从机发送应答,从机同样的读取主机的应答是通过判断SDA线的电平,同样“1”是无应答,“0”则是有应答,程序如下:

void MyIIC_WriteACK(char ACK)
{
	MyIIC_SelectMode(GPIO_Mode_OUT);
	
	SCL = 0
	delay_us(2);
	SDA_W = ACK;
	delay_us(3);
	
	SCL = 1;
	delay_us(5);
	SCL = 0;
}

7、停止信号
最后在要结束通信的时候需要主机发送结束信号给从机,但是在停止前主机是读状态的话,在读完之后需要发送“1”应答给从机告诉从机不读了,然后再发送停止信号。
在这里插入图片描述

停止程序如下:

void MyIIC_Stop(void)
{
	MyIIC_SelectMode(GPIO_Mode_OUT);
	SCL = 0;
	SDA_W = 0;
	delay_us(5);
	
	SCL = 1;
	delay_us(5);
	SDA_W = 1;
}

二、AT24C02

我开发板上的E方芯片四AT24C02,容量是2Kbit,其中分为32页,每页存放的数据是8个字节。
在这里插入图片描述由图可以看出设备的写模式地址为0xA0,读模式地址为0A1。

AT24C02的写操作如下:
在这里插入图片描述

1、单字节写入操作:

对E方进行数据操作其实就是一个完整的IIC通信过程,步骤如以下代码所示,利用主机读从机应答来判断当前操作是否成功。

int AT244Cxx_WriteByte(u8 Addr,u8 Wdata)
{
	MyIIC_Start();
	
	MyIIC_WriteByte(0xA0);
	if(MyIIC_ReadACK())
	{
		printf("连接从机地址失败\r\n");
		return -1;
	}
	
	MyIIC_WriteByte(Addr);
	if(MyIIC_ReadACK())
	{
		printf("连接存储地址失败\r\n");
		return -2;
	}
	
	MyIIC_WriteByte(Wdata);
	if(MyIIC_ReadACK())
	{
		printf("写入数据失败\r\n");
		return -3;
	}
	
	MyIIC_Stop();
}

2、多字节写入操作:

多字节连续写入操作的难点在于AT24C02每一页只能存储8个字节的数据,如果满8个字节之后没有跳到下一页则会将前面的数据覆盖成新的数据,比如存入“hello world!”不跳页的话就会变成“rld!o wo”所以连续存储跳页很重要,我自己写了一段连续存储的程序如下,有哪里需要改进的请各位大佬指点一下,谢谢!

int AT24Cxx_WriteBytes(u8 Addr,u8 *Wdata,int len)
{
	u8 *p = Wdata;
	char i;
	while(len)
	{		
		if(len>=8)//当len足够写完一页的时候进入
		{
			MyIIC_Start();
		
			MyIIC_WriteByte(0xA0);//写模式设备地址
			if(MyIIC_ReadACK())
			{
				printf("device address failed\r\n");
				return -1;
			}
			
			
			MyIIC_WriteByte(Addr);//发送要存入的地址
			if(MyIIC_ReadACK())
			{
				printf("write address failed\r\n");
				return -2;
			}
			
			for(i=0; i<8; i++)
			{
				MyIIC_WriteByte(*p++);//发送要存入的数据
				if(MyIIC_ReadACK())
				{
					printf("write data success\r\n");
					return -3;
				}
				
				Addr++;
				len--;
			}	
		}
		else//剩下不足8个字节的数据另开一页存入,在过页的时候必须重新启动IIC
		{
			MyIIC_Start();
		
			MyIIC_WriteByte(0xA0);//写模式设备地址
			if(MyIIC_ReadACK())
			{
				printf("device address failed\r\n");
				return -1;
			}
			
			MyIIC_WriteByte(Addr);//发送要存入的地址
			if(MyIIC_ReadACK())
			{
				printf("write address failed\r\n");
				return -2;
			}
			while(len)
			{
				MyIIC_WriteByte(*p++);//发送要存入的数据
				if(MyIIC_ReadACK())
				{
					printf("write data success\r\n");
					return -3;
				}
				len--;
			}	
		}
	}	
	MyIIC_Stop();
	
	return 0;
}

3、数据读操作:

读操作相对于写操作就简单很多了,因为读操作没有页的限制,只要持续地读就行了,但是有一点需要注意的地方是每读一个字节需要给从机回应是否要继续读,所以在最后的一个字节读完要给从机发送应答“1”,告诉从机不读了。如下图所示。

在这里插入图片描述所以在程序段中循环读的时候,循环的次数应该是所需要的读的字节数-1,程序如下所示:

int AT24Cxx_ReadBytes(u8 Addr,u8 *buf ,int len)
{
	u8 *p = tem;
	
	MyIIC_Start();
	
	MyIIC_WriteByte(0xA0);
	if(MyIIC_ReadACK())
	{
		printf("device address failed\r\n");
		return -4;
	}
	
	MyIIC_WriteByte(Addr);
	if(MyIIC_ReadACK())
	{
		printf("write address failed\r\n");
		return -5;
	}
	
	MyIIC_Start();
	
	MyIIC_WriteByte(0xA1);
	if(MyIIC_ReadACK())
	{
		printf("read device address failed\r\n");
		return -6;
	}
	
	len = len-1;
	
	while(len--)
	{
		*p = MyIIC_ReadByte();
		p++;
		MyIIC_WriteACK(0);
	}
	
	*p = MyIIC_ReadByte();
	MyIIC_WriteACK(1);
	
	printf("read data success\r\n");
	
	MyIIC_Stop();
	
	return 0;
}

结束语

本人程序菜鸟,很希望能得到大佬的指点,望各位大佬不吝赐教!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STC15系列单片机是一种高性能的8位单片机,具有丰富的外设资源和较大的存储器容量。其中包括EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦可编程只读存储器)。 多字节读写是指可以一次读取或写入多个字节的数据。在STC15系列单片机中,可以通过使用相关的指令和函数实现EEPROM的多字节读写操作。 首先,进行多字节写操作。首先要确定要写入的起始EEPROM地址和要写入的数据。然后,通过指令和函数将数据写入EEPROM。具体操作步骤如下: 1. 设置起始EEPROM地址,可以使用代码设置或者使用相关指令将地址值写入内部寄存器。 2. 将要写入的数据存储在一个数组中。 3. 使用相关指令或函数将数组中的数据写入EEPROM。 4. 写入完成后,可以通过读取写入的数据进行验证。 接下来,进行多字节读操作。步骤如下: 1. 确定要读取的EEPROM起始地址和要读取的字节数量。 2. 通过指令或函数将读取起始地址写入内部寄存器。 3. 使用指令或函数进行连续读取操作,将EEPROM中的数据读取到指定的存储器中。 4. 读取完成后,可以通过输出读取到的数据进行验证。 需要注意的是,在进行多字节读写操作时,要确保地址范围和数据范围的有效性,以免读写越界。 以上就是关于STC15系列单片机中实现EEPROM多字节读写的简要介绍。具体的实现方式可以参考相关的技术资料和开发文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值