软件模拟I2C实现方法

在I2C通讯中,使用IO引脚模拟I2C通讯,称为“软件模拟I2C”。由于缺少硬件I2C接口,大多数程序员都喜欢用它来实现I2C通讯。


1、设计注意事项:
1)、SCL和SDA引脚必须外接上拉电阻,通常为3.3K~10K,增强总线的抗干扰能力;
2)、上电时,SCL和SDA为输入高阻态,由于上拉电阻的存在,SCL和SDA表现为高电平;但在初始化SCL和SDA引脚时,我们要先初始化SCL引脚,
并设置为高电平;然后再初始化SDA引脚,置为高电平;
3)、在EEPROM上电后,先发送启动条件,接着发送一个停止条件,令EEPROM复位,接入通讯状态。
波形图如下:

2、如何防止EEPROM丢数据:
1)、由于EEPROM不直到CPU什么时候会重启,因此使用可控的LDO给EEPROM单独供电。
通常使用IO引脚控制LDO的电压输出。CPU每次重启,都需要对开关一次EEPROM的电源,保证EEPROM可靠复位。
如果没有使用“可控的LDO给EEPROM单独供电”,则使用“软件复位EEPROM”;实现方法如下:
发送“启动条件”后,接着SDA发送9个1,再发送“启动条件”产生,这个启动条件准备时发送一个1,合计10个1,然后紧接着在下一个时钟周期
发送“停止条件”,EEPROM就会复位。
5V供电,编程电流为3mA,因此不建议使用IO引脚直接控制EEPROM的电源。
也有人将EEPROM的WP引脚和CPU的复位引脚连接在一起。此方法可以解决上电复位,但不能解决CPU其它复位所带来的问题。
2)、EEPROM工作电压范围要比MCU的宽;
3)、做好电源滤波,等电源开机稳定后才去读写EEPROM,这个很重要;
4)、采用数据备份:当修改完某个数据后,同时还要对备份区域的相应位置也进行修改,并分别设置校验数据,防止因意外导致数据丢失。
当加载EEPROM“修改区域的数据”时,校验正确,则使用修改区的数据。
如果“修改区域的数据”校验错误,则使用“备份区域的数据”,然后覆盖“对应的修改区域的数据”。
如果“备份区域的数据”也发生校验错误,则表示EEPROM中的数据被损坏了,无法修复。
5)、CPU读写EEPROM时因中断发生,可能会引起EEPROM产生复位。因此,在读写EEPROM时,最好关闭所有的中断,防止因EEPROM复位引起数据没有写完整。

3、AT24C256C的EEPROM的复位波形图:

发送“启动条件”后,接着SDA发送9个1,再发送“启动条件”产生,这个启动条件准备时发送一个1,合计10个1,然后紧接着在下一个时钟周期发送“停止条件”,EEPROM就会复位。在初始化时,可以利用这个条件,令EEPROM复位。

5V供电,编程电流为3mA,见下图:

4、读写EEPROM的步骤
1)、从EEPROM读取1个字节的步骤:
发送“I2C启动条件”
发送“写器件地址”,告诉“这个地址的I2C从机”做好通讯准备,24LC256的“写器件地址”为0xA0
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址高8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址低8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;

发送“I2C重启动条件”
发送“读器件地址”,告诉“这个地址的I2C从机”做好通讯准备
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
从EEPROM读取一个字节
CPU发送不应答信号
发送“I2C停止条件”

2)、从EEPROM读取2个字节的步骤:
发送“I2C启动条件”
发送“写器件地址”,告诉“这个地址的I2C从机”做好通讯准备,24LC256的“写器件地址”为0xA0
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址高8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址低8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;

发送“I2C重启动条件”
发送“读器件地址”,告诉“这个地址的I2C从机”做好通讯准备
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
从EEPROM读取第1个字节
CPU发送应答信号
从EEPROM读取最后1个字节
CPU发送不应答信号
发送“I2C停止条件”

3)、向EEPROM写1个字节的步骤:
发送“I2C启动条件”
发送“写器件地址”,告诉“这个地址的I2C从机”做好通讯准备,24LC256的“写器件地址”为0xA0
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址高8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址低8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;

无须发送“I2C重启动条件”,直接向EEPROM写入1个字节
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送“I2C停止条件”

5、必须在SCL为低电平时,改变SDA的输出方向

软件模拟I2C,SDA在切换为输出时,可能是高电平,见下图:

6、软件模拟I2C应用程序

#define    EEPROM_SCL_SET()  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET)    //设置PA15输出高电平
#define    EEPROM_SCL_CLR()  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET)  //设置PA15输出低电平
#define EEPROM_SCL()      HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15)  //读取PA15

#define    EEPROM_SDA_SET()  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET)    //设置PB9输出高电平
#define    EEPROM_SDA_CLR()  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET)  //设置PB9输出低电平
#define EEPROM_SDA()      HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9)         //读取PB9         

//函数功能:sel=1设置GPIOB9为输出口,sel=0,则设置GPIOB9为输入上拉
void EEPROM_SDA_PIN_Configuration(uint8_t sel)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if(sel==1)
    {
        GPIO_InitStruct.Pin = GPIO_PIN_9;     //选择第9脚
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  //选择推挽输出
        GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
  }
    else
    {
    GPIO_InitStruct.Pin = GPIO_PIN_9;               //选择第9脚
      GPIO_InitStruct.Pull = GPIO_PULLUP;             //引脚上拉被激活
//  GPIO_InitStruct.Pull = GPIO_NOPULL;           //引脚上拉和下拉都没有被激活
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; //配置GPIO速度为中速
      GPIO_InitStruct.Mode = GPIO_MODE_INPUT;         //设置引脚工作模式为输入模式
      HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
      //根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
    }
}

//函数功能:sel=1设置GPIOA15为输出口,sel=0,则设置GPIOA15为输入上拉
void EEPROM_SCL_PIN_Configuration(uint8_t sel)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if(sel==1)
    {
        GPIO_InitStruct.Pin = GPIO_PIN_15;     //选择第15脚
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  //选择推挽输出
        GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
  }
    else
    {
    GPIO_InitStruct.Pin = GPIO_PIN_15;               //选择第15脚
      GPIO_InitStruct.Pull = GPIO_PULLUP;             //引脚上拉被激活       
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; //配置GPIO速度为中速
      GPIO_InitStruct.Mode = GPIO_MODE_INPUT;         //设置引脚工作模式为输入模式
      HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
      //根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
    }
}

//函数功能:EEPROM引脚初始化
void EEPROM_I2c_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE();                   //GPIOA时钟使能
    
    GPIO_InitStruct.Pin = GPIO_PIN_15;     //选择第15脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  //选择推挽输出
    GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
    EEPROM_SCL_SET();  //PA15输出高电平

    GPIO_InitStruct.Pin = GPIO_PIN_9;     //选择第9脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  //选择推挽输出
    GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
    EEPROM_SDA_SET();  //PB9输出高电平
    
    IWDG_Counter_Reload();  //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
  delay_ms(10); //上电等待时间为10ms;
    IWDG_Counter_Reload();  //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
}

//主机发送"启动条件"函数;
 void I2CStart_EEPROM(void)
 {
     EEPROM_SCL_SET(); //EEPROM SCL输出高电平,将EEPROM SDA脚和EEPROM SCL置为高电平,为发送启动条件做准备
     delay_us(1);
     EEPROM_SDA_PIN_Configuration(1); //设置SDA为输出口,由于有上拉电阻,所以SDA的电平状态为高电平
     EEPROM_SDA_SET(); //SDA输出高电平;
     delay_us(2);      //空闲时间至少保持2us;
     EEPROM_SDA_CLR(); //在EEPROM CL为高电平期间,EEPROM SDA脚出现下降沿,表示"起动I2C"
     delay_us(2);      //启动保持时间为2us;
   EEPROM_SCL_CLR(); //EEPROM SCL脚置低电平,为下次移位做准备;
     delay_us(5);
 }

//主机发送"停止条件"函数;
void I2CStop_EEPROM(void)
{
//将EEPROM SDA脚和EEPROM SCL置为低电平,为发送"停止条件"做准备
    EEPROM_SCL_CLR();  //SCL输出低电平
    delay_us(1);      //保持时间为1us;
    EEPROM_SDA_PIN_Configuration(1); //设置SDA为输出口
    EEPROM_SDA_CLR(); //EEPROM SDA输出低电平
    delay_us(2); //时钟低电平时间至少保持2us
    EEPROM_SCL_SET(); //EEPROM SCL输出高电平
    delay_us(2); //停止条件建立时间至少为2us;
    EEPROM_SDA_SET(); //EEPROM SDA输出高电平,在EEPROM_SCLK为高电平期间,EEPROM_IO脚出现上降沿,表示"停止I2C"
    delay_us(4); //停止条件保持时间至少为4us;
}

 //从I2C输出一个字节
void write_I2C_byte_To_EEPROM(unsigned char txByte)
{
  unsigned char mask;
  unsigned char tmpValue;
    
    IWDG_Counter_Reload();  //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
    EEPROM_SCL_CLR();  //SCL输出低电平
    delay_us(1);//防止干扰
  for(mask=0x80; mask>0;) //shift bit for masking (8 times)
  {
        tmpValue=(unsigned char)(mask & txByte);
        if ( tmpValue == 0 ) EEPROM_SDA_CLR();  //SDA输出低电平
        else EEPROM_SDA_SET();  //SDA输出高电平;
        delay_us(1);       //数据建立时间至少为0.25us
        EEPROM_SCL_SET();  //SCL输出高电平 //generate clock pulse on SCL
        delay_us(3);       //时钟高电平时间至少保持2us
        EEPROM_SCL_CLR();  //SCL输出低电平
        delay_us(4);       //时钟低电平时间至少保持4us
        mask=(unsigned char)(mask>>1); //将mask的值右移一位,左端补0,为写下一位值做准备
    }
}

//从I2C读入一个字节
unsigned char read_I2C_byte_Fr_EEPROM(void)
{
  unsigned char mask,rxByte=0;
    
    IWDG_Counter_Reload();  //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
    EEPROM_SCL_CLR();  //SCL输出低电平
    delay_us(1);//防止干扰
    EEPROM_SDA_PIN_Configuration(0); //设置SDA为输入口,这句必须保留
    delay_us(1);//数据建立时间
  for(mask=0x80;mask>0;) //shift bit for masking (8 times)
    {
        EEPROM_SCL_SET();  //SCL输出高电平 //start clock on SCL-line
        delay_us(2);       //时钟高电平时间至少保持2us
        if( EEPROM_SDA() ) rxByte=(unsigned char)(rxByte | mask); //read bit
        delay_us(2);       //SDA时间至少保持2us
        EEPROM_SCL_CLR();  //SCL输出低电平
        delay_us(5);       //时钟低电平时间至少保持4us
        mask=(unsigned char)(mask>>1); //将mask的值右移一位,左端补0,为读取下一位值做准备
    }
    return(rxByte);
}

//函数说明:I2C专用,等待从器件接收方的应答
bool WaitAck_Fr_EEPROM(void)
{
    unsigned char errtime=255;//因故障接收方无ACK,超时值为255.

    EEPROM_SCL_CLR();  //SCL输出低电平
    delay_us(1);
    EEPROM_SDA_PIN_Configuration(0); //设置SDA为输入口
    delay_us(1); //数据建立时间至少为0.25us;
    EEPROM_SCL_SET();  //SCL输出高电平,//EEPROM SCL上升沿到来时,EEPROM发送ACK信号;
    delay_us(3); //时钟高电平时间至少保持3us;

    while(EEPROM_SDA())
    {
        errtime--;
        delay_ms(1); //等待1ms
        if (!errtime)
        {
            I2CStop_EEPROM();
            delay_ms(3000);
            IWDG_Counter_Reload();  //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
            return FALSE;
        }
    }

    EEPROM_SCL_CLR();  //SCL输出低电平
    delay_us(4); //时钟低电平时间至少保持4us;
    EEPROM_SDA_PIN_Configuration(1); //设置SDA为输出口,为下次发送数据做准备
    EEPROM_SDA_CLR();
    return TRUE;
}

//函数说明:I2C专用,CPU为接收方,EEPROM为发送方时,CPU发送应答信号;
 void SendAck_To_EEPROM(void)
 {
     EEPROM_SCL_CLR();  //SCL输出低电平
     delay_us(1);
     EEPROM_SDA_PIN_Configuration(1); //设置SDA为输出口
     EEPROM_SDA_CLR(); //将EEPROM_IO脚设置为低电平,该位值是即发出的"应答信号";
     delay_us(1); //数据建立时间至少为0.25us;
     EEPROM_SCL_SET();  //SCL输出高电平//EEPROM SCL上升沿到来时,EEPROM收到一位值;
     delay_us(3); //时钟高电平时间至少保持3us;
     EEPROM_SCL_CLR();  //SCL输出低电平//EEPROM_SCLK脚置低电平,一个移位时钟结束;
     delay_us(1); //防止干扰
     EEPROM_SDA_PIN_Configuration(0); //设置SDA为输入口,为下一次读做准备
     delay_us(3); //时钟低电平时间至少保持3us
 }
 
//函数说明:I2C专用,主器件为接收方,从器件为发送方时,接收最后一个字节时,CPU发送非应答信号。
void SendNotAck_To_EEPROM(void)
{
    EEPROM_SCL_CLR();  //SCL输出低电平
    delay_us(1);
    EEPROM_SDA_PIN_Configuration(1); //设置SDA为输出口//将EEPROM_IO脚设置为高电平,该位值是即发出的"非应答信号";
    EEPROM_SDA_SET(); //将EEPROM_IO脚设置为高电平,发送非应答信号;
  delay_us(1);       //数据建立时间至少为0.25us;
    EEPROM_SCL_SET();  //SCL输出高电平//EEPROM_SCLK脚上升沿到来时,EEPROM收到一位值;
  delay_us(3);       //时钟高电平时间至少保持3us;
    EEPROM_SCL_CLR();  //SCL输出低电平//EEPROM_SCLK脚置低电平,一个移位时钟结束;
    delay_us(2); //时钟低电平时间至少保持1us;
    EEPROM_SDA_CLR(); //将EEPROM_IO脚设置为低电平,准备发送停止条件;
    delay_us(2); //时钟低电平时间至少保持1us
}

//开始24LC256驱动程序
 /**-------------------------------------------------------------------------------
 函数说明:从EEPROM芯片24LC256的某个地址单元addr,读取到rerurn_value中.
           并将读到的值返回;
 ---------------------------------------------------------------------------------*/

uint8_t EEPROM_U8_Data_Read1(uint16_t addr)
{
    uint8_t rerurn_value;
  union EEPROM_Addr_TYPE  temp;

  temp.Address=addr;
  delay_ms(1);                       //在连续进行"字节连续读"时,此处必须加delay_ms(1),否则程序可能读不到EEPROM中的数据;
  I2CStart_EEPROM();                          //发开始条件
  write_I2C_byte_To_EEPROM(0xA0);             //发送"写器件地址",24LC256的"写器件地址"为0xA0
  WaitAck_Fr_EEPROM();                        // 等待从器件接收方的应答 
  write_I2C_byte_To_EEPROM(temp.b[1]);  //发送器件的子地址高8位值;
  WaitAck_Fr_EEPROM();                        // 等待从器件接收方的应答 
  write_I2C_byte_To_EEPROM(temp.b[0]);     //发送器件的子地址低8位值;
  WaitAck_Fr_EEPROM();                        //等待EEPROM_IO脚被24LC256拉低;

  //delay_ms(1); 
  I2CStart_EEPROM();                      //发重新开始条件
  write_I2C_byte_To_EEPROM(0xA1);         //发送"读器件地址"
  WaitAck_Fr_EEPROM();                    //等待EEPROM_IO脚被24LC256拉低;
  rerurn_value=read_I2C_byte_Fr_EEPROM();
  SendNotAck_To_EEPROM();                //CPU发送不应答信号
  I2CStop_EEPROM();
  return(rerurn_value);
}

/**-------------------------------------------------------------------------------
 函数说明:从EEPROM芯片24LC256的某个地址单元开始,连续读取count个字节;
           设置要读的第一个地址addr,并设置读取的字节数count,则会一次把数据读取到buff中.
 ---------------------------------------------------------------------------------*/

void EEPROM_U8_Data_Readn(uint8_t *buff,uint8_t count,uint16_t addr)
{
    unsigned char i;
    union EEPROM_Addr_TYPE  temp;
     
    temp.Address=addr;
    delay_ms(1);                          //在连续进行"字节连续读"时,此处必须加delay_ms(5),否则程序可能读不到EEPROM中的数据;
    I2CStart_EEPROM();                    //发开始条件
    write_I2C_byte_To_EEPROM(0xA0);       //发送"写器件地址",24LC256的"写器件地址"为0xA0
    WaitAck_Fr_EEPROM();                  // 等待从器件接收方的应答 
    write_I2C_byte_To_EEPROM(temp.b[1]);  //发送器件的子地址高8位值;
    WaitAck_Fr_EEPROM();                  // 等待从器件接收方的应答
    write_I2C_byte_To_EEPROM(temp.b[0]);  //发送器件的子地址低8位值;
    WaitAck_Fr_EEPROM();                  //等待EEPROM_IO脚被24LC256拉低;
     
    I2CStart_EEPROM();                    //发重新开始条件
  write_I2C_byte_To_EEPROM(0xA1);       //发送"读器件地址"
    WaitAck_Fr_EEPROM();                   //等待EEPROM_IO脚被24LC256拉低;
    for (i=0;i<count;i++)
  {
        buff[i]=read_I2C_byte_Fr_EEPROM();
        if (i!=count-1) SendAck_To_EEPROM(); //除最后一个字节外,其他都要从MASTER发应答。
  }
    SendNotAck_To_EEPROM();                //CPU发送不应答信号
    I2CStop_EEPROM();
}

#define CLI()  __set_PRIMASK(1)         //关闭总中断
#define SEI()  __set_PRIMASK(0)        //打开总中断

/**-------------------------------------------------------------------------------
 函数说明:将x的值写入指定的EEPROM芯片24LC256的某个地址单元addr;
      注意:在I2CStop_EEPROM()和I2CStart_EEPROM()两句之间,必须有delay_ms(5)语句,否则,读写可能不正确;
 ---------------------------------------------------------------------------------*/

void EEPROM_U8_Data_Write(uint8_t x,uint16_t addr)
{
    union EEPROM_Addr_TYPE  temp;
    
    IWDG_Counter_Reload();  //喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
    CLI(); //关闭总中断

  temp.Address=addr;
  I2CStart_EEPROM();                   //发开始条件
  write_I2C_byte_To_EEPROM(0xA0);      //发送"写器件地址",24LC256的"写器件地址"为0xA0
  WaitAck_Fr_EEPROM();                 //等待EEPROM_IO脚被EEPROM拉低;
  write_I2C_byte_To_EEPROM(temp.b[1]); //发送器件的子地址高8位值;
  WaitAck_Fr_EEPROM();                 // 等待从器件接收方的应答 
  write_I2C_byte_To_EEPROM( temp.b[0] ); //发送器件的子地址低8位值;
  WaitAck_Fr_EEPROM();             //等待EEPROM_IO脚被EEPROM拉低;
  write_I2C_byte_To_EEPROM(x); //将value的值写入EEPROM;
  WaitAck_Fr_EEPROM();             //等待EEPROM_IO脚被EEPROM拉低;
  I2CStop_EEPROM();

    SEI();//打开总中断
  delay_ms(5);           //写等待时间至少保持4ms;
}

//函数功能:CPU令EEPROM复位
//AT24C256C可以使用软件复位EEPROM

//Microchip的24LC256没有复位序列,只能使用电源复位。
void CPU_Reset_EEPROM(void)
{
    unsigned char i;

    I2CStart_EEPROM();
  for(i=0;i<9;i++)
  {
        EEPROM_SDA_SET();  //SDA输出高电平;
        delay_us(3);       //数据建立时间至少为0.25us
        EEPROM_SCL_SET();  //SCL输出高电平 
        delay_us(3);       //时钟高电平时间至少保持2us
        EEPROM_SCL_CLR();  //SCL输出低电平
        delay_us(4);       //时钟低电平时间至少保持4us
    }
    I2CStart_EEPROM();//启动条件准备前,SDA输出一个高电平,合计10个1
    delay_us(4);
    I2CStop_EEPROM();
}

//函数功能:初始化EEPROM引脚;
void EEPROM_PIN_INIT(void)
{
    EEPROM_I2c_Init();
  delay_ms(10); //上电等待时间为10ms;

  CPU_Reset_EEPROM(); //令EEPROM复位
    My_data=0x8080;
}

void Test_EEPROM(void)
{
    uint8_t tmp;
    uint8_t buf[2];

    EEPROM_U8_Data_Write('A',100);//向EEPROM地址为100的存储区写入一个字节值为'A'
    EEPROM_U8_Data_Write('B',101);//向EEPROM地址为101的存储区写入一个字节值为'B'

    tmp=EEPROM_U8_Data_Read1(100);//从EEPROM地址为100的存储区读取一个字节,保存到tmp中
    if(tmp=='A') printf("Write EEPROM OK1\r\n");
    else printf("Write EEPROM Error1\r\n");

   
    EEPROM_U8_Data_Readn(buf,2,100);
   if(buf[0]=='A' && buf[1]=='B') printf("Write EEPROM OK2\r\n");
    else printf("Write EEPROM Error2\r\n");
}

上一篇:MySQL中的数字数据类型的一个概述


下一篇:Mybatis快速入门