STM32F10x_模拟I2C读写EEPROM(2)(切换SDA方向 + 读ACK位 + 完整代码)

文章目录

  • 前言
  • 一、宏定义
  • 二、I2C延时函数
    • 1. 注意
  • 三、起始 / 停止信号
    • 1. 时序图
    • 2. 起始信号
    • 3. 停止信号
  • 四、切换SDA方向
    • 1. SDA配置为输入模式
    • 2. SDA配置为输出模式
  • 五、应答位信息
    • 1. 主机(MCU)读取 应答位
    • 2. 主机(MCU)产生 应答位
  • 六、I2C读 / 写 一个字节
    • 1. 注意
    • 2. I2C写一个字节
    • 3. I2C读一个字节
  • 七、E2 页写 / 连续读
    • 1. E2 页写
    • 2. E2 连续读
  • 八、小结


前言

  • 关于此文一些名词术语不太理解的,可以去看我这篇博文
    《STM32F10x_模拟I2C读写EEPROM(1)》

  • 读写E2函数(带备份区+校验和判断),可以去看我这篇博文
    《STM32F10x_模拟I2C读写EEPROM(3)(读写E2备份区 + 校验位 + 完整代码 + 应用实例)》

  • E2的中文资料可以到我博客资源里下载,没有积分下载的,可以评论Ding我o( ̄▽ ̄)ブ

一、宏定义

	
   // I2C引脚
   #define PORT_I2C_SCL              GPIOx
   #define PORT_I2C_SDA              GPIOx
   
   #define PIN_I2C_SCL               GPIO_Pin_x
   #define PIN_I2C_SDA               GPIO_Pin_x
   
   // 控制 SDA / SCL 高低电平
   #define I2C_SCL_LOW               (PORT_I2C_SCL->BRR  = PIN_I2C_SCL)
   #define I2C_SCL_HIGH              (PORT_I2C_SCL->BSRR = PIN_I2C_SCL)
   #define I2C_SDA_LOW               (PORT_I2C_SDA->BRR  = PIN_I2C_SDA)
   #define I2C_SDA_HIGH              (PORT_I2C_SDA->BSRR = PIN_I2C_SDA)
   
   // 读 SDA 电平状态
   #define I2C_SDA_READ              (PORT_I2C_SDA->IDR & PIN_I2C_SDA)
   
   // 应答位信息
   #define I2C_ACK                   0                        //应答
   #define I2C_NOACK                 1                        //非应答

   /*
   	1、"地址长度"根据芯片型号不同略有不同
   		 8位: AT24C01、AT24C02
   		16位: AT24C04、AT24C08、AT24C16、AT24C32、AT24C64、AT24C128、AT24C256、AT24C512
   	
   	2、"页长度"根据芯片型号不同略有不同
   		  8字节: AT24C01、AT24C02
   		 16字节: AT24C04、AT24C08、AT24C16
   		 32字节: AT24C32、AT24C64
   		 64字节: AT24C128、AT24C256
   		128字节: AT24C512
   */
   
   	// 此文的E2型号 - AT24C512
   	#define EEPROM_WORD_ADDR_SIZE   16		//地址长度
   	#define EEPROM_PAGE_SIZE        128		//页长度

   	#define EEPROM_DEV_ADDR			0xA0	//地址(设备地址:与A2、A1、A0有关)

   	#define EEPROM_WR				0x00	//写
   	#define EEPROM_RD				0x01	//读
   

二、I2C延时函数

  • 1. 注意

    • 此函数实现的是非标准延时,请根据MCU速度 调节大小
      
      /************************************************
      函数名称  : I2C_Delay
      功    能 : I2C延时(非标准延时,请根据MCU速度 调节大小)
      参    数 : 无
      返 回 值 : 无
      *************************************************/
      static void I2C_Delay(void)
      {
      	uint16_t cnt = 100;
      	while(cnt--);
      }
      			   	
      

三、起始 / 停止信号

  • 1. 时序图

    Alt

  • 2. 起始信号

    • 时钟线 SCL 保持高电平期间 数据线 SDA 电平从高到低的跳变作为I2C总线的起始信号
      
      /************************************************
      函数名称  : I2C_Start
      功    能 : I2C开始
      参    数 : 无
      返 回 值 : 无
      *************************************************/
      void I2C_Start(void)
      {
      	I2C_SCL_HIGH;				//SCL高
      	I2C_Delay();
      	
      	I2C_SDA_HIGH;				//SDA高
      	I2C_Delay();
      	I2C_SDA_LOW;				//SDA低
      	I2C_Delay();
      	
      	I2C_SCL_LOW;				//SCL低
      	I2C_Delay();
      }
         	
      

  • 3. 停止信号

    • 时钟线 SCL 保持高电平期间 数据线SDA 电平从低到高的跳变作为I2C总线的停止信号
      
      /************************************************
      函数名称 : I2C_Stop
      功    能 : I2C停止
      参    数 : 无
      返 回 值 : 无
      *************************************************/
      void I2C_Stop(void)
      {
      	I2C_SDA_LOW;			//SDA低
      	I2C_Delay();
      	
      	I2C_SCL_HIGH;			//SCL高
      	I2C_Delay();
      	
      	I2C_SDA_HIGH;			//SDA高
      	I2C_Delay();
      }
      	   	
      

四、切换SDA方向

  • 1. SDA配置为输入模式

    
    /************************************************
    函数名称 : I2C_SDA_SetInput
    功    能 : I2C_SDA设置为输入
    参    数 : 无
    返 回 值 : 无
    *************************************************/
    void I2C_SDA_SetInput(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Pin   = PIN_I2C_SDA;
    	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;	// I2C_SDA设置为 浮空输入
    	GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
    }
    	   	
    

  • 2. SDA配置为输出模式

    
    /************************************************
    函数名称  : I2C_SDA_SetOutput
    功    能 : I2C_SDA设置为输出
    参    数 : 无
    返 回 值 : 无
    *************************************************/
    void I2C_SDA_SetOutput(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Pin   = PIN_I2C_SDA;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;		// I2C_SDA设置为 开漏输出
    	GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
    }
    	   	
    

五、应答位信息

  • 1. 主机(MCU)读取 应答位

    • 主机(MCU)数据的时候,从机(E2)产生应答,主机读取应答位进行检测;
      STM32F10x_模拟I2C读写EEPROM(2)(切换SDA方向 + 读ACK位 + 完整代码)_第1张图片
      /************************************************
      函数名称  : I2C_GetAck
      功    能 : I2C主机读取应答(或非应答)位
      参    数 : 无
      返 回 值 : I2C_ACK ----- 应答
                 I2C_NOACK --- 非应答
      *************************************************/
      uint8_t I2C_GetAck(void)
      {
      	uint8_t ack;
      	
      	I2C_SCL_LOW;			//SCL低(此时从机可变化SDA电平产生应答位信息)
      	I2C_Delay();
      	
      	I2C_SDA_SetInput();		//SDA配置为输入模式(切换SDA方向)
      	
      	I2C_SCL_HIGH;			//SCL高(SCL上升沿时,从机发应答位信息到SDA线上)
      	I2C_Delay();
      	
      	if(I2C_SDA_READ)		//读取从机应答位信息
      	 ack = I2C_NOACK;		//非应答
      	else
      	 ack = I2C_ACK;			//应答
      	
      	I2C_SCL_LOW;			//SCL低(防止误操作起始 / 结束信号)
      	I2C_Delay();
      	
      	I2C_SDA_SetOutput();	//SDA配置为输出模式(切换SDA方向)
      	
      	return ack;				//返回应答位
      }
      
      

  • 2. 主机(MCU)产生 应答位

    • 主机(MCU)数据的时候,主机产生应答,从机(E2)读取应答位进行检测;
      STM32F10x_模拟I2C读写EEPROM(2)(切换SDA方向 + 读ACK位 + 完整代码)_第2张图片

      /************************************************
      函数名称  : I2C_PutAck
      功    能 : I2C主机产生应答(或非应答)位
      参    数 : I2C_ACK ----- 应答
                 I2C_NOACK --- 非应答
      返 回 值 : 无
      *************************************************/
      void I2C_PutAck(uint8_t Ack)
      {
      	I2C_SCL_LOW;			//SCL低(此时主机可变化SDA电平产生应答位信息)
      	I2C_Delay();
      	
      	if(I2C_ACK == Ack)
      		I2C_SDA_LOW;		//主机产生 → 应答
      	else
      		I2C_SDA_HIGH;		//主机产生 → 非应答
      	I2C_Delay();
      	
      	I2C_SCL_HIGH;			//SCL高 (SCL上升沿时,主机发应答位信息到SDA线上)
      	I2C_Delay();
      	I2C_SCL_LOW;			//SCL低(防止误操作起始 / 结束信号)
      	I2C_Delay();
      }
      
      

六、I2C读 / 写 一个字节

  • 1. 注意

    • I2C读 / 写一字节不是 E2 读 / 写一字节(需要区分开来)。
  • 2. I2C写一个字节

    STM32F10x_模拟I2C读写EEPROM(2)(切换SDA方向 + 读ACK位 + 完整代码)_第3张图片

    • 主机每完一个字节后,读取从机返回的应答位:

      • 应答位若为0,表示从机应答,能继续下一步操作;
      • 应答位若为1,表示从机非应答,不能进行下一步操作。
      /************************************************
      函数名称  : I2C_WriteByte
      功    能 : I2C写一字节
      参    数 : Data -------- 数据
      返 回 值 : I2C_ACK ----- 应答
       		   I2C_NOACK --- 非应答
      *************************************************/
      uint8_t I2C_WriteByte(uint8_t Data)
      {
         	uint8_t cnt;
         
         // 发送一个字节(8位)
         	for(cnt = 0; cnt < 8; cnt++)
         	{
         		I2C_SCL_LOW;			//SCL低(SCL为低电平时,主机变化SDA有效,产生SDA数据)
         		I2C_Delay();
         		
         		if(Data & 0x80)
         			I2C_SDA_HIGH;		//SDA高
         		else
         			I2C_SDA_LOW;		//SDA低
         		Data <<= 1;
         		I2C_Delay();
         		
         		I2C_SCL_HIGH;			//SCL高(SCL上升沿,主机发送数据出去)
         		I2C_Delay();
         	}
         	I2C_SCL_LOW;				//SCL低(防止误操作起始 / 结束信号)
         	I2C_Delay();
         	
         	return I2C_GetAck();		//主机读取应答位
      }	
      
  • 3. I2C读一个字节

    STM32F10x_模拟I2C读写EEPROM(2)(切换SDA方向 + 读ACK位 + 完整代码)_第4张图片
    • 主机每到一个字节(8位)后,产生应答位给从机检测:
      • 应答位若为0,表示主机应答,主机不继续读取数据了;
      • 应答位若为1,表示主机非应答,主机可继续读取数据。
      /************************************************
      函数名称  : I2C_ReadByte
      功    能 : I2C读一字节
      参    数 : ack --------- 产生应答(或者非应答)位
      返 回 值 : data -------- 读取的一字节数据
      *************************************************/
      uint8_t I2C_ReadByte(uint8_t ack)
      {
      	uint8_t cnt;
      	uint8_t data;
      	
      	I2C_SCL_LOW;					//SCL低(此时从机可变化SDA电平产生数据)
      	I2C_Delay();
      	
      	I2C_SDA_SetInput();				//SDA配置为输入模式(切换SDA方向)
      	
      	for(cnt = 0; cnt < 8; cnt++)
      	{
      		I2C_SCL_HIGH;				//SCL高(SCL上升沿时,从机发数据到SDA线上)
      		I2C_Delay();
      		
      		data <<= 1;
      		if(I2C_SDA_READ)			//SDA为高(数据有效)
      			data |= 0x01; 
      		
      		I2C_SCL_LOW;				//SCL低(防止误操作起始 / 结束信号)
      		I2C_Delay();
      	}
      	I2C_SDA_SetOutput();			//SDA配置为输出模式(切换SDA方向)
      	
      	I2C_PutAck(ack);				//产生应答(或者非应答)位
      	
      	return data;					//返回数据
      }	
      

七、E2 页写 / 连续读

  • 1. E2 页写

    STM32F10x_模拟I2C读写EEPROM(2)(切换SDA方向 + 读ACK位 + 完整代码)_第5张图片

    • 一次页写操作写入的数据字节数最大值为E2的页大小

    • 不同型号的E2对应的页大小可能不同(下面简称页大小的值为 P ,举例:AT24C512 → P = 128);

    • E2 页写时序说明:

      • 举例说明:MCU写nn <= P)个字节数据进E2;
      • ① 主器件发送起始信号和从器件地址信息( R/W 位置零 )给从器件,主器件检测返回应答位信息;
      • ② 主器件发送起始字节地址,主器件检测返回应答位信息;
      • ③ 非发送最后一个字节时,主器件每发送完一个字节后,不产生停止信号,主器件检测应答位信息。循环此步骤③ n - 1次,按顺序发送数据;
      • ④ 主器件发送最后一个字节后,主器件检测应答位信息,然后主器件产生停止信号;
      • ⑤ 从器件接收到停止信号后,从器件开始内部数据的擦写,在擦写过程中,从器件不再应答主器件的任何请求。
    • 注意:如果进行页写操作时,n > p,地址计数器将自动翻转,先前写入的数据会被覆盖,所以要注意每次页写的字节数不能大于E2的页大小P,否则会影响数据的正确性。

      /************************************************
      函数名称  : EEPROM_WritePage
      功    能 : EEPROM写页
      参    数 : Addr ------ 地址
                 pData ----- 数据
                 Length -----长度(<=EEPROM_PAGE_SIZE)
      返 回 值 : I2C_ACK ----应答
                 I2C_NOACK - 非应答
      *************************************************/
      uint8_t EEPROM_WritePage(uint16_t Addr, uint8_t *Data, uint16_t Length)
      {
      	uint8_t  ack;
      	uint8_t  i;
      	
      	/* 1.开始信号 */
      	I2C_Start();
      	
      	/* 2.设备地址/写 */
      	ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR);
      	if(I2C_NOACK == ack)
      	{
      		I2C_Stop();
      		return I2C_NOACK;
      	}
      	
      	/* 3.数据地址 */
      	#if(8 == EEPROM_WORD_ADDR_SIZE)
      		ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));   //数据地址(8位)			
      	if(I2C_NOACK == ack)
      	{
      		I2C_Stop();
      		return I2C_NOACK;
      	}
      	
      	#else
      		ack = I2C_WriteByte((uint8_t)(Addr>>8));       //数据地址(16位)
      	if(I2C_NOACK == ack)
      	{
      		I2C_Stop();
      		return I2C_NOACK;
      	}
      	
      	ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));	
      	if(I2C_NOACK == ack)
      	{
      		I2C_Stop();
      		return I2C_NOACK;
      	}
      	#endif
      	
      	/* 4.写一字节数据(循环) */
      	for(i = 0; i < Length; i++)
      	{
      		ack = I2C_WriteByte(*(Data + i));
      		if(I2C_NOACK == ack)
      		{
      			I2C_Stop();
      			return I2C_NOACK;
      		}
      	}
      	
      	/* 5.停止信号 */
      	I2C_Stop();
      	
      	return I2C_ACK;
      }
      
      
  • 2. E2 连续读

    • 从E2读取数据是一个复合的I2C时序,它实际上包含一个写过程和一个读过程
    • E2 连续读时序说明:
      • 举例说明:MCU连续读E2里的nn <= P)个字节数据;
      • ① 主器件发送起始信号和从器件地址信息( R/W 位置1 )给从器件,主器件检测返回应答位信息;
      • ② 主器件发送数据起始字节地址,主器件检测返回应答位信息;
      • ③ 主器件再次发送起始信号和从器件地址信息( R/W 位置0 )给从器件,主器件检测返回应答位信息;
      • ③ E2会向主机返回从"数据起始字节地址"开始的数据,一个字节一个字节地传输,主器件每接收完一个字节后,不产生停止信号,主器件响应“应答信号ACK”给E2。只要主器件的响应为"应答信号ACK",E2就会一直传输下去,循环此步骤③ n - 1次;
      • ④ 主器件接收最后一个字节后,主器件响应“非应答信号NOACK”给E2,然后主器件产生停止信号;
      • ⑤ 从器件接收到停止信号后,从器件停止传输数据,E2 连续读结束。
      /************************************************
      函数名称  :  EEPROM_ReadSequential
      功    能 :  EEPROM读一字节
      参    数 :  Addr ------ 地址
                  Data ------ 数据
                  Length -----长度
      返 回 值 :  I2C_ACK --- 应答
                  I2C_NOACK - 非应答
      *************************************************/
      uint8_t EEPROM_ReadSequential(uint16_t Addr, uint8_t *Data, uint16_t Length)
      {
      	uint8_t  ack;
      	uint8_t  i;
      	
      	 /* 1.开始信号 */
      	 I2C_Start();
      	
      	 /* 2.设备地址/写 */
      	 ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR);
      	 if(I2C_NOACK == ack)
      	 {
      	   I2C_Stop();
      	   return I2C_NOACK;
      	 }
      	
      	 /* 3.数据地址 */
      	#if (8 == EEPROM_WORD_ADDR_SIZE)
      	 ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));   //数据地址(8位)
      	 if(I2C_NOACK == ack)
      	 {
      	   I2C_Stop();
      	   return I2C_NOACK;
      	 }
      	 
      	#else
      	 ack = I2C_WriteByte((uint8_t)(Addr>>8));       //数据地址(16位)
      	 if(I2C_NOACK == ack)
      	 {
      	   I2C_Stop();
      	   return I2C_NOACK;
      	 }
      	 
      	 ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));
      	 if(I2C_NOACK == ack)
      	 {
      	   I2C_Stop();
      	   return I2C_NOACK;
      	 }
      	#endif
      	
      	 /* 4.重新开始 */
      	 I2C_Start();
      	
      	 /* 5.设备地址/读 */
      	 ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_RD);
      	 if(I2C_NOACK == ack)
      	 {
      	   I2C_Stop();
      	   return I2C_NOACK;
      	 }
      	
      	 /* 6.读一字节数据 */
      	for(i = 0; i < Length - 1; i++)
      	{
      		*(Data + i) = I2C_ReadByte(I2C_ACK); 	//只读取1字节(产生应答)
      	}
      	*(Data + i) = I2C_ReadByte(I2C_NOACK);		//只读取1字节(产生非应答)
      	
      	 /* 7.停止信号 */
      	 I2C_Stop();
      	
      	 return I2C_ACK;
      }
      
      

八、小结

  • 写的着急,欢迎纠正
  • ☆⌒(*^-゜)v THX!!
  • 码字不易,记得点小心心 ( •̀ ω •́ )✧

你可能感兴趣的:(#,模拟I2C读写EEPROM,-,笔记,stm32,单片机,eeprom,I2C)