zoukankan      html  css  js  c++  java
  • [嵌入式]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跳变的组合信号进行多设备整合的总线方案简单、有效,容错高,软件上易于实现,硬件上则更加方便。

    Showing off sucks.
  • 相关阅读:
    WCF中的ServiceHost初始化两种方式(宿主)
    WCF实例上下文以及会话学习
    MSSQL有关时间函数知识(转)
    [转载红鱼儿]kbmmw 开发点滴:kbmMW缓存机制
    [转载红鱼儿]kbmmw 开发点滴:kbmMWEventService的本质
    [转载红鱼儿]kbmmw 开发点滴:ErrorTable用法
    [转载红鱼儿]kbmmw 开发点滴:kbmMW客户端提交事务的现场处理
    [转载红鱼儿]kbmmw 开发点滴:解决QueryService重复查询问题
    [转载红鱼儿]kbmmw 开发点滴:TkbmMWLock用法
    [转载红鱼儿]kbmmw 开发点滴:Authorization failed.
  • 原文地址:https://www.cnblogs.com/lancelod/p/3849375.html
Copyright © 2011-2022 走看看