Quantcast
Channel: Sam的技术Blog
Viewing all articles
Browse latest Browse all 158

I2C基础学习

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

最近想在Android-ARM上添加一个9轴Sensor,倾向使用I2C串行总线。这就涉及到以下几个方面:
A: 通过I2C,将Sensor与开发板连接起来。
B: 在Linux层写Driver。使上层程序可以拿到Sensor数据。(此处指NativeC程序可以得到数据)
C: 使Android Framework得到Sensor数据,并通过SensorManager使Java程序可以得到Sensor数据。

那所有的基础,就是I2C。在这里学习之。

0. I2C串行总线简介:
采用串行总线技术,可以使系统的硬件设计大为简化,系统的体积减小,可靠性提高。并使更改和扩展变得更容易。

0.1: 常见的串行扩展总线包括:
I2C(Inter  IC BUS)
单总线(1-WIRE BUF)
SPI(Serial Peripheral Interface)总线
Microwire/PLUE

0.2: I2C串行总线概述:
I2C总线是PHLIPS推出的一种串行总线,具备多主机系统所需的总线裁决和高低速器件同步功能的高性能串行总线。
I2C总线只有两根双向信号线,一根是数据线SDA, 另一根是时钟线SCL.

多个I2C器件可以连接在同一I2C总线上。如何区分数据是发送给谁的呢?
每个连接到I2C总线上的器件都有一个唯一的地址。


0.3:I2C 数据位的有效性标准:
I2C总线数据位的有效性规定如下:时钟信号(SCL)为高电平时,数据线(SDA)上的数据必须保持稳定. 只有时钟信号为低电平期间,数据线上的电平财允许变化。



0.4: I2C起始信号和终止信号:
主机(只有主机)会发送起始和终止信号,起始和终止信号之间,总线处于被占用状态。
起始信号,终止信号如下:
SCL为高电平时,SDA由高向低的变化,即为起始信号。
SCL为高电平时,SDA由低向高的变化,即为终止信号。


很显然,因为数据位在SCL为高电平时是不变的,所以SDA在SCL为高时的两种变化,很容易被区分出来。他们就是起始和终止信号
总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。

 
0.5: I2C总线寻址:
I2C总线协议中规定:采用7Bit寻址,1-7bit表示从机地址,0bit表示数据方向(即:读/写,0: 表示从主机向从机写,1:表示由从机往主机读)
从机地址还包括:固定地址部分,和可编程地址部分。
器件类型由:D7-D4 4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的
用户自定义地址码:D3-D13位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。
所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了


0.6: I2C数据传送格式
数据传送时,1个字节8位数据,后面跟一个应答位。所以,一桢有9位。



主机在发送起始信号后,后面必须立刻发送一个从机地址,以表明这个数据是发送给哪个从机的。从机发现这个地址和自己的地址匹配,则必须发送应答信号,表示自己在线。
主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位,此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。


0.7: I2C总线操作(数据的读写)
操作I2C总线,无非就是主机和丛机之间数据交换,大致分以下三种情况:
A:主机向从机写数据:


主机发送起始信号,后面紧跟7bit的从机地址,1bit的写标记。表明数据是从主机到从机的。
8bit后,从机发送ACK,表明自己存在。
之后主机不断送数据,从机相应发送ACK。
直到最终主机发送停止信号
 
 
B:主机由从机读数据:

主机发送起始信号,后面紧跟7bit的从机地址,1bit的读标记。表明数据是由从机到主机的。
8bit后,从机发送ACK,表明自己存在。
之后主机不断由从机读取数据,从机相应发送ACK。
直到没有数据,则回复NAK。
主机则发送停止信号。停止这次通讯
 
C:


利用RS,重复的起始条件,可以更快的开始下一次读写。要比停止信号后再次开始更有效率。
 
 
 
 
1. I2C硬件接口电路:
这部分电路全部来自网络。
 

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

ADT75是一个带I2C接口的温度传感器器件。 它的数据手册上说明:地址跟A0、A1、A2引脚的接法有关。



我们这里的实例是将A0、A1、A2全部接到高电平上,因此其地址是:1001111(即0x4F),又因根据协议再给地址添加一个最低位(方向位,默认给写方向),因此最后这个温度传感器作为从设备的地址是:10011110(即0x9E)。

 
 
Sam在处理MPU9250时,也看到:AD0 = 0时,地址1101000, AO0 =1时地址是1101001. 当然还要添加读写位。这里感谢Jeamson指出,以前一直以为这个设置方法是使用程序实现的呢。
所以:IIC地址,固定部分由芯片出厂时指定,可编程部分可由芯片Pin脚接线调整。
 
 
 
2. 无操作系统下IIC的使用简介
2.1: IIC主机寄存器介绍:
以LPC2368为例:
 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

 
 2.2:无操作系统下,主机控制IIC数据传输方法
对I2C总线上主从设备的读写可使用两种方法,一是使用轮询的方式,二是使用中断的方式。轮询方式即是在一个循环中判断I2C状态寄存器当前的状态值来确 定总线当前所处的状态,然后根据这个状态来进行下一步的操作。中断方式即是使能I2C中断,注册I2C中断服务程序,在服务程序中读取I2C状态寄存器的 当前状态值,再根据状态值来确定下一步的操作。
 
IIC状态寄存器一些关键值如下:

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

 

例1:轮询方式:


#define 
SC16IS740_ADDR 0x92 
#define 
ADT75A_ADDR 0x9E 
#define 
ADT75A_TEMP 0x00 


#define 
CHANNEL_GPRS 0
#define CHANNEL_TEMPERATURE 1


#define 
BIT(x) (1 <</SPAN><</SPAN> 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


void 
I2C0_Init(void)
{
    
    PINSEL0 |= (0x03 <</SPAN><</SPAN> 0) | (0x03 <</SPAN><</SPAN> 2);

    
    PCONP |= (0x01 <</SPAN><</SPAN> 7 );

    
    I20CONCLR = (0x01 <</SPAN><</SPAN> 2) | (0x01 <</SPAN><</SPAN> 3) | (0x01 <</SPAN><</SPAN> 5) | (0x01 <</SPAN><</SPAN> 6);

    
    I20CONSET = (0x01 <</SPAN><</SPAN> 6);

    
    I20SCLH = 0x5A;
    I20SCLL = 0x5A;
}


BOOL 
I2C0_ReadRegister(uint32 channel, uint8 registerAddress, uint8 *pData)
{
    
    uint32 loopSafetyCounter = 0;
    uint32 addressSendSafetyCounter = 0; 

    
    do
    {
        
        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 <</SPAN><</SPAN> 3; 
    I20CONCLR = I2C_SI; 

    
    loopSafetyCounter = 0;
    while (~I20CONSET & I2C_SI)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    
    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 | 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; 

    
    I20CONSET = I2C_STO;
    I20CONCLR = I2C_SI;

    
    loopSafetyCounter = 0;
    while (I20CONSET & I2C_STO)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    return TRUE;
}


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

    
    do
    {
        
        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 <</SPAN><</SPAN> 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; 
        }
    }

    
    I20CONSET = I2C_STO;
    I20CONCLR = I2C_SI;

    
    loopSafetyCounter = 0;
    while (I20CONSET & I2C_STO)
    {
        loopSafetyCounter ++;
        if (loopSafetyCounter > SAFETY_COUNTER_LIMIT)
        {
            return FALSE; 
        }
    }

    return TRUE;
}

 

 

 


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


在Linux下驱动I2C总线不像单片机中那样简单的操作几个寄存器了,而是把I2C总线结构化、抽象化了,符合通用性和Linux设备模型
这就是后面要学习的内容了。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 再次感谢:

 

Viewing all articles
Browse latest Browse all 158

Trending Articles