不枉初心,砥砺前行

皮皮祥的博客

欢迎留言,评论

导航

IIC通信协议

1、IIC简介
IIC(Inter-Intergated Circuit,集成电路总线)由飞利浦(Pilliphs)公司发明,是一种串行总线通信。

有两根线:
		SDA:Serial DAta 串行数据线
			数据传输按bit位,属于半双工的协议。先传送最高bit(MSB)。
		SCL:Serial CLock 串行时钟线
			传递时钟信号,时钟信号是用来同步信号的。

同步:约定好发送数据只能在时钟线低电平,接收(采样)数据只能在时钟线的高电平。

因为只有一根数据线,所以IIC是半双工通信。

IIC通信设备都会挂载在SDA和SCL总线上,或者说SDA和SCL总线上会挂载很多设备。那么任意时刻,只能有一个设备向总线上发送数据,但是接收没有限制,都可以收。

为了让数据精确到达(而不是以广播的形式发送),我们给IIC总线上的每一个设备都给一个唯一的地址,这个地址就是设备地址,用来区分不同的I2C设备。

**2、IIC时序图(IIC协议)
在这里插入图片描述在这里插入图片描述
IIC数据通信的大概流程:

a. 总线空闲(空闲指没有数据通信时总线的状态)

IIC总线在空闲的时候,SDA和SCL都处于高电平。
如果你想开始发送数据,则你需要先发送一个起始信号。

	/*空闲*/
	SCL = 1;
	SDA = 1;

b. 起始信号:用来表示我要向总线上发送数据啦

SCL维持高电平不变
SDA从高到低产生下降沿

 	/* 起始信号 */
 	SDA = 0;

有没有可能IIC总线上的两个设备同时发送起始信号?

有可能。所以需要总线仲裁:决定谁的信号有效。比如:在发送起始信号前,判断IIC总线是否空闲。

怎么做:

	time_out = SCL_T;//超时时间为一个SCL时钟周期
	while(SCL == 1 && SDA == 1 && time_out--);

c. 发送数据

数据包括用户真正要发送的数据,也包括设备地址(指定通信方)。因为总线上有多个设备,其中一个发起一个起始信号,表示它要跟总线上的其它设备或者多个设备进行通信。

所以IIC协议规定,每一个IIC总线上的设备都必须要有一个IIC设备地址(7bit/10bit)并且同一个IIC总线上的设备的地址必须不一样。

IIC中数据(包括地址)的发送都是按8bit进行发送。

设备地址 = 7bit + 1bit R/W(读写位,占最低位)

bit0: 0	W 表示我要给指定地址的设备写入数据
bit0: 1 R 表示我要从指定地址的设备读取数据

发送完一个字节(8bit)后,对方(接收方)必须要回一个ACK(应答位)

ACK:在SDA数据线上的第9个时钟周期,接收方给SDA一个低
电平。如果数据的最后一个bit本身就是一个低电平,哪怕
对方不应答,那么SDA在第九个时钟周期的电平状态就是0,
这个时候,发送方可能会认为对方应答啦。

发送方在发送完8bit的数据后,一般都会释放SDA数据线(SDA == 1)。

例如

CPU(发送方):START 0000 0010   1100 0100   STOP
  A(接收方):			    ACK 		ACK 

数据的发送规则:

数据发送起始就是根据要发送的数据的bit的情况给SDA
线低电平或高电平,先发送MSB(最高位)。

发送数据时,更改数据线的要求如下:
	在SCL时钟线低跳变的时候,可以改变SDA数据线的
电平,所以发送是下降沿触发,每一个下降沿可以发送
1bit的数据。
	在SCL时钟线高跳变的时候,SDA数据线应该要保持稳定。
	接收是上升沿触发,每一个上升沿到来,就会去SDA上
采集1bit的数据。

d. 停止信号

	SCL保持高电平
	SDA从低电平到高电平跳变

通过谁控制SCL线区分不同的IIC设备:

IIC主设备:Master		
	产生IIC时钟输出的设备,它控制IIC总线的传输速率。
	
IIC从设备:Slave
	被动接收IIC时钟的设备。

Master-Send 主发	Master-Receive 主收
	时钟提供者既可以收也可以发
Slave-Send 从发		Slave-Receive 从收

IIC总线上的时钟频率一般在几十K hz~400K hz 之间,频率越低通信速率就越慢,但是也越稳定。

3、模拟IIC
在某些芯片上(如:C51)它没有I2C总线,没有I2C控制器,所以需要用两个GPIO口去模拟SDA和SCL

/*
	IIC_Send_Start:发送IIC起始信号
*/
void IIC_Send_Start(void)
{
	/*空闲*/
	SCL = 1;
	SDA = 1;
	delay(IIC_T);	//IIC_T:IIC时钟信号的周期-->延时一个时钟周期
	
	/*起始信号*/
	SDA = 0;
	delay(IIC_T);
}
/*
	IIC_Send_Stop:发送IIC停止信号
*/
void IIC_Send_Stop(void)
{
	SCL = 1;
	SDA = 0;
	delay(IIC_T);	//IIC_T:IIC时钟信号的周期-->延时一个时钟周期
	
	SDA = 1;
	delay(IIC_T);
}

/*
	IIC_Send_Byte:将一个字节的数据发送出去
		@ch 	: 要发送的数据,1个字节
		@返回值	: 发送并成功返回1(表示接收方回应了一个Ack)
				  失败返回0
*/
int IIC_Send_Byte(char ch)
{
	/*MSB(最高位)先发,每次发送8bit,并且是在SCL下降沿的时候发送*/
	int i;
	for(i = 7;i >= 0;i--)	//8个SCL时钟周期中发送8bit
	{
		SCL = 0;			//下降沿产生
		SDA = (ch >> i) & 0x01;	//每次发送一个bit
		delay(IIC_T/2);
		
		SCL = 1;
		delay(IIC_T/2);		//延时一会等待对方去接收
	}
	
	/*发送发在发送完8个bit后,一般会释放SDA数据线*/
	/*同时在第9个周期,等待接收方回应一个ACK*/
	SCL = 0;				//第9个周期开始
	SDA = 1;				//释放数据线
	delay(IIC_T/2);			//等接收方应答
	
	SCL = 1;
	if(SDA)
	{
		return 0;			//意味着别人没有应答我
	}
	else
	{
		return 1;			//意味着别人应答我啦
	}	
	delay(IIC_T/2);			//第九个周期结束
}

/*
	IIC_Recv_Byte:从IIC总线上接收一个字节
		@返回值	: 将接收到的字节返回
*/
unsigned char IIC_Recv_Byte(void)
{
	/*先接收最高bit,陆陆续续的会收到8bit*/
	int i;
	unsigned char ch = 0;
	for(i = 7;i >= 0;i--)
	{
		SCL = 0;	//给半个时钟周期的低电平,让对方发送数据
		delay(IIC_T/2);
		SCL = 1;	//上升沿读取数据
		if(SDA)
		{
			ch |= (1 << i);
		}
		delay(IIC_T/2);
	}
	
	/*在第九个时钟周期会回应对方(发送ACK)*/
	SCL = 0;		//第九个时钟周期开始
	delay();		//延时一段非常短的时间让对方释放数据线
	SDA = 0;		//回应应答信号ACK
	delay(IIC_T/2);
	SCL = 1;
	delay(IIC_T/2);	//第九个周期结束
	return ch;
}

/*
	IIC_Write_Bytes:向指定的IIC设备发送数据
		@addr	: 7bit的目标IIC设备的   地址
		@str	: 要发送的数据字符串
		@len	: 要发送的数据字符串的长度
		@返回值 : 发送成功返回1,返回失败返回0
*/
int IIC_Write_Bytes(unsigned char addr,unsigned char *str,int len)
{
	//发送起始信号
	IIC_Send_Start();
	
	//发送设备地址 = addr + 读写位(0)
	int ret = IIC_Send_Byte((addr << 1) | 0);
	if(ret == 0)				//代表没有应答
	{
		IIC_Send_Stop();		//发送停止信号
		return 0;
	}
	
	//发送数据
	int i;
	for(i = 0;i < len;i++)
	{
		ret = IIC_Send_Byte(str[i]);
		if(ret == 0)			//代表没有应答
		{
			IIC_Send_Stop();	//发送停止信号
			return 0;
		}
	}
	
	//发送停止信号
	IIC_Send_Stop();
	return 1;
}

4、STM32F4xx IIC控制器
STM32F4xx 有三个IIC控制器,有三条IIC的总线。
IIC控制器是一个IIC的设备,它负责产生IIC的时序以及协议逻辑。
在STM32F4xx中,CPU与I2C是通过系统总线进行通信的。

如果没有I2C的控制器,那么CPU只能通过GPIO口
来模拟,即I2C时序、协议逻辑等都需要软件去模拟。

5、STM32F4xx I2C固件库函数
IIC控制器的SDA和SCL其实也是通过GPIO口复用而来。

1)初始化I2C引脚
	a. 使能GPIO分组时钟
		PB8-->I2C_SCL
		PB9-->I2C_SDA
	b. GPIO初始化配置
		GPIO_Init() ---> AF_MODE(复用模式)
	c. 将GPIO口复用成什么功能
		GPIO_PinAFConfig()

2) 初始化IIC控制器
	a. 使能IIC控制器
		RCC_APB1...
	b. 初始化IIC
		I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct)
		@I2Cx:指定I2C控制器的编号
				I2C1/I2C2/I2C3
		@I2C_InitStruct:指向初始化信息结构体
		
3)配置I2C控制器的一些其它功能
	比如:中断
	I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)

4)开启I2C控制器
	I2C_Cmd(I2C_TypeDef* I2Cx,FunctionalState NewState)
5)I2C总线读写流程
	a. 发送起始信号
		I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)
	b. 获取指定的事件
		I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
	例如:		
		//发送一个起始信号
		I2C_GenerateSTART(I2C1,ENABLE);
		//在发送起始信号之后,应该要等待EV5事件(等待起始信号发生成功/从从模式切换到主模式)发生
		while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
	
	c. 发送一个7bit的从设备地址
		I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
	d. 发送数据
		void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)
	e. 接收数据
		uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx)
	f. 发送停止信号
		I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)
	g. 获取I2C控制器的状态标志
		FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
		@I2C_FLAG:指定状态标志位
			I2C_FLAG_BUSY表示I2C总线是否忙碌
			如果被设置,意味着总线忙碌,不能发送起始信号
	h. 清除I2C控制器的状态标志
		I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)

6、AT24C02
采用IIC进行通信的EEPROM存储器芯片–>AT24C02

EEPROM: 是一个小容量的存储器芯片,一般存储几K的数据,在实际产品中,一般用来存储一些其它模块的ID/MAC/版本号…

a. 器件地址
	24C02 address : 1010 000 (A2 A1 A0在物理上都已经接地)

b. 内存存储结构
	24C02一共有2K bit(256bytes),分为32页,每页8个字节。
	
	每页有自己的页地址(5bit)
	每一个字节都有自己的字节地址(3bit)
	
	所以24C02的存储单元地址为:
	8bit = 5bit page_addr + 3bit word_addr 
	
c.	AT24C02写操作
	AT24C02的读写操作可以分为写操作时序(写一个字节)、页写操作时序(写1页)
	
	1.	写操作时序(写一个字节)
		在中文参考手册第8页
假设需要往AT24C02内部字节地址为0x55处写入一个数据0xAA
	MCU: START 从设备地址(1010 0000)  字节地址(0x55)   数据(0xAA)	STOP
AT24C02:						   ACK	           ACK           ACK			

		
	2.	页写操作时序(写一页-->8bytes)
		AT24C02一页有8个字节,一次写操作最多可以连续写8个字节,在页写时序时,每写入一个字节后,字地址会自动 + 1
	需要注意的是地址仅仅是字地址(低3bit)会+1,页地址不会+1--->不能跨页
		

假设需要往AT24C02内部字节地址为0x00处写入一个数据0x01,0x02....0x08
    MCU: START 从设备地址(1010 0000) 字节地址(0x00)  0x01 0x02 ... STOP
AT24C02:	  ACK			       ACK		      ACK
			
d.	读操作时序
	一般在读取之前,需要先写入一个字地址,表示从设备的哪个存储单元开始读。
	如果读之前,不写字节地址,而直接从芯片内部的地址计数器指定的地址那里开始读。
	
	地址计数器:相当于光标
	
    MCU: START 从设备地址(1010 0000)	  字地址   START 从设备地址(1010 0001)       A     A          NA STOP
AT24C02:						   ACK      ACK     					  A DATA1 DATA2  ....DATAn
/*
	IIC1_Init:IIC1的初始化函数
		IIC1主要用来与MCU内部集成的AT24C02存储器芯片进行通信
		PB8-->I2C1_SCL
		PB9-->I2C1_SDA
*/
void IIC1_Init(void)
{
	//1.初始化I2C引脚
	//1.1 使能GPIO分组时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
		
	//1.2 GPIO初始化配置
	GPIO_InitTypeDef g;
	g.GPIO_Mode		= GPIO_Mode_AF;			//GPIO复用
	/*
		如果配置成推挽模式,当要实现输入检测的时候,就会受到输出电路没有关闭的影响,因为之前的输出电平是一直存在的,造成输入电路和输出电路的短接的情况,所以只能配置成开漏电路,就算当CPU输出1,由于P-MOS管处于关闭状态,IO端口的电平将完全由外部电路决定,因此CPU可以在输入数据寄存器中读取到外部电路的信号而不是自己输出的1。
	*/
	g.GPIO_OType	= GPIO_OType_OD;		//输出开漏
	g.GPIO_Pin		= GPIO_Pin_8 | GPIO_Pin_9;
	g.GPIO_PuPd		= GPIO_PuPd_UP;			//上拉
	g.GPIO_Speed    = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&g);		

	//1.3 将GPIO口复用成什么功能
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);

	//2.初始化IIC控制器
	//2.1 使能IIC控制器
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);

	//2.2 初始化IIC
	I2C_InitTypeDef i;
	i.I2C_Ack				  = I2C_Ack_Enable;				//自动回应ACK
	i.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//IIC控制器地址为7bit
	i.I2C_ClockSpeed		  = 400000;						//通信速率400KhZ
	i.I2C_DutyCycle			  = I2C_DutyCycle_16_9;
	i.I2C_Mode				  = I2C_Mode_I2C;
	i.I2C_OwnAddress1		  = 0x66;	//随便写
	I2C_Init(I2C1,&i);

	//3.启动I2C
	I2C_Cmd(I2C1,ENABLE);
}

/*
	Wait_IIC_Event:用于等待IIC1总线上的某个事件发生(限时等待)
		@I2Cx	: 要等待的是哪个总线上的事件
		@I2C_EVENT:要等待的是哪个事件
		@timeout: 最多等待多久(超时时间,单位us)
		@返回值 : 等待的事件发生返回1,超时没有发生则返回0
*/
static u32 Wait_IIC_Event(I2C_TypeDef *I2Cx,u32 I2C_EVENT,int timeout)
{
	//当事件没有发生或没有超时的时候则继续等待
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) == ERROR && timeout--)
	{
		//每隔1us判断一次事件有没有发生或者有没有超时
		Delay_us(1);
	}

	//timeout为-1的话则意味着超时啦
	return (timeout == -1) ? 0 : 1;
}

/*
	AT24C02_Is_Busy:测试IIC总线是否繁忙以及24C02是否能与MCU正常通信
		@返回值	: 收到24C02的应答则返回0,没有收到ACK则返回1
*/
static u32 AT24C02_Is_Busy(void)
{
	int time = 20;
	while(time--) //测试多次
	{
		//测试IIC总线是否繁忙
		I2C_GenerateSTART(I2C1,ENABLE);	//发送起始位
		if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
		{
			//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
			I2C_GenerateSTOP(I2C1,ENABLE);			//结束此次测试
			continue;
		}

		//意味着起始信号发送成功-->I2C总线不繁忙
		
		//接下来测试24C02是否能与MCU正常通信-->根据24C02有无回应即可
		I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
		if(Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
		{
			//意味着EV6事件(对方应答啦)发生
			I2C_GenerateSTOP(I2C1,ENABLE);			//结束此次测试
			return 0;
		}
	}
	I2C_GenerateSTOP(I2C1,ENABLE);					//发送结束信号
	return 1;
}

/*
	Write_A_Byte_To_24C02:用来往24C02中特定的存储单元中写入一个字节
		@addr	: 要往24C02的哪个存储单元中写入数据
		@data	: 要写入的数据
		@返回值	: 写入成功返回0,写入失败返回-1
	时序:
	MCU:	START 从设备地址(1010 0000)	  字节地址     数据	    STOP
AT24C02:						       ACK		  ACK	    ACK
*/
s32 Write_A_Byte_To_24C02(u8 addr,u8 data)
{	
	//判断IIC总线是否繁忙,繁忙则等,不繁忙则发送数据
	//while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) == SET);

	//实际项目中,我们一般不这么做,因为有可能会陷入死等
	if(AT24C02_Is_Busy())
	{
		printf("IIC Is Busy!!\r\n");
		return -1;
	}
    printf("IIC is ready to write!\r\n");

	//发送起始信号
	I2C_GenerateSTART(I2C1,ENABLE);				//发送起始位
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
	{
		//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
		I2C_GenerateSTOP(I2C1,ENABLE);			
		return -1;
	}

	//发送从设备地址
	I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
	{
		//意味着EV6事件(发送从设备地址之后的应答事件)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//发送字地址
	I2C_SendData(I2C1,addr);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
	{
		//意味着EV8_2事件(数据已经发送完成)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}	

	//发送真正的数据
	I2C_SendData(I2C1,data);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
	{
		//意味着EV8_2事件(数据未发送完成)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//发送停止信号,结束此次数据传输
	I2C_GenerateSTOP(I2C1,ENABLE);			

	return 0;
}

/*
	Read_A_Byte_From_24C02:用来从24C02的addr存储空间中读取一个字节
		@addr	: 指定从24C02的哪个存储空间中读取数据
		@data  	: 读取出来的数据保存到data指向的空间中去
		@返回值 : 读取成功返回0,读取失败返回-1
	时序:
	MCU:	START 从设备地址(1010 0000)	  字地址    START 从设备地址(1010 0001)           NA STOP
AT24C02:						      ACK       ACK     					   A  DATA	
*/
s32 Read_A_Byte_From_24C02(u8 addr,u8 *data)
{
	if(AT24C02_Is_Busy())
	{
		printf("IIC Is Busy!!\r\n");
		return -1;
	}
	printf("IIC is ready to read!\r\n");

	//发送起始信号
	I2C_GenerateSTART(I2C1,ENABLE);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
	{
		//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
		I2C_GenerateSTOP(I2C1,ENABLE);			
		return -1;
	}

	//发送从设备地址
	I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
	{
		//意味着EV6事件(对方应答啦)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//发送字地址
	I2C_SendData(I2C1,addr);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
	{
		//意味着EV8_2事件(数据未发送完成)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}	
	
	//发送起始信号
	I2C_GenerateSTART(I2C1,ENABLE);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
	{
		//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
		I2C_GenerateSTOP(I2C1,ENABLE);			
		return -1;
	}

	//发送从设备地址
	I2C_Send7bitAddress(I2C1,Read24C02_ADDR,I2C_Direction_Receiver);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000))
	{
		//意味着EV7事件(主机可以读取数据啦)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//读取数据
	*data = I2C_ReceiveData(I2C1);

	//发送停止信号
	I2C_GenerateSTOP(I2C1,ENABLE);

	return 0;
}

/*
	Read_Bytes_From_24C02:用于从24C02中连续读取多个字节
		@addr	: 表示从24C02的哪个存储单元开始读
		@data	: 指向的空间用来保存读取出来的数据
		@count	: 表示读取的字节数
		@返回值 : 读取成功返回真正读取到的字节数,读取失败返回-1
*/
s32 Read_Bytes_From_24C02(u8 addr,u8 *data,int count)
{
	if(addr > 0xff)
	{
		return -1;
	}
	if(AT24C02_Is_Busy())
	{
		printf("IIC Is Busy!!\r\n");
		return -1;
	}
	printf("IIC is ready to read!\r\n");

	//发送起始信号
	I2C_GenerateSTART(I2C1,ENABLE);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
	{
		//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
		I2C_GenerateSTOP(I2C1,ENABLE);			
		return -1;
	}

	//发送从设备地址
	I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
	{
		//意味着EV6事件(对方应答啦)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//发送字地址
	I2C_SendData(I2C1,addr);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
	{
		//意味着EV8_2事件(数据未发送完成)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}	
	
	//发送起始信号
	I2C_GenerateSTART(I2C1,ENABLE);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
	{
		//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
		I2C_GenerateSTOP(I2C1,ENABLE);			
		return -1;
	}

	//发送从设备地址
	I2C_Send7bitAddress(I2C1,Read24C02_ADDR,I2C_Direction_Receiver);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,1000))
	{
		//意味着EV6事件(主机切换成主收模式)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//读取数据
	//先读取count-1个字节,因为我们在初始化IIC1的时候指定IIC1控制器会自动回应ACK,
	//所以IIC1在接收完24C02 count-1个字节的时候已经帮我们回复了count-1g个ACK
	//但是在接收最后一个字节(第count个字节)的时候,IIC1不应该继续回复ACK啦
	int i;
	for(i = 0;i < count - 1;i++)
	{
		if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000))
		{
			//意味着EV7事件(主机可以读取数据啦)未发生
			I2C_GenerateSTOP(I2C1,ENABLE);		
			return -1;
		}

		data[i] = I2C_ReceiveData(I2C1);
	}

	//在去接收最后一个24C02发给IIC1的字节的时候让IIC1停止自动回复ACK
	//那么IIC1在接收完最后一个字节后,24C02就会收不到IIC1的回应而选择不继续给IIC1发送数据
	I2C_AcknowledgeConfig(I2C1,DISABLE);

	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED,1000))
	{
		//意味着EV7事件(主机可以读取数据啦)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	data[i++] = I2C_ReceiveData(I2C1);	

	//发送停止信号
	I2C_GenerateSTOP(I2C1,ENABLE);

	//让IIC1恢复自动回复ACK
	I2C_AcknowledgeConfig(I2C1,ENABLE);

	return i;	
}

/*
	Write_Bytes_To_24C02:用于向24C02中连续写入多个字节
		@addr	: 表示从24C02的哪个存储单元开始写
		@data	: 要写入到24C02中的数据
		@count	: 要写入的字节数
		@返回值	: 成功返回写入的字节数,失败返回-1
*/
s32 Write_Bytes_To_24C02(u8 addr,u8 *data,int count)
{
	//用来记录已经写入了多少个字节
	int bytes = 0;

	//判断count是否合法
	count = (count <= (256 - addr)) ? count : (256 - addr);

page_write:
	if(AT24C02_Is_Busy())
	{
		printf("IIC Is Busy!!\r\n");
		return -1;
	}

	//发送起始信号
	I2C_GenerateSTART(I2C1,ENABLE);				//发送起始位
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_MODE_SELECT,1000))
	{
		//意味着等待超时,等待的EV5事件(发送起始信号之后的应答事件)没有发生
		I2C_GenerateSTOP(I2C1,ENABLE);			
		return -1;
	}

	//发送从设备地址
	I2C_Send7bitAddress(I2C1,Write24C02_ADDR,I2C_Direction_Transmitter);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,1000))
	{
		//意味着EV6事件(对方应答啦)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}

	//发送字地址
	I2C_SendData(I2C1,addr);
	if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
	{
		//意味着EV8_2事件(数据未发送完成)未发生
		I2C_GenerateSTOP(I2C1,ENABLE);		
		return -1;
	}	

	//写入的字节数不能超过当前页剩余的字节数
	//计算出当前写入位置addr距离该页的末尾有多少个字节
	//8 - 页内地址 = 当前页剩余的字节数
	int page_bytes = 8 - (addr & 0x07);
	page_bytes = (page_bytes > (count - bytes)) ? (count - bytes) : page_bytes;
    printf("IIC is ready to write %d bytes!\r\n",page_bytes);   
    
	//往24C02中写入数据
	int i;
	for(i = 0;i < page_bytes;i++)
	{
		I2C_SendData(I2C1,data[bytes]);
		if(!Wait_IIC_Event(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED,1000))
		{
			//意味着EV8_2事件(数据未发送完成)未发生
			I2C_GenerateSTOP(I2C1,ENABLE);		
			return -1;
		}

		bytes++;
	}

	
	//发送停止信号,结束此次数据传输
	I2C_GenerateSTOP(I2C1,ENABLE);	

	if(bytes < count)
	{
		//意味着还没有写完,得从下一页继续开始写
		//addr += page_bytes;
		//页地址加1
		addr = ((addr >> 3) + 1) << 3;

		goto page_write;
	}

	return bytes;
}

posted on 2022-11-21 19:23  皮皮祥  阅读(528)  评论(0编辑  收藏  举报