[嵌入式]I2C协议指东

最近闲来无聊,入了一块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读一个BYTE

u8 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无ACK

u8 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相关的操作。

//写MPU60X0

u8 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; } //读MPU60X0

u8 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跳变的组合信号进行多设备整合的总线方案简单、有效,容错高,软件上易于实现,硬件上则更加方便。

你可能感兴趣的:(嵌入式)