ESP32官方MPU6050组件介绍

前言

(1)因为我需要使用MPU6050的组件,但是又需要在这条I2C总线上挂载多个设备,所以我本人打算自己对官方的MPU6050的组件进行微调。建立一个I2C总线,设备依赖于这个总线挂载。
(2)既然要做移植工作,所以就需要弄明白这个组件应当如何使用。

MPU6050组件函数介绍

mpu6050_create()

使用方法

(1)此函数用于创建一个指向MPU6050初始化信息的mpu6050_handle_t无符号类型指针。我们可以通过这个指针访问到mpu6050.c这个组件中的mpu6050_dev_t结构体。
(2)这样做能够做到常说的高内聚,低耦合的作用。如果不明白也没事,记住他返回的这个数据是和MPU6050的信息有关,我们能用就行
(3)如何传入数据:
<1>当你的MPU6050是挂载在ESP32的I2C0时候,传入I2C_NUM_0。如果是I2C1,传入I2C_NUM_1。如果是挂载在低功耗I2C中,传入LP_I2C_NUM_0。需要注意的是,不是所有的ESP32都具有I2C1和低功耗I2C,这个需要自行查阅芯片手册。
<2>关于MPU6050的地址,有两种。当MPU6050的第9号引脚AD0为低电平的时候地址为0x68,即MPU6050_I2C_ADDRESS。当这个引脚为高电平时候地址为0x69,即MPU6050_I2C_ADDRESS_1

/**
 * @brief  初始化MPU6050相关信息
 * 
 * @param   port       要挂载在哪个I2C总线上
 *         -dev_addr   MPU6050的地址
 * 
 * @return  NULL       初始化MPU6050的信息失败
 *         -非NULL指针 初始化MPU6050的信息成功
*/
mpu6050_handle_t mpu6050_create(i2c_port_t port, const uint16_t dev_addr)

简单概述底层实现

(1)mpu6050_handle_t存放在头文件中,用于暴露接口。
(2)mpu6050_dev_t这个结构体具体作用讲实话我也没有搞太明白,只能说我知道的认为重要的两个部分。
<1>bus,存放mpu6050是挂载在哪个I2C下。
<2>dev_addr,MPU6050的地址。
(3)函数实现部分介绍,注意,我也只能讲我懂的部分:
<1>使用calloc()函数分配1个mpu6050_dev_t类型的空间,并且将这块空间全部初始化为0。
<2>对申请到的sensor变量进行初始化,将MPU6050挂载的I2C信息,MPU6050地址信息存入这个变量。可能有些人会有疑惑,为什么地址信息dev_addr << 1需要进行一次右移操作。这个和I2C的时序逻辑有关,一般I2C设备的地址为7bit,最后1bit负责存放是对设备读还是写的信息。因此这里需要进行右移一位。
<3>最后返回的数据进行强制类型转换为mpu6050_handle_t这样就能够实现我上述所说的高内聚,低耦合的功能。

/*--- mpu6050.h ---*/
typedef void *mpu6050_handle_t;

/*--- mpu6050.c ---*/
typedef struct {
    i2c_port_t bus;
    gpio_num_t int_pin;
    uint16_t dev_addr;
    uint32_t counter;
    float dt;  /*!< delay time between two measurements, dt should be small (ms level) */
    struct timeval *timer;
} mpu6050_dev_t;

mpu6050_handle_t mpu6050_create(i2c_port_t port, const uint16_t dev_addr)
{
    mpu6050_dev_t *sensor = (mpu6050_dev_t *) calloc(1, sizeof(mpu6050_dev_t));
    sensor->bus = port;
    sensor->dev_addr = dev_addr << 1;
    sensor->counter = 0;
    sensor->dt = 0;
    sensor->timer = (struct timeval *) calloc(1, sizeof(struct timeval));
    return (mpu6050_handle_t) sensor;
}

mpu6050_config()

使用介绍

(1)用于设置MPU6050的加速度计满量程和陀螺仪满量程。
(2)如何传入数据:
<1>传入mpu6050_create()函数创建的mpu6050_handle_t指针。
<2>ACCE_FS_2G,加速度计满量程为+/-2g。ACCE_FS_4G,为+/-4g。ACCE_FS_8G,为+/-8g。ACCE_FS_16G,为+/-16g。
<3>GYRO_FS_250DPS,陀螺仪满量程是+/- 250度每秒。GYRO_FS_500DPS,为500度每秒。GYRO_FS_1000DPS,为1000度每秒。GYRO_FS_2000DPS,为2000度每秒。
(3)关于这个设置由你自己看情况决定。如果值太高将会降低分辨率,值太低就无法测量过高的数据。

/**
 * @brief  设置MPU6050的加速度计满量程和陀螺仪满量程
 * 
 * @param   sensor   mpu6050_create()函数创建的mpu6050_handle_t指针
 *         -acce_fs  设置加速度计满量程
 *         -gyro_fs  设置陀螺仪满量程
 * 
 * @return  ESP_OK   MPU6050配置成功
 *         -ESP_FAIL MPU6050配置失败
*/
esp_err_t mpu6050_config(mpu6050_handle_t sensor, 
						 const mpu6050_acce_fs_t acce_fs,
					     const mpu6050_gyro_fs_t gyro_fs)

简单概述底层实现

(1)进入mpu6050_config()函数,我们会看到他先建立了一个8bit的无符号数组,将陀螺仪配置放在前面,加速度计配置放在后面。这是因为MPU6050的陀螺仪配置寄存器GYRO_CONFIG在加速度计寄存器ACCEL_CONFIG的前面,而MPU6050的每个寄存器为8bit。

esp_err_t mpu6050_config(mpu6050_handle_t sensor, 
						 const mpu6050_acce_fs_t acce_fs, 
						 const mpu6050_gyro_fs_t gyro_fs)
{
    uint8_t config_regs[2] = {gyro_fs << 3,  acce_fs << 3};
    return mpu6050_write(sensor, MPU6050_GYRO_CONFIG, config_regs, sizeof(config_regs));
}

mpu6050_write()

MPU6050写数据I2C格式

(1)前面我们知道了mpu6050_config()配置如何实现的,但是又有一个问题mpu6050_write()里面做了什么。
(2)我们讲解mpu6050_write()函数前,需要知道主机和MPU6050的通讯格式。
(3)原图在MPU-6000 and MPU-6050 Product Specification Revision 3.4手册的35页,也就是9.3 I2C Communications Protocol章节。直接看图,我不想过多讲解,图依旧很清晰了。

ESP32官方MPU6050组件介绍_第1张图片
ESP32官方MPU6050组件介绍_第2张图片

函数解析

(1)前面了解了MPU6050的写数据的格式之后,就可以开始解析函数了。
<1>首先对sensor这个mpu6050_handle_t类型指针强制类型转换,前面我们说了,这个mpu6050_handle_t本质就是一个无符号类型指针,利用他能够做到高内聚低耦合。这里你只需传入创建的指针数据,然后强制类型转换即可对这个指针访问。
<2>i2c_cmd_link_create()创建一个I2C 连接的句柄。这个听起来是不是很术语,看不懂。讲实话,我看到这个我也是懵逼的。我查看了一下这个函数的源码,个人理解就是,一个双向链表,里面存储了一些I2C数据传输的信息。之后的I2C数据传输,需要利用这个链表来进行设置。至于为什么使用链表,原因很简单,你不知道I2C通讯会传输多少个数据,而且I2C数据传输肯定是从头往下走,所以采用的链表是很好的决定。
其实和上面对sensor这个mpu6050_handle_t类型指针强制类型转换是一个道理。I2C相关的函数都在i2c.c里面,用于实现高内聚低耦合。
<3>i2c_master_start(),由于I2C协议规定,我们需要先发送一个起始信号。所以这个函数就是将起始信号写入缓存区。
<4>i2c_master_write_byte(),由于I2C协议规定,你发送起始信号之后,I2C上的从机都被激活。这个时候主机需要告诉从机我是在和谁通讯,因此需要传入从机地址信息,也就是MPU6050地址信息。当从机知道主机是在和谁交互时候,I2C总线上其他没有被选中的从机将会进入休眠。只有主机和从机开始通讯。
<5>主机和从机通讯建立完成之后,主机将会告诉从机,我要对那个寄存器进行操作。
<6>i2c_master_write(),现在主机和从机联系和对那个寄存器操作都有一个清晰的认识,于是可以开始传输数据了。
<7>i2c_master_stop(),数据传输完成之后主机需要告诉从机,我数据写完了,你可以休息了。
<8>i2c_master_cmd_begin(),上述操作进行完之后,起始ESP32的I2C并没有真正的工作。上述就是在缓冲区写入数据,调用这个函数,才是真正的将缓冲区的数据输出。
<9>i2c_cmd_link_delete(),通讯结束之后,我们需要调用这个函数删除与I2C的连接。

/**
 * @brief  设置MPU6050的加速度计满量程和陀螺仪满量程
 * 
 * @param   sensor         mpu6050_create()函数创建的mpu6050_handle_t指针
 *         -reg_start_addr 要进行写入数据的寄存器
 *         -data_buf       写入寄存器中的数据
 *         -data_len       写入寄存器中的数据长度
 * 
 * @return  ESP_OK         MPU6050寄存器数据写入成功
 *         -ESP_FAIL       MPU6050寄存器数据写入失败
*/
static esp_err_t mpu6050_write(mpu6050_handle_t sensor, 
						       const uint8_t reg_start_addr, 
						       const uint8_t *const data_buf, 
						       const uint8_t data_len)
{
    mpu6050_dev_t *sens = (mpu6050_dev_t *) sensor;
    esp_err_t  ret;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    ret = i2c_master_start(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, sens->dev_addr | I2C_MASTER_WRITE, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, reg_start_addr, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write(cmd, data_buf, data_len, true);
    assert(ESP_OK == ret);
    ret = i2c_master_stop(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_cmd_begin(sens->bus, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);

    return ret;
}

mpu6050_wake_up()

使用介绍

(1)这里只需要传入MPU6050的句柄即可唤醒MPU6050。

/**
 * @brief  唤醒MPU6050
 * 
 * @param   sensor   mpu6050_create()函数创建的mpu6050_handle_t指针
 * @return  RT_EOK   MPU6050唤醒成功
 *         -RT_ERROR MPU6050唤醒失败
*/
esp_err_t mpu6050_wake_up(mpu6050_handle_t sensor)

简单概述底层实现

(1)这里其实也不难,就是对MPU6050的PWR_MGMT_1寄存器bit6清零即可。因为PWR_MGMT_1寄存器的bit6如果为1,MPU6050将会进入低功耗休眠状态。
(2)至于为什么需要先读取MPU6050的数据,很简单,如果直接写入一个数据,可能导致其他数据位数据被破坏。

esp_err_t mpu6050_wake_up(mpu6050_handle_t sensor)
{
    esp_err_t ret;
    uint8_t tmp;
    ret = mpu6050_read(sensor, MPU6050_PWR_MGMT_1, &tmp, 1);
    if (ESP_OK != ret) {
        return ret;
    }
    tmp &= (~BIT6);
    ret = mpu6050_write(sensor, MPU6050_PWR_MGMT_1, &tmp, 1);
    return ret;
}

mpu6050_read()

MPU6050读数据I2C格式

(1)mpu6050_wake_up()函数里面有一个mpu6050_read()没有进行介绍,这里介绍一下。
(2)MPU6050的读数据有两次起始信号,而写数据只有一次起始信号。第二次起始信号开始之后,就可以读数据了。
(3)原图在MPU-6000 and MPU-6050 Product Specification Revision 3.4手册的36页,也就是9.3 I2C Communications Protocol章节。

ESP32官方MPU6050组件介绍_第3张图片

函数解析

(1)这里要根据上图一起理解。与mpu6050_write()函数相同部分我就不再赘述了。
<1>依旧是强制类型转换,建立I2C连接,发送起始信号。
<2>两个i2c_master_write_byte(),这里注意,虽然我们主机ESP32是要读取数据,但是第一次还是写数据,因为从机需要知道,主机接下来是要读取那个寄存器的数据。
<3>第二次i2c_master_start()i2c_master_write_byte(),这个是告诉从机,主机已经可以开始读取你的数据了。
<4>i2c_master_read(),读取数据,这个函数最后一个参数为I2C_MASTER_LAST_NACK表示主机每次收到数据返回一个ACK,不过主机最后一次收到数据返回NACK,告诉从机要停止发数据了。
<5>最后依旧是发送停止信息,使用i2c_master_cmd_begin()函数将缓冲区数据输出。删除与I2C的联系。

/**
 * @brief  设置MPU6050的加速度计满量程和陀螺仪满量程
 * 
 * @param   sensor         mpu6050_create()函数创建的mpu6050_handle_t指针
 *         -reg_start_addr 要进行读取数据的寄存器
 *         -data_buf       读取到的数据存入空间
 *         -data_len       要读取数据的长度
 * 
 * @return  ESP_OK         MPU6050寄存器数据读取成功
 *         -ESP_FAIL       MPU6050寄存器数据读取失败
*/
static esp_err_t mpu6050_read(mpu6050_handle_t sensor, 
                              const uint8_t reg_start_addr, 
                              uint8_t *const data_buf, 
                              const uint8_t data_len)
{
    mpu6050_dev_t *sens = (mpu6050_dev_t *) sensor;
    esp_err_t  ret;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    ret = i2c_master_start(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, sens->dev_addr | I2C_MASTER_WRITE, true);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, reg_start_addr, true);
    assert(ESP_OK == ret);
    ret = i2c_master_start(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_write_byte(cmd, sens->dev_addr | I2C_MASTER_READ, true);
    assert(ESP_OK == ret);
    ret = i2c_master_read(cmd, data_buf, data_len, I2C_MASTER_LAST_NACK);
    assert(ESP_OK == ret);
    ret = i2c_master_stop(cmd);
    assert(ESP_OK == ret);
    ret = i2c_master_cmd_begin(sens->bus, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);

    return ret;
}

mpu6050_get_xxx简单介绍

(1)因为本人精力有限,不可能每个函数都完完全全介绍一边。经过上面的了解,重要的部分读者应该都了解了。其他的一些函数,对底层实现感兴趣的可以自行查看MPU-6050_Register_Map手册,对照着代码进行理解。
(2)上面的几个函数了解了之后,就只有如下这三个函数需要了解了。
<1>mpu6050_get_acce()函数,获取MPU6050的加速度值。
<2>mpu6050_get_gyro()函数,获取MPU6050的陀螺仪值。
<2>mpu6050_get_temp()函数,获取MPU6050的温度值。
(4)这三个函数,第一个都是传入的I2C句柄。(mpu6050_create()函数创建的mpu6050_handle_t指针)第二个参数略有不同:
<1>mpu6050_get_acce()函数,他需要传入一个mpu6050_acce_value_t类型结构体指针,最终对数据进行处理是采用acce.acce_x方法。

typedef struct {
    float acce_x;    //x轴加速度
    float acce_y;    //y轴加速度
    float acce_z;    //z轴加速度
} mpu6050_acce_value_t;

typedef struct {
    float gyro_x;    //x轴的角速度
    float gyro_y;    //y轴的角速度
    float gyro_z;    //z轴的角速度
} mpu6050_gyro_value_t;

typedef struct {
    float temp;      //MPU6050温度
} mpu6050_temp_value_t;

MPU6050组件使用

单元测试函数简单介绍

(1)在官方MPU6050的组件中,你会看到很多TEST_ASSERT开头的函数,这个是Unity测试单元。作用类似于assert的断言,当测试结果失败,将会终止程序进行复位操作。
(2)GitHub链接:https://github.com/ThrowTheSwitch/Unity
(3)函数简单介绍:
<1>TEST_ASSERT_NOT_NULL_MESSAGE(),用于测试传入的第一个参数是不是空指针,如果是空指针控制台将会输出第二个参数数据,并且终止程序。
<2>TEST_ASSERT_EQUAL(),判断第二个参数是否和第一个参数相等。如果不相等将会终止程序,并且输出信息。
<3>TEST_ASSERT_EQUAL_MESSAGE(),判断第二个参数是否和第一个参数相等。如果不相等将会输出第三个参数数据。

官方例程

(1)如下为官方测试例程。

/*
 * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include 
#include "unity.h"
#include "driver/i2c.h"
#include "mpu6050.h"
#include "esp_system.h"
#include "esp_log.h"

#define I2C_MASTER_SCL_IO 26      /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO 25      /*!< gpio number for I2C master data  */
#define I2C_MASTER_NUM I2C_NUM_0  /*!< I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */

static const char *TAG = "mpu6050 test";
static mpu6050_handle_t mpu6050 = NULL;

/**
 * @brief i2c master initialization
 */
static void i2c_bus_init(void)
{
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = (gpio_num_t)I2C_MASTER_SDA_IO;
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = (gpio_num_t)I2C_MASTER_SCL_IO;
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
    conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL;

    esp_err_t ret = i2c_param_config(I2C_MASTER_NUM, &conf);
    TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, ret, "I2C config returned error");

    ret = i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
    TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, ret, "I2C install returned error");
}

/**
 * @brief i2c master initialization
 */
static void i2c_sensor_mpu6050_init(void)
{
    esp_err_t ret;

    i2c_bus_init();
    mpu6050 = mpu6050_create(I2C_MASTER_NUM, MPU6050_I2C_ADDRESS);
    TEST_ASSERT_NOT_NULL_MESSAGE(mpu6050, "MPU6050 create returned NULL");

    ret = mpu6050_config(mpu6050, ACCE_FS_4G, GYRO_FS_500DPS);
    TEST_ASSERT_EQUAL(ESP_OK, ret);

    ret = mpu6050_wake_up(mpu6050);
    TEST_ASSERT_EQUAL(ESP_OK, ret);
}

TEST_CASE("Sensor mpu6050 test", "[mpu6050][iot][sensor]")
{
    esp_err_t ret;
    uint8_t mpu6050_deviceid;
    mpu6050_acce_value_t acce;
    mpu6050_gyro_value_t gyro;
    mpu6050_temp_value_t temp;

    i2c_sensor_mpu6050_init();

    ret = mpu6050_get_deviceid(mpu6050, &mpu6050_deviceid);
    TEST_ASSERT_EQUAL(ESP_OK, ret);
    TEST_ASSERT_EQUAL_UINT8_MESSAGE(MPU6050_WHO_AM_I_VAL, mpu6050_deviceid, "Who Am I register does not contain expected data");

    ret = mpu6050_get_acce(mpu6050, &acce);
    TEST_ASSERT_EQUAL(ESP_OK, ret);
    ESP_LOGI(TAG, "acce_x:%.2f, acce_y:%.2f, acce_z:%.2f\n", acce.acce_x, acce.acce_y, acce.acce_z);

    ret = mpu6050_get_gyro(mpu6050, &gyro);
    TEST_ASSERT_EQUAL(ESP_OK, ret);
    ESP_LOGI(TAG, "gyro_x:%.2f, gyro_y:%.2f, gyro_z:%.2f\n", gyro.gyro_x, gyro.gyro_y, gyro.gyro_z);

    ret = mpu6050_get_temp(mpu6050, &temp);
    TEST_ASSERT_EQUAL(ESP_OK, ret);
    ESP_LOGI(TAG, "t:%.2f \n", temp.temp);

    mpu6050_delete(mpu6050);
    ret = i2c_driver_delete(I2C_MASTER_NUM);
    TEST_ASSERT_EQUAL(ESP_OK, ret);
}

你可能感兴趣的:(#,ESP32S3,物联网)