I2C(inter-integrated Circuit):集成电路总线,由飞利浦公司在上个世纪80年代所设计的,是一种两线式串行通信总线,使用多主从架构,用作为不同器件之间的通讯。
主机:发送命令的设备成为主机。
从机:接受命令并响应命令的设备成为从机。
设计模式
I2C只使用两条双向漏级开路(数据线SDA和时钟线SCL),iic设备通过这两条总线连接到处理器的IIC总线控制器上。连接到总线的接口数量只由总线电容是 400pF 的限制决定。
PS:漏级开路(Open Drain),是一个NPN型三极管,并不输出某一特定电压值或电流值。根据三极管基极所接的集成电路来决定(三极管发射极接地),通过三极管集电极,使其开路而输出。(后续再补充这方面内容)
IIC特点
- IIC支持不同的模式:快速模式(400 kbit/s)、快速+模式(1 Mbit/s)高速模式(3.4 Mbit/s)
2.每一个设备都可以通过唯一的设备地址进行单独访问,并且I2C 总线协议不需要地址译码器。 - 它是一个多主机系统,在一条总线上可以同时有多个主机存在。同一时间只能有一个主机占用总线(仲裁,通过冲突检测和延时等待,可以保证数据不被破坏。
- 只要求两条总线线路:SDA和SCL。
- 片上的滤波器可以滤去总线数据线上的毛刺波 保证数据完整
IIC总线的信号类型
开始信号S:SCL为高点平时,SDA由高电平向低电平跳变。
结束信号P:SCL为高点平时,SDA由低电平向高电平跳变。
响应信号ACK:从机接收到8位数据后,在第9个时钟周期拉低SDA电平,表示以及收到数据。
从图中可知:空闲时间时,两条线都是高电平数据的变化都是在SCL为低电平时SDA进行改变,并且在SCL为高电平时被读取到数据(此时SDA需要保持电平)。当SCL为高电平时,SDA改变会产生控制信号。
维基:
- Data transfer is initiated with a start condition (S) signaled by SDA being pulled low while SCL stays high
- SCL is pulled low, and SDA sets the first data bit level while keeping SCL low (during blue bar time).
- The data are sampled (received) when SCL rises for the first bit (B1). For a bit to be valid, SDA must not change between a rising edge of SCL and the subsequent falling edge (the entire green bar time).
- This process repeats, SDA transitioning while SCL is low, and the data being read while SCL is high (B2, ...Bn)
- The final bit is followed by a clock pulse, during which SDA is pulled low in preparation for the stop bit.
- A stop condition (P) is signaled when SCL rises, followed by SDA rising.
IIC发送数据过程
数据传输的格式:
发送的数据必须是一个字节,但是字节的数量是不受限制的,每个字节后必须跟一个响应位 ,首先传输的是数据的最高位 MSB。
如果从机要完成一些其他功能后 ,例如一个内部中断服务程序才能接收或发送下一个完整的数据字节 ,可以使时钟线 SCL 保持低电平迫使主机进入等待状态, 当从机准备好接收下一个数据字节并释放时钟线 SCL 后 数据传输继续。
响应:
当从机不能响应从机地址时 例如它正在执行一些实时函数不能接收或发送 从机必须使数据线保持
高电平 主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输
相关的响应时钟脉冲由主机产生 在响应的时钟脉冲期间 发送器释放 SDA 线
数据传输过程:
主机通过SDA向从机发送数据,总线空闲时SDA和SCL都处于高电平。
- 当主机检测到总线空闲时,主机发出开始信号S
- 主机发送8位数据,前7位表示从机地址,第8位表示数据的传输方向。(0表示向从机发送数据)。
- 被选中的从机发出响应信号ACK
- 从机传输一系列的字节和响应位
- 主机接受数据并发送停止符。
地址:一个固定和可编程的部分组成,如有3位可编程的地址位,则总线上一共可以连接8个相同的器件。
仲裁:
所有主机在 SCL 线上产生它们自己的时钟来传输 I2C 总线上的报文,数据只在时钟的高电平周期有效。如果两个或多个主机都向总线上发送启动信号并开始传送数据,这样就形成了冲突。 需要仲裁。
I2C总线上仲裁分为两部分:SCL线的同步和SDA线的仲裁。I2C总线的仲裁机制
-
SCL线同步
SCL同步是由于总线具有线“与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。
时钟同步 - SDA仲裁
SDA线的仲裁也是建立在总线具有线“与”逻辑功能的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。 - 仲裁过程
- 主节点1、2同时发送起始信号时,两个主节点都发送了高电平信号。这时总线上呈现的信号为高电平,两个主节点都检测到总线上的信号与自己发送的信号相同,继续发送数据。
- 第2个时钟周期,2个主节点都发送低电平信号,在总线上呈现的信号为低电平,仍继续发送数据。
- 在第3个时钟周期,主节点1发送高电平信号,而主节点2发送低电平信号。根据总线的线“与”的逻辑功能,总线上的信号为低电平,这时主节点1检测到总线上的数据和自己所发送的数据不一样,就断开数据的输出级,转为从机接收状态。
-
这样主节点2就赢得了总线,而且数据没有丢失,即总线的数据与主节点2所发送的数据一样,而主节点1在转为从节点后继续接收数据,同样也没有丢掉SDA线上的数据。因此在仲裁过程中数据没有丢失。
仲裁特殊情况:
- 重复起始条件和数据位。
数据传输一般由主机产生的停止位 P 终止, 但是如果主机仍希望在总线上通讯,它可以产生重复起始条件和寻址另一个从机,而不是首先产生一个停止条件。 - 停止条件和数据位。
- 重复起始条件和停止条件。
从机不被卷入仲裁过程。
// Hardware-specific support functions that MUST be customized:
//It illustrates all of the I2C features described before (clock stretching, arbitration, start/stop bit, ack/nack)
//ACK:Acknowledgement,它是一种正向反馈,接收方收到数据后回复消息告知发送方。 NACK:Negative //Acknowledgement,则是一种负向反馈,接收方只有在没有收到数据的时候才通知发送方。
#define I2CSPEED 100
void I2C_delay(void);
bool read_SCL(void); // Return current level of SCL line, 0 or 1
bool read_SDA(void); // Return current level of SDA line, 0 or 1
void set_SCL(void); // Do not drive SCL (set pin high-impedance)
void clear_SCL(void); // Actively drive SCL signal low
void set_SDA(void); // Do not drive SDA (set pin high-impedance阻抗)
void clear_SDA(void); // Actively drive SDA signal low
void arbitration_lost(void);
bool started = false; // global data
//开始信号S:SCL为高点平时,SDA由高电平向低电平跳变。
void i2c_start_cond(void) {
if (started) {
// if started, do a restart condition
set_SDA();// set SDA to 1
I2C_delay();
set_SCL();// Do not drive SCL (set pin high-impedance)
// read_SCL() ,Return current level of SCL line, 0 or 1
while (read_SCL() == 0) { // Clock stretching
//PS:clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行
// You should add timeout to this loop
}
// Repeated start setup time, minimum 4.7us
I2C_delay();
}
if (read_SDA() == 0) {
arbitration_lost();
}
// SCL is high, set SDA from 1 to 0.
// SCL为高,SDA高到低时为开始启动
//SCL为低时,SDA变化为传输数据。
clear_SDA();
I2C_delay();
clear_SCL();
started = true;
}
//结束信号P:SCL为高点平时,SDA由低电平向高电平跳变。
void i2c_stop_cond(void) {
// set SDA to 0
clear_SDA();
I2C_delay();
set_SCL();
// Clock stretching
while (read_SCL() == 0) {
// add timeout to this loop.
}
// Stop bit setup time, minimum 4us
I2C_delay();
// SCL is high, set SDA from 0 to 1
set_SDA();
I2C_delay();
if (read_SDA() == 0) {
arbitration_lost();
}
started = false;
}
//数据的变化都是在SCL为低电平时SDA进行改变,并且在SCL为高电平时被读取到数据
// Write a bit to I2C bus
void i2c_write_bit(bool bit) {
if (bit) {
set_SDA();
} else {
clear_SDA();
}
// SDA change propagation delay
I2C_delay();
// Set SCL high to indicate a new valid SDA value is available
set_SCL();
// Wait for SDA value to be read by slave, minimum of 4us for standard mode
I2C_delay();
while (read_SCL() == 0) { // Clock stretching
// You should add timeout to this loop
}
// SCL is high, now data is valid
// If SDA is high, check that nobody else is driving SDA
if (bit && (read_SDA() == 0)) {
arbitration_lost();
}
// Clear the SCL to low in preparation for next change
clear_SCL();
}
// Read a bit from I2C bus
bool i2c_read_bit(void) {
bool bit;
// Let the slave drive data
set_SDA();
// Wait for SDA value to be written by slave, minimum of 4us for standard mode
I2C_delay();
// Set SCL high to indicate a new valid SDA value is available
set_SCL();
while (read_SCL() == 0) { // Clock stretching
// You should add timeout to this loop
}
// Wait for SDA value to be written by slave, minimum of 4us for standard mode
I2C_delay();
// SCL is high, read out bit
bit = read_SDA();
// Set SCL low in preparation for next operation
clear_SCL();
return bit;
}
// Write a byte to I2C bus. Return 0 if ack by the slave.
bool i2c_write_byte(bool send_start,
bool send_stop,
unsigned char byte) {
unsigned bit;
bool nack;
if (send_start) {
i2c_start_cond();
}
for (bit = 0; bit < 8; ++bit) {
i2c_write_bit((byte & 0x80) != 0);
byte <<= 1;
}
nack = i2c_read_bit();
if (send_stop) {
i2c_stop_cond();
}
return nack;
}
// Read a byte from I2C bus
unsigned char i2c_read_byte(bool nack, bool send_stop) {
unsigned char byte = 0;
unsigned char bit;
for (bit = 0; bit < 8; ++bit) {
byte = (byte << 1) | i2c_read_bit();
}
i2c_write_bit(nack);
if (send_stop) {
i2c_stop_cond();
}
return byte;
}
void I2C_delay(void) {
volatile int v;
int i;
for (i = 0; i < I2CSPEED / 2; ++i) {
v;
}
}