
5.7 I2C5.7.1 I2C规范介绍I2CInter-Integrated Circuit是一种只使用两根信号线的多主机、多从机总线。两根线分别为SDA串行数据和SCL串行时钟虽然有名为时钟的SCL线但本质上并不能称为同步串行总线因为SCL还参与了许多协议内容。I2C总线使用线与逻辑充分利用了SDA、SCL这两根线实现了许多较为复杂的协议内容包括·多从机7位或10位编址从机寻址和应答。·时钟同步。·多主机和冲突仲裁。·支持主从一体。·不同速率从机混合使用。·数据应答。·广播等等。下面将介绍I2C规范中常用的一些内容。对于完整的规范读者应参阅规范文档。1.I2C电气连接图5-32是I2C总线的典型连接。所有主从机的SDA和SCL均为漏极开路输出。所有SDA连接在一起、SCL连接在一起并由RP电阻上拉构成线与。任何一个主机或从机下拉将使得总线变低所有主、从机均释放总线时才会由电阻上拉到高。VCC由主机和从机的工作电压决定常见为3.3V、3.0V、2.5V或5VRP的取值与VCC和总线速度有关VCC越低或速度越快RP越小RP也可替换为1mA至3mA的电流源。RS可用于消除主从机之间的些许时序差异导致的毛刺。图5-32 I2C总线的电气连接I2C总线的数据率即SCL频率在规范中定义的有标准速率100kHz、快速400kHz和高速3.4MHz三种。2.I2C位定义SCL和SDA在空闲时均为高电平所有主从机均释放总线。数据位传输时要求在SCL为高电平期间SDA保持稳定即SDA只能在SCL为低电平时跳变如图5-33所示。SCL为高时的SDA跳变则被专门定义为一次传输的起始和停止称为起始位和停止位如图5-34和图5-35所示。图5-33 I2C位传输图5-34 I2C起始位图5-35 I2C停止位注意图5-33图5-35中上跳沿为曲线段而下降沿为直线段。曲线段代表了释放总线由外部电阻上拉到高的过程。因而一次I2C传输过程的波形将如图5-36所示。其中还以快速模式400kHz为例标注了一些重要的时序要求。图5-36 I2C传输波形和时序以400kHz速率为例除正常的起始位之外还可以有重复起始位。在一次传输结束时不发送停止位而发送起始位紧接着进行下一次传输称为重复起始位。重复起始位用于直接发起下一次传输而不让总线回到空闲状态这样主机可以一直掌握总线控制权避免进入空闲状态后被其他主机抢夺控制权。图5-37所示是重复起始位。图5-37 重复起始位3.I2C字节传输和寻址I2C的数据传输以字节为单位并且MSB在先每个字节后附一位应答位因此传输中的位以9位为一组一次完整传输的位数必然是9的整数倍。应答位的意义在规范中并没有严格定义可以是接收方对字节数据的确认也可以是接收方表达欲结束传输之意图甚至还可以是发送方发送的附加信息这都取决于从机的功能定义。应答位的“确认”、“应答”之含义一般以低电平表达记为“ACK”“不确认”、“无应答”则以高电平表达记为“NAK”。在7位从机地址寻址的模式下每次I2C传输的第一个字节用作从机寻址。这个字节的高7位为从机地址而最低位则指示本次传输中后续数据的方向是主机向从机发送数据称为写操作理解为主机视角还是从机向主机发送数据称为读操作写操作以低电平表示读操作以高电平表示。总线上的从机均会至少监听每次传输的第一字节如果其中的高7位与某个从机预先分配的地址一致则该从机会在第一字节后的应答位给出ACK低电平并配合主机完成后续数据传输如果没有从机地址吻合则总线上不会有应答主机应忽略此次操作可立即发送停止位或走完预先的传输流程当然后续也得不到任何有效数据或应答。7位一共可以表达128个地址但其中有十余个地址在规范中定义为特殊用途如广播、高速模式主机码、10位寻址扩展等并不能作为从机地址。图5-38所示是一次包含一字节数据写从主机到从机操作的传输从机应答了寻址和数据。其中从机的7位地址为0b10011010x4d传输的数据为0xa5。有时也常常将一个从机的读写分开表述为两个8位地址即读地址和写地址那么这个从机的读地址和写地址就分别为0b100110110x9b和0b100110100x9a。注意在第一个应答前主机释放SDA上升而后从机拉低SDA应答而产生了一个小毛刺是正常现象。图5-38 向从机0x4d写一字节数据0xa5的示例图5-38中“S”表示起始位Start“P”表示停止位“stoP”。图5-39所示则是向从机写入一个字节后立即重复起始条件然后向同一个从机读取一字节的例子。在这个例子中主机从从机读取到的字节数据为0xf5但最后主机给出NAK即表达“即将结束传输不必准备下一个数据”之意。图5-39 重复起始条件的示例波形图5-39中“sA”表示来自从机的ACK“mNA”表示来自主机的NAK“rS”表示重复起始位repeat Start。图5-39所示的I2C总线波形也常简化为图5-40所示的图形其中灰色部分为主机操纵SDA白色部分为从机操纵SDA。图5-40 简化的I2C总线操作4.从机内部地址数据字节是否有进一步的意义一般不是I2C规范的范畴这取决于从机的功能。例如有些从机是存储设备或内部有大量寄存器构成一个地址空间则往往将写操作中的前一个或数个数据字节用作内部地址寻址。例如一个256字节存储器会使用写操作中的第一个数据字节作为内部地址当它接收到这个表达内部地址的字节时会将内部操作指针指向这个内部地址以便以后的写或读操作在这个地址上进行。图5-41演示了向一个从机地址为0x52的存储器的内部地址0x39处写入数据0x7a的操作。图5-41 带有内部地址的从机的写数据操作而图5-42则演示了从该存储器内部地址0x76处读取数据的操作。因设定内部地址需要用I2C写传输而读取数据需要用I2C读传输因而整个操作用一个重复起始位拆分成两次I2C传输且不会丢失总线控制权。读出的数据为0xcf。图5-42 带有内部地址的从机的读操作大多数带有内部地址的从机在每次写进数据后或在读出数据并收到主机ACK时会将内部操作指针自增1以便主机向连续地址上写入或读取多个数据。图5-43演示了向上述存储器的内部地址0x39~0x3b分别写入0xf2、0xf3、0xf4的操作。图5-43 带有内部地址的从机的连续写数据操作图5-44则演示了从上述存储器的内部地址0x39~0x3b读出数据的操作。读出的数据为0xf2、0xf3和0xf4。图5-44 带有内部地址的从机的连续读数据操作5.时钟同步和仲裁这里简单介绍时钟同步机制和多主机的仲裁机制。当多个主机同时操作从机时如果两者SCL步调不一致根据线与逻辑只要有一个将SCL拉低SCL线便为低因而先下拉SCL的主机将导致SCL下跳而后释放SCL的主机才会使SCL上跳。如图5-45所示在T0时刻主机1拉低SCLSCL即刻下跳同时运作的主机2在T1时刻监测到SCL为低与自身意图不一致立即开始其低电平周期与主机1同步下降沿。在T2时刻主机1意图释放SCL但主机2仍然拉低SCL此时主机1监测到SCL电平与自身意图不一致便进入等待直到主机2释放SCL才继续自己的高电平周期与主机2同步上升沿。最终SCL的低电平时间由低电平最长的主机决定而高电平时间由高电平最短的主机决定。从机也可在SCL为低时下拉住SCL线使得主机释放SCL时SCL不上跳迫使主机进入等待。如图5-46所示T1时刻从机可能因内部逻辑忙而跟不上主机的节奏于是拉低SCL到T2时刻主机意图释放SCL但从机仍然拉低SCL迫使主机进入等待直到T3时刻从机准备好继续传输过程而释放SCL。图5-45 I2C主机间的时钟同步图5-46 I2C从机迫使主机等待主机只能在总线空闲的时候发起传输因而前述的重复起始位可以避免总线回到空闲状态从而避免后面可能与别的主机抢夺控制权失败。在SCL线为高电平期间主机们也会监测SDA电平是否与自身意图一致监测到与自身意图不一致的主机丧失控制权剩下的主机们继续直到最终剩余一个主机或者剩下的主机都对同一个外设做一模一样的操作并完成传输。这个过程称为仲裁。容易想到访问小地址值从机的主机会赢得仲裁。输掉仲裁的主机可以继续控制SCL直到当前字节完成。主从一体机在作为主机输掉仲裁时有可能检测到赢得仲裁的主机欲访问的就是自身的从机角色此时它应立即切换到从机角色发送应答并配合赢得仲裁的主机完成传输。5.7.2 通用I2C主机设计这里我们定义一个命令FIFO驱动的I2C主机向FIFO中写入命令来控制I2C主机工作这些命令包括·产生起始位/重复起始位。·产生停止位。·写字节并写ACK。·写字节并读ACK读ACK也等同于发送NAK。·读字节并写ACK。·读字节并读ACK读ACK也等同于发送NAK。上层逻辑只需将所需的I2C操作转换为上述6种命令的序列写入FIFO即可控制I2C完成工作。从I2C总线读到的数据8位数据和1位应答位将被I2C主机写入到一个数据FIFO因写出字节时可能更需要读入应答位所以I2C主机并不区分读写的数据而是会记录总线上的每次字节传输及其应答位以供上层逻辑根据上层协议选择分析。我们定义命令FIFO中的命令格式如表5-7所示每个命令均为10位。从表中可以看到发送数据0xff与读入数据并没有区别因为对于I2C总线来说都是释放SDA。I2C协议和时序相对复杂因此这里并不能像前两节介绍的UART或SPI主机那样依赖简单的计数来产生时序。为降低模块复杂度可将位时序生成和字节处理均独立成模块分别取名为位引擎Bit Engine模块和字节引擎Byte Engine模块。表5-7 I2C主机的控制命令对于位时序生成参见图5-47可将起始位、停止位和数据位的各个时序要求总结为图中所示的9个时间段。图5-47 I2C主机BitEngine的时序分段这9个时间段如下。·起始位相关SSU起始位建立时间SH起始位保持时间SDH起始位后的SDA保持时间。·数据位相关DSU数据建立时间SCLH数据位中SCL的高电平时间DH数据保持时间。·停止位相关PDSU停止位前的数据保持时间PSU停止位建立时间PH停止位保持时间。定义好这9个时间段的长度即可保证I2C总线时序。在位引擎中使用一个9状态的状态机来完成时序的生成状态机的状态转换如图5-48所示。状态转换依赖一个计数器t_cnt用于计每一个状态所需的时间这些时间以参数形式在模块中给出。根据前述的I2C时序SDH时间和DH时间、PSDU时间和DSU时间可合用同一个参数。图5-48 BitEngine的状态机位引擎模块的参数和端口定义如表5-8所示。表5-8 BitEngine模块的参数和端口说明根据上述说明BitEngine模块描述为代码5-7注意这个位引擎并未实现多主机的时钟同步和仲裁。对于几个用来设定时序的参数应遵循以下限制·TDSU应不小于其他6个参数。·TDSU最大为1023/CLK_FREQ因t_cnt为10位。总线空闲的时间为TSSUTPH总线时钟频率为1TDSUTSCLHTDH。代码5-7 I2C主机的BitEngine模块对于字节引擎功能定义为从FIFO读取命令并控制位引擎工作也使用一个状态机来控制其工作流程。状态转换如图5-49所示。图5-49 ByteEngine的状态机其中的状态输出·fifo_rd读取命令FIFO因RDFIFO状态只会持续一个周期fifo_rd为单周期。·bit_go控制BitEngine开始产生一个I2C位。·iic_bit根据data记录的命令FIFO中的命令向BitEngine给出位类型连接至BitEngine的iic_bit输入端口。其中的事件·9b_end输出事件表示一次9位传输结束用于指示将收到的数据写到数据FIFO。·datacmd[80]内部事件将命令FIFO读到的命令中的内容赋给datadata将用作移位寄存器参与构造iic_bit。·data1内部事件将data左移一位。字节引擎模块的端口定义如表5-9所示。表5-9 ByteEngine模块的端口说明IicByteEngine的描述如代码5-8所示。代码5-8 I2C主机的ByteEngine模块最后将BitEngine和ByteEngine模块实例化到一个完整的IicMaster模块中并使用一个简单的移位寄存器模块记录总线上的数据。表5-10是其参数和端口定义。表5-10 IicMaster模块的端口说明代码5-9是完整的IicMaster代码。其中将I2C总线信号定义到了一个名为IicBus的接口中因无论主从机SCL和SDA的形态均一样所以并不需要使用modport关键字定义角色IicBus中将SCL和SDA的读、写和三态控制分开定义应在整个工程的顶层模块中使用它们实例化三态门实现模拟漏极开路输入。代码5-9 完整的IicMaster模块5.7.3 通用I2C从机设计这里我们定义一个I2C从机IicSlave模块的功能如下·7位从机地址参数化设定。·可配置内部地址位宽或无内部地址。·提供一个存储器读写口如配置为有内部地址则可直接连接存储器如配置为无内部地址则由其写信号指示从I2C总线获取的数据更新到了写数据口上。与前述的UART接收器、SPI从机类似通过将SCL和SDA的跳沿转换为使能信号来控制模块的工作。但I2C协议较复杂还需要使用状态机根据上述功能定义和I2C协议可总结出状态机的转换图如图5-50所示。当然这个状态机的工作还需要两个位计数数据位计数bit_cnt和内部地址位计数ia_bit_cnt来配合因较为复杂在图中并未画出它们的驱动事件应对照图5-51和后文代码来理解。图5-50 IicSlave模块中的状态机数据位计数、内部地址计数以及状态变化的波形如图5-51所示。图中以IADDR_WIDTH8为例。图5-51 IicSlave模块的工作波形IicSlave模块的参数和端口定义如表5-11所示。表5-11 IicSlave模块的参数和端口说明IicSlave模块的描述如代码5-10所示。注意其中的状态机部分的状态驱动和状态转移两段为节约篇幅将state、bitcnt和ia_bitcnt都写在了同一个过程里与前述的带有辅助计数器的状态机稍有不同读者应着重理解。代码5-10 IicSlave模块