I2C总线详解

article/2025/11/7 2:25:30

1.1 I2C总线知识

1.1.1  I2C总线物理拓扑结构
 
    I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

1.1.2  I2C总线特征
    I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
    I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。
    I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
    I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输

1.1.3  I2C总线协议
    I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。如图所示:

    在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位,此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。数据传输的过程如图所示:
 
    在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。如图所示:
 
1.1.4  I2C总线操作
    对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:
    第一,主设备往从设备中写数据。数据传输格式如下:
    


    第二,主设备从从设备中读数据。数据传输格式如下:
    

    第三,主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:

    第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。

1.2 I2C总线硬件接口电路示例

1.2.1 I2C总线硬件接口电路示例一
 
    这个电路是基于LPC2368 ARM7芯片进行设计的,使用其内部的I2C接口作为主设备,使用ADT75和SC16IS740作为两个从设备的I2C总线应用。

    ADT75是一个带I2C接口的温度传感器器件,数据手册上对其地址的描述如下:
     
    由此,其地址跟A0、A1、A2引脚的接法有关,我们这里的实例是将A0、A1、A2全部接到高电平上,因此其地址是:1001111(即0x4F),又因根据协议再给地址添加一个最低位(方向位,默认给写方向),因此最后这个温度传感器作为从设备的地址是:10011110(即0x9E)。

    SC16IS740是一个具有I2C或者SPI接口的扩展UART的器件(通过第8脚来决定使用I2C还是SPI接口,我们这里要求使用I2C接口,因此将第8脚接到高电平)。根据数据手册,我们同样的可以知道地址跟A0、A1的接法有关,我们这里的A0接高电平,A1接低电平。因此这个器件作为从设备的地址是:10010010(即0x92)。

1.2.2 I2C总线硬件接口电路示例二


 
    这个电路是Mini2440开发板上I2C总线接口的应用。我们可以看到,SDA和SCL线上接了一个10K的上拉排阻。AT24C08是一个容量为8Kbit的EEPROM存储器件(注意是8Kbit,也就是1KB) ,根据数据手册中器件地址部分的描述,AT24C08的地址是:1010+A2A1A0+方向位,其中1010是EEPROM的类型识别符;仅仅使用A2来确定总线访问本器件的从设备地址,这里接的低电平,所以为0;A1和A0是器件内部页地址,在对器件擦除或者编程时使用,虽然这里也接的低电平,但器件内部并不使用引脚的输入值,也就是说A1和A0的值是由软件进行设定的。

1.3 脱离操作系统的I2C总线驱动示例(以电路示例一为例)

1.3.1 LPC2368中I2C接口寄存器描述
    LPC2368中有三个I2C总线接口,分别表示为I2C0、I2C1和I2C2,每个I2C接口都包含7个寄存器。它们分别是:
I2C控制置位寄存器(I2CONSET): 8位寄存器,各位不同的设置是对I2C总线不同的控制。

 

符号
描述
复位值
1:0
-
保留,用户软件不要向其写入1。从保留位读出的值未被定义
NA
2
AA
声明应答标志。为1时将为需要应答的情况产生一个应答
0
3
SI
I2C中断标志。当I2C状态改变时该位置位
0
4
STO
总线停止条件控制。1发出一个停止条件,当总线检测到停止条件时,STO自动清零
0
5
STA
总线起始条件控制。1进入主模式并发出一个起始条件
0
6
I2EN
总线使能控制。1为使能
0
7
-
保留,用户软件不要向其写入1。从保留位读出的值未被定义
NA

 


I2C控制清零寄存器(I2CONCLR): 8位寄存器,对I2CONSET寄存器中的相应为清零。

 

符号
描述
复位值
1:0
-
保留,用户软件不要向其写入1。从保留位读出的值未被定义
NA
2
AAC
声明应答标志清零位。向该位写入1清零I2CONSET寄存器中的AA
0
3
SIC
中断标志清零位。向该位写入1清零I2CONSET寄存器中的SI
0
4
-
保留,用户软件不要向其写入1。从保留位读出的值未被定义
NA
5
STAC
起始条件清零位。向该位写入1清零I2CONSET寄存器中的STA
0
6
I2ENC
总线禁能控制。写入1清零I2CONSET寄存器中的I2EN
0
7
-
保留,用户软件不要向其写入1。从保留位读出的值未被定义
NA

 


I2C状态寄存器(I2STAT): 8位只读寄存器,用于监控总线的实时状态(可能存在26种状态)。
 
符号
描述
复位值
2:0
-
3个位不使用且总是为0 
0
7:3
Status
这些位给出I2C接口的实时状态,不同的值代表不同的状态,状态码请参考数据手册
0x1F

I2C数据寄存器(I2DAT): 8位寄存器,在SI置位期间,I2DAT中的数据保持稳定。
 
符号
描述
复位值
7:0
Data
该寄存器保留已经接收到或者准备要发送的数据值 
0

I2C从地址寄存器(I2ADR): 8位寄存器,I2C总线为从模式时才使用。主模式中该寄存器无效。
 
符号
描述
复位值
0
GC
通用调用使能位 
0
7:1
Address
从模式的I2C器件地址
0x00

SCH占空比寄存器(I2SCLH): 16位寄存器,用于定义SCL高电平所保持的PCLK周期数。
 
符号
描述
复位值
15:0
SCLH
SCL高电平周期选择计数
0x0004

SCL占空比寄存器(I2SCLL): 16位寄存器,用于定义SCL低电平所保持的PCLK周期数。
 
符号
描述
复位值
15:0
SCLL
SCL低电平周期选择计数
0x0004

    在前面的I2C总线特征中我们提到过,I2C总线的速率通过可编程时钟来调整,即必须通过软件对I2SCLH和I2SCLL寄存器进行设置来选择合适的数据频率和占空比。 频率由下面的公式得出(fPCLK是PCLK的频率)。
 

1.3.2 LPC2368中I2C总线操作
    在1.1.4中我们已经讲过了对I2C总线的操作,但那只是从协议和时序上的描述,那我们如何从软件上去体现出来呢?接下来我们就讨论这个问题。
    对I2C总线上主从设备的读写可使用两种方法,一是使用轮询的方式,二是使用中断的方式。轮询方式即是在一个循环中判断I2C状态寄存器当前的状态值来确定总线当前所处的状态,然后根据这个状态来进行下一步的操作。中断方式即是使能I2C中断,注册I2C中断服务程序,在服务程序中读取I2C状态寄存器的当前状态值,再根据状态值来确定下一步的操作。
    不管使用哪种方法,看来I2C状态寄存器的值是至关重要的。这些状态值代表什么意思呢?下面我们描述一些常用的状态值(详细的状态值含义请参考数据手册)。


0x08: 表明主设备向总线已发出了一个起始条件;
0x10: 表明主设备向总线已发出了一个重复的起始条件;
0x18: 表明主设备向总线已发送了一个从设备地址(写方向)并且接收到从设备的应答;
0x20: 表明主设备向总线已发送了一个从设备地址(写方向)并且接收到从设备的非应答;
0x28: 表明主设备向总线已发送了一个数据字节并且接收到从设备的应答;
0x30: 表明主设备向总线已发送了一个数据字节并且接收到从设备的非应答;
0x40: 表明主设备向总线已发送了一个从设备地址(读方向)并且接收到从设备的应答;
0x48: 表明主设备向总线已发送了一个从设备地址(读方向)并且接收到从设备的非应答;
0x50: 表明主设备从总线上已接收一个数据字节并且返回了应答;
0x58: 表明主设备从总线上已接收一个数据字节并且返回了非应答;

1.3.3 示例代码
一、 轮询方式读写总线:

    对于代码中从设备内部寄存器的操作请参考该设备的数据手册。例如,要读取温度传感器的温度值只需要调用:I2C0_ReadRegister(CHANNEL_TEMPERATURE, ADT75A_TEMP, &value),如果读取成功,则value中的数据就是通过I2C总线读取温度传感器中的温度数据。

二、 中断方式读写总线:
    这里的从设备地址定义、I2C控制寄存器宏定义和I2C初始化与上面轮询中的类似,只是要在初始化函数中加上中断申请的代码,中断服务程序名称为:I2C0_Exception。这里不再贴出以上代码了,这里只贴出关键性的代码。

/*定义I2C状态标志*/
typedef enum
{
    I2C_IDLE = 0,
    I2C_STARTED = 1,
    I2C_RESTARTED = 2,
    I2C_REPEATED_START = 3,
    I2C_DATA_ACK = 4,
    I2C_DATA_NACK = 5
} I2C_STATUS_FLAG;

/*定义I2C数据传输缓冲区大小和传输超时大小*/
#define I2C_BUFSIZE 0x200
#define I2C_TIMEOUT 0x00FFFFFF

/*定义I2C当前状态标志*/
volatile I2C_STATUS_FLAG I2C_Flag;

/*I2C当前的模式,0为主发送器模式,1为主接收器模式*/
volatile uint32 I2CMasterMode = 0;

/*分别定义I2C接收和发送缓冲区、要发送或要接收的字节数、实际发送或接收的字节数*/
volatile uint8 I2CReadBuf[I2C_BUFSIZE], I2CWriteBuf[I2C_BUFSIZE];
volatile uint32 I2CReadLength, I2CWriteLength;
volatile uint32 I2C_RD_Index, I2C_WR_Index;

/****************************************************************************
** Function name: I2C0_Exception
** Descriptions : I2C0中断服务程序
** Input : 无
** Output : 无
** Created Date : 2011-03-24 
*****************************************************************************/

void I2C0_Exception(void)
{
    volatile uint32 stat_value;
    stat_value = I20STAT;

    switch(stat_value)
    {
        case 0x08:
            /*发出了一个起始条件,接下来将发送从地址然后清零SI位和STA位*/
            I2C_Flag = I2C_STARTED;
            I20DAT = I2CWriteBuf[I2C_WR_Index];
            I2C_WR_Index++;
            I20CONCLR = I2C_STA | I2C_SI;
            break;
        case 0x10:
            /*一个重复的起始条件发送完成,接下来要将发送从地址然后清零SI位和STA位*/
            I2C_Flag = I2C_RESTARTED;
            if(I2CMasterMode == 1)
            {
                /*注意I2CWriteBuf中的第0位是设备从地址和写方向位,因这里是读操作,故将第0位的方向位变为读*/
                I20DAT = I2CWriteBuf[0] | 0x01;
            }
            I20CONCLR = I2C_STA | I2C_SI;
            break;
        case 0x18 /*(注:SLA+W表示从设备地址+写方向)*/
            /*发送SLA+W后已接收到ACK,接下来开始发送数据字节到数据寄存器然后清零SI位*/
            if(I2C_Flag == I2C_STARTED)
            {
                I2C_Flag = I2C_DATA_ACK;
                I20DAT = I2CWriteBuf[I2C_WR_Index];
                I2C_WR_Index++;
            }
            I20CONCLR = I2C_SI;
            break;
        case 0x28:
            /*此状态表明已发送I2DAT中的字节且接收到ACK,接下来继续发送下一个字节*/
        case 0x30:
            /*已发送I2DAT中的字节且接收到非ACK,接下来可能发出停止条件或重启起始条件*/
            if(I2C_WR_Index != I2CWriteLength)
            {
                /*实际发送的字节数与要发送的不相等则继续发送,但可能是最后一次*/
                I20DAT = I2CWriteBuf[I2C_WR_Index];
                I2C_WR_Index++;

                if(I2C_WR_Index != I2CWriteLength)
                {
                    I2C_Flag = I2C_DATA_ACK;
                }
                else
                {
                    /*如果实际发送与要发送的相等了,表明主发送端数据发送完成*/
                    I2C_Flag = I2C_DATA_NACK;

                    if(I2CReadLength != 0)
                    {
                        /*如果主发送端有等待接收的字节,则切换为主接收模式,重启起始条件*/
                        I2C_Flag = I2C_REPEATED_START;
                        I20CONSET = I2C_STA | I2C_SI;
                    }
                }
            }
            else
            {
                /*如果实际发送与要发送的相等了,表明主发送端数据发送完成*/
                I2C_Flag = I2C_DATA_NACK;

                if(I2CReadLength != 0)
                {
                    /*如果主发送端有等待接收的字节,则表明需切换为主接收模式,重启起始条件*/
                    I2C_Flag = I2C_REPEATED_START;
                    I20CONSET = I2C_STA;
                }
            }
            I20CONCLR = I2C_SI;
            break;
        case 0x40:
            /*此状态表明已发送SLA+R后已接收到ACK*/
            I20CONCLR = I2C_SI;
            break; 
        case 0x50:
            /*此状态表明已接收数据字节后已接收到ACK*/
        case 0x58:
            /*此状态表明已接收数据字节后已接收到非ACK*/
            I2CReadBuf[I2C_RD_Index] = I20DAT;
            I2C_RD_Index++;

            if(I2C_RD_Index != I2CReadLength)
            {
                /*如果实际接收的字节与要接收的不相等,则继续接收*/
                I2C_Flag = I2C_DATA_ACK;
            }
            else
            {
                /*否则接收完毕*/
                I2C_RD_Index = 0;
                I2C_Flag = I2C_DATA_NACK;
            }
            I20CONCLR = I2C_AA | I2C_SI;
            break;
        case 0x20:
            /*此状态表明已发送SLA+W后已接收到非ACK*/
        case 0x48:
            /*此状态表明已发送SLA+R后已接收到非ACK*/
            I2C_Flag = I2C_DATA_NACK;
            I20CONCLR = I2C_SI;
            break;
        default:
            I20CONCLR = I2C_SI;
            break;
    }

    VICVectAddr = 0x00; 
}

/****************************************************************************
** Function name: I2C0_Start
** Descriptions : 设置I2C0总线传输起始条件
** Input : 无
** Output : 返回TRUE/FALSE, FALSE为设置超时
** Created Date : 2011-03-24 
*****************************************************************************/

BOOL I2C0_Start(void)
{
    uint32 timeout = 0;
    BOOL retVal = FALSE;

    /*设置配置寄存器STA位开始条件*/
    I20CONSET = I2C_STA | I2C_SI;
    I20CONCLR = I2C_SI;

    /*等待起始条件完成*/
    while(1)
    {
        if(I2C_Flag == I2C_STARTED)
        {
            retVal = TRUE;
            break; 
        }

        if(timeout >= I2C_TIMEOUT)
        {
            retVal = FALSE;
            break;
        }

        timeout++;
    }

    return retVal;
}

/****************************************************************************
** Function name: I2C0_Stop
** Descriptions : 设置I2C0总线传输停止条件
** Input : 无
** Output : 返回TRUE
** Created Date : 2011-03-24 
*****************************************************************************/

BOOL I2C0_Stop(void)
{
    /*设置配置寄存器STO位停止条件和清除SI标志*/
    I20CONSET = I2C_STO; 
    I20CONCLR = I2C_SI; 

    /*等待停止条件完成*/
    while(I20CONSET & I2C_STO);

    return TRUE;
}

/****************************************************************************
** Function name: I2C0_Engine
** Descriptions : 完成I2C0总线从开始到停止的传输,传输过程在中断服务程序中进行
** Input : 无
** Output : 返回TRUE/FALSE
** Created Date: 2011-03-24 
*****************************************************************************/

BOOL I2C0_Engine(void)
{
    I2C_Flag = I2C_IDLE;
    I2C_RD_Index = 0;
    I2C_WR_Index = 0;

    if(I2C0_Start() != TRUE)
    {
        I2C0_Stop();

        return FALSE;
    }

    while(1)
    {
        if(I2C_Flag == I2C_DATA_NACK)
        {
            I2C0_Stop();
            break;
        }
    } 

    return TRUE;
}


    从上面代码中看,如果要使用I2C总线启动一次数据传输只需要先初始化好发送或接收缓冲区,然后调用I2C0_Engine()函数即可。如下代码所示:

/****************************************************************************
** Function name: I2C0_ReadWriteTransmission
** Descriptions : I2C总线数据读写传输
** Input : read_buf-读数据缓冲区
                  read_len-读数据长度
                  write_buf-写数据缓冲区
                  write_len-写数据长度
** Output : 数据读写传输是否成功
** Created Date : 2011-03-24 
*****************************************************************************/

BOOL I2C0_ReadWriteTransmission(uint8 **read_buf, uint32 read_len, uint8 *write_buf, uint32 write_len)
{
    uint32 i;
    BOOL result = FALSE;

    /*数据传输长度检查*/
    if(read_len > I2C_BUFSIZE || write_len > I2C_BUFSIZE)
    {
        return FALSE;
    }

    /*清空I2C接收和发送缓冲区内容*/
    for(= 0; i < I2C_BUFSIZE; i++)
    {
        I2CReadBuf[i] = 0;
        I2CWriteBuf[i] = 0;
    }

    /*确定I2C总线模式(0为主发送模式,1为主接收模式)*/
    I2CMasterMode = (read_len == 0)? 0 : 1;
    I2CReadLength = read_len;
    I2CWriteLength = write_len;

    /*要写入I2C从设备的数据(第一个字节包含从设备地址和方向位)*/
    for(= 0; i < write_len; i++)
    {
        I2CWriteBuf[i] = write_buf[i];
    }

    /*启动I2C传输*/
    result = I2C0_Engine();

    /*如果有向从设备读取的数据*/
    if(read_len > 0 && result == TRUE)
    {
        uint8 *buf = (uint8 *)malloc(read_len * sizeof(uint8));

        for(= 0; i < read_len; i++)
        {
            uf[i] = I2CReadBuf[i];
        }

        *read_buf = buf;
    }

    return result;
}

1.4 Linux下I2C子系统框架
    在Linux下要使用I2C总线并没有像无系统中的那样简单,为了体现Linux中的模块架构,Linux把I2C总线的使用进行了结构化。这种结构分三部分组成,他们分别是:I2C核心部分、I2C总线驱动部分和I2C设备驱动。结构图如下:
           
    由此看来,在Linux下驱动I2C总线不像单片机中那样简单的操作几个寄存器了,而是把I2C总线结构化、抽象化了,符合通用性和Linux设备模型。

/*I2C从设备地址*/
#define SC16IS740_ADDR 0x92 /*I2C转UART设备*/
#define ADT75A_ADDR 0x9E /*温度传感器设备*/
#define ADT75A_TEMP 0x00 /*温度传感器内部寄存器*/

/*从设备选择标识*/
#define CHANNEL_GPRS 0
#define CHANNEL_TEMPERATURE 1

/*定义I2C控制寄存器各位操作宏*/
#define BIT(x) (<< x)
#define I2C_EN BIT(6)
#define I2C_STA BIT(5)
#define I2C_STO BIT(4)
#define I2C_SI BIT(3)
#define I2C_AA BIT(2)

/*用作超时计数*/
#define SAFETY_COUNTER_LIMIT 3000

/******************************************************************
** Function name: I2C0_Init
** Descriptions : I2C0初始化
** Input : 无
** Output : 无
** Created Date : 2011-03-24 
*******************************************************************/

void I2C0_Init(void)
{
    /*设置P0.0,P0.1为I2C0接口的SDA和SCL功能*/
    PINSEL0 |= (0x03 << 0) | (0x03 << 2);

    /*设置I2C0接口功率/时钟控制位*/
    PCONP |= (0x01 << 7 );

    /*清空I2C0配置寄存器的各位*/
    I20CONCLR = (0x01 << 2) | (0x01 << 3) | (0x01 << 5) | (0x01 << 6);

    /*使能I2C0为主发送器模式*/
    I20CONSET = (0x01 << 6);

    /*设置I2C0总线速率为100 KHz */
    I20SCLH = 0x5A;
    I20SCLL = 0x5A;
}

/****************************************************************************
** Function name: I2C0_ReadRegister
** Descriptions : 从I2C0总线上读从设备的数据
** Input : 从设备选择标识、从设备内部寄存器地址、读出的字节数据
** Output : 读取是否成功
** Created Date : 2011-03-28 
*****************************************************************************/

BOOL I2C0_ReadRegister(uint32 channel, uint8 registerAddress, uint8 *pData)
{
    /*用作延时等待计数*/
    uint32 loopSafetyCounter = 0;
    uint32 addressSendSafetyCounter = 0; 

    /*使用循环判断I2C状态寄存器I20STAT 的值*/
    do
    {
        /*向总线发送I2C起始条件*/
        I20CONSET = I2C_STA | I2C_SI;
        I20CONCLR = I2C_SI;

        /*等待起始条件发送完成*/
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; /*超时退出*/
            }
        }

        /*发送从设备地址*/
        if(channel == CHANNEL_GPRS)
            I20DAT = SC16IS740_ADDR;
        else if(channel == CHANNEL_TEMPERATURE)
            I20DAT = ADT75A_ADDR;

        I20CONCLR = I2C_STA | I2C_SI;

        /*等待从设备地址发送完成*/
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; /*超时退出*/
            }
        }

        addressSendSafetyCounter ++;
        if (addressSendSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }

    } while (I20STAT != 0x18); /*在前面已经描述了0x18的含义*/

    /*发送从设备内部寄存器地址,根据数据手册描述该内部地址要左移3位*/
    I20DAT = registerAddress << 3; 
    I20CONCLR = I2C_SI; 

    /*等待从设备内部寄存器地址发送完成*/
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    /*重启I2C起始条件进行总线读*/
    I20CONSET = I2C_STA | I2C_SI;
    I20CONCLR = I2C_SI;

    /*等待重启条件发送完成*/
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    /*发送从设备地址(方向位为读,注意与上0x01将地址最低位变为1即为读方向)*/
    if(channel == CHANNEL_GPRS)
        I20DAT = SC16IS740_ADDR | 0x01;
    else if(channel == CHANNEL_TEMPERATURE)
        I20DAT = ADT75A_ADDR | 0x01;

    I20CONCLR = I2C_STA | I2C_SI;

    /*等待从设备地址发送完成*/
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    /*开始准备读取数据*/
    I20CONCLR = I2C_SI | I2C_AA; 

    /*等待数据接收*/
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    /*数据接收*/
    *pData = I20DAT; 

    /*发送I2C停止条件*/
    I20CONSET = I2C_STO;
    I20CONCLR = I2C_SI;

    /*等待停止条件发送完成*/
    loopSafetyCounter = 0;
    while (I20CONSET & I2C_STO)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    return TRUE;
}

/****************************************************************************
** Function name: I2C0_WriteRegister
** Descriptions : 从I2C0总线上写从设备的数据
** Input : 从设备选择标识、从设备内部寄存器地址、要写入的数据字节
** Output : 写入是否成功
** Created Date : 2011-03-28 
*****************************************************************************/

BOOL I2C0_WriteRegister(uint32 channel, uint8 registerAddress, uint8 data)
{
    uint32 loopSafetyCounter = 0;
    uint32 addressSendSafetyCounter = 0;

    /*使用循环判断I2C状态寄存器I20STAT 的值*/
    do
    {
        /*向总线发送I2C起始条件*/
        I20CONSET = I2C_STA | I2C_SI;
        I20CONCLR = I2C_SI;

        /*等待起始条件发送完成*/
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; /*超时退出*/
            }
        }

        /*发送从设备地址*/
        if(channel == CHANNEL_GPRS)
            I20DAT = SC16IS740_ADDR;
        else if(channel == CHANNEL_TEMPERATURE)
            I20DAT = ADT75A_ADDR;

        I20CONCLR = I2C_STA | I2C_SI;

        /*等待从设备地址发送完成*/
        loopSafetyCounter = 0;
        while (~I20CONSET & I2C_SI)
        {
            loopSafetyCounter ++;
            if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
            {
                return FALSE; /*超时退出*/
            }
        }

        addressSendSafetyCounter ++;
        if (addressSendSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }

    } while (I20STAT != 0x18); 

    /*发送从设备内部寄存器地址*/
    I20DAT = registerAddress << 3; 
    I20CONCLR = I2C_SI; 

    /*等待寄存器地址发送完成*/
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    /*开始发送数据*/
    I20DAT = data; 
    I20CONCLR = I2C_SI; 

    /*等待数据发送完成*/
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    /*发送I2C停止条件*/
    I20CONSET = I2C_STO;
    I20CONCLR = I2C_SI;

    /*等待停止条件发送完成*/
    loopSafetyCounter = 0;
    while (I20CONSET & I2C_STO)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; /*超时退出*/
        }
    }

    return TRUE;
}


http://chatgpt.dhexx.cn/article/IjhJ01lr.shtml

相关文章

【i2c协议介绍】

文章目录 协议简单介绍五种速度模式master/slave和transmitter/receiver关系第一种情况&#xff1a;master作为transmitter&#xff0c;slave作为receiver第二种情况&#xff1a;当master作为receiver&#xff0c;slave作为transmitter i2c基本信号start产生stop信号数据传输有…

I2C详解(一)

I2C Bus(Inter-Integrated Circuit Bus) 最早是由Philips半导体&#xff08;现被NXP收购&#xff09;开发的两线时串行总线&#xff0c;常用于微控制器与外设之间的连接。要想了解详细I2C协议&#xff0c;目前最新的I2C标准协议是2014年第6版本&#xff0c;如下&#xff1a; I2…

I2C详细介绍

1、I2C总线具有两根双向信号线&#xff0c;一根是数据线SDA&#xff0c;另一根是时钟线SCL 2、IIC总线上可以挂很多设备&#xff1a;多个主设备&#xff0c;多个从设备&#xff08;外围 设备&#xff09;。上图中主设备是两个单片机&#xff0c;剩下的都是从设备。 3、多主机…

OLED显示屏I2C接口

简介 本文将介绍两种不同尺寸的OLED显示屏&#xff1a;0.96寸和0.91寸。他们都是4引脚I2C接口的&#xff0c;如下图所示&#xff1a; 注&#xff1a;经过项目测试发现&#xff0c;本文介绍的驱动方式和代码也完全适用于 1.54寸SSD1309主控的1.54寸OLED和1.29寸CH1115主控的OLE…

关于I2C接口的介绍

I 2C:Inter-Integrated Circuit 内部集成电路,应该翻译为集成电路总线,IC的正确读法为“I方C”。 这种通信协议由Philip最早提出,一种由数据线SDA和时钟线SCL两根信号线组成的串行通信总线,具体规范可以在NXP网站找到v2.1。不像UART没有CLOCK线,不能实现同步,I2C和SPI都…

【原创】【I2C】I2C介绍

一、这是个什么玩意 I2C首先它也是一个通信接口&#xff0c;同理通信接口就是用于模块之间的通信的&#xff0c;同SPI接口学习思路一样&#xff0c;首先明白它是一个接口&#xff0c;然后再扣它是一个怎么样的接口。 I2C(Inter&#xff0d;Integrated Circuit)总线是由Philips公…

I2C接口

一、I2C总线协议内容 1. I2C总线引脚定义 SDA (I2C数据引脚) CLK (I2C数据引脚) 2. I2C总线物理连接 I2C总线物理连接如下图所示&#xff0c;SDA和CLK连接线上连有两个上拉电阻&#xff0c;当总线空闲时&#xff0c;两根线均为高电平。连到总线上的任一器件输出的低电平&am…

“如何成为阿里云P8架构师?“ ”当然是考取阿里云新版ACE认证啊”

**简介&#xff1a;**阿里云新版云计算架构师ACE认证全面重构上线&#xff01;为建立云计算生态领域含金量第一的专家级人才标准和认证体系&#xff0c;影响泛云生态高层次技术人才&#xff0c;阿里云历时一年&#xff0c;组织近百位专家&#xff0c;对云计算架构师ACE认证进行…

阿里云认证(ACA/ACP/ACE)的分类以及官网价格

阿里云认证分类&#xff1a; 阿里云认证分为三个等级&#xff0c;分别为初级(ACA)、中级(ACP)、高级&#xff08;ACE&#xff09; 下面还有很多的子分类 在子分类中&#xff0c;云计算是认证中最受欢迎的&#xff0c;无论是ACA云计算&#xff0c;还是ACP云计算&#xff0c;都…

阿里云ACA、ACP、ACE认证考试区别,报名入口及模拟试题分享

阿里云认证考试包含ACA、ACP、ACE三种认证类型&#xff0c;是阿里云针对不同产品类别、用户成长阶段、生态岗位&#xff0c;精心打造不同的认证考试。获得阿里云认证考试证书能够基于阿里云产品解决实际问题。获得更多阿里云生态下的就业机会。 助理工程师&#xff08;ACA&…

成为阿里云架构师的进阶之路——阿里云首批ACE认证通过者逸疏专访

自2018年3月阿里云发布云计算架构师ACE(Alibaba Cloud Certified Expert,阿里云认证高级工程师)级别认证后,上线不到3个月,吸引了近百位业界优秀从业者参与考试。获得阿里云ACE认证,对于业界资深架构师来说,是自身实力的最好证明。 阿里云大学致力于培养云生态链路上的…

阿里云P8架构师|历时4个月1次通关ACE认证

李东是我在阿里云厂商这边了解到第一个自学通过ACE认证P8架构师&#xff0c;现将他学习考证记录分享给大家 写在前面&#xff1a; 我是来自阿里云数字政府行业线的解决方案架构师李东。 2022年3月28日阿里云发布了新版ACE&#xff08;Alibaba Cloud Certified Expert - Cloud…

阿里云新版云计算架构师ACE认证专家解读会重磅来袭

简介&#xff1a;专家命题人非常解读 9600元全额代金券限量发放&#xff01;活动钉群&#xff1a;31889256。 3月28日&#xff0c;阿里云新版云计算架构师ACE认证正式升级发布&#xff0c;面向云架构师、解决方案架构师、资深运维/交付等人群&#xff0c;提供技术能力认证。自升…

通过新版阿里ACE认证,实验操作题你来解一下

大家都知道了哈&#xff0c;阿里云新版ACE认证&#xff0c;现在是云认证界的塔尖位置&#xff0c;因为要成功拿到新版ACE认证&#xff0c;需要破关斩将&#xff0c;三个守门的将&#xff0c;分别是笔试&#xff0c;实验操作&#xff0c;以及面试 1&#xff0c;笔试 相对实验操…

为什么我不建议在阿里云官网报考ACP/ACE认证?

考acp的时候刚开始以为直接在阿里云官网寻找课程备考报名就可以了,官方提供的课程应该没有问题,但是实际学习的时候发现,官方提供的内容虽然涵盖了大部分知识点,但是由于缺少对应的学习服务,学习效果上总是差强人意。 除了学习效果意外,再就是两个更重要的原因,贵且麻烦…

阿里云ACA、ACP、ACE认证考试常见问题

阿里云专业技术认证包含&#xff1a; 阿里云Apsara Clouder技能认证 阿里云助理工程师认证ACA级别&#xff08;Alibaba Cloud Certified Associate&#xff09; 阿里云专业工程师认证ACP级别&#xff08;Alibaba Cloud Certified Professional&#xff09; 阿里云基于岗位的…

阿里云的ACP认证与ACE认证含金量高吗?

该文章详细的介绍了阿里云认证。 云计算ACP认证介绍&#xff1a; 阿里云云计算专业认证(ACP 级-Alibaba Cloud Certification Professional)是面向使用阿里云云计算产品的架构、开发、运维类人员的专 业技术认证&#xff0c;主要涉及阿里云的计算、存储、网络、安全类的核心…

阿里云认证未来网络学院ACE公开课——30分钟掌握ACE考试通关攻略

简介&#xff1a;新版ACE通关攻略&#xff0c;这4点经验&#xff0c;帮你斩获高分&#xff01;入群即赠价值3000元ACP全套线上课资料包&#xff01;公开课钉群&#xff1a;44832770。 新版ACE通关攻略&#xff0c;这4点经验&#xff0c;帮你斩获高分 阿里云云计算架构师 ACE 认…

技能证里的天花板-阿里云云计算架构师ACE认证将全面升级!

近年来&#xff0c;随着国内数字化实践的不断深化&#xff0c;中国企业上云意识和积极性明显提高&#xff0c;上云比例和应用场景深度有所提升。根据亿欧智库2022年2月发布的《2021中国公有云服务商能力指数研究报告》指出&#xff1a;随着数字经济和新技术的发展&#xff0c;预…

阿里云ACE认证介绍

01-阿里云ACE认证概述 阿里云云计算架构师ACE认证是针对云架构师&#xff0c;解决方案架构师&#xff0c;云运维专家等技术人员的阿里云高级别技术认证。 通过该技术认证可以有效证明该认证人员具备丰富的需求分析能力&#xff0c;云架构设计能力&#xff0c;云架构部署实施能…