浅尝IIC
在称为嵌入式驱动工程师的道路上,IIC协议是我必须掌握的一个协议,我打算这周末为深入学习IIC协议,并且完成实验以及独立编写IIC驱动代码。
前言:
参考**《STM32库开发实战指南》**以及《_Z小旋的博客-CSDN博客_i2c通信的详细讲解》
在计算机科学中,大部分复杂问题都可以通过分层来简化。如Linux嵌入式设备,可分为硬件层、裸机层、内核层、应用层。芯片被分为内核层和片上外设(比如usart等)。
对于通信协议,我们也通过分层的方式来理解,最基本的分为软件层和协议层
简单来说,硬件层规定我们是使用嘴巴说还是肢体表示,而协议层规定我们使用英语还是汉语交流。 ——STM32库开发实战指南(火哥)
IIC物理层:
1)它是一个支持设备的总线。支持多个设备公用,但只有一个主设备。基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。
2)只有两个信号线,一个是双向串行数据线(SDA),一个是串行时钟线(SCL)。
3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址访问不太的设备。
4)总线通过上拉电阻接到了电源,为了保证信号线稳定。因为IIC设备都空闲时,会输出高阻态,相当于所有的设备都和两根总线断开了,上拉电阻会把总线拉到高电平。 如果不接上拉电阻,那么信号线就相当于浮空会变得不稳定。(下面有开漏模式的基本解释)。
5)同一个IIC总线上的设备不能超过使两个线之间的等效电容超过400PF?
6)为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。除了主设备。
开漏模式:配置到IO口后,只能输出/输入低电平和高阻态,高阻态顾名思义,就是非常高(大)的阻值,即断路。
开漏在IIC的作用:
将IO口设置为开漏后,io口可以输出/输入低电平和高阻态,主要目的是为了防止总线信号的混乱,如为了使空闲设备不再接受总线上的信号,将空闲设备io口设置为了高阻态,它就不会总线上的信号影响。其他正在用的设备并不会收到干扰。
总结物理层:
IIC有有两根信号总线,都带有一个上拉电阻到高电平为了设备空闲保证信号稳定,然后所有的设备都被设置为了开漏模式(不论输出输入)。
IIC协议层:
开始信号:
//I2C总线启动
void I2CStart(void)
{
SDA_Output(1);delay1(500);
SCL_Output(1);delay1(500);
SDA_Output(0);delay1(500);
SCL_Output(0);delay1(500);//提前拉低SCL,准备传输数据
}
停止信号:
//I2C总线停止
void I2CStop(void)
{
SCL_Output(0); delay1(500);
SDA_Output(0); delay1(500);
SCL_Output(1); delay1(500);
SDA_Output(1); delay1(500);
}
一般延时时间(>4.7us)
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
一般下只有在SCL信号为0时,SDA才能变化。数据的传输时通过SDA的电平变化来传输的。如果在SCL信号为1时,SDA信号仍然发生电平变化,说明是起始信号或者停止信号。
感到疑惑的地方:
有些IIC驱动的负责发送起始信号的函数后面还跟着一个将SCL拉低的操作,实测起始不要拉低也可以,这里最好也拉低,如果不拉低,造成数据误判。
应答信号:
每当主机向从机发出一个字节的数据信号后,主机总是需要等待从机传回一个应答信号,以确定从机是否收到数据。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
//发送应答位
void I2CSendAck(void)
{
SDA_Output(0);delay1(500);
delay1(500);
SCL_Output(1); delay1(500);
SCL_Output(0); delay1(500);
}
//
void I2CSendNotAck(void)
{
SDA_Output(1);
delay1(500);
SCL_Output(1); delay1(500);
SCL_Output(0); delay1(500);
}
IIC数据传送信号:
SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
然后数据传送信号 有分成了两种信号,一个是器件地址数据传送信号,一个是数据传送信号。
在起始信号后必须传送一个从机的地址(7位) 1~7位为7位接收器件地址,第8位为读写位,用“0”表示主机发送数据(W)(一帧一共9位),“1”表示主机接收数据 (R), 第9位为ACK应答位,紧接着的为第一个数据字节(8位),然后是一位应答位(也是一帧9位),后面继续第2个数据字节。
IIC写入数据:
主机要向从机写数据时:
1、起始信号,打开iic总线
2、发生地址位和控制位,发送从设备地址到总线上,地址一共7位,后面跟上一位控制字在(0)写入还是写出的位(W/R),(中间将SDA引脚设置为了下拉输入),然后等待一位应答位(输入)(当对应从机接受到主机要访问它的信号时,它就会把SDA拉低,为成功应答),然后在进行下一步。
3、收到应答后,发送具体你要访问的从设备的地址,也是和数据信号一样,先发一个8bin地址,然后等待一个应答信号(输入)
4、收到应答后,发送数据信号,包括一个8字节的数据信号,然后等待一个应答信号(输入)
5、收到应答后,发送一个结束信号
打个比方来说:
主机和从机为蓝天和白云两个人在打仗,然后他们之间通过一个对讲机沟通
1、蓝天想联系白云时,他会在对讲机的频道中喊道“白云,在吗?!”,然后等待白云回应“白云,收到”,(这一步是为了确认白云存活)
2、当白云回应蓝天后(收到应答信号),蓝天会继续喊道“请移动到1314.1111点”,然后等待白云回应“白云,收到”(这里一步是告诉了白云具体的坐标)
3、当白云回应蓝天后(收到应答信号),蓝天会继续喊道“请释放烟雾弹”,然后等待白云回应“白云,收到”(这里一步是告诉了白云在当前位置需要做什么事情)
4、当白云回应蓝天后(收到应答信号),停止通讯。
void x24_write(u8 add,u8 data)
{
I2CStart(); //开始信号
I2CSendByte(0xa0); //发送从设备地址
I2CWaitAck(); //等待从设备应答
I2CSendByte(add); //发送具体要访问从设备的哪个地址(或寄存器)
I2CWaitAck(); //等待从设备应答
I2CSendByte(data); //发送要写入从设备这个地址(或寄存器)中的值
I2CWaitAck(); //等待从设备应答
I2CStop(); //结束信号
}
IIC读数据:
1、发送起始信号,打开IIC总线
2、发送从设备的地址(7位)加上一位读取位,加上0(代表向从设备写入),然后等待应答
3、收到应答后,发送具体你要访问的从设备中的地址,先发一个8bin地址,然后等待一个应答信号(输入)
4、收到应答后,重新发出起始信号,然后发送访问的从设备的地址,跟上一个1(代表读操作),然后等待应答信号
5、收到应答后,重新发送起始信号,然后发送从设备的地址(7位)加上一位读取位,加上1(代表向从设备读取),等待应答
6、收到应答后,然后将主设备SDA设置为输入,从设备设置为输出,开始接受数据,当读取完一个字节后,主设备发送非应答信号
同样用蓝天白云来说明:
1、蓝天想联系白云时,他会在对讲机的频道中喊道“白云,在吗?!”,然后等待白云回应“白云,收到”,(这一步是为了确认白云存活)
2、当白云回应蓝天后(收到应答信号),蓝天会继续喊道“请移动到1314.1111点”,然后等待白云回应“白云,收到”(这里一步是告诉了白云具体的坐标)
3、当白云回应蓝天后(收到应答信号),蓝天给白云,请汇报你现在的情况,等待回应
4、当白云回应蓝天后(收到应答信号),开始给蓝天汇报情况
5、在蓝天收到完整的一个情报后,蓝天回复白云“收到”,结束通讯。
u8 x24_read(u8 add)
{
u8 data;
I2CStart(); //开始信号
I2CSendByte(0xa0); //发送从设备的地址,并且是写操作
I2CWaitAck();
I2CSendByte(add); //发送要进行操作的地址
I2CWaitAck();
I2CStart(); //重新发送开始信号
I2CSendByte(0xa1); //告诉从设备要进行读操作
I2CWaitAck(); //等待从设备应答
data=I2CReceiveByte(); //主设备接受一个字节的数据
I2CSendNotAck(); //主机发送非应答信号,表示不在接收数据
return data;
}