目录
1 I2C总线简介
总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,一般总线用于多个模块之间的通信。
I2C(Inter-Integrated Circuit)总线是由Philips公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。I2C总线上分成主机和从机两种设备,主机用于启动总线传送数据并产生时钟以同步从机,此时任何被寻址的器件均被认为是从器件。
I2C总线规范现在已经发展到了2.0版,总线速率最高为3.4Mbits/s。通常分为三种工作模式:标准模式(最高100kbits/s)、快速模式(最高400kbits/s)、高速模式(最高3.4Mbits/s)、超快速模式(5Mbits/s),常用的器件都支持标准和快速模式。
I2C总线只有两根通信线,SDA(串行数据线)和SCL(串行时钟线),这两根线都是双向I/O线。接口电路为开漏输出,需通过上拉电阻接电源VCC。当总线空闲时.两根线都是高电平,连接总线的外同器件都是CMOS器件,输出级也是开漏电路.各器件的SDA和SCL都是线“与”关系。
MOS器件采用OD(Open Drain)开漏模式,TTL器件采用OC(Open Colletor)集电极开路模式。当器件输出“0”时,将总线和地接通,输出“1”时,断开输出,所以总线必须采用外部上拉电阻把总线上拉到电源。由于所有器件采用OD或OC,输出“1”其实不对总线有影响,只是由于外部有上拉电阻,所以总线表线出来是高电平;当输出“0”时,器件接通MOS管,直当于把总线接地了,总线被强制拉到地,总线变成了低电平。
当所有器件输出“1”,总线表现出高电平;当所有器件输出“0”,总线表现出低电平。如果一部分输出“1”,一部分输出“0”,总线电平就会变成“0”,这就是线“与”。线“与”就是总线最终的表现出来的是所器件输出信号相“与”的结果。
典型的I2C总线应用示例如下图所示:
I2C总线支持多主机和多从机,总线上连接的设备只受到总线负载400pF的限制。这么多设备全部连接在总线上,为方便区分各个器件,每个器件必须有一个唯一的地址。这个地址一般是7位,也可以是10位,10位地址也叫扩展地址。
2 I2C总线的工作原理
2.1 位传输和字节传输
数据的有效性规定:
时钟信号(SCL)为高期间,数据总线(SDA)上的数据必须保持稳定,只有在SCL为低电平期间,才允许改变SDA上的数据,如下图:
起始(Start)和终止信号(Stop):
SCL为高电平期间,SDA由高变低表示起始信号;SCL高电平期间,SDA由低变高表示终止信号。标准模式要求图中t必须大于4微秒。Start一般简写为S,Stop简写为P。
应答信号:
I2C总线上的数据都是以8位字节为单位传送的,发送器每发送一个字节(首先传输的是数据的最高位 MSB),就在第9个脉冲期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK);应答信号为高电平时,规定为非应答位(NACK)。
重新起始信号(Start Repeated):
一般通信过程中都是起始信号开始,数据传输完成,以终止信号结束。但有些情况下,传输部分数据后,还需要再进行另外一次通信(一般都有写切换到读的过程),就会发送一个重新起始信号。
Start Repeated一般简写为Sr。
主机发送起始信号、终止信号和应答信号必须遵守的最小时间规定如下图:
2.2 常见数据传输格式
写传输格式
MCU作为主设备向从设备发送两个字节数据,首先发送起始信号(S),接着发送7位地址(MSB高位在前),再发送“0”(代表写),等从机确认(ACK)后,接着发送第一个字节数据(同时发送SCL和SDA信号),等从机确认后,再发送第二个数据,等从机确认后,发送终止信号,结束本次通信。
读传输格式
MCU作为主设备向从设备读取两个字节数据,首先发送起始信号(S),接着发送7位地址(MSB高位在前),再发送“1”(代表读),等从机确认(ACK)后,开始接收第一个字节数据(只发送SCL信号,从SDA上读返回的数据),收到8位数据后,主机发送(ACK);再开始接收第二个数据,收到8位数据后(如果不是最一个字节,主机发ACK;如果是最后一个字节,主机发NACK),然后发送终止信号,结束本次通信。
复合传输格式
写传输格式和读传输格式,在整个数据通信过程中,数据传输方向都不会改变。如果需要传输中间改变传输方向,就要在传输中间发送Sr(重新起始信号)。相当于写传输格式和读传输格式的组合。
2.3 几个相关定义
理解上述基本概念后,一般的I2C总线相关编程就没有问题了。为不干扰初学者,下列几个概念这里就不作解释了,想深入了解的可以自行找相关资料。
插入等待时间
总线封锁状态
总线竞争的仲裁
时钟信号的同步
3 软件模拟I2C主机程序
/*
PA8 - SCL
PC9 - SDA
Util_DelayUs 函数需另外定义
例程采用标准库函数版本,如需采用其它库函数,可自行修改相关函数
例程只是定义了I2C基本操作函数,具体应用还需参考相关器件的手册
*/
static void SCL_GPIO_Init(void)
{
GPIO_InitStructure.GPIO_Pins = GPIO_Pins_8; //
GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_OD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pins_8);
GPIO_InitStructure.GPIO_Pins = GPIO_Pins_9; //
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC, GPIO_Pins_9);
}
static void SCL_OPEN(void)
{
GPIO_SetBits(GPIOA, GPIO_Pins_8);
}
static void SCL_LOW(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pins_8);
}
static void SDA_OPEN(void)
{
GPIO_SetBits(GPIOC, GPIO_Pins_9);
}
static void SDA_LOW(void)
{
GPIO_ResetBits(GPIOC, GPIO_Pins_9);
}
static uint8_t SCL_READ()
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pins_8);
}
static uint8_t SDA_READ()
{
return GPIO_ReadInputDataBit(GPIOC, GPIO_Pins_9);
}
static void I2c_GenStart()
{
SDA_OPEN();
Util_DelayUs(1);
SCL_OPEN();
Util_DelayUs(1);
SDA_LOW();
Util_DelayUs(4); // hold time start condition (t_HD;STA)
SCL_LOW();
Util_DelayUs(4);
}
static void I2c_GenStop()
{
SCL_LOW();
Util_DelayUs(1);
SDA_LOW();
Util_DelayUs(1);
SCL_OPEN();
Util_DelayUs(4); // set-up time stop condition (t_SU;STO)
SDA_OPEN();
Util_DelayUs(4);
}
static int I2c_WriteByte(uint8_t txByte)
{
uint8_t mask;
int Acked = 1;
for(mask = 0x80; mask > 0; mask >>= 1) { // shift bit for masking (8 times)
if((mask & txByte) == 0)
SDA_LOW(); // masking txByte & write to SDA-Line
else
SDA_OPEN();
Util_DelayUs(1); // data set-up time (t_SU;DAT)
SCL_OPEN(); // generate clock pulse on SCL
Util_DelayUs(5); // SCL high time (t_HIGH)
SCL_LOW();
Util_DelayUs(1); // data hold time(t_HD;DAT)
}
SDA_OPEN(); // release SDA-line
SCL_OPEN(); // clk #9 for ack
Util_DelayUs(5); // data set-up time (t_SU;DAT)
if(SDA_READ())
Acked = 0; // check ack from i2c slave
SCL_LOW();
Util_DelayUs(20); // wait to see byte package on scope
return Acked; // return error code
}
static uint8_t I2c_ReadByte(int GenAck)
{
uint8_t mask;
uint8_t rxByte = 0;
SDA_OPEN(); // release SDA-line
for(mask = 0x80; mask > 0; mask >>= 1) { // shift bit for masking (8 times)
SCL_OPEN(); // generate clock pulse on SCL
Util_DelayUs(1); // data set-up time (t_SU;DAT)
while(SCL_READ() == 0){} // wait while hold master
Util_DelayUs(5); // SCL high time (t_HIGH)
if(SDA_READ()) rxByte = rxByte | mask; // read bit
SCL_LOW();
Util_DelayUs(1); // data hold time(t_HD;DAT)
}
if(GenAck)
SDA_LOW(); // send acknowledge if necessary
else
SDA_OPEN();
Util_DelayUs(1); // data set-up time (t_SU;DAT)
SCL_OPEN(); // clk #9 for ack
Util_DelayUs(5); // SCL high time (t_HIGH)
SCL_LOW();
SDA_OPEN(); // release SDA-line
Util_DelayUs(20); // wait to see byte package on scope
return rxByte; // return error code
}