• STC8H开发(七): I2C驱动MPU6050三轴加速度+三轴角速度检测模块


    目录

    MPU-6050

    MPU-6050是InvenSense生产的六轴运动跟踪芯片, 芯片尺寸4×4×0.9mm, QFN封装. 整合了三轴陀螺仪, 三轴加速度计, 片内温度传感器和数字运动处理器(DMP), 可以使用I2C接口外接三轴电子罗盘的输入,提供完整的九轴运动融合输出.

    MPU-6050包含6个16位ADC, 3个用于陀螺仪输出, 3个用于加速度计输出. 用户可以设置的陀螺仪满量程范围为±250,±500,±1000,±2000°/秒(dps), 可设置的加速度计满量程范围为±2g, ±4g, ±8g和±16g. 通信使用 400kHz的 I2C接口或 1MHz的 SPI接口(SPI仅MPU-6000可用).

    关于I2C通信

    在 FwLib_STC8 中, I2C 通信部分的头文件代码如下

    typedef enum
    {
        I2C_MasterCmd_Wait          = 0x00, // Wait, idle
        I2C_MasterCmd_Start         = 0x01, // START
        I2C_MasterCmd_Send          = 0x02, /* Send data. This command will generate 8 clocks on SCL, and 
                                               send I2CTXD to SDA bit by bit from MSB */
        I2C_MasterCmd_RxAck         = 0x03, /* Recive Ack. This command will generate 1 clock on SCL, and
                                               save the received bit to MSACKI(I2CMSST.1) */
        I2C_MasterCmd_Recv          = 0x04, // Recive data
        I2C_MasterCmd_TxAck         = 0x05, /* Send Ack. This command will generate 1 clock on SCL, and 
                                               write the value of MSACKO(I2CMSST.0) to SDA */
        I2C_MasterCmd_Stop          = 0x06, // STOP. This command will send STOP signal, and reset MSBUSY flag
        I2C_MasterCmd_StartSendRxAck = 0x09, // START + Send data + RxAck
        I2C_MasterCmd_SendRxAck     = 0x0A, // Send data + RxAck
        I2C_MasterCmd_RecvTxAck0    = 0x0B, // Receive data + TxAck(0)
        I2C_MasterCmd_RecvNAck      = 0x0C, // Receive data + NAck
    } I2C_MasterCmd_t;
    
    #define I2C_SendMasterCmd(__CMD__) {                                 \
                    (I2CMSCR) =  (I2CMSCR) & ~(0x0F) | ((__CMD__) & 0x0F);  \
                    while (!(I2CMSST & 0x40));                              \
                    I2CMSST &= ~0x40;                                       \
                }
    
    #define I2C_MasterStart()               I2C_SendMasterCmd(I2C_MasterCmd_Start)
    #define I2C_MasterSendData(__DATA__)    do{I2CTXD = (__DATA__); I2C_SendMasterCmd(I2C_MasterCmd_Send);}while(0)
    #define I2C_MasterRxAck()               I2C_SendMasterCmd(I2C_MasterCmd_RxAck)
    #define I2C_MasterAck()                 do{I2CMSST &= ~(0x01); I2C_SendMasterCmd(I2C_MasterCmd_TxAck);}while(0)
    #define I2C_MasterNAck()                do{I2CMSST |= 0x01; I2C_SendMasterCmd(I2C_MasterCmd_TxAck);}while(0)
    #define I2C_MasterStop()                I2C_SendMasterCmd(I2C_MasterCmd_Stop)
    

    对这部分的说明如下:
    STC8H 对 I2C 通信涉及的功能做了硬件封装, 通过在 I2CMSCR 中写入命令完成I2C操作. 命令执行的结束, 通过 I2CMSST 的 B6 判断, 命令完成时这一位会置1, 需要软件清零, 这就是这两行代码的作用

    while (!(I2CMSST & 0x40));                              \
    I2CMSST &= ~0x40;  
    

    所有的命令执行完都要使用这两行进行处理.

    主设备往从设备的写操作实现如下, 可以看到在每一个数据发送后, 都要用RxAck产生一个时钟, 读取从设备在SDA上产生的电平, 可以用于判断是ACK还是NAK

    uint8_t I2C_Write(uint8_t devAddr, uint8_t memAddr, uint8_t *dat, uint16_t size)
    {
        SFRX_ON();
        I2C_MasterStart();
        I2C_MasterSendData(devAddr & 0xFE);
        I2C_MasterRxAck();
        I2C_MasterSendData(memAddr);
        I2C_MasterRxAck();
        while(size--)
        {
            I2C_MasterSendData(*dat++);
            I2C_MasterRxAck();
        }
        I2C_MasterStop();
        SFRX_OFF();
        return HAL_OK;
    }
    

    主设备对从设备的读操作实现如下, 可以看到在写地址部分和写操作是一样的, 在读操作部分, 每次读取完之后, 主设备会回写ACK或NAK响应, 告诉从设备是否要继续给主设备发送数据.

    uint8_t I2C_Read(uint8_t devAddr, uint8_t memAddr, uint8_t *buf, uint16_t size)
    {
        SFRX_ON();
        I2C_MasterStart();
        I2C_MasterSendData(devAddr & 0xFE);
        I2C_MasterRxAck();
        I2C_MasterSendData(memAddr);
        I2C_MasterRxAck();
        I2C_MasterStart();
        I2C_MasterSendData(devAddr | 0x01);
        I2C_MasterRxAck();
        while(size--)
        {
            I2C_SendMasterCmd(I2C_MasterCmd_Recv);
            *buf++ = I2CRXD;
            if (size == 0)
            {
                I2C_MasterNAck();
            }
            else
            {
                I2C_MasterAck();
            }
        }
        I2C_MasterStop();
        SFRX_OFF();
        return HAL_OK;
    }
    

    I2C的响应问题

    对于每一个从设备(slaver), 当它被寻址后, 都要求在接收到每一个字节后产生一个响应. 因此主设备必须产生一个额外的时钟脉冲用于读取从设备的响应.
    在这个脉冲期间, 主设备释放对SDA的控制, 从设备必须将SDA拉低并在时钟的高电平期间保持住, 这代表返回了一个ACK; 如果不拉低SDA, 就代表返回了NACK.
    在从设备发送完最后一个字节后, 主设备(读取方)必须产生一个不响应位, 用以通知从设备不要再发送信息, 这样从机就知道该将SDA释放了, 而后主设备发出停止信号.

    模块与STC8H的接线

    市面上的模块, 一般是8个pin脚, 如果只是获取6轴采样和温度, 与STC8H只需要连接4根线

        P32   -> SCL
        P33   -> SDA
        GND   -> GND
        3.3V  -> VCC
    

    未连接的其它4个pin分别是

    • XDA和XCL: I2C主设备接口, 用于外接I2C从设备,
    • AD0: I2C从设备地址LSB
    • INT: 中断输出

    模块与STC8H的通信

    I2C频率最高为400KHz, STC8H的设置如下, 其中I2C功能复用选择的是P32/P33, 如果换到其它复用脚需要相应调整

    void I2C_Init(void)
    {
        // 主节点模式
        I2C_SetWorkMode(I2C_WorkMode_Master);
        /**
         * I2C 时钟 = SYSCLK / 2 / (__prescaler__ * 2 + 4)
         * MPU6050 works with i2c clock up to 400KHz
         * 
         * 44.2368 / 2 / (26 * 2 + 4) = 0.39 MHz
        */
        I2C_SetClockPrescaler(0x1A);
        // 复用口选择
        I2C_SetPort(I2C_AlterPort_P32_P33);
        // 启动 I2C
        I2C_SetEnabled(HAL_State_ON);
    }
    

    与MPU6050的通信可以直接使用SDK中的I2C读写方法, 需要注意的几点

    1. 一次性读出的双字节结果, 如果直接转uint16_t, 其高字节和低字节位置是相反的, 需要调换后再转换
    2. 可以一次性读出6轴+温度数据
    uint16_t swap(uint16_t num)
    {
        return (num >> 8) | (num << 8);
    }
    
    void MPU6050_Write(uint8_t addr, uint8_t dat)
    {
        I2C_Write(MPU6050_ADDR, addr, &dat, 1);
    }
    
    uint8_t MPU6050_Read(uint8_t addr)
    {
        uint8_t ret;
        I2C_Read(MPU6050_ADDR, addr, &ret, 1);
        return ret;
    }
    
    // 读取16位数据
    uint16_t MPU6050_ReadInt(uint8_t addr)
    {
        uint16_t ret;
        I2C_Read(MPU6050_ADDR, addr, (uint8_t *)&ret, 2);
        return swap(ret); // swap high/low bits for correct order
    }
    
    // 一次性读取7个16位数据
    void MPU6050_ReadAll(uint16_t *buf)
    {
        uint8_t i;
        I2C_Read(MPU6050_ADDR, MPU6050_REG_ACCEL_XOUT_H, (uint8_t *)buf, 14);
        for (i = 0; i < 7; i++)
        {
            *(buf + i) = swap(*(buf + i));
        }
    }
    

    主要的寄存器设置

    几个可能会用到的寄存器说明

    电源管理寄存器 0x6B 和 0x6C

    这两个寄存器控制了MPU6050的工作模式: 睡眠模式(Sleep), 节电模式(Cycle)和正常工作模式

    • 睡眠模式: 0x6B 的第六位置1开启, 睡眠模式下可以通信, 但是所有的检测转换都是停止的, 结果读取的值都是0
    • 节电模式: 是一种睡眠和正常交替的模式, MPU6050每隔一段时间做一次检测转换, 其余时间都处在睡眠状态
      • 进入节电模式, 需要将sleep位置0, cycle位置1, 禁止温度采样位置1, STBY_XG, STBY_YG, STBY_ZG置1
      • 节点模式下的采样频率可以设置为 1.25Hz, 5Hz, 20Hz, 40Hz
    • 正常模式: 正常模式按设置进行正常的检测和转换

    在 0x6C 中, 还可以指定六个检测轴中的哪些轴暂停检测

    采样速率寄存器 0x19

    这个寄存器用于设置陀螺仪输出速率分频系数, 采样速率通过陀螺仪输出速率除以此寄存器的值产生:

    采样速率 = 陀螺仪输出速率 / (1 + SMPLRT_DIV)
    

    其中, 禁用DLPF(低通过滤)时陀螺仪输出速率为8KHz(DLPF_CFG = 0 or 7), 当启用DLPF时为1KHz.

    配置寄存器 0x1A

    这个寄存器用于设置外部帧同步(FSYNC)引脚采样和数字陀螺仪和加速度计的低通滤波器(DLPF). 连接到FSYNC引脚的外部信号可以通过配置EXT_SYNC_SET来采样, FSYNC引脚的信号变化被锁存, 以便捕捉信号, FSYNC信号将以寄存器0x19中定义的采样速率进行采样。采样后锁存器将复位到当前的FSYNC信号状态.

    • 位3,4,5: 设置FSYNC位的位置
    • 位0,1,2: 取值0 - 6, 设置DLPF, 数字越大延迟越大.

    角速度检测(陀螺仪)配置寄存器 0x1B

    这个寄存器用于设置角速度三轴的自检和满刻度范围

    加速度检测配置寄存器 0x1C

    这个寄存器用于设置加速度三轴的自检和满刻度范围

    模块的中断类型及设置

    中断功能通过中断配置寄存器进行配置。 可配置的项目包括INT引脚配置,中断锁存和清除方法以及中断触发器。 可触发中断的项目有:

    1. 时钟发生器锁定到新的参考振荡器(用于切换时钟源)
    2. 可以读取新数据(来自FIFO和数据寄存器)
    3. 加速度计事件中断
    4. MPU-6050 没有收到辅助传感器的确认I2C总线

    中断状态可以从中断状态寄存器读取。

    检测数据

    MPU6050在检测过程中, 根据采样速率不断输出六轴加温度的检测值, 如果设置的满刻度范围较大, 则测量输出的数字较小, 灵敏度较低, 要增加灵敏度可以调小满刻度范围. 相比较 ADXL345, MPU6050更适合运动中的物体检测, 能检测到更准确的运动中物体姿态变化, 同样的, 如果没有接入电子罗盘(磁强计), 只能得到俯仰角和横滚角数据, 不能得到航向角数据.

    演示代码

    演示代码以100ms的时间间隔, 不断读取7个检测数据并通过UART1串口输出, 接线正确的话, 可以通过串口软件观察到模块运动带来的检测值变化

  • 相关阅读:
    制作自己的Docker镜像
    Docker 常见应用部署
    一文读懂Docker相关命令
    linux在下软件太卡?手把手教你配置国内镜像源
    2013年蓝桥杯省赛C组笔记
    java基本数据类型之间的转换
    h5中的分组元素figure、figcaption、hgroup元素介绍
    初识WSGI接口
    h5中的结构元素header、nav、article、aside、section、footer介绍
    提交 linux kernel 补丁流程备忘录
  • 原文地址:https://www.cnblogs.com/milton/p/15832869.html
Copyright © 2020-2023  润新知