嵌入式Linux内核I2C子系统详解

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。这里不再贴出以上代码了,这里只贴出关键性的代码。

  1 /*定义I2C状态标志*/
  2 typedef enum
  3 {
  4     I2C_IDLE = 0,
  5     I2C_STARTED = 1,
  6     I2C_RESTARTED = 2,
  7     I2C_REPEATED_START = 3,
  8     I2C_DATA_ACK = 4,
  9     I2C_DATA_NACK = 5
 10 } I2C_STATUS_FLAG;
 11 
 12 /*定义I2C数据传输缓冲区大小和传输超时大小*/
 13 #define I2C_BUFSIZE 0x200
 14 #define I2C_TIMEOUT 0x00FFFFFF
 15 
 16 /*定义I2C当前状态标志*/
 17 volatile I2C_STATUS_FLAG I2C_Flag;
 18 
 19 /*I2C当前的模式,0为主发送器模式,1为主接收器模式*/
 20 volatile uint32 I2CMasterMode = 0;
 21 
 22 /*分别定义I2C接收和发送缓冲区、要发送或要接收的字节数、实际发送或接收的字节数*/
 23 volatile uint8 I2CReadBuf[I2C_BUFSIZE], I2CWriteBuf[I2C_BUFSIZE];
 24 volatile uint32 I2CReadLength, I2CWriteLength;
 25 volatile uint32 I2C_RD_Index, I2C_WR_Index;
 26 
 27 /****************************************************************************
 28 ** Function name: I2C0_Exception
 29 ** Descriptions : I2C0中断服务程序
 30 ** Input : 无
 31 ** Output : 无
 32 ** Created Date : 2011-03-24 
 33 *****************************************************************************/
 34 void I2C0_Exception(void)
 35 {
 36     volatile uint32 stat_value;
 37     stat_value = I20STAT;
 38 
 39     switch(stat_value)
 40     {
 41         case 0x08:
 42             /*发出了一个起始条件,接下来将发送从地址然后清零SI位和STA位*/
 43             I2C_Flag = I2C_STARTED;
 44             I20DAT = I2CWriteBuf[I2C_WR_Index];
 45             I2C_WR_Index++;
 46             I20CONCLR = I2C_STA | I2C_SI;
 47             break;
 48         case 0x10:
 49             /*一个重复的起始条件发送完成,接下来要将发送从地址然后清零SI位和STA位*/
 50             I2C_Flag = I2C_RESTARTED;
 51             if(I2CMasterMode == 1)
 52             {
 53                 /*注意I2CWriteBuf中的第0位是设备从地址和写方向位,因这里是读操作,故将第0位的方向位变为读*/
 54                 I20DAT = I2CWriteBuf[0] | 0x01;
 55             }
 56             I20CONCLR = I2C_STA | I2C_SI;
 57             break;
 58         case 0x18 /*(注:SLA+W表示从设备地址+写方向)*/
 59             /*发送SLA+W后已接收到ACK,接下来开始发送数据字节到数据寄存器然后清零SI位*/
 60             if(I2C_Flag == I2C_STARTED)
 61             {
 62                 I2C_Flag = I2C_DATA_ACK;
 63                 I20DAT = I2CWriteBuf[I2C_WR_Index];
 64                 I2C_WR_Index++;
 65             }
 66             I20CONCLR = I2C_SI;
 67             break;
 68         case 0x28:
 69             /*此状态表明已发送I2DAT中的字节且接收到ACK,接下来继续发送下一个字节*/
 70         case 0x30:
 71             /*已发送I2DAT中的字节且接收到非ACK,接下来可能发出停止条件或重启起始条件*/
 72             if(I2C_WR_Index != I2CWriteLength)
 73             {
 74                 /*实际发送的字节数与要发送的不相等则继续发送,但可能是最后一次*/
 75                 I20DAT = I2CWriteBuf[I2C_WR_Index];
 76                 I2C_WR_Index++;
 77 
 78                 if(I2C_WR_Index != I2CWriteLength)
 79                 {
 80                     I2C_Flag = I2C_DATA_ACK;
 81                 }
 82                 else
 83                 {
 84                     /*如果实际发送与要发送的相等了,表明主发送端数据发送完成*/
 85                     I2C_Flag = I2C_DATA_NACK;
 86 
 87                     if(I2CReadLength != 0)
 88                     {
 89                         /*如果主发送端有等待接收的字节,则切换为主接收模式,重启起始条件*/
 90                         I2C_Flag = I2C_REPEATED_START;
 91                         I20CONSET = I2C_STA | I2C_SI;
 92                     }
 93                 }
 94             }
 95             else
 96             {
 97                 /*如果实际发送与要发送的相等了,表明主发送端数据发送完成*/
 98                 I2C_Flag = I2C_DATA_NACK;
 99 
100                 if(I2CReadLength != 0)
101                 {
102                     /*如果主发送端有等待接收的字节,则表明需切换为主接收模式,重启起始条件*/
103                     I2C_Flag = I2C_REPEATED_START;
104                     I20CONSET = I2C_STA;
105                 }
106             }
107             I20CONCLR = I2C_SI;
108             break;
109         case 0x40:
110             /*此状态表明已发送SLA+R后已接收到ACK*/
111             I20CONCLR = I2C_SI;
112             break; 
113         case 0x50:
114             /*此状态表明已接收数据字节后已接收到ACK*/
115         case 0x58:
116             /*此状态表明已接收数据字节后已接收到非ACK*/
117             I2CReadBuf[I2C_RD_Index] = I20DAT;
118             I2C_RD_Index++;
119 
120             if(I2C_RD_Index != I2CReadLength)
121             {
122                 /*如果实际接收的字节与要接收的不相等,则继续接收*/
123                 I2C_Flag = I2C_DATA_ACK;
124             }
125             else
126             {
127                 /*否则接收完毕*/
128                 I2C_RD_Index = 0;
129                 I2C_Flag = I2C_DATA_NACK;
130             }
131             I20CONCLR = I2C_AA | I2C_SI;
132             break;
133         case 0x20:
134             /*此状态表明已发送SLA+W后已接收到非ACK*/
135         case 0x48:
136             /*此状态表明已发送SLA+R后已接收到非ACK*/
137             I2C_Flag = I2C_DATA_NACK;
138             I20CONCLR = I2C_SI;
139             break;
140         default:
141             I20CONCLR = I2C_SI;
142             break;
143     }
144 
145     VICVectAddr = 0x00; 
146 }
147 
148 /****************************************************************************
149 ** Function name: I2C0_Start
150 ** Descriptions : 设置I2C0总线传输起始条件
151 ** Input : 无
152 ** Output : 返回TRUE/FALSE, FALSE为设置超时
153 ** Created Date : 2011-03-24 
154 *****************************************************************************/
155 BOOL I2C0_Start(void)
156 {
157     uint32 timeout = 0;
158     BOOL retVal = FALSE;
159 
160     /*设置配置寄存器STA位开始条件*/
161     I20CONSET = I2C_STA | I2C_SI;
162     I20CONCLR = I2C_SI;
163 
164     /*等待起始条件完成*/
165     while(1)
166     {
167         if(I2C_Flag == I2C_STARTED)
168         {
169             retVal = TRUE;
170             break; 
171         }
172 
173         if(timeout >= I2C_TIMEOUT)
174         {
175             retVal = FALSE;
176             break;
177         }
178 
179         timeout++;
180     }
181 
182     return retVal;
183 }
184 
185 /****************************************************************************
186 ** Function name: I2C0_Stop
187 ** Descriptions : 设置I2C0总线传输停止条件
188 ** Input : 无
189 ** Output : 返回TRUE
190 ** Created Date : 2011-03-24 
191 *****************************************************************************/
192 BOOL I2C0_Stop(void)
193 {
194     /*设置配置寄存器STO位停止条件和清除SI标志*/
195     I20CONSET = I2C_STO; 
196     I20CONCLR = I2C_SI; 
197 
198     /*等待停止条件完成*/
199     while(I20CONSET & I2C_STO);
200 
201     return TRUE;
202 }
203 
204 /****************************************************************************
205 ** Function name: I2C0_Engine
206 ** Descriptions : 完成I2C0总线从开始到停止的传输,传输过程在中断服务程序中进行
207 ** Input : 无
208 ** Output : 返回TRUE/FALSE
209 ** Created Date: 2011-03-24 
210 *****************************************************************************/
211 BOOL I2C0_Engine(void)
212 {
213     I2C_Flag = I2C_IDLE;
214     I2C_RD_Index = 0;
215     I2C_WR_Index = 0;
216 
217     if(I2C0_Start() != TRUE)
218     {
219         I2C0_Stop();
220 
221         return FALSE;
222     }
223 
224     while(1)
225     {
226         if(I2C_Flag == I2C_DATA_NACK)
227         {
228             I2C0_Stop();
229             break;
230         }
231     } 
232 
233     return TRUE;
234 }

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

 1 /****************************************************************************
 2 ** Function name: I2C0_ReadWriteTransmission
 3 ** Descriptions : I2C总线数据读写传输
 4 ** Input : read_buf-读数据缓冲区
 5                   read_len-读数据长度
 6                   write_buf-写数据缓冲区
 7                   write_len-写数据长度
 8 ** Output : 数据读写传输是否成功
 9 ** Created Date : 2011-03-24 
10 *****************************************************************************/
11 BOOL I2C0_ReadWriteTransmission(uint8 **read_buf, uint32 read_len, uint8 *write_buf, uint32 write_len)
12 {
13     uint32 i;
14     BOOL result = FALSE;
15 
16     /*数据传输长度检查*/
17     if(read_len > I2C_BUFSIZE || write_len > I2C_BUFSIZE)
18     {
19         return FALSE;
20     }
21 
22     /*清空I2C接收和发送缓冲区内容*/
23     for(i = 0; i < I2C_BUFSIZE; i++)
24     {
25         I2CReadBuf[i] = 0;
26         I2CWriteBuf[i] = 0;
27     }
28 
29     /*确定I2C总线模式(0为主发送模式,1为主接收模式)*/
30     I2CMasterMode = (read_len == 0)? 0 : 1;
31     I2CReadLength = read_len;
32     I2CWriteLength = write_len;
33 
34     /*要写入I2C从设备的数据(第一个字节包含从设备地址和方向位)*/
35     for(i = 0; i < write_len; i++)
36     {
37         I2CWriteBuf[i] = write_buf[i];
38     }
39 
40     /*启动I2C传输*/
41     result = I2C0_Engine();
42 
43     /*如果有向从设备读取的数据*/
44     if(read_len > 0 && result == TRUE)
45     {
46         uint8 *buf = (uint8 *)malloc(read_len * sizeof(uint8));
47 
48         for(i = 0; i < read_len; i++)
49         {
50             uf[i] = I2CReadBuf[i];
51         }
52 
53         *read_buf = buf;
54     }
55 
56     return result;
57 }

1.4 Linux下I2C子系统框架
    在Linux下要使用I2C总线并没有像无系统中的那样简单,为了体现Linux中的模块架构,Linux把I2C总线的使用进行了结构化。这种结构分三部分组成,他们分别是:I2C核心部分、I2C总线驱动部分和I2C设备驱动。结构图如下:

  由此看来,在Linux下驱动I2C总线不像单片机中那样简单的操作几个寄存器了,而是把I2C总线结构化、抽象化了,符合通用性和Linux设备模型。

  1 /*I2C从设备地址*/
  2 #define SC16IS740_ADDR 0x92 /*I2C转UART设备*/
  3 #define ADT75A_ADDR 0x9E /*温度传感器设备*/
  4 #define ADT75A_TEMP 0x00 /*温度传感器内部寄存器*/
  5 
  6 /*从设备选择标识*/
  7 #define CHANNEL_GPRS 0
  8 #define CHANNEL_TEMPERATURE 1
  9 
 10 /*定义I2C控制寄存器各位操作宏*/
 11 #define BIT(x) (1 << x)
 12 #define I2C_EN BIT(6)
 13 #define I2C_STA BIT(5)
 14 #define I2C_STO BIT(4)
 15 #define I2C_SI BIT(3)
 16 #define I2C_AA BIT(2)
 17 
 18 /*用作超时计数*/
 19 #define SAFETY_COUNTER_LIMIT 3000
 20 
 21 /******************************************************************
 22 ** Function name: I2C0_Init
 23 ** Descriptions : I2C0初始化
 24 ** Input : 无
 25 ** Output : 无
 26 ** Created Date : 2011-03-24 
 27 *******************************************************************/
 28 void I2C0_Init(void)
 29 {
 30     /*设置P0.0,P0.1为I2C0接口的SDA和SCL功能*/
 31     PINSEL0 |= (0x03 << 0) | (0x03 << 2);
 32 
 33     /*设置I2C0接口功率/时钟控制位*/
 34     PCONP |= (0x01 << 7 );
 35 
 36     /*清空I2C0配置寄存器的各位*/
 37     I20CONCLR = (0x01 << 2) | (0x01 << 3) | (0x01 << 5) | (0x01 << 6);
 38 
 39     /*使能I2C0为主发送器模式*/
 40     I20CONSET = (0x01 << 6);
 41 
 42     /*设置I2C0总线速率为100 KHz */
 43     I20SCLH = 0x5A;
 44     I20SCLL = 0x5A;
 45 }
 46 
 47 /****************************************************************************
 48 ** Function name: I2C0_ReadRegister
 49 ** Descriptions : 从I2C0总线上读从设备的数据
 50 ** Input : 从设备选择标识、从设备内部寄存器地址、读出的字节数据
 51 ** Output : 读取是否成功
 52 ** Created Date : 2011-03-28 
 53 *****************************************************************************/
 54 BOOL I2C0_ReadRegister(uint32 channel, uint8 registerAddress, uint8 *pData)
 55 {
 56     /*用作延时等待计数*/
 57     uint32 loopSafetyCounter = 0;
 58     uint32 addressSendSafetyCounter = 0; 
 59 
 60     /*使用循环判断I2C状态寄存器I20STAT 的值*/
 61     do
 62     {
 63         /*向总线发送I2C起始条件*/
 64         I20CONSET = I2C_STA | I2C_SI;
 65         I20CONCLR = I2C_SI;
 66 
 67         /*等待起始条件发送完成*/
 68         loopSafetyCounter = 0;
 69         while (~I20CONSET & I2C_SI)
 70         {
 71             loopSafetyCounter ++;
 72             if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
 73             {
 74                 return FALSE; /*超时退出*/
 75             }
 76         }
 77 
 78         /*发送从设备地址*/
 79         if(channel == CHANNEL_GPRS)
 80             I20DAT = SC16IS740_ADDR;
 81         else if(channel == CHANNEL_TEMPERATURE)
 82             I20DAT = ADT75A_ADDR;
 83 
 84         I20CONCLR = I2C_STA | I2C_SI;
 85 
 86         /*等待从设备地址发送完成*/
 87         loopSafetyCounter = 0;
 88         while (~I20CONSET & I2C_SI)
 89         {
 90             loopSafetyCounter ++;
 91             if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
 92             {
 93                 return FALSE; /*超时退出*/
 94             }
 95         }
 96 
 97         addressSendSafetyCounter ++;
 98         if (addressSendSafetyCounter > SAFETY_COUNTER_LIMIT)
 99         {
100             return FALSE; /*超时退出*/
101         }
102 
103     } while (I20STAT != 0x18); /*在前面已经描述了0x18的含义*/
104 
105     /*发送从设备内部寄存器地址,根据数据手册描述该内部地址要左移3位*/
106     I20DAT = registerAddress << 3; 
107     I20CONCLR = I2C_SI; 
108 
109     /*等待从设备内部寄存器地址发送完成*/
110     loopSafetyCounter = 0;
111     while (~I20CONSET & I2C_SI)
112     {
113         loopSafetyCounter ++;
114         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
115         {
116             return FALSE; /*超时退出*/
117         }
118     }
119 
120     /*重启I2C起始条件进行总线读*/
121     I20CONSET = I2C_STA | I2C_SI;
122     I20CONCLR = I2C_SI;
123 
124     /*等待重启条件发送完成*/
125     loopSafetyCounter = 0;
126     while (~I20CONSET & I2C_SI)
127     {
128         loopSafetyCounter ++;
129         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
130         {
131             return FALSE; /*超时退出*/
132         }
133     }
134 
135     /*发送从设备地址(方向位为读,注意与上0x01将地址最低位变为1即为读方向)*/
136     if(channel == CHANNEL_GPRS)
137         I20DAT = SC16IS740_ADDR | 0x01;
138     else if(channel == CHANNEL_TEMPERATURE)
139         I20DAT = ADT75A_ADDR | 0x01;
140 
141     I20CONCLR = I2C_STA | I2C_SI;
142 
143     /*等待从设备地址发送完成*/
144     loopSafetyCounter = 0;
145     while (~I20CONSET & I2C_SI)
146     {
147         loopSafetyCounter ++;
148         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
149         {
150             return FALSE; /*超时退出*/
151         }
152     }
153 
154     /*开始准备读取数据*/
155     I20CONCLR = I2C_SI | I2C_AA; 
156 
157     /*等待数据接收*/
158     loopSafetyCounter = 0;
159     while (~I20CONSET & I2C_SI)
160     {
161         loopSafetyCounter ++;
162         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
163         {
164             return FALSE; /*超时退出*/
165         }
166     }
167 
168     /*数据接收*/
169     *pData = I20DAT; 
170 
171     /*发送I2C停止条件*/
172     I20CONSET = I2C_STO;
173     I20CONCLR = I2C_SI;
174 
175     /*等待停止条件发送完成*/
176     loopSafetyCounter = 0;
177     while (I20CONSET & I2C_STO)
178     {
179         loopSafetyCounter ++;
180         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
181         {
182             return FALSE; /*超时退出*/
183         }
184     }
185 
186     return TRUE;
187 }
188 
189 /****************************************************************************
190 ** Function name: I2C0_WriteRegister
191 ** Descriptions : 从I2C0总线上写从设备的数据
192 ** Input : 从设备选择标识、从设备内部寄存器地址、要写入的数据字节
193 ** Output : 写入是否成功
194 ** Created Date : 2011-03-28 
195 *****************************************************************************/
196 BOOL I2C0_WriteRegister(uint32 channel, uint8 registerAddress, uint8 data)
197 {
198     uint32 loopSafetyCounter = 0;
199     uint32 addressSendSafetyCounter = 0;
200 
201     /*使用循环判断I2C状态寄存器I20STAT 的值*/
202     do
203     {
204         /*向总线发送I2C起始条件*/
205         I20CONSET = I2C_STA | I2C_SI;
206         I20CONCLR = I2C_SI;
207 
208         /*等待起始条件发送完成*/
209         loopSafetyCounter = 0;
210         while (~I20CONSET & I2C_SI)
211         {
212             loopSafetyCounter ++;
213             if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
214             {
215                 return FALSE; /*超时退出*/
216             }
217         }
218 
219         /*发送从设备地址*/
220         if(channel == CHANNEL_GPRS)
221             I20DAT = SC16IS740_ADDR;
222         else if(channel == CHANNEL_TEMPERATURE)
223             I20DAT = ADT75A_ADDR;
224 
225         I20CONCLR = I2C_STA | I2C_SI;
226 
227         /*等待从设备地址发送完成*/
228         loopSafetyCounter = 0;
229         while (~I20CONSET & I2C_SI)
230         {
231             loopSafetyCounter ++;
232             if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
233             {
234                 return FALSE; /*超时退出*/
235             }
236         }
237 
238         addressSendSafetyCounter ++;
239         if (addressSendSafetyCounter > SAFETY_COUNTER_LIMIT)
240         {
241             return FALSE; /*超时退出*/
242         }
243 
244     } while (I20STAT != 0x18); 
245 
246     /*发送从设备内部寄存器地址*/
247     I20DAT = registerAddress << 3; 
248     I20CONCLR = I2C_SI; 
249 
250     /*等待寄存器地址发送完成*/
251     loopSafetyCounter = 0;
252     while (~I20CONSET & I2C_SI)
253     {
254         loopSafetyCounter ++;
255         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
256         {
257             return FALSE; /*超时退出*/
258         }
259     }
260 
261     /*开始发送数据*/
262     I20DAT = data; 
263     I20CONCLR = I2C_SI; 
264 
265     /*等待数据发送完成*/
266     loopSafetyCounter = 0;
267     while (~I20CONSET & I2C_SI)
268     {
269         loopSafetyCounter ++;
270         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
271         {
272             return FALSE; /*超时退出*/
273         }
274     }
275 
276     /*发送I2C停止条件*/
277     I20CONSET = I2C_STO;
278     I20CONCLR = I2C_SI;
279 
280     /*等待停止条件发送完成*/
281     loopSafetyCounter = 0;
282     while (I20CONSET & I2C_STO)
283     {
284         loopSafetyCounter ++;
285         if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
286         {
287             return FALSE; /*超时退出*/
288         }
289     }
290 
291     return TRUE;
292 }
原文地址:https://www.cnblogs.com/Huluwa-Vs-Aoteman/p/4162600.html