一篇文章讲清楚I2C通信及软件编程--附开源软件I2C驱动程序

1、导读

        如果你想深入了解和学习I2C通信,请阅读全篇文章,如果你只是要临时快速的完成I2C通信外设的驱动,可以直接看代码,复制到你的工程中去,编译,调试很快就解决问题。本文重点还是想教你真正的理解了I2C通信的原理与编程,I2C通信一要掌握原理,二要自己真正的去编程实践,如果你看完本篇文章,你能自己编写一个软件模拟I2C驱动程序,你就真正的掌握了I2C通信原理。

2、I2C通信协议简介

    I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。I2C总线上分成主机和从机两种设备

     主机用于启动总线传送数据并产生时钟以同步从机,此时任何被寻址的器件均被认为是从器件.在总线上主机和从机、发送和接收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。

     

2.1 工作原理 

 

      SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,接口电路为开漏输出,需通过上拉电阻接电源VCC。当总线空闲时.两根线都是高电平,连接总线的外同器件都是CMOS器件,输出级也是开漏电路.在总线上消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容.而线路中电容会影响总线传输速度.当电容过大时,有可能造成传输错误.所以,其负载能力为400pF,因此可以估算出总线允许长度和所接器件数量。

2.2  I2C总线特点

(1)在硬件上,I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,不需要特殊的接口电路,而且片上接口电路的滤波器可以滤去总线数据上的毛刺.因此I2C总线简化了硬件电路PCB布线,降低了系统成本,提高了系统可靠性。因为I2C芯片除了这两根线和少量中断线,与系统再没有连接的线,常用的IC外设可以很容易形成标准化和模块化,便于重复利用。

(2)I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏,每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。

(3)I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也利于标准化和模块化,缩短开发时间。

(4)连接到相同总线上的IC数量只受总线最大电容的限制,串行的8位双向数据传输位速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。

(5)总线具有极低的电流消耗.抗高噪声干扰,增加总线驱动器可以使总线电容扩大10倍,传输距离达到15m;兼容不同电压等级的器件,工作温度范围宽

    

2.3  字节格式

     I2C总线上传输数据前要先发送一个起始位(起始条件 START),之后SDA 线上发送数据,数据的每个字节必须为8 位,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个应答位(ACK)。首先传输的是数据的最高位(MSB),如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线SCL 保持低电平,迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线SCL 后数据传输继续, 数据传输完成后再发送一个停止位(STOP)表示一次通信完成。

 

 

2.4   应答响应

   数据传输必须带响应位,相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间发送器释放SDA 线(高)。

   在响应的时钟脉冲期间,接收器必须将SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。

   通常被寻址的接收器在接收到的每个字节后,

 

必须产生一个响应。当从机不能响应从机地址时(例如它正在执行一些实时函数不能接收或发送),从机必须使数据线保持高电平,主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输。

      如果从机接收器响应了从机地址,但是在传输了一段时间后不能接收更多数据字节,主机必须再一次终止传输。这个情况用从机在第一个字节后没有产生响应来表示。从机使数据线保持高电平,主机产生一个停止或重复起始条件。

     如果传输中有主机接收器,它必须通过在从机发出的最后一个字节时产生一个响应,向从机发送器通知数据结束。从机发送器必须释放数据线,允许主机产生一个停止或重复起始条件。

2.5 时钟同步

       所有主机在SCL线上产生它们自己的时钟来传输I2C总线上的报文。数据只在时钟的高电平周期有效,因此需要一个确定的时钟进行逐位仲裁。

       时钟同步通过线与连接I2C 接口到SCL 线来执行。这就是说SCL 线的高到低切换会使器件开始数它们的低电平周期,而且一旦器件的时钟变低电平,它会使SCL 线保持这种状态直到到达时钟的高电平。但是如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变SCL 线的状态。因此SCL 线被有最长低电平周期的器件保持低电平。此时低电平周期短的器件会进入高电平的等待状态。

     当所有有关的器件数完了它们的低电平周期后,时钟线被释放并变成高电平。之后器件时钟SCL线的状态没有差别,而且所有器件会开始数它们的高电平周期。首先完成高电平周期的器件会再次将SCL线拉低。

这样产生的同步SCL 时钟的低电平周期由低电平时钟周期最长的器件决定,而高电平周期由高电平时钟周期最短的器件决定。

3、软件模拟I2C通信驱动程序编写

        根据上面I2C通信协议的介绍,一个段I2C的通信时序分成“起始位”,“数据位”,“停止位”,“应答位”,软件模拟I2C通信就是实现这几个数据位的函数。

         本文的开源的软件I2C驱动以STM32F103为硬件,底层的IO调用使用了STM32F103的驱动,如果你使用的CPU与此不同,需要进行底层IO驱动部分的修改。

3.1  初始化函数

        软件模拟I2C通信驱动程序要编写的第一个函数i2c_init, 就是I2C通信接口使用的SCL, SDA两个IO的初始化,根据I2C协议,这两个IO要初始化成开漏模式。i2c_init函数首先打开SCL, SDA  IO口的时钟,设置SCL, SDA为输出开漏模式,输出为高电平。

I2C_NUM是一个宏定义,用于定义软件驱动支持的软件模拟的I2C的通道的数量,可以设置为1或2。后面的i2c_start, i2c_stop,i2c_ack等函数均有一个输入参数取值为0或1分别表示第1路,第2路I2C驱动。

        使用软件模拟I2C驱动程序,需要首先调用这个函数进行一次初始化。

/****************************************************************************************
** Function name:      i2c_init()
** Descriptions:        I2C使用的IO管脚初始化函数
** input parameters:   无
** output parameters:  无
** Returned value:      无
****************************************************************************************/
OTP_VOID i2c_init(void)
{

#if(2 == I2C_NUM)
    /*打开I2C0使用的端口时钟*/
    RCC_APB2PeriphClockCmd(I2C0_SOFT_SCL_PORT_PERIPH |  I2C0_SOFT_SDA_PORT_PERIPH, Enable);
    RCC_APB2PeriphClockCmd(I2C1_SOFT_SCL_PORT_PERIPH |  I2C1_SOFT_SDA_PORT_PERIPH , Enable);
    
    SDA_OUT(0);
    SDA_OUT(1);
    SDAH(0);
    SDAH(1);
    
    SCL_OUT(0);
    SCL_OUT(1);
    SCLH(0);
    SCLH(1);
     
#else
    
    RCC_APB2PeriphClockCmd(I2C0_SOFT_SCL_PORT_PERIPH |  I2C0_SOFT_SDA_PORT_PERIPH , Enable);
    
    SDA_OUT(0);
    SDAH(0);
    
    SCL_OUT(0);
    SCLH(0);
#endif    

}

     

3.2  起始位,停止位函数

           i2c_start函数用于产生起始位条件,即保持SCL高电平时, SDA产生一个下降沿。

           i2c_stop函数用于产生停止条件,即保持SCL高电平时,SDA产生一个上升沿。

/*********************************************************************************************************
** Function name:      i2c_delay()
** Descriptions:        I2C延时函数
** input parameters:   延时值
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
OTP_VOID i2c_delay(OTP_UINT32 time)
{
    OTP_UINT32 i;
    for( i = 0; i < time; i++)
    {
        /*do nothing*/
    }
}


/*********************************************************************************************************
** Function name:      i2c_start()
** Descriptions:        产生I2C起始条件
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL OTP_VOID i2c_start(OTP_UINT8 id)
{   
    SDA_OUT(id);  
    SDAH(id);
    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);
    SDAL(id); 
    i2c_delay(I2C_DELAY_TIME);
    SCLL(id);
    SDAH(id);
    
}

/*********************************************************************************************************
** Function name:      i2c_stop()
** Descriptions:        产生I2C停止条件
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL OTP_VOID i2c_stop(OTP_UINT8 id)
{
    SDA_OUT(id);
    
    SCLL(id);
    SDAL(id);
    i2c_delay(I2C_DELAY_TIME);
    
    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);

    SDAH(id);
    i2c_delay(I2C_DELAY_TIME);
}

3.3 应答

      应答分成应答位ACK, 非应答NACK两个种。当主机进行写数据给从机时,需要调用i2c_wait_ack等待从机应答获取从机已经接收到写入的数据。当主机进行读取数据时,需要调用i2c_send_ack给从机进行应答,告诉从机传输下一下字节,读取到最后一个字节后调用i2c_sens_no_ack给从机产生非应答,告诉从机读取完成,不要再向总线上输出数据了。

/*********************************************************************************************************
** Function name:      i2c_send_ack()
** Descriptions:        产生I2C应答条件
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL OTP_VOID i2c_send_ack(OTP_UINT8 id)   
{
    /*发送0,在scl为高电平时使sda信号为低*/
    SDA_OUT(id);
    
    SCLL(id);                  /*数据线可以输出 */

    SDAL(id);                  /*在此发出应答或非应答信号 */    
    i2c_delay(I2C_DELAY_TIME); 

    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);  /*时钟低电平周期大于4.7μs*/

    SCLL(id);
    SDAH(id);
    i2c_delay(I2C_DELAY_TIME);
}

/*********************************************************************************************************
** Function name:      i2c_send_no_ack()
** Descriptions:        产生I2C非应答条件
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL OTP_VOID i2c_send_no_ack(OTP_UINT8 id)
{
    /*发送1,在scl为高电平时使sda信号为高*/
    SDA_OUT(id);
    SCLL(id);           /*数据线可以输出 */
    
    SDAH(id);
    i2c_delay(I2C_DELAY_TIME);

    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);  

    SCLL(id);
    SDAH(id);
}

/*********************************************************************************************************
** Function name:      i2c_wait_ack()
** Descriptions:        等待I2C应答信号
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL STATUS i2c_wait_ack(OTP_UINT8 id)
{
    OTP_UINT32 flag = 0; 
    SCLL(id);   
    SDA_IN(id);
                        
    i2c_delay(I2C_DELAY_TIME);
    
    SCLH(id);       /*读取应答位 */
    i2c_delay(I2C_DELAY_TIME);
    
    flag = SDA_R(id);
    SCLL(id);
    i2c_delay(I2C_DELAY_TIME);
    
    if(flag)
    {
        return ERROR;
    }
    return OK;
}

3.4 写字节和读字节

         I2C传输数据是一个字节为单位来进行传输,读写数据首先要实现读写一个字节的函数,即读字节i2c_read_byte和写字节i2c_write_byte两个。

/*********************************************************************************************************
** Function name:      i2c_write_byte()
** Descriptions:        向I2C写一个字节
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL STATUS i2c_write_byte(OTP_UINT8 id, OTP_UINT8 data)
{
    /*向I2C总线写一个字节*/
    OTP_UINT8 i = 0;

    SDA_OUT(id);
        
    for (i = 0; i < 8; i++ )    
    {   
        SCLL(id);
        i2c_delay(I2C_DELAY_TIME / 2);
        if (data & 0x80 )   
        {
            SDAH(id);
        }
        else
        {   
            SDAL(id);
        }
        data <<= 1;
        i2c_delay(I2C_DELAY_TIME / 2);

        SCLH(id);
        i2c_delay(I2C_DELAY_TIME);      
    }
    return i2c_wait_ack(id );
}

/*********************************************************************************************************
** Function name:      i2c_read_byte()
** Descriptions:        从I2C读取一个字节
** input parameters:   操作的I2C端口
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
OTP_UINT8 i2c_read_byte(OTP_UINT8 id)
{
    OTP_UINT8 i = 0, data=0;
    
    SDA_IN(id);
    i2c_delay(I2C_DELAY_TIME);

    for(i = 0; i < 8; i++)
    {   
        SCLL(id);                       /*置时钟线为低,准备接收数据位*/
        i2c_delay(I2C_DELAY_TIME);  

        SCLH(id);                       /*置时钟线为高使数据线上数据有效*/
        i2c_delay(I2C_DELAY_TIME / 2);

        if(SDA_R(id))
        {
            data |= 1 << (7 - i) ;        /*读数据位,接收的数据位放入retc中 */
        }
           
        i2c_delay(I2C_DELAY_TIME / 2);
    }
    SCLL(id);
    return(data);
    
}

3.5  总线数据读写

    I2C总线一般通信都是读写多个字节,I2C从器件如EEPROM,  IO扩展芯片,传感器芯片(这芯片在I2C总线上做从器件)等,芯片内部设置有寄存器,对于这些芯片的访问就是读写寄存器。

     当写从器件寄存器时,I2C总线上需要先写入器件的从机地址,再写入寄存器的地址,最后写入寄存器的数据,具体的写入时序如下图所示。根据时序图,编写一个向I2C从器件写寄存器的函数i2c_send_data,这个函数调用上面介绍的函数,来实现一次对从器件的数据写入。

/*********************************************************************************************************
** Function name:      i2c_send_data()
** Descriptions:        向i2c总线发送数据
** input parameters:   id--操作的I2C端口
**                      i2cAddr--器件地址
**                      addr--器件内部地址
**                      addr_len--内部地址长度
**                      pSendData--缓冲区地址
**                      sendLen--数据长度
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
STATUS i2c_send_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,    OTP_UINT16 addr,  
                     OTP_UINT8 addr_len, OTP_UINT8 *pSendData, OTP_UINT8 sendLen)
{
    OTP_UINT8 i =0;
    OTP_UINT8 temp[3];
    
    temp[0] = i2cAddr & I2C_WRITE;
    if(2 == addr_len)
    {    
        temp[1] = addr >> 8;
    }
    else
    {
        temp[1] = addr;
    }    
    temp[2] = addr;
    
    i2c_stop(id);
    i2c_start(id);
    
    for(i = 0; i < addr_len + 1; i++)
    {
        if( OK != i2c_write_byte(id, temp[i]))
        {
            ASSERT(0);
            i2c_stop(id);  
            return ERROR;
        }    
    }    
    
        
    while (sendLen--)
    {   
        if( OK != i2c_write_byte(id, *pSendData++) )
        {
            ASSERT(0);
            i2c_stop(id); 
            return ERROR;
        }
    }
    
    i2c_stop(id);
    
    return(OK);

}

     当读从器件寄存器时,I2C总线上需要先发乘客器件的从机写地址,再写入寄存器的地址,产生重复超始条件,发送器件从机读地址,最后读取寄存器的数据,根据这种应用,编写一个向I2C从器件写寄存器的函数i2c_recv_data,这个函数调用上面介绍的函数来实现,时序图如下图所示,注意这个函数实现的I2C操作时序要复杂一些,当读取到最后一个字节后,还要额外再读取一个字节来给从机产生非应答位来通信从机读取完成, 不用再向总线上传输数据。


/*********************************************************************************************************
** Function name:      i2c_rcv_data()
** Descriptions:        从i2c总线读取数据
** input parameters:   id--操作的I2C端口
**                      Addr--地址
**                      pSendData--缓冲区地址
**                      sendLen--数据长度
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
STATUS i2c_rcv_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,   OTP_UINT16 addr,  
                    OTP_UINT8 addr_len, OTP_UINT8 *pRcvData, OTP_UINT8 rcvLen)
{
    OTP_UINT8 buf[3];
    OTP_UINT8 i = 0;
    
    buf[0] = i2cAddr & I2C_WRITE;
    if(2 == addr_len)
    {    
        buf[1] = addr >> 8;
    }
    else
    {
        buf[1] = addr;
    } 
    buf[2] = addr;
    
     
    i2c_start(id);
    
    /*发送读地址*/
    for(i = 0; i < addr_len + 1; i++)
    {
        if( OK != i2c_write_byte(id, buf[i]))
        {
            ASSERT(0);
            i2c_stop(id);  
            return ERROR;
        }      
    
    }    
    
    
    i2c_start(id);
    /*发送读取命令*/
    if(OK != i2c_write_byte (id, i2cAddr | I2C_READ))
    {
        /*ASSERT(0);*/
        i2c_stop(id); 
        return ERROR;
    }

    while (rcvLen--)
    {
        *pRcvData++ = i2c_read_byte(id);
        i2c_send_ack(id);
    }
    /*多读取一个数据,用于产生非应答信号*/
    (OTP_VOID)i2c_read_byte(id);
    i2c_send_no_ack(id);
    
    i2c_stop(id);
    
    return OK;
}

3.6  头文件及功能配置

        到此,已经讲解完了I2C软件模拟的全部驱动程序了,下面贴出驱动程序中所使用的相关宏定义的头文件。

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

/*********************************************************************************************************
*                定义软件I2C接口数目
*********************************************************************************************************/
#define     I2C_NUM  2 

/*********************************************************************************************************
*                定义I2C驱动管脚
*********************************************************************************************************/
#define I2C0_SOFT_SCL_PORT_PERIPH  RCC_APB2Periph_GPIOA
#define I2C0_SOFT_SCL_PORT         GPIOA
#define I2C0_SOFT_SCL              GPIO_Pin_11 

#define I2C0_SOFT_SDA_PORT_PERIPH  RCC_APB2Periph_GPIOA
#define I2C0_SOFT_SDA_PORT         GPIOA
#define I2C0_SOFT_SDA              GPIO_Pin_12

#define I2C1_SOFT_SCL_PORT_PERIPH  RCC_APB2Periph_GPIOB
#define I2C1_SOFT_SCL_PORT         GPIOB
#define I2C1_SOFT_SCL              GPIO_Pin_6 

#define I2C1_SOFT_SDA_PORT_PERIPH  RCC_APB2Periph_GPIOB
#define I2C1_SOFT_SDA_PORT         GPIOB
#define I2C1_SOFT_SDA              GPIO_Pin_7



/*********************************************************************************************************
*                定义软件I2C接口速率
*********************************************************************************************************/
#define    I2C_SPEED         50000                                   /*定义I2C速率为100K                */
#define    I2C_DELAY_TIME    (CPU_FREQ/I2C_SPEED/20)                 /*定义I2C时钟延时时间,半个时钟周期 */

/*********************************************************************************************************
*                定义软件I2C接口读写命令
*********************************************************************************************************/
#define    I2C_WRITE    0xFE
#define    I2C_READ     0x01



typedef struct S_I2C_IO_STRUCT
{
    GPIO_TypeDef   *sclPort;        /*SCL的port口*/
    OTP_UINT16      sclPin;         /*SCL的pin*/
    GPIO_TypeDef   *sdaPort;        /*SDA的port口*/
    OTP_UINT16      sdaPin;        /*SDA的pin*/
}S_I2C_IO;


/*********************************************************************************************************
** Function name:      i2c_init()
** Descriptions:        I2C使用的IO管脚初始化函数
** input parameters:   无
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
OTP_VOID i2c_init(void);


/*********************************************************************************************************
** Function name:      i2c_rcv_data()
** Descriptions:        从i2c总线读取数据
** input parameters:   id--操作的I2C端口
**                      Addr--地址
**                      pSendData--缓冲区地址
**                      sendLen--数据长度
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
STATUS i2c_rcv_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,    OTP_UINT16 addr,  
                    OTP_UINT8 addr_len, OTP_UINT8 *pRcvData, OTP_UINT8 rcvLen);

/*********************************************************************************************************
** Function name:      i2c_send_data()
** Descriptions:        向i2c总线发送数据
** input parameters:   id--操作的I2C端口
**                      i2cAddr--器件地址
**                      addr--器件内部地址
**                      addr_len--内部地址长度
**                      pSendData--缓冲区地址
**                      sendLen--数据长度
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
STATUS i2c_send_data(OTP_UINT8 id, OTP_UINT8 i2cAddr, OTP_UINT16 addr,  
                     OTP_UINT8 addr_len, OTP_UINT8 *pSendData, OTP_UINT8 sendLen);

          上面的头文件还有一些宏定义是引用软件模拟I2C驱动程序之外的头文件,在这里做一个说明。

/*定义CPU的工作频率, STM32主频工作频率设置为72MHz*/
#define    CPU_FREQ                  72000000


/*数据结构重定义*/

typedef char            OTP_CHAR;   /* 不保证是否有符号*/
typedef void            OTP_VOID;                                       
typedef signed char     OTP_INT8;   /* 有符号8位 */
typedef unsigned char   OTP_UINT8;  /* 无符号8位 */
typedef signed short    OTP_INT16;  /* 有符号16位 */
typedef unsigned short  OTP_UINT16; /* 无符号16位 */
typedef signed int      OTP_INT32;  /* 有符号32位 */
typedef unsigned int    OTP_UINT32; /* 无符号32位 */

/*与定义成了long的移植代码对接,显式说明长度为32位*/
typedef signed long     OTP_LONG32; /* 有符号32位 */
typedef unsigned long   OTP_ULONG32;    /* 无符号32位 */
typedef signed long long    OTP_INT64;  /* 有符号64位 */
typedef unsigned long long OTP_UINT64;  /* 无符号64位 */

/* 8位寄存器类型 */
typedef volatile OTP_UINT8 OTP_REG8;

/* 指令 */
typedef unsigned int    OTP_INSTRUCT;


/* 局部变量或局部函数修饰符 */
#ifndef LOCAL
#define LOCAL           static
#endif

#ifndef IMPORT
#define IMPORT          extern
#endif


/* OK ERROR 与 STATUS 一起使用*/
#ifndef STATUS
#define STATUS int 
#endif
#ifndef OK
#define OK              0
#endif
#ifndef ERROR
#define ERROR           (-1)
#endif

4、使用软件模拟I2C驱动程序读写EEPROM

         上面我们已经自己编写了一个软件模拟I2C驱动程序,下面就来实现一个EEPROM  AT24C64的读写函数。



/*AT24Cxx 使用第几个I2C id*/
#define AT24CXX_I2C_ID		0
#define AT24C08_I2C_ADDR	0xA0
#define AT24C64_I2C_ADDR	0xA0


/*定义页大小*/
#define AT24C08_PAGE_SIZE   16
#define AT24C64_PAGE_SIZE   32

/*********************************************************************************************************
** Function name:      at24cxx_delay()
** Descriptions:        AT24Cxx操作延时函数
** input parameters:   无
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
LOCAL OTP_VOID at24cxx_delay(OTP_UINT8 time)
{
    OTP_UINT16 i = 0;
    OTP_UINT16 j = 0;

    for( i = 0; i < time; i++ )
    {
        /*主频33M,延时1ms要执行4800次循环*/
        for(j = 0; j < 4800; j++)
        {
        }
    }
}


/*********************************************************************************************************
** Function name:      at24c64_read()
** Descriptions:        AT24C64读操作函数
** input parameters:   无
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
STATUS  at24c64_read(OTP_UINT16 addr, OTP_UINT8 *pBuf, OTP_UINT16 len)
{
    OTP_UINT16  pageleft = 0;   

    /*ASSERT(NULL != pBuf);*/
    if(NULL==pBuf)
    {
        ASSERT(0);
        return ERROR;
    }
   
    while(len)
    {
        /*  当前页还有多少空间可以读   */
        pageleft = AT24C64_PAGE_SIZE - (addr & (AT24C64_PAGE_SIZE - 1));

        /*  当前页可以读出多少字节的数据*/
        pageleft = (pageleft >= len) ? len : pageleft;

       
        /*读出数据*/
        if( OK != i2c_rcv_data(AT24CXX_I2C_ID,
                               AT24C64_I2C_ADDR, addr, sizeof(addr),
                               pBuf, pageleft)) 

        
        {
            ASSERT(0);
            return ERROR;
        }

        len = len - pageleft;
        addr = addr + pageleft;
        pBuf = pBuf + pageleft;
        
     }

     return OK;
     
}

/*********************************************************************************************************
** Function name:      at24c64_write()
** Descriptions:        AT24C64读操作函数
** input parameters:   无
** output parameters:  无
** Returned value:      无
*********************************************************************************************************/
STATUS at24c64_write(OTP_UINT16 addr, OTP_UINT8 *pBuf, OTP_UINT16 len)
{
    OTP_UINT16   pageleft = 0;  


    if(NULL == pBuf)
    {
        return ERROR;
    }
    
    
    while(len)
    {
        /*  当前页还有多少空间可以读   */
        pageleft = AT24C64_PAGE_SIZE - (addr & (AT24C64_PAGE_SIZE - 1));

        /*  当前页可以读出多少字节的数据*/
        pageleft = (pageleft >= len) ? len : pageleft;


        /*写入数据*/
        if(OK != i2c_send_data(AT24CXX_I2C_ID, AT24C64_I2C_ADDR, addr, sizeof(addr), pBuf, pageleft))
        {
            return ERROR;
        }



        len = len - pageleft;
        addr = addr + pageleft;
        pBuf = pBuf + pageleft;
        /*写完一页数据延时10ms,等待其写入*/
        at24cxx_delay(10);
     }

     
     return OK;

}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章