你不能不知的I2C总线

4.8 I2C总线

I2C总线(Inter-Integrated Circuit Bus)是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。某些书籍或者文档中也写作IIC,读作“I方C”。

I2C是嵌入式中最常见的,也是最重要的总线通信协议之一。很多传感器、外围芯片都使用I2C协议。它具有如下特点:

(1)硬件线路简单:I2C总线只需要一根数据线和一根时钟线两根线,

(2)灵活:数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。

(3)可以连接设备数量多:连接到相同总线上的IC数量只受总线最大电容的限制。

4.8.1 I2C器件地址

I2C总线是一个主从结构的总线,所有的数据传输都必须由主机发起,通常单片机做主机,其他连接在I2C总线的设备称之为从机或者器件。

I2C还有一个重要的概念:器件地址。连接在I2C总线上的设备,除了主机之外,每个器件都有自己的地址。主机想要和某个器件通信时,先往I2C总线发送器件地址。

I2C器件地址一般为8位,最后一位是读写标志位。0表示主机要读取器件的数据;1表示主机要往器件写数据。

4.8.2 I2C时序

I2C总线只需要两根线,分别是时钟线(SCL)、数据线(SDA)。其中时钟线提供时间周期,时间周期越短则数据传输速率越快。数据线用来传输起始位、应答位,数据等。时序图如4.37所示。

你不能不知的I2C总线

图4.37 I2C时序图

数据格式主要有起始位、停止位、数据位、应答位(ACK)、NACK。

1. 起始位

当主机想要启动I2C数据传输时,需要要先往I2C总线发送起始位。起始位的条件是SCL线为高电平时,SDA线从高电平向低电平切换。

2. 停止位

当主机想要终止I2C数据传输时,需要往I2C总线发送停止位,释放I2C总线的占用。停止位的条件是SCL线为高电平时,SDA线从低电平向高电平切换。

3. 数据位

SDA数据线上的每个字节必须是8位,每次传输的字节数量没有限制。每个字节后必须跟一个响应位(ACK)。首先传输的数据是最高位(MSB),SDA上的数据必须在SCL高电平周期时保持稳定,数据的高低电平翻转变化发生在SCL低电平时期。

4. 应答位

每个字节传输必须带响应位,相关的响应时钟也由主机产生,在响应的时钟脉冲期间(第9个时钟周期),发送端释放SDA线,接收端把SDA拉低。

5. NACK位

以下情况会导致出现NACK位:

(1)接收机没有发送机响应的地址,接收端没有任何ACK发送给发射机

(2)由于接收机正在忙碌处理实时程序导致接无法接收或者发送

(3)传输过程中,接收机识别不了发送机的数据或命令

(4)接收机无法接收

(5)主机接收完成读取数据后,要发送NACK结束告知从机

4.8.3 模拟I2C

I2C属于比较简单的总线,完全可以根据I2C的时序,使用I/O模拟I2C。本文将使用STM32的GPIO口实现模拟I2C的功能,帮助读者理解I2C的时序控制。

打开Chapter4\05_I2C_24c02\mdk\IIC24c02.uvproj工程文件,接着打开24c02.c文件,模拟IIC的代码都在这个文件中。

1. I2C初始化

I2C的初始化部分代码主要是对STM32的GPIO进行初始化。GPIOB_9作为数据引脚(SDA),GPIOB_8作为时钟引脚(SCL),代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c???? 5行



//I2C初始化

void IIC_Init(void)

{????????????????????

? GPIO_InitTypeDef? GPIO_InitStructure;

? RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);? //打开GPIOB时钟

? //GPIOB8,B9

? GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;

? GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;????????? //输出模式

? GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;???????? //开漏输出

? GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;???? //100MHz

? GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;?????????? //上拉

? GPIO_Init(GPIOB, &GPIO_InitStructure);???????????????? //初始化

????

? IIC_Stop();?? //先给停止信号,复位I2C总线上所有的设备

}

?

2. 起始信号

当SCL为高电平时,SDA出现一个下降沿表示I2C总线启动信号,代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c???? 23行



//I2C启动信号

void IIC_Start(void)

{

???? //先把SDA输出引脚置高

???? IIC_SDAOUT=1;???

???? //SCL引脚置高? ?? ??

???? IIC_SCL=1;

???? //等待4us

???? delay_us(4);

???? //SDA引脚拉低

???? IIC_SDAOUT=0;

???? //等待4us

???? delay_us(4);

???? //SCL引脚拉低

???? IIC_SCL=0;???? //准备发送数据或者接收数据

}

?

IIC_SCL指I2C的SCL引脚,IIC_SDAOUT 指I2C的SDA引脚输出,在24c02.h文件中分别被定义成PBout(8)和PBout(9),代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.h???? 8行



#define IIC_SCL????? PBout(8)? //SCL

#define IIC_SDAOUT?? PBout(9) // SDA??

?

IIC_SDAOUT=1表示SDA引脚输出高电平。这里是GPIO输出高低电平的另外一种写法,等价于之前的GPIO_WriteBit(GPIOB, GPIO_Pin_9, Bit_SET)。

IIC_Start函数使用SDA、SCL引脚,通过输出高低电平和延时的操作,模拟了I2C启动信号。其时序如图4.38所示。

你不能不知的I2C总线

图4.38 I2C启动信号时序

3. 停止信号

当SCL高电平时,SDA出现一个上升沿表示I2C总线停止信号,代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c???? 34行



void IIC_Stop(void)

{

???? //SDA先低电平,这样才能出现上升沿

???? IIC_SDAOUT=0;

???? delay_us(4);

???? //SCL高电平

???? IIC_SCL=1;

? ?? delay_us(4);

???? //SDA由低电平变高电平,此时出现一个上升沿。????????????????????????????

???? IIC_SDAOUT=1;???????????????????????????? ?? ?????

}

?

4.应答信号

I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,在响应的时钟脉冲期间(第9个时钟周期),由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK)。主机等待从机应答信号的相关代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c???? 50行



//返回值:1表示NACK, 0表示 ACK

u8 MCU_Wait_Ack(void)

{

???? u8 ack;

?

???? IIC_SDAOUT=1;

???? delay_us(1);???? ??

???? IIC_SCL=1;

???? delay_us(1);

???? //读取SDA总线电平

???? if (IIC_SDAIN)??????????

???? {

???????????? ack = 1;???????? //高电平则表示NACK应答

???? }

???? else

???? {

???????????? ack = 0;???????? //低电平则表示NACK应答



???? }

???? IIC_SCL=0;

???? delay_us(1);

???? return ack;?

}

?

5.数据位发送

在I2C总线上传送的每一位数据都有一个时钟脉冲相对应。在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1则为高电平。时序如图4.39所示。

你不能不知的I2C总线

图4.39 数据位发送时序

6.发送一个字节

I2C写一个字节相当于往I2C总线发送了8个数据位,根据图4.39数据位发送时序,我们可以用I/O模拟,代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c? 113行



//参数:Senddata 要发送的数据

void IIC_write_OneByte(u8 Senddata)

{???????????????????????

???? u8 t;??

????????????

???? IIC_SCL=0;???

???? for(t=0;t<8;t++)

???? {

???????????? //先发送高位

???????????? IIC_SDAOUT=(Senddata&0x80)>>7;

???????????? //左移1位

???????????? Senddata=(Senddata<<1); ? ??

???????????? delay_us(2);??

???????????? IIC_SCL=1;

???????????? delay_us(2);

???????????? IIC_SCL=0;??????

???????????? delay_us(2);

???? }???????

}

?

其中比较关键的代码是Senddata的移位操作。

根据 & 和 >> 的特性,(Senddata&0x80)>>7相当于保留Senddata的最高位,其它位清零,同时再把最高位右移到最低位。相当于把Senddata最高位的数值赋给IIC_SDAOUT,从而实现SDA引脚根据Senddata的最高位输出响应的高低电平。

之后Senddata=(Senddata<<1),把Senddata的第2高位通过左移1位的方式,使Senddata的第2高位变成最高位。

再通过for循环,重复这两步操作,把Senddata的每一位都发送出去。为了方便直观理解,我们假设Senddata等于170,十六进制为:0xAA,二进制为:10101010。整个for循环的移位操作可以用图4.40直观的表示出来。

你不能不知的I2C总线

图4.40 移位操作流程图

?

7.读一个字节

读时序和发送时序相同,不同的是发送时需要在SCL低电平的时候更改SDA数据位,而读时需要在SCL高电平的时候读取SDA数据位。同时,每读取一位数据,都需要左移1位,保证高位在前。读完数据后需要发送ACK或者NACK应答信号。代入如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c? 137行



u8 IIC_Read_OneByte(u8 ack)

{

?? u8 i,receivedata=0;



??? for(i=0;i<8;i++ )

?? {

??????? IIC_SCL=0;

??????? delay_us(2);

????? IIC_SCL=1;

??????? receivedata<<=1;

??????? if(IIC_SDAIN)

????? {

???????????? receivedata++;??

????? }

????? delay_us(1);

??? }?????????????????????????????????????????

??? if (!ack)

??????? MCU_NOAck();

??? else

??????? MCU_Send_Ack();

??? return receivedata;

}

?

4.8.4 小结

I2C是嵌入式中最常见的总线通信协议,读者需要熟练掌握,了解IIC的时序,并能使用I/O模拟I2C操作。

你不能不知的I2C总线

上一篇:sftp的使用


下一篇:nginx配置