由于大规模集成电路技术的发展,在单个芯片集成 CPU 以及组成一个单独工作系统所必须的 ROM、RAM、I/O 端口、A/D、D/A 等外围电路和已经实现,这就是常说的单片机或微控制器。目前,世界上许多公司生产单片机,品种很多:包括各种字长的 CPU,各种容量和品种的 ROM、RAM,以及功能各异的 I/O 等等。但是,单片机品种规格有限,所以只能选用某种单片机再进行扩展。扩展的方法有两种:一种是并行总线,另一种是串行总线。由于串行总线连线少,结构简单,往往不用专用的母板和插座而直接用导线连接各个设备即可。因此,采用串行总线大大简化了系统硬件设计。PHILIPS 公司早在十几年就前推出了I2C 串行总线,它是具备多主机系统所需的包括裁决和高低速设备同步等功能的高性能串行总线。
总线对设备接口电路的制造工艺和电平都没有特殊的要求(NMOS、CMOS 都可以兼容)。数据传送率按I2C 总线可高达每秒十万位,高速方式可高达每秒四十万位。总线上允许连接的设备数以总线上的电容量不超过 400pF 为限。
总线的运行(数据传输)由主机控制。所谓主机即启动数据的传送(发出启动信号),发出时钟信号,传送结束时发出停止信号的设备,通常主机是微处理器。被主机寻访的设备都称为从机。为了进行通讯,每个接到I2C 总线的设备都有一个唯一的地址,以便于主机寻访。主机和从机的数据传送,可以由主机发送数据到从机,也可以是从机发到主机。凡是发送数据到总线的设备称为发送器,从总线上接收数据的设备被称为接受器。
I2C 总线上允许连接多个微处理器及各种外围设备,如存储器、LED 及 LCD驱动器、A/D 及 D/A 转换器等。为了保证数据可靠地传送,任一时刻总线只能有由某一台主机控制一个微处理器应该在总线空闲时发启动数据,为了妥善解决多台微处理器同时发启数据传送(总线控制权)的冲突,并决定由哪一台微处理器控制总线。I2C 总线允许连接不同传送速率的设备,多台设备之间时钟信号的同步过程称为同步化。
I2C 串行总线有两根信号线:一根双向的数据线 SDA;另一根是时钟线 SCL。所有接到I2C总线上的设备的串行数据都接到总线的SDA线,各设备的时钟线SCL接到总线的 SCL。
为了避免总线信号的混乱,要求各设备连接到总线的输出端必须是开漏输出或集电极开路输出的结构。设备上的串行数据线 SDA 接口电路应该是双向的,输出电路用于向总线上发数据,输入电路用于接收总线上的数据。串行时钟线也应是双向的,作为控制总线数据传送的主机要通过 SCL 输出电路发送时钟信号,同时要检测总线上 SCL 上的电平以决定什么时候发下一个时钟脉冲电平;作为接受主机命令的从机,要按总线上的 SCL 的信号发出或接收 SDA 上的信号,也可以向 SCL 线发出低电平信号以延长总线时钟信号周期。总线空闲时,因各设备都是开漏输出,上拉电阻 RP 使 SDA 和 SCL 线都保持高电平。任一设备输出的低电平都使相应的总线信号线变低,也就是说各设备的 SDA 是“与”关系,SCL 也是“与”关系。
在 I2C 总线通信的过程中,参与通信的双方互相之间所传输的信息种类归纳如下。主控器向被控器发送的信息种类有:启动信号、停止信号、7 位地址码、读/写控制位、10 位地址码、数据字节、重启动信号、应答信号、时钟脉冲。被控器向主控器发送的信息种类有:应答信号、数据字节、时钟低电平。下面对 I2C 总线通信过程中出现的几种信号状态和时序进行分析。
①总线空闲状态
I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
②启动信号
在时钟线 SCL 保持高电平期间,数据线 SDA 上的电平被拉低(即负跳变),定义为 I2C 总线总线的启动信号,它标志着一次数据传输的开始。启动信号是一种电平跳变时序信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前 I2C 总线必须处于空闲状态。
③停止信号
在时钟线 SCL 保持高电平期间,数据线 SDA 被释放,使得 SDA 返回高电平(即正跳变),称为 I2C总线的停止信号,它标志着一次数据传输的终止。停止信号也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信号之后,I2C 总线将返回空闲状态。
④数据位传送
在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。进行数据传送时,在 SCL 呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据 0,高电平为数据 1。只有在 SCL 为低电平期间,才允许 SDA 上的电平改变状态。逻辑 0 的电平为低电压,而逻辑 1 的电平取决于器件本身的正电源电压 VDD(当使用独立电源时)。
⑤应答信号。
I2C 总线上的所有数据都是以 8 位字节传送的,发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是,接收器在第9个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号P。
⑥插入等待时间
如果被控器需要延迟下一个数据字节开始传送的时间,则可以通过把时钟线 SCL 电平拉低并且保持,使主控器进入等待状态。一旦被控器释放时钟线,数据传输就得以继续下
去,这样就使得被控器得到足够时间转移已经收到的数据字节,或者准备好即将发送的数据字节。带有CPU的被控器在对收到的地址字节做出应答之后,需要一定的时间去执行中断服务子程序,来分析或比较地址码,其间就把 SCL 线钳位在低电平上,直到处理妥当后才释放 SCL 线,进而使主控器继续后续数据字节的发送。
⑦重启动信号
在主控器控制总线期间完成了一次数据通信(发送或接收)之后,如果想继续占用总线再进行一次数据通信(发送或接收),而又不释放总线,就需要利用重启动 Sr 信号时序。重启动信号 Sr 既作为前一次数据传输的结束,又作为后一次数据传输的开始。利用重启动信号的优点是,在前后两次通信之间主控器不需要释放总线,这样就不会丢失总线的控制权,即不让其他主器件节点抢占总线。
⑧时钟同步
如果在某一I2C总线系统中存在两个主器件节点,分别记为主器件 1 和主器件 2,其时钟输出端分别为CLK1 和 CLK2,它们都有控制总线的能力。假设在某一期间两者相继向 SCL 线发出了波形不同的时钟脉冲序列 CLK1 和 CLK2(时钟脉冲的高、低电平宽度都是依靠各自内部专用计数器定时产生的),在总线控制权还没有裁定之前这种现象是可能出现的。鉴于 I2C 总线的“线与”特性,使得时钟线 SCL 上得到的时钟信号波形,既不像主器件 1 所期望的CLK1,也不像主器件 2 所期望的 CLK2,而是两者进行逻辑与的结果。
CLKI 和 CLK2 的合成波形作为共同的同步时钟信号,一旦总线控制权裁定给某一主器件,则总线时钟信号将会只由该主器件产生。
⑨总线冲突和总线仲裁
假如在某 I2C 总线系统中存在两个主器件节点,分别记为主器件 1 和主器件 2,其数据输出端分别为DATA1 和 DATA2,它们都有控制总线的能力,这就存在着发生总线冲突(即写冲突)的可能性。假设在某一瞬间两者相继向总线发出了启动信号,鉴于:I2C 总线的“线与”特性,使得在数据线 SDA上得到的信号波形是 DATA1 和 DATA2 两者相与的结果,该结果略微超前送出低电平的主器件 1,其 DATA1的下降沿被当做 SDA 的下降沿。在总线被启动后,主器件 1 企图发送数据“101……”,主器件 2 企图发送数据据“100101……”。两个主器件在每次发出一个数据位的同时都要对自己输出端的信号电平进行抽检,只要抽检的结果与它们自己预期的电平相符,就会继续占用总线,总线控制权也就得不到裁定结果。主器件 1 的第 3 位期望发送“1”,也就是在第 3 个时钟周期内送出高电平。
在该时钟周期的高电平期间,主器件 1 进行例行抽检时,结果检测到一个不相匹配的电平“0”,这时主器件 1 只好决定放弃总线控制杈;因此,主器件 2 就成了总线的惟一主宰者,总线控制权也就最终得出了裁定结果,从而实现了总线仲裁的功能。
从以上总线仲裁的完成过程可以得出:仲裁过程主器件1和主器件2都不会丢失数据;各个主器件没有优先级别之分,总线控制权是随机裁定的,即使是抢先发送启动信号的主器件1最终也并没有得到控制杈。系统实际上遵循的是“低电平优先”的仲裁原则,将总线判给在数据线上先发送低电平的主器件,而其他发送高电平的主器件将失去总线控制权。
⑩总线封锁状态。
在特殊情况下,如果需要禁止所有发生在 I2C 总线上的通信活动,封锁或关闭总线是一种可行途径,只要挂接于该总线上的任意一个器件将时钟线 SCL 锁定在低电平上即可。
最后,附上用STM32的GPIO口模拟的I2C总线协议的代码。
1 #define I2C_GPIO GPIOB 2 #define I2C_SCL GPIO_Pin_10 3 #define I2C_SDA GPIO_Pin_11 4 #define I2C_RCC RCC_APB2Periph_GPIOB 5 6 #define SCL_H() I2C_GPIO->BSRR = I2C_SCL 7 #define SCL_L() I2C_GPIO->BRR = I2C_SCL 8 #define SDA_H() I2C_GPIO->BSRR = I2C_SDA 9 #define SDA_L() I2C_GPIO->BRR = I2C_SDA 10 #define SCL_Read() I2C_GPIO->IDR & I2C_SCL 11 #define SDA_Read() I2C_GPIO->IDR & I2C_SDA 12 13 void I2C_GPIO_Init(void) //I2C引脚进行初始化 14 { 15 GPIO_InitTypeDef GPIO_InitStructure; 16 RCC_APB2PeriphClockCmd(I2C_RCC, ENABLE ); 17 GPIO_InitStructure.GPIO_Pin = I2C_SCL; 18 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 19 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; 20 GPIO_Init(I2C_GPIO, &GPIO_InitStructure); 21 22 GPIO_InitStructure.GPIO_Pin = I2C_SDA; 23 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 24 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; 25 GPIO_Init(I2C_GPIO, &GPIO_InitStructure); 26 } 27 28 void I2C_Delay(void) //延时函数,用空函数进行简单延时 29 { 30 31 } 32 33 int I2C_Start(void) //I2C启动信号 34 { 35 SDA_H(); 36 SCL_H(); 37 I2C_Delay(); 38 if(!SDA_Read) //SDA线为低电平则总线忙,退出 39 return 0; 40 SDA_L(); 41 I2C_Delay(); 42 if(SDA_Read) //SDA线为高电平则总线出错,退出 43 return 0; 44 SDA_L(); 45 I2C_Delay(); 46 47 return 1; 48 } 49 50 void I2C_Stop(void) //I2C停止信号 51 { 52 SCL_L(); 53 I2C_Delay(); 54 SDA_L(); 55 I2C_Delay(); 56 SCL_H(); 57 I2C_Delay(); 58 SDA_H(); 59 I2C_Delay(); 60 } 61 62 void I2C_Ack(void) //I2C应答信号 63 { 64 SCL_L(); 65 I2C_Delay(); 66 SDA_L(); 67 I2C_Delay(); 68 SCL_H(); 69 I2C_Delay(); 70 SCL_L(); 71 I2C_Delay(); 72 } 73 74 void I2C_NoAck(void) //I2C无应答信号 75 { 76 SCL_L(); 77 I2C_Delay(); 78 SDA_H(); 79 I2C_Delay(); 80 SCL_H(); 81 I2C_Delay(); 82 SCL_L(); 83 I2C_Delay(); 84 } 85 86 int I2C_WaitAck(void) //I2C等待应答信号 返回值:1有ACK,0无ACK 87 { 88 SCL_L(); 89 I2C_Delay(); 90 SDA_H(); 91 I2C_Delay(); 92 SCL_H(); 93 I2C_Delay(); 94 if(SDA_Read) 95 { 96 SCL_L(); 97 I2C_Delay(); 98 return 0; 99 } 100 SCL_L(); 101 I2C_Delay(); 102 103 return 1; 104 } 105 106 void I2C_SendByte(unsigned int SendByte) //I2C发送一个字节 107 { 108 unsigned int i=8; 109 while(i--) 110 { 111 SCL_L(); 112 I2C_Delay(); 113 if(SendByte&0x80) 114 SDA_H(); 115 else 116 SDA_L(); 117 SendByte<<=1; 118 I2C_Delay(); 119 SCL_H(); 120 I2C_Delay(); 121 } 122 SCL_L(); 123 } 124 125 unsigned int I2C_ReadByte(void) //I2C读取一个字节 126 { 127 unsigned int i=8; 128 unsigned int ReceiveByte=0; 129 130 SDA_H(); 131 while(i--) 132 { 133 ReceiveByte<<=1; 134 SCL_L(); 135 I2C_Delay(); 136 SCL_H(); 137 I2C_delay(); 138 if(SDA_Read) 139 { 140 ReceiveByte|=0x01; 141 } 142 } 143 SCL_L(); 144 145 return ReceiveByte; 146 } 147 148 int I2C_Single_Write(unsigned int SlaveAddress,unsigned int REG_Address,unsigned int REG_Data) //I2C单字节写入 149 { 150 if(!I2C_Start()) 151 return 0; 152 I2C_SendByte(SlaveAddress); 153 if(!I2C_WaitAck()) 154 { 155 I2C_Stop(); 156 return 0; 157 } 158 I2C_SendByte(REG_Address ); //设置低起始地址 159 I2C_WaitAck(); 160 I2C_SendByte(REG_Data); 161 I2C_WaitAck(); 162 I2C_Stop(); 163 164 return 1; 165 } 166 167 int I2C_Single_Read(unsigned int SlaveAddress,unsigned int REG_Address) //I2C单字节读出 168 { 169 unsigned char REG_Data; 170 if(!I2C_Start()) 171 return 0; 172 I2C_SendByte(SlaveAddress); 173 if(!I2C_WaitAck()) 174 { 175 I2C_Stop(); 176 return 0; 177 } 178 I2C_SendByte((unsigned int) REG_Address); 179 I2C_WaitAck(); 180 I2C_Start(); 181 I2C_SendByte(SlaveAddress+1); 182 I2C_WaitAck(); 183 184 REG_Data= I2C_ReadByte(); 185 I2C_NoAck(); 186 I2C_Stop(); 187 188 return REG_data; 189 } 190 191 int I2C_Mult_Read(unsigned int SlaveAddress,unsigned int REG_Address,unsigned int * ptChar,unsigned int size) //I2C多字节读出 192 { 193 unsigned int i; 194 195 if(size < 1) 196 return 0; 197 if(!I2C_Start()) 198 return 0; 199 I2C_SendByte(SlaveAddress); 200 if(!I2C_WaitAck()) 201 { 202 I2C_Stop(); 203 return 0; 204 } 205 I2C_SendByte(REG_Address); 206 I2C_WaitAck(); 207 208 I2C_Start(); 209 I2C_SendByte(SlaveAddress+1); 210 I2C_WaitAck(); 211 212 for(i=1;i<size; i++) 213 { 214 *ptChar++ = I2C_ReadByte(); 215 I2C_Ack(); 216 } 217 *ptChar++ = I2C_ReadByte(); 218 I2C_NoAck(); 219 I2C_Stop(); 220 221 return 1; 222 } 223 224 225 226 227