I2C软件模拟EEPROM通讯实验

I2C是一种串行通讯总线,由于只有串行数据线SDA和串行时钟线SCL两个总线而被广泛使用。

I2C软件模拟通信的本质是用芯片上任意两个引脚模拟I2C通信,也就是说通过控制任意两个引脚电平的高低变化来模拟I2C时序。写代码之前,需要非常熟悉I2C时序图。

准备基础知识:

如下I2C时序图,I2C总线在空闲状态下SCL和SDA都保持高电平。

I2C总线在传送数据过程中共有三种类型信号:开始信号、结束信号和应答信号。

开始信号:SCL为高电平时,SDA 由高向低时,开始传送数据。

结束信号:SCL为高电平时,SDA 由低向高时,结束传送数据。

应答信号:在一个字节传输的8个时钟后的第9个时钟期间,接收器必须回送一个应答位(ACK)给发送器。

本项目所用芯片STM32F103RCT6,最小系统自带I2C存储器AT24C02,原理图如下:

由于I2C可以接多个主设备,多个从设备(外围设备),因此进行通讯是需要进行器件寻址,以此分辨是于哪个设备进行通讯,每个设备地址唯一。

EEPROM器件寻址:起始条件使能芯片读写操作后,EEPROM都要求有8位的器件地址信息。该器件地址信息的LSB为读/写操作选择位,高为读操作,低为写操作。若比较器件地址一致,EEPRON输出应答立答“0”。如果不一致,则返向待机状态。

AT24C系列E2PROM芯片地址的固定部分为1010。设备地址需要看芯片引脚的具体连接情况,本项目中1,2,3引脚均接地,则A2,A1,A0三位均为0,因此写地址为10100000,即0xA0;读地址为10100001,即0xA1。

要点:

模拟I2C是GPIO应设置为通用开漏输出或通用推挽输出,电平每跳变一次,需要加延时函数。

设备地址需要看芯片引脚的具体连接情况,数据地址的长度根据芯片不同而不同。

SCL时钟电平为低时,才可以改换SDA数据线的电平,SCL为高时SDA必须保持稳定。SDA数据在SCL时钟为低电平时准备好,在SCL上升沿的过程发送出去。

在写完8位数据之后,将要读取应答信号,这里也就是要SDA将从输出状态变为输入状态。

接收器拉低SDA线表示应答,并在应答脉冲期间保持稳定的低电平。当主器件作接收器时,必须发出数据传输结束的信号给发送器,即它在最后一个字节之后的应答脉冲期间不会产生应答信号(不拉低SDA)。这种情况下,发送器必须释放SDA线为高以便主器件产生停止条件。

在写读写程序的时候根据SDA时序及要求进行。也就是说必须看明白下面两个图。

主器件在EEPROM收到每个数据后都应答“0”。最后由主器件发送停止条件,终止写序列。

EEPROM接收器件地址和数据字地址并产生应答ACK,主器件就产生一个重复的起始条件。主器件发送器件地址(读/写选择位为“1”,读操作),EEPROM应答ACK,并随时钟发送数据,主器件不发送“0”时,产生停止条件。

时序混乱会导致程序无法运行,多写或者少写数据,因此每一步的高低电平时序都要谨慎思考。

模拟I2C传送的数据很慢,比硬件I2C要慢很多,而且占用CPU资源,而硬件IIC速度效率高,但用法复杂,不够稳定,容易产生卡死,各有利弊,但工程中常用硬件I2C。

I2C软件模拟EEPROM步骤:

1、配置所需要的宏

2、配置时钟

3、相关GPIO初始化

4、SDA输入输出方向配置

5、I2C起始信号及I2C结束信号

6、应答信号及非应答信号的产生

7、设置等待应答信号

8、主机发送数据

9、主机接收数据

10、EEPROM写入一个数据

11、EEPROM读出一个数据

12、EEPROM写入定长数据

13、EEPROM读出定长数据

14、EEPROM写入数组数据

15、EEPROM读出数组数据

16、EEPROM检查是否正常

代码如下:项目I2C模拟引脚用PB6,PB7。

//main.h  宏定义


#ifndef _I2C_H_
#define _I2C_H_
#include "stm32f10x.h"


#define I2C_SCL    GPIO_Pin_6
#define I2C_SDA    GPIO_Pin_7
#define GPIO_I2C   GPIOB
#define I2C_SCL_H  GPIO_SetBits(GPIO_I2C, I2C_SCL)  
#define I2C_SCL_L  GPIO_ResetBits(GPIO_I2C, I2C_SCL) 
#define I2C_SDA_H  GPIO_SetBits(GPIO_I2C, I2C_SDA)  
#define I2C_SDA_L  GPIO_ResetBits(GPIO_I2C, I2C_SDA)  

#define AT24C02		255
#define EE_TYPE   AT24C02



void RCC_Init(void);        
void I2C_GPIO_Config(void);
void I2C_OUT(void);
void I2C_IN(void);
void I2C_Start(void);
void I2C_Stop(void);
void Send_Byte(uint8_t data);
uint8_t Read_Byte(uint8_t ack);
uint8_t I2C_Wait_ACK(void);
void I2C_ACK(void);
void I2C_NACK(void);



uint8_t AT24C02_ReadOneByte(uint16_t ReadAddr);
void AT24C02_WriteOneByte(u16 WriteAddr,u8 DataToWrite);
void AT24C02_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len);
uint32_t AT24C02_ReadLenByte(uint16_t ReadAddr,uint8_t Len);
void AT24C02_Write(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite);
void AT24C02_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead);
uint8_t AT24C02_Check(void);

#endif

//main.c

#include "sys.h"
#include "stm32f10x.h"
#include "string.h"
#include "stdio.h"
#include "main.h"
#include "tools.h"



//时钟配置
void RCC_Init()                                     
{    
	    ErrorStatus HSEStartUpStatus;                    
		RCC_DeInit();                                    
		RCC_HSEConfig(RCC_HSE_ON);                       
		HSEStartUpStatus=RCC_WaitForHSEStartUp();       
		if(HSEStartUpStatus==SUCCESS)                   
		{
				
		    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);           
            FLASH_SetLatency(FLASH_Latency_2);                            
		    RCC_HCLKConfig(RCC_SYSCLK_Div1);                                
			RCC_PCLK1Config(RCC_HCLK_Div2);                                 
			RCC_PCLK2Config(RCC_HCLK_Div1);                                
			RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);            
		}	
		    else
	    {
			 /*do nothing*/
        }
        RCC_PLLCmd(ENABLE); 
     	while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET)                  
		{
		}
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);                          
		while (RCC_GetSYSCLKSource()!=0x08)          
		{
		}
}



//GPIO初始化
void I2C_GPIO_Config()                                           
{		
    GPIO_InitTypeDef GPIO_InitStruct; 
   
	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);               

    GPIO_InitStruct.GPIO_Pin=I2C_SCL|I2C_SDA;                                 
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;                         
    GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;                     
    GPIO_Init(GPIOC,&GPIO_InitStruct);                                  
	        
    I2C_SCL_H;
    I2C_SDA_H;  
	
}




//SDA输出
void I2C_OUT()
{      
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin=I2C_SDA;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;  
    GPIO_Init(GPIOC,&GPIO_InitStructure);

    I2C_SCL_H;
    I2C_SDA_H;   
}
	
//SDA输入
void I2C_IN()  
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin=I2C_SDA;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  
    GPIO_Init(GPIOC,&GPIO_InitStructure);
}


//起始信号
void I2C_Start(void)
{
    I2C_OUT();       
    I2C_SDA_H;
    I2C_SCL_H;
    DelayUS(4);   
    I2C_SDA_L; 
    DelayUS(4);
    I2C_SCL_L;      

}


//停止信号
void I2C_Stop(void)
{

    I2C_OUT(); 
    I2C_SCL_L;
    I2C_SDA_L;                 
    DelayUS(4); 
	I2C_SCL_H; 
    I2C_SDA_H;
    DelayUS(4); 

}



//产生应答信号
void I2C_ACK(void)
{
     I2C_SCL_L;
	 I2C_OUT(); 
     I2C_SDA_L;
     DelayUS(2); 
     I2C_SCL_H;
     DelayUS(2); 
     I2C_SCL_L;

}


//产生非应答信号
void I2C_NACK(void)
{
     I2C_SCL_L;   
	 I2C_OUT();
     I2C_SDA_H;
     DelayUS(2); 
     I2C_SCL_H;
     DelayUS(2); 
     I2C_SCL_L;

}


//等待应答信号
uint8_t I2C_Wait_ACK()  
 {
            
     int time=0;  
     I2C_IN();    
     I2C_SDA_H;
     DelayUS(4); 
     I2C_SCL_H;
     DelayUS(4); 
	 
     while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))  
     {
          time++;
          if(time>250)  
          {
              I2C_Stop();
              return 1;
          }
                                  
      }
          I2C_SCL_L;
		  I2C_OUT();
          return 0;  
}



//发送数据
void Send_Byte(uint8_t data)
{
    uint8_t i;
	I2C_OUT();
    I2C_SCL_L;
    DelayUS(2); 
    for(i=0;i<8;i++)  
    {
        if((data&0x80)>>7)
	    {
			I2C_SDA_H;
		}
		else
		{
			I2C_SDA_L;
		    data <<= 1; 
		    DelayUS(2);
			
			I2C_SCL_H;
			DelayUS(2);
		}					
			I2C_SCL_L;
		    DelayUS(2); 
			
     }

 }



//接收数据
uint8_t Read_Byte(uint8_t ack)
{
     uint8_t cnt;
     uint8_t rev_Data;  
     I2C_IN();
	
	 I2C_SCL_L;
     DelayUS(2); 
	 I2C_SDA_H;
	
     for(cnt=0;cnt<8;cnt++)
     {
        
         I2C_SCL_H; 
		 DelayUS(2); 
			 
		 rev_Data<<=1;
         if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA)) 
         {
			 rev_Data++;
			 I2C_SCL_L;
			 DelayUS(2); 
		 }
      }
      if(ack==0)  
	 {
          I2C_NACK();
	 }
      else                 
	 {
          I2C_ACK();  
	 }
     return rev_Data;
 }




//读单个字节
uint8_t AT24C02_ReadOneByte(uint16_t ReadAddr)
{				  
	    uint8_t temp=0;		  	    																 
      I2C_Start();  
	    if(EE_TYPE>2047)            
	    {
	    	  Send_Byte(0XA0);	            
	    	  I2C_Wait_ACK();
	    	  Send_Byte(ReadAddr>>8);       
	    	  //I2C_Wait_ACK();		 
	    }
	    else
	    {	
	    	  Send_Byte(0XA0+((ReadAddr/256)<<1)); 
		}				
	     I2C_Wait_ACK();
         Send_Byte(ReadAddr%256); 
	     I2C_Wait_ACK();    
	     I2C_Start();  	 	   
	     Send_Byte(0XA1);      
	     I2C_Wait_ACK(); 
         temp=Read_Byte(0);	 
         I2C_Stop();  	
	     return temp;
}



//写单个字节
void AT24C02_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
{				   	  	    																 
    I2C_Start();  
	  if(EE_TYPE>2047)
	  {
		    Send_Byte(0XA0);	   
		    I2C_Wait_ACK(); 
		    Send_Byte(WriteAddr>>8);  
 	  }
		else
	  {
		    Send_Byte(0XA0+((WriteAddr/256)<<1));   
	  }	 
	  I2C_Wait_ACK();    
      Send_Byte(WriteAddr%256);   
	  I2C_Wait_ACK(); 	 										  		   
	  Send_Byte(DataToWrite);   				   
	  I2C_Wait_ACK();  		    	   
      I2C_Stop();   
	  DelayMS(10); 	 
}



//写入定长数据
void AT24C02_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{  	
	   uint8_t t;
	   for(t=0;t<Len;t++)
	   {
	   	    AT24C02_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
	   }												    
}



//读取定长数据
uint32_t AT24C02_ReadLenByte(uint16_t ReadAddr,uint8_t Len)
{  	
	   uint8_t t;
	   uint32_t temp=0;
	   for(t=0;t<Len;t++)
	   {
	   	temp<<=8;
	   	temp+=AT24C02_ReadOneByte(ReadAddr+Len-t-1); 	 				   
	   }
	   return temp;												    
}




//读数组数据
void AT24C02_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead)
{
	   while(NumToRead)
	   {
	   	*pBuffer++=AT24C02_ReadOneByte(ReadAddr++);	
	   	NumToRead--;
	   }
}



//写入数组数据
void AT24C02_Write(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite)
{
	while(NumToWrite--)
	{
		AT24C02_WriteOneByte(WriteAddr,*pBuffer);
		WriteAddr++;
		pBuffer++;
	}
}



//检查AT24C02是否正常
 uint8_t AT24C02_Check(void)
{
	   uint8_t temp;
	   temp=AT24C02_ReadOneByte(255);  
	   if(temp==0X55)
	   {
	   	   return 0;		
	   }		
	   else
	   {
	   	   AT24C02_WriteOneByte(255,0X55);
	       temp=AT24C02_ReadOneByte(255);	  
	   	   if(temp==0X55)
	   		{
	   			return 0;
	   		}
	   }
	   return 1;											  
}




uint8_t  readDate[10]={0};
uint8_t  writeDate[8]={1,2,3,4,5,6,7,8};


int main()
{
	uint8_t i=0;
		
   	RCC_Init();
    I2C_GPIO_Config();

	 
	AT24C02_Check();
	AT24C02_Write(0,writeDate,10);
	for(i=0;i<10;i++)
	{
		readDate[i]=writeDate[i];
	}
	  
    AT24C02_Read(0,readDate,10);
	

	DelayMS(500);
	while(1)
	{
			
		
	} 
}		
	

实验结果:

通过程序模拟输出数组的数据,输出正确。

版权声明:本文为CSDN博主「Artemis_yuer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Artemis_yuer/article/details/122312885

生成海报
点赞 0

Artemis_yuer

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

RT-Thread Studio移植LAN8720A驱动

RTT网络协议栈驱动移植(霸天虎) 1、新建工程 ​ 工程路径不含中文路径名,工程名用纯英文不含任何符号。 2、用CubeMx配置板子外设 2.1、配置时钟 ​ 按照自己板子配置相应时钟。

【STM32Cube笔记】12-配置外部中断

【STM32Cube笔记】系列文章目录 1-基于STM32的VSCode入门级教程前言 2-STM32Cube安装教程 3-STM32CubeIDE汉化 4-STM32Cube配置时钟设置 5-跑马灯引脚配置 6-Cortex-M7内核基本配

stm32cubemx+HAL+串口接收中断

stm32cubemxHAL串口接收中断 在cubemx配置完串口和global interrupt后需要在keil中添加如下代码。 第一步:在main函数中添加接收中断标志位开启函数 HAL_UART_Receive_IT