【STM32Cube HAL】IIC(九)

 实验内容:分别采用软件/硬件IIC读写EEPROM(AT24C02)。

一、原理图

—— —— —— —— —— —— —— —— 软件IIC—— —— —— —— —— —— —— ——

二、 CubeMX配置

Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。

 Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。

  

Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)

 

  Step4.串口配置(主要为了在串口调试助手显示读写数据),因为没有用到中断和DMA所以我们就不过多讲解。

Step5.配置软件IIC所需要的的GPIO口,需要注意的是这边要设置成开漏输出模式,上拉模式。这里选用PB6,PB7作为SCL,SDA,因为是软件IIC,所以这里的选择接口是不受限制的。

 到这里关于软件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

 三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)

//重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
	/* 发送一个字节数据到串口DEBUG_USART */
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);	
	
	return (ch);
}

//重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{		
	int ch;
	HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);	
	return (ch);
}

2、为了把IIC和EEPROM的相关接口区分开,方便以后移植到其他IIC设备。在cubemx自动生成的gpio.c/gpio.h IIC的相关接口,同时新建了i2c_ee.c/i2c_ee.c中写读写EEPROM的接口。

(1)IIC相关接口的书写。

首先是在gpio.h中,对SCL/SDA相关IO口的写0/1和读取进行宏定义,提高代码可读性。以及等会涉及到读写位进行宏定义。最后就是函数的声明。


#define I2C_SCL_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
#define I2C_SCL_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);

#define I2C_SDA_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
#define I2C_SDA_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);

#define I2C_SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)
#define I2C_WR 0 //写控制bit
#define I2C_RD 1 //读控制bit

void MX_GPIO_Init(void);

void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t HS_Byte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);

然后就是在gpio.c中,把我们刚才声明的函数进行完善。

/*延时函数*/
static void i2c_Delay(void)
{
	uint8_t i;

	/* 
	 	下面的时间是通过逻辑分析仪测试得到的。
    工作条件:CPU主频72MHz ,MDK编译环境,1级优化
  
		循环次数为10时,SCL频率 = 205KHz 
		循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us 
	 	循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us 
	*/
	for (i = 0; i < 10; i++);
}
/* CPU发起I2C总线启动信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换*/
void i2c_Start(void)
{
	I2C_SCL_1();
	I2C_SDA_1();
	i2c_Delay();
	I2C_SDA_0();
	i2c_Delay();
	I2C_SCL_0();
}
/* CPU发起I2C总线停止信号:当 SCL 是高电平时 SDA 线由低电平向高电平切换*/
void i2c_Stop(void)
{
	I2C_SCL_1();
	I2C_SDA_0();
	i2c_Delay();
	I2C_SDA_1();
	i2c_Delay();
	I2C_SCL_0();
}
/*CPU向I2C总线设备发送8bit数据*/
void i2c_SendByte(uint8_t HS_Byte)
{
	uint8_t i;
	for(i=0;i<8;i++)
	{
		
		if(HS_Byte&0X80)//先发送高位
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		i2c_Delay();
		I2C_SCL_1();
		i2c_Delay();
		I2C_SCL_0();
		if(i==7)//发送完成一个字节,释放数据总线,接收应答/非应答信号
		{
			I2C_SDA_1();
		}
		HS_Byte<<=1;//左移一位
		i2c_Delay();
	}
}
/*CPU从I2C总线设备读取8bit数据*/
uint8_t i2c_ReadByte(void)
{
	uint8_t SH_Byte, i;
	
	SH_Byte=0;
	for(i=0;i<8;i++)//先读取高位
	{
		SH_Byte <<= 1;
		I2C_SCL_1();
		i2c_Delay();
		if(I2C_SDA_READ())
		{
			SH_Byte++;
		}
		I2C_SCL_0();
	  i2c_Delay();
	}
	return SH_Byte;
}
/*CPU产生一个时钟,并读取器件的ACK应答信号*/
uint8_t i2c_WaitAck(void)
{
	uint8_t re;

	I2C_SDA_1();	// CPU释放SDA总线 
	i2c_Delay();
	I2C_SCL_1();	//CPU驱动SCL = 1, 此时器件会返回ACK应答 
	i2c_Delay();
	if (I2C_SDA_READ())	// CPU读取SDA口线状态 
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
	I2C_SCL_0();
	i2c_Delay();
	return re;
}
/*CPU产生1个ACK信号*/
void i2c_Ack(void)
{
	I2C_SDA_0();	// CPU驱动SDA = 0 ,应答信号
	i2c_Delay();
	I2C_SCL_1();	// CPU产生1个时钟 
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
	I2C_SDA_1();	// CPU释放SDA总线
}
/*CPU产生1个NACK信号*/
void i2c_NAck(void)
{
	I2C_SDA_1();	// CPU驱动SDA = 1 ,非应答信号
	i2c_Delay();
	I2C_SCL_1();	// CPU产生1个时钟 
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
}
/*检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在*/
uint8_t i2c_CheckDevice(uint8_t Addr)
{
	uint8_t ucAck;

	i2c_Start();		//发送启动信号

	/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
	i2c_SendByte(Addr|I2C_WR);
	ucAck = i2c_WaitAck();	// 检测设备的ACK应答

	i2c_Stop();			// 发送停止信号 

	return ucAck;
}

(2)使用刚才写好的IIC的接口编写对EEPROM的读写、擦除等程序。

还是一样在i2c_ee.h中,进行相关代码宏定义,提高代码可读性。还有就是函数的声明。

 * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
 * 32 pages of 8 bytes each
 *
 * Device Address
 * 1 0 1 0 A2 A1 A0 R/W
 * 1 0 1 0 0  0  0  0 = 0XA0
 * 1 0 1 0 0  0  0  1 = 0XA1 
 */

/* AT24C01/02每页有8个字节 
 * AT24C04/08A/16A每页有16个字节 
 */
	

#define EEPROM_DEV_ADDR			0xA0		// 24xx02的设备地址 
#define EEPROM_PAGE_SIZE		  8			  // 24xx02的页面大小 
#define EEPROM_SIZE				  256			  // 24xx02总容量 


uint8_t ee_CheckOk(void);
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);
void ee_Erase(void);
uint8_t ee_Test(void);

 在i2c_ee.c完善相关函数

/* 判断串行EERPOM是否正常 */
uint8_t ee_CheckOk(void)
{
	if(i2c_CheckDevice(EEPROM_DEV_ADDR))
	{
		i2c_Stop();//失败后,切记发送I2C总线停止信号
		return 0;//设备异常
	}
	else
	{
		return 1;//设备正常
	}
}

/*从串行EEPROM指定地址处开始读取若干数据*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i;
	i2c_Start();//第1步:启动I2C总线
	
	i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发送地址+写指令
	if(i2c_WaitAck()!=0)//第3步:判断设备是否响应
	{
		goto res_error;
	}

	i2c_SendByte(_usAddress);//第4步:发送字节地址
	if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
	{
		goto res_error;
	}
	
	i2c_Start();//第6步:重启I2C总线,开始读数据
	
	i2c_SendByte(EEPROM_DEV_ADDR|I2C_RD);//第7步:发送地址+读指令
	if(i2c_WaitAck()!=0)//第8步:判断设备是否响应
	{
		goto res_error;
	}
	
	for(i=0;i<_usSize;i++)
	{
		_pReadBuf[i]=i2c_ReadByte();//第9步:开始读取数据
		if(i!=_usSize-1)//第10步:在接收_usSize-1个字节前都发送应答信号,最后一个字节发送非应答信号
		{
			i2c_Ack();
		}
		else
		{
			i2c_NAck();
		}
	}
	
	i2c_Stop();//第11步:停止I2C总线
	return 1;//读取成功

	res_error:
			printf("\r\n响应超时\r\n");
			return 0;//读取失败
}

/*向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i,m,Addr;
	Addr=_usAddress;

	for(i=0;i<_usSize;i++)
	{
		
		if((i==0)||(Addr&(EEPROM_PAGE_SIZE - 1))==0)//当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址
		{
			i2c_Stop();//发停止信号,启动内部写操作
			for(m=0;m<1000;m++)//通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms,CLK频率为200KHz时,查询次数为30次左右
			{
				i2c_Start();//第1步:发起I2C总线启动信号
				
				i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 
				
				if(i2c_WaitAck()==0)//第3步:判断设备是否响应
				{
					break;
				}
			}
			if(m==1000)//响应超时
			{
				goto res_error;
			}
			
			i2c_SendByte(Addr);// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 
			if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
			{
				goto res_error;
			}
		}
		
		i2c_SendByte(_pWriteBuf[i]);//第6步:开始写入数据
		if(i2c_WaitAck()!=0)//第7步:判断设备是否响应
	  {
		  goto res_error;
	  }
		Addr++;
	}
	
	i2c_Stop();//第8步:停止I2C总线
	return 1;//写入成功
	
	res_error:
			printf("\r\n响应超时\r\n");
			return 0;//写入失败
}

/* 擦除EEPROM */
void ee_Erase(void)
{
	uint16_t i;
	uint8_t buf[EEPROM_SIZE];
	
	for (i = 0; i < EEPROM_SIZE; i++)// 填充缓冲区
	{
		buf[i] = 0xFF;
	}
	
	if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)//写EEPROM, 起始地址 = 0,数据长度为 256
	{
		printf("擦除eeprom出错!\r\n");
	}
	else
	{
		printf("擦除eeprom成功!\r\n");
	}
}

/* 测试程序 */
uint8_t ee_Test(void) 
{
	uint16_t i;
	uint8_t write_buf[EEPROM_SIZE];
  uint8_t read_buf[EEPROM_SIZE];
  
  if (ee_CheckOk() == 0)// 没有检测到EEPROM 
	{
		printf("没有检测到串行EEPROM!\r\n");		
		return 0;
	}
    
	for (i = 0; i < EEPROM_SIZE; i++)//填充测试缓冲区
	{		
		write_buf[i] = i;
	}
	
  if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)//向EEPROM写入数据
	{
		printf("写eeprom出错!\r\n");
		return 0;
	}
	else
	{		
		printf("写eeprom成功!\r\n");
	}
  
  HAL_Delay(1);//写完之后需要适当的延时再去读,不然会出错

  if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)//从EEPROM读取数据
	{
		printf("读eeprom出错!\r\n");
		return 0;
	}
	else
	{		
		printf("读eeprom成功,数据如下:\r\n");
	}

  for (i = 0; i < EEPROM_SIZE; i++)//比较读取和写入的数据是否一致
	{
		if(read_buf[i] != write_buf[i])
		{
			printf("0x%02X ", read_buf[i]);
			printf("错误:EEPROM读出与写入的数据不一致");
			return 0;
		}
    printf(" %02X", read_buf[i]);
		
		if ((i & 15) == 15)
		{
			printf("\r\n");	
		}		
	}
  printf("eeprom读写测试成功\r\n");
  return 1;
}

3、在主函数中调用i2c_ee.c中的测试函数即可。

ee_Test(); 

关于软件IIC有几点需要注意的:

1、我们传输速率的设置:硬件IIC,标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。这里使用软件IIC的设置SCL频率200kbit/s左右,大家可以根据需要设置gpio.c中i2c_Delay(void)的延时时间来修改SCL频率。

2、在i2c_ee.c 写ee_ReadBytes、ee_WriteBytes,可以发现步骤有一点区别,ee_WriteBytes在多了一个for循环来重复判断设备内的写操作是否完成,而不是直接采用判断一次。虽然后者的写法在实验结果上也没有出错,但是为了代码严谨性,避免不必要的错误,选用前者。

3、在写入数据到EEPROM的时候,根据数据的大小,如果需要读写的话,需要进行一定的延时,否则会错。

—— —— —— —— —— —— —— ——硬件IIC—— —— —— —— —— —— —— ——

二、 CubeMX配置

step1-step4与上面软件IIC模式一致

step5.IIC外设配置,这里选用的I2C1,因为使用的主机模式,所以从机参数默认即可。

到这里关于硬件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上: 软件IIC)

2、在i2c.h文件中添加相关的宏定义以及函数声明。

#define EEPROM_ADDRESS			0xA0		// 24xx02的设备地址 
#define EEPROM_PAGESIZE		  8			  // 24xx02的页面大小 
#define  DATA_Size			7//256   
#define  EEP_Firstpage  0x02   // 0x00		

void MX_I2C1_Init(void);
uint8_t I2C_Test(void);
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite);
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead);

 3、在i2c.c实现EEPROM的读写函数,以及用于测试的测试函数。

/*将缓冲区中的数据写到I2C EEPROM中*/
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

  Addr = WriteAddr % EEPROM_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐
  count = EEPROM_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页
  NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)
  NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)
 
  if(Addr == 0) //判断写入的首地址是否与页地址对齐
  {
    if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页
    {
      I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不满一页,直接写入数据
    }
    else  //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式)
    {
      while(NumOfPage--)//循环写入数据:先写入完整页
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE); 
        WriteAddr +=  EEPROM_PAGESIZE;
        pBuffer += EEPROM_PAGESIZE;
      }

      if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
  else 
  {
    if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
    {
			if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
			{
				I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
			}
			else
			{
				I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
				I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
			}
    }
    
    else
    {
			/*如果数据满一页,对数据进行分离*/
      NumByteToWrite -= count;//扣除第一页数据个数
      NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页
      NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;	//计算写完完整页剩下的数据个数
      
      if(count != 0)
      {  
        I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写入首页数据
        WriteAddr += count;//写地址自增
        pBuffer += count;//缓冲区指针自增
      } 
      
      while(NumOfPage--)//依次写入完整页的数据
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
        WriteAddr +=  EEPROM_PAGESIZE;//写地址自增
        pBuffer += EEPROM_PAGESIZE; //缓冲区指针自增 
      }
      if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后一页的数据
      }
    }
  }  
}

/*写一个字节到I2C EEPROM中*/
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
{
	HAL_StatusTypeDef status = HAL_OK;

	status = HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100); 

	/* 检测IIC外设是否传输完成,并准备进行下一次通信 */
	if(status != HAL_OK)
	{
	/* Execute user timeout callback */
	}
	while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
	{
		
	}
	/* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/
	while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);

//	while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
//	{
//		
//	}
	return status;
}

/*在EEPROM的一个写循环中可以写多个字节,但一次写入
的字节数不能超过EEPROM页的大小,AT24C02每页有8个字节*/
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
{
	HAL_StatusTypeDef status = HAL_OK;
	status=HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);
	/* 检测IIC外设是否传输完成,并准备进行下一次通信 */
	while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
	{
		
	}
	/* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/
	while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);

//	while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
//	{
//		
//	}
	return status;
}

/*从EEPROM里面读取一块数据 */
uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)
{
	HAL_StatusTypeDef status = HAL_OK;
	
	status=HAL_I2C_Mem_Read(&hi2c1,EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);

	return status;
}

uint8_t I2C_Test(void)
{
	uint16_t i;

	printf("写入的数据");

	for ( i=0; i<DATA_Size; i++ ) //填充缓冲
	{   
		I2c_Buf_Write[i] =i;
		printf("0x%02X ", I2c_Buf_Write[i]);
		if(i%16 == 15)    
		printf("\n\r");    
	}

	//将I2c_Buf_Write中顺序递增的数据写入EERPOM中 
	I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, DATA_Size);

	printf("读出的数据");
	//将EEPROM读出数据顺序保持到I2c_Buf_Read中
	I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, DATA_Size); 
	//将I2c_Buf_Read中的数据通过串口打印
	for (i=0; i<DATA_Size; i++)
	{	
		if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
		{
			printf("0x%02X ", I2c_Buf_Read[i]);
			printf("错误:I2C EEPROM写入与读出的数据不一致");
			return 0;
		}
		printf("0x%02X ", I2c_Buf_Read[i]);
		if(i%16 == 15)    
		printf("\n\r");

	}
	printf("I2C(AT24C02)读写测试成功");
	return 1;
}

4、在主函数中添加测试代码验证功能是否正常。

I2C_Test();

以上程序主要参考野火的硬件IIC程序,但是在页写入程序中添加了一个判断,用于判断在地址不对齐,且数据不满一页的情况下,数据是否跨页。数据不跨页直接写入数据,跨页分两次写入数据。代码如下:

if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
    {
			if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
			{
				I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
			}
			else
			{
				I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
				I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
			}
    }

同时需要注意的EEPROM的几个关键特性:(AT24C02)

在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。

还有需要有一个页的概念,页写入的时候,写入的数据有跨页现象,需要进行数据分离处理,否则数据会重复覆盖当前页(发送的内存地址所在页),而不会到下一页,这也是我们代码中进行页写入的时候提前处理数据的原因。

写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms。

这里总结软件IIC和硬件IIC的优缺点:

1、软件IIC可灵活选用引脚;硬件IIC引脚固定。                                                                         

2、软件IIC需要通过代码模拟时序以及读写;硬件IIC只需调用库函数。                                     

3、硬件IIC效率更高,可以使用DMA ,中断。                                                                           

4、软件IIC代码相对较多,但是流程更清晰;硬件IIC由于使用很多封装函数,处理的过程不够直观,不易理解。                                                                                                                                 

猜你喜欢

转载自blog.csdn.net/qq_29031103/article/details/119894112