I2C协议指东

来源:互联网 发布:grub2手动引导ubuntu 编辑:程序博客网 时间:2024/05/01 18:53

最近闲来无聊,入了一块MPU6050,手头本来就有一块原子的STM32 MINI开发板,凑活着学习了一下IIC,特此总结。

IIC,是集成电路总线【Inter-Intergrated Circuit】的缩写,属于飞利浦公司的原创。

主要用两根线:数据线SDA和时钟线SCL。

关于时序方面本文就不截图了,网上一大堆。

下面就具体说IIC的传输过程中,比较重要的几个方法,下文的代码均是在STM32中实现,是一种模拟IIC。

SCL为输出模式的PC(12),SDA则根据情况切换输入和输出模式,为PC(11)。


1、开始信号

开始信号定义为:SCL高电平时,SDA的下降沿

//开始信号void IIC_Start(void){SDA_OUT(); //SDA输出模式IIC_SDA=1;IIC_SCL=1;delay_us(IIC_DELAY);IIC_SDA=0;//SCL高电平时SDA的下降沿delay_us(IIC_DELAY);}

2、结束信号

结束信号定义为:SCL高电平时,SDA的上升沿

//结束信号void IIC_Stop(void){SDA_OUT();IIC_SDA=0;IIC_SCL=1;delay_us(IIC_DELAY);IIC_SDA=1;//SCL高电平时SDA的上升沿delay_us(IIC_DELAY);}

其中的SDA_OUT()是STM32的IO口模式设置,其他MCU可忽略或更改。IIC_DELAY是定义的宏,可以控制延迟时间从而控制IIC速率。


3、IIC写一个字节

这里的写一个字节是说,控制了IIC总线的主机往总线上写数据。

void IIC_Send_Byte(u8 data){u8 i;SDA_OUT();//输出模式for(i=0;i<8;i++){IIC_SCL=0;//拉低时钟 占据总线delay_us(IIC_DELAY);IIC_SDA=(data&0x80)>>7;//每次1位,先高位data<<=1;delay_us(IIC_DELAY);IIC_SCL=1;delay_us(IIC_DELAY);}IIC_SCL=0;}
这里默认是先MSB后LSB,IIC_SDA根据数据位依次置1或0,传输数据时,SCL必须拉低,以此告诉其他器件“传输进行中”,在传输结束后,还需要再次拉高SCL总线。在送完一个字节后,拉低SCL,等待应答。


4、IIC读一个字节

//IIC读一个BYTEu8 IIC_Read_Byte(void){u8 i,receive=0;SDA_IN();//输入模式READ_SDA=1;for(i=0;i<8;i++){receive<<=1;//先接收的是高位IIC_SCL=0;delay_us(IIC_DELAY);IIC_SCL=1;delay_us(IIC_DELAY);receive|=READ_SDA; }IIC_SCL=0;return receive;}
这里同样的默认是先高位后低位,使用receive|=READ_SDA;来组成数据,接收数据位时,需要先拉低SCL再拉高SCL,然后再读取SDA的数据。这里的READ_SDA和IIC_SDA都是PC(11),只不过是不同的模式。


5、应答

在IIC中,应答不是必须的,所以对于应答的检测其实也不是必须的

下面是应答和不应答的代码。

//产生ACK应答void IIC_Ack(void){SDA_OUT();IIC_SCL=0;delay_us(IIC_DELAY);IIC_SDA=0;delay_us(IIC_DELAY);IIC_SCL=1;delay_us(IIC_DELAY);IIC_SCL=0;//SDA为低时 拉低时钟线delay_us(IIC_DELAY);}//不产生ACK应答void IIC_NAck(void){IIC_SCL=0;SDA_OUT();IIC_SDA=1;delay_us(IIC_DELAY);IIC_SCL=1;delay_us(IIC_DELAY);IIC_SCL=0;// SDA为高时 SCL的脉冲delay_us(IIC_DELAY);}

6、应答检测

经过我的检验,当STM32写MPU6050时,是不需要进行应答检测的;但是当STM32读MPU6050时,如果不进行应答检测,就会出现数据出错/检测不到MPU6050等奇怪的错误,所以在应用IIC总线协议时,一律增加应答检测是比较好的一种规范做法

应答检测返回一个值,但是大多数情况中不需要用到这个返回值。

//应答信号确认//1有ACK//0无ACKu8 IIC_Wait_Ack(void){u8 ucErrTime=0;SDA_IN();//   SDA输入模式IIC_SCL=0;delay_us(IIC_DELAY);IIC_SDA=1;delay_us(IIC_DELAY);IIC_SCL=1;delay_us(IIC_DELAY);while(READ_SDA){ucErrTime++;if(ucErrTime>250){IIC_Stop();return 0;}}IIC_SCL=0;//关闭时钟return 1;}

如果SDA一直是高电平没有被从设备【此处为MPU6050】拉低,则说明MPU没有应答,此时停止传输,并返回0.

如果接收到应答了,则把时钟线拉低,等待下一次开始信号。

7、MPU6050相关。

关于IIC的所有函数已经讲完了,下面贴一下MPU6050相关的操作。

//写MPU60X0u8 IIC_Write_One_Byte(u8 regaddr, u8 data){IIC_Start();                  //起始信号    IIC_Send_Byte(SlaveAddress);   //发送设备地址+写信号    if(IIC_Wait_Ack()==0) {IIC_Stop();return 0;}IIC_Send_Byte(regaddr);    //内部寄存器地址    //IIC_Wait_Ack();IIC_Send_Byte(data);       //内部寄存器数据    //IIC_Wait_Ack();IIC_Stop();                   //发送停止信号return 1;}//读MPU60X0u8 IIC_Read_One_Byte(u8 regaddr){u8 REG_data=0;IIC_Start();                  //起始信号    IIC_Send_Byte(SlaveAddress);   //发送设备地址+写信号    if(IIC_Wait_Ack()==0) {IIC_Stop();return 0;}IIC_Send_Byte(regaddr);     //发送存储单元地址,从0开始IIC_Wait_Ack();IIC_Start();                   //起始信号IIC_Send_Byte(SlaveAddress+1);  //发送设备地址+读信号IIC_Wait_Ack();REG_data=IIC_Read_Byte();       //读出寄存器数据,并且不应答IIC_NAck();//不回应IIC_Stop();                    //停止信号return REG_data;}

可以看到写一个字节的应答检测被我注释掉了,实验证明依旧可以正确写入MPU


以上就是IIC的所有内容。


总结:IIC主要使用SDA,SCL两条线进行传输,其中SCL是独立的SDA是接入总线的。当SCL为高时,说明有“事件”:比如始信号、终止信号或者传输过程;当SCL为低时,说明总线闲,只要某一个设备拉高总线,并使得SDA总线产生一个下降沿,则主设备就可以得知是哪个设备的请求。这种通过独立SCL电平+SDA跳变的组合信号进行多设备整合的总线方案简单、有效,容错高,软件上易于实现,硬件上则更加方便。


原创粉丝点击