首页 > 代码库 > II协议
II协议
预备知识:
处理器和芯片间的通信可以形象的比喻成两个人讲话:1、你说的别人得能听懂:双方约定信号的协议。2、你的语速别人得能接受:双方满足时序要求。
因此我们主要学的是:
(1):该协议的概述与术语的定义;
(2):该协议所支持的总线接口;
(3):该协议的特征和原理;
(4):信号的类型和对应的信号帧格式以及传输的时序要求;
(5):支持的一些速率和基于该协议的相关设备,要记住。
(6):软件模拟举例。
一.IIC协议的概述与术语的定义:
I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送。I2C总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。I2C总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。如下图所示。
I2C 只是用两条双向的线:
SCL( Serial Clock Line):上升沿将数据输入到每个EEPROM器件中;下降沿驱动EEPROM器件输出数据.(边沿触发)
SDA(Serial Data Line ):双向数据线,为OD门,与其它任意数量的OD与OC门成"线与"关系。
二.IIC总线接口:
如图所示:
每一个I2C总线器件内部的SDA、SCL引脚电路结构都是一样的,引脚的输出驱动与输入缓冲连在一起。其中输出为漏极开路的场效应管,输入缓冲为一只高输入阻抗的同相器,这种电路具有两个特点:
1)由于SDA、SCL为漏极开路结构(OD),因此它们必须接有上拉电阻,阻值的大小常为 1k8, 4k7 and 10k ,但1k8 时性能最好;当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线"与"关系。
2)引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致,为"时钟同步"和"总线仲裁"提供了硬件基础。
- 主设备与从设备
系统中的所有外围器件都具有一个7位的"从器件专用地址码",其中高4位为器件类型,由生产厂家制定,低3位为器件引脚定义地址,由使用者定义。主控器件通过地址码建立多机通信的机制,因此I2C总线省去了外围器件的片选线,这样无论总线上挂接多少个器件,其系统仍然为简约的二线结构。终端挂载在总线上,有主端和从端之分,主端必须是带有CPU的逻辑模块,在同一总线上同一时刻使能有一个主端,可以有多个从端,从端的数量受地址空间和总线的最大电容 400pF的限制。 主端主要用来驱动SCL line;从设备对主设备产生响应;
每个接到I2C总线上的器件都有唯一的地址。主机与其它器件间的数据传送可以是由主机发送数据到其它器件,这时主机即为发送器。由总线上接收数据的器件则为接收器。
在多主机系统中,可能同时有几个主机企图启动总线传送数据。为了避免混乱, I2C总线要通过总线仲裁,以决定由哪一台主机控制总线。
三、IIC总线的特征和原理
IIC总线的一些特征:
a. 只要求两条总线线路:一条串行数据线(SDA),一条串行时钟线(SCL);
b.每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系软件设定地址,主机可以作为主机发送器或主机接收器;
c.它是一个真正的多主机总线,如果两个或更多主机同时初始化数据传输可以通过冲突检测和仲裁防止数据被破坏;
d. 串行的 8 位双向数据传输位速率在标准模式下可达 100kbit/s ,快速模式下可达 400kbit/s, 高速模式下可达 3.4Mbit/s;
e.片上的滤波器可以滤去总线数据线上的毛刺波,保证数据完整;
f.连接到相同总线的 IC 数量只受到总线的最大电容 400pF 限制.
一些重要概念的原理:
I2C总线仲裁与时钟发生
在多主的通信系统中。总线上有多个节点,它们都有自己的寻址地址,可以作为从节点被别的节点访问,同时它们都可以作为主节点向其它的节点发送控制字节和传送数据。但是如果有两个或两个以上的节点都向总线上发送启动信号并开始传送数据,这样就形成了冲突。要解决这种冲突,就要进行仲裁的判决,这就是I2C总线上的仲裁。
I2C总线上的仲裁分两部分:SCL线的同步和SDA线的仲裁。
A、 SCL线的同步(时钟同步)
SCL同步是由于总线具有线“与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。
当所有的节点都发送高电平时,总线才能表现为高电平。
图3
由于线“与”逻辑功能的原理,当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号。这就是SCL的同步原理。
B、 SDA仲裁
SDA线的仲裁也是建立在总线具有线“与”逻辑功能的原理上的。
节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。
SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线。
C、 仲裁过程
图4
DATA1和DATA2分别是主节点向总线所发送的数据信号;
SDA为总线上所呈现的数据信号,SCL是总线上所呈现的时钟信号。
当主节点1、2同时发送起始信号时,两个主节点都发送了高电平信号。这时总线上呈现的信号为高电平,两个主节点都检测到总线上的信号与自己发送的信号相同,继续发送数据。
第2个时钟周期,2个主节点都发送低电平信号,在总线上呈现的信号为低电平,仍继续发送数据。
在第3个时钟周期,主节点1发送高电平信号,而主节点2发送低电平信号。根据总线的线“与”的逻辑功能,总线上的信号为低电平,这时主节点1检测到总线上的数据和自己所发送的数据不一样,就断开数据的输出级,转为从机接收状态。
这样主节点2就赢得了总线,而且数据没有丢失,即总线的数据与主节点2所发送的数据一样,而主节点1在转为从节点后继续接收数据,同样也没有丢掉SDA线上的数据。因此在仲裁过程中数据没有丢失。
四、信号的类型和对应的信号帧格式
1.空闲状态 I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。 2.起始位与停止位的定义:
- 起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
- 停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
3.ACK
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
如下图逻辑分析仪的采样结果:释放总线后,如果没有应答信号,sda应该一直持续为高电平,但是如图中蓝色虚线部分所示,它被拉低为低电平,证明收到了应答信号。 这里面给我们的两个信息是:1)接收器在SCL的上升沿到来之前的低电平期间拉低SDA;2)应答信号一直保持到SCL的下降沿结束;正如前文红色标识所指出的那样。
4.数据的有效性:
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 我的理解:虽然只要求在高电平期间保持稳定,但是要有一个提前量,也就是数据在SCL的上升沿到来之前就需准备好,因为在前面I2C总线之(一)---概述一文中已经指出,数据是在SCL的上升沿打入到器件(EEPROM)中的。
5.数据的传送:
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
二、工作过程
总线上的所有通信都是由主控器引发的。在一次通信中,主控器与被控器总是在扮演着两种不同的角色。
1.主设备向从设备发送数据
主设备发送起始位,这会通知总线上的所有设备传输开始了,接下来主机发送设备地址,与这一地址匹配的slave将继续这一传输过程,而其它slave将会忽略接下来的传输并等待下一次传输的开始。主设备寻址到从设备后,发送它所要读取或写入的从设备的内部寄存器地址; 之后,发送数据。数据发送完毕后,发送停止位:
写入过程如下:
发送起始位
- 发送从设备的地址和读/写选择位;释放总线,等到EEPROM拉低总线进行应答;如果EEPROM接收成功,则进行应答;若没有握手成功或者发送的数据错误时EEPROM不产生应答,此时要求重发或者终止。
- 发送想要写入的内部寄存器地址;EEPROM对其发出应答;
- 发送数据
- 发送停止位.
- EEPROM收到停止信号后,进入到一个内部的写入周期,大概需要10ms,此间任何操作都不会被EEPROM响应;(因此以这种方式的两次写入之间要插入一个延时,否则会导致失败,博主曾在这里小坑了一下)
详细:
需要说明的是:①主控器通过发送地址码与对应的被控器建立了通信关系,而挂接在总线上的其它被控器虽然同时也收到了地址码,但因为与其自身的地址不相符合,因此提前退出与主控器的通信;
2.主控器读取数据的过程:
读的过程比较复杂,在从slave读出数据前,你必须先要告诉它哪个内部寄存器是你想要读取的,因此必须先对其进行写入(dummy write):
发送起始位;
发送slave地址+write bit set;
发送内部寄存器地址;
重新发送起始位,即restart;
重新发送slave地址+read bit set;
读取数据
主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。
发送停止位
详细:
五:基于IIC协议的相关设备:2、I2C总线8位并行IO口扩展芯片PCF8574/JLC1562;
3、I2C接口实时时钟芯片DS1307/PCF8563/SD2000D/M41T80/ME901/ISL1208/;
4、I2C数据采集ADC:MCP3221(12bitADC)/ADS1100(16bitADC)/ADS1112(16bitADC)/MAX1238(12bitADC)/MAX1239(12bitADC)
5、I2C接口数模转换DAC芯片DAC5574(8bitDAC)/DAC6573(10bitDAC)/DAC8571(16bitDAC);
6、I2C接口温度传感器TMP101/TMP275/DS1621/MAX6625.
六:I2C总线注意点
1. 进行数据传送时,在SCL为高电平期间,SDA线上电平必须保持稳定,只有SCL为低时,才允许SDA线上电平改变状态。并且每个字节传送时都是高位在前。
2. 对于应答信号,ACK=0时为有效应答位,说明从机已经成功接收到该字节,若为1则说明接受不成功。
3. 如果从机需要延迟下一个数据字节开始传送的时间,可以通过把SCL电平拉低并保持来强制主机进入等待状态。
4. 主机完成一次通信后还想继续占用总线在进行一次通信,而又不释放总线,就要利用重启动信号。它既作为前一次数据传输的结束,又作为后一次传输的开始。
5. 总线冲突时,按“低电平优先”的仲裁原则,把总线判给在数据线上先发送低电平的主器件。
6. 在特殊情况下,若需禁止所有发生在I2C总线上的通信,可采用封锁或关闭总线,具体操作为在总线上的任一器件将SCL锁定在低电平即可。
7. SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的。
模拟代码实例:
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @Purpose: I2C Communication driver(By IO) * @Author: Purple * @Version: 1.0 * @Date: Create By Purple 2014.08.09 * * * Copyright (C) BlueWhale Tech. * All rights reserved. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Include Files */ #include "IICDrv.h" /* Function Prototypes */ static void I2CDelay(void); /* Function Definitions */ /* * FunctionName: I2CDelay * Purpose: I2C时序模拟SCL时间间隔(周期),需要根据Slave性能及单片机工作频率调整 * Parameters: 无 */ static void I2CDelay(void) { _asm("nop"); _asm("nop"); _asm("nop"); _asm("nop"); _asm("nop"); } /* * FunctionName: I2CStart * Purpose: 模拟I2C开始信号 * Parameters: 无 */ void I2CStart(void) { I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 I2C_SCL=1; I2CDelay(); I2C_SDA=1; I2CDelay(); I2C_SDA=0; I2CDelay(); I2C_SCL=0; I2CDelay(); } /* * FunctionName: I2CStop * Purpose: 模拟I2C结束信号 * Parameters: 无 */ void I2CStop(void) { I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 I2C_SCL = 0; I2CDelay(); I2C_SDA = 0; I2CDelay(); I2C_SDA = 1; I2CDelay(); I2C_SCL = 1; I2CDelay(); } /* * FunctionName: I2CFree * Purpose: 模拟I2C空闲状态信号 * Parameters: 无 */ void I2CFree(void) { I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 I2C_SDA = 1; I2CDelay(); I2C_SCL = 1; I2CDelay(); } /* * FunctionName: I2CSendACK * Purpose: 模拟I2C发送ACK响应 * Parameters: 无 */ void I2CSendACK(void) { I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 I2C_SCL=0; I2CDelay(); I2C_SDA=0; I2CDelay(); I2C_SCL=1; I2CDelay(); I2C_SCL=0; I2CDelay(); } /* * FunctionName: I2CSendNoACK * Purpose: 模拟I2C无ACK响应 * Parameters: 无 */ void I2CSendNoACK(void) { I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 I2C_SCL=0; I2CDelay(); I2C_SDA=1; I2CDelay(); I2C_SCL=1; I2CDelay(); I2C_SCL=0; I2CDelay(); } /* * FunctionName: I2CCheckACK * Purpose: 检查I2C是否有ACK响应 * Parameters: 无 */ bool I2CCheckACK(void) { bool tempACK; I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 I2C_SDA=1; I2CDelay(); I2C_SCL = 1; I2C_SDA_IO=IO_IN_MODE; //设置SDA端口为输入端口,检查Slave是否有响应 I2CDelay(); if(I2C_SDA) tempACK=FALSE; else tempACK=TRUE; I2C_SCL=0; I2CDelay(); return tempACK ; } /* * FunctionName: I2CSendByte * Purpose: 模拟I2C发送一个字节数据 * Parameters: sendData-发送的一个字节数据 */ void I2CSendByte(unsigned char sendData) { unsigned char serialNum = 0; I2C_SDA_IO=IO_OUT_MODE; //设置SDA端口为输出端口 I2C_SCL_IO=IO_OUT_MODE; //设置SCL端口为输出端口 for(serialNum=8;serialNum>=1;serialNum--) //以MSB方式按位发送一个字节数据 { I2C_SDA = (sendData>>(serialNum-1))&0x01; I2CDelay(); I2C_SCL = 1; I2CDelay(); I2C_SCL = 0; I2CDelay(); } } /* * FunctionName: I2CReceiveByte * Purpose: 模拟I2C接收一个字节数据 * Parameters: 无 */ unsigned char I2CReceiveByte(void) { unsigned char serialNum = 0; unsigned char dataValue=http://www.mamicode.com/0;>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @Purpose: I2C Communication driver(By IO) * @Author: Purple * @Version: 1.0 * @Date: Create By Purple 2014.08.09 * * * Copyright (C) BlueWhale Tech. * All rights reserved. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef IICDRV_H #define IICDRV_H /* Include Files */ /* Macros */ #define I2C_SDA PTT_PTT0 //举例以Freescale PT0端口为SDA线,PT1端口为SCL线 #define I2C_SDA_IO DDRT_DDRT0 #define I2C_SCL PTT_PTT1 #define I2C_SCL_IO DDRT_DDRT1 #define IO_OUT_MODE 1 //Freescale:1为输出模式,0为输入模式;NEC:0为输出模式,1为输入模式 #define IO_IN_MODE 0 /* Function Prototypes */ void I2CStart(void); void I2CStop(void); void I2CFree(void); void I2CSendACK(void); void I2CSendNoACK(void); bool I2CCheckACK(void); void I2CNoAck(void); void I2CSendByte(unsigned char sendData); unsigned char I2CReceiveByte(void); #endif/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @Purpose: Communication with EEPROM 24C04 * @Author: Purple * @Version: 1.0 * @Date: Create By Purple 2014.08.09 * * * Copyright (C) BlueWhale Tech. * All rights reserved. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Include Files */ #include "EEApp.h" /* Function Definitions */ /* * FunctionName: Slave24C04Write * Purpose: 向EEPROM24C04中写入一个字节数据 * Parameters: tarAddress-写入数据的起始地址 * wrNumber-待写入数据的长度(字节单位) * wrPointer-待写入数据的首字节地址 */ void Slave24C04Write(unsigned char tarAddress,unsigned char wrNumber,unsigned char* wrPointer) { bool rxdACK; I2CStart(); I2CSendByte(SLAVE_ADDRESS); //发送24C04的器件地址,地址LSB最后一位为0代表写入,1代表读取 rxdACK=I2CCheckACK(); I2CSendByte(tarAddress); //发送写入数据的起始地址 rxdACK=I2CCheckACK(); for(;wrNumber!=0;wrNumber--,wrPointer++) { I2CSendByte(*wrPointer); //按字节写入数据 rxdACK=I2CCheckACK(); } I2CStop(); } /* * FunctionName: Slave24C04Read * Purpose: 从EEPROM24C04中读取一个字节数据 * Parameters: tarAddress-读取数据的起始地址 * wrNumber-读取数据的长度(字节单位) * wrPointer-读取数据的首字节存放地址 */ void Slave24C04Read(unsigned char tarAddress,unsigned char rdNumber,unsigned char* rdPointer) { bool rxdACK; I2CStart(); I2CSendByte(SLAVE_ADDRESS); //发送24C04的器件地址 rxdACK=I2CCheckACK(); I2CSendByte(tarAddress); //发送读取数据的起始地址 rxdACK=I2CCheckACK(); I2CStart(); I2CSendByte(SLAVE_ADDRESS+1); //发送24C04的器件地址,地址LSB最后一位为1代表读取 rxdACK=I2CCheckACK(); for(;rdNumber!=0;rdNumber--,rdPointer++) { *rdPointer=I2CReceiveByte(); //按字节读取数据 if(rdNumber!=1) I2CSendACK(); else I2CSendNoACK(); } I2CStop(); }/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @Purpose: Communication with EEPROM 24C04 * @Author: Purple * @Version: 1.0 * @Date: Create By Purple 2014.08.09 * * * Copyright (C) BlueWhale Tech. * All rights reserved. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef EEAPP_H #define EEAPP_H /* Include Files */ #include "IICDrv.h" /* Macros */ #define SLAVE_ADDRESS (0xA0) /* Function Prototypes */ void Slave24C04Write(unsigned char tarAddress,unsigned char wrNumber,unsigned char* wrPointer); void Slave24C04Read(unsigned char tarAddress,unsigned char rdNumber,unsigned char* rdPointer); #endif参考文献:
http://blog.csdn.net/bluewhaletech/article/details/38706093
http://blog.csdn.net/bluewhaletech/article/details/37876111
http://blog.csdn.net/zailushangha/article/details/8233448
http://baike.baidu.com/link?url=3wBTkcJ5i8tGf4j4Keron5hp5LPmCbB6ATonll_3USJhTF2HXNqxo4L1Jt87QgCOuFbO-G2tUxLgkCrM-7TDoK
http://blog.csdn.net/subkiller/article/details/4508441
http://blog.163.com/gxhy_lyf/blog/static/170345637201172884910825/
http://www.cnblogs.com/BitArt/archive/2013/06/01/3112042.htmlII协议