ESP32 official MPU6050 component introduction

Preface

(1) Because I need to use MPU6050 components, but also need to mount multiple devices on this I2C bus, I plan to fine-tune the official MPU6050 components myself. Create an I2C bus and the device depends on this bus to be mounted.
(2) Since transplantation work is to be done, it is necessary to figure out how this component should be used.

MPU6050 component function introduction

mpu6050_create()

Instructions

mpu6050_handle_t(1) This function is used to create an unsigned type pointer pointing to MPU6050 initialization information . We can access the structure mpu6050.cin this component through this pointer. (2) This can achieve the often said high cohesion and low coupling effects. If you don’t understand, it’s okay. Remember that the data he returns is related to the information of MPU6050, as long as we can use it . (3) How to pass in data: <1> When your MPU6050 is mounted on I2C0 of ESP32, pass it in . If it is I2C1, pass it in . If it is mounted in low-power I2C, pass it in . It should be noted that not all ESP32s have I2C1 and low-power I2C. You need to consult the chip manual for this. <2> Regarding the address of MPU6050, there are two types. When pin No. 9 AD0 of MPU6050 is low level, the address is 0x68, that is . When this pin is high, the address is 0x69, that is .mpu6050_dev_t


I2C_NUM_0I2C_NUM_1LP_I2C_NUM_0
MPU6050_I2C_ADDRESSMPU6050_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)

A brief overview of the underlying implementation

(1) mpu6050_handle_tStored in the header file to expose the interface.
(2) mpu6050_dev_tTo be honest, I don’t understand the specific function of this structure. I can only talk about the two parts that I know and think are important.
<1> bus, store the I2C under which mpu6050 is mounted.
<2> dev_addr, the address of MPU6050.
(3) Introduction to the function implementation part. Note that I can only talk about the parts I understand:
<1> Use calloc()a function to allocate a mpu6050_dev_ttype of space and initialize all this space to 0.
<2> Initialize the applied sensorvariable and store the I2C information and MPU6050 address information mounted on the MPU6050 into this variable. Some people may be confused as to why the address information dev_addr << 1needs to be shifted to the right. This is related to the timing logic of I2C. Generally, the address of an I2C device is 7 bits, and the last 1 bit is responsible for storing information about whether to read or write the device. Therefore, a right shift is required here.
<3>The finally returned data is forced to be type-converted so mpu6050_handle_tthat the high cohesion and low coupling functions I mentioned above can be achieved.

/*--- 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()

Introduction to use

(1) Used to set the accelerometer full-scale range and gyroscope full-scale range of MPU6050.
(2) How to pass in data:
<1> Pass in the mpu6050_handle_t pointer created by the mpu6050_create() function.
<2> ACCE_FS_2G, the full scale range of the accelerometer is +/-2g. ACCE_FS_4G, is +/-4g. ACCE_FS_8G, is +/-8g. ACCE_FS_16G, is +/-16g.
<3> GYRO_FS_250DPS, the full scale range of the gyroscope is +/- 250 degrees per second. GYRO_FS_500DPS, is 500 degrees per second. GYRO_FS_1000DPS, is 1000 degrees per second. GYRO_FS_2000DPS, is 2000 degrees per second.
(3) It is up to you to decide on this setting. If the value is too high, the resolution will be reduced, and if the value is too low, the data will not be measured.

/**
 * @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)

A brief overview of the underlying implementation

(1) Entering mpu6050_config()the function, we will see that he first creates an 8-bit unsigned array, puts the gyroscope configuration in the front and the accelerometer configuration in the back. This is because the gyroscope configuration register of MPU6050 GYRO_CONFIGis in front of the accelerometer register ACCEL_CONFIG, and each register of MPU6050 is 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 writes data I2C format

(1) We know mpu6050_config()how the configuration is implemented earlier, but there is another question about mpu6050_write()what is done in it.
(2) Before we explain mpu6050_write()the function, we need to know the communication format between the host and MPU6050.
(3) The original picture is on page 35 of the MPU-6000 and MPU-6050 Product Specification Revision 3.4 manual, which is Chapter 9.3 I2C Communications Protocol. Just look at the picture, I don’t want to explain too much, the picture is still very clear.

Insert image description here
Insert image description here

Function analysis

(1) After understanding the format of writing data of MPU6050, you can start to parse the function.
<1>First, force type conversion of sensorthis mpu6050_handle_ttype pointer. As we said before, this mpu6050_handle_tis essentially an unsigned type pointer, which can achieve high cohesion and low coupling. Here you only need to pass in the created pointer data, and then cast the type to access this pointer.
<2> i2c_cmd_link_create()Create a handle to the I2C connection. Doesn't this sound very jargon-y? I don't understand it. To be honest, I was confused when I saw this. I checked the source code of this function, and my personal understanding is that it is a two-way linked list that stores some I2C data transmission information. Subsequent I2C data transmission needs to use this linked list for settings. As for why linked lists are used, the reason is very simple. You don’t know how much data will be transmitted by I2C communication, and I2C data transmission must go from the beginning to the bottom, so using a linked list is a good decision. In fact , it is the same as the forced type conversion of this type pointer
above . I2C related functions are all in i2c. c, used to achieve high cohesion and low coupling. <3> , due to the I2C protocol, we need to send a start signal first. So this function is to write the start signal into the buffer area. <4> , due to the I2C protocol, after you send the start signal, all slaves on the I2C are activated. At this time, the host needs to tell the slave who I am communicating with, so it needs to pass in the slave address information, which is the MPU6050 address information. When the slave knows who the master is interacting with, other slaves on the I2C bus that are not selected will go to sleep. Only the master and slave start communication. <5>After the communication between the master and the slave is established, the master will tell the slave which register I want to operate. <6> , now the master and slave have a clear understanding of the communication and the operation of the register, so they can start transmitting data.sensormpu6050_handle_t
i2c_master_start()
i2c_master_write_byte()

i2c_master_write()
<7> i2c_master_stop(), after the data transmission is completed, the host needs to tell the slave that I have finished writing the data and you can rest.
<8> i2c_master_cmd_begin(), after the above operation is completed, the I2C of the starting ESP32 does not really work. The above is to write data in the buffer. Calling this function will actually output the data in the buffer.
<9> i2c_cmd_link_delete(), after the communication is completed, we need to call this function to delete the connection with 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()

Introduction to use

(1) Here you only need to pass in the handle of MPU6050 to wake up 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)

A brief overview of the underlying implementation

(1) This is actually not difficult, just clear bit6 of the PWR_MGMT_1 register of the MPU6050. Because if bit6 of the PWR_MGMT_1 register is 1, the MPU6050 will enter a low-power sleep state.
(2) As for why it is necessary to read the data of MPU6050 first, it is very simple. If one data is written directly, other data bits may be damaged.

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 reads data I2C format

(1) mpu6050_wake_up()One of the functions mpu6050_read()has not been introduced, so I will introduce it here.
(2) The MPU6050 has two start signals for reading data, but only one start signal for writing data. After the second start signal starts, the data can be read.
(3) The original picture is on page 36 of the MPU-6000 and MPU-6050 Product Specification Revision 3.4 manual, which is Chapter 9.3 I2C Communications Protocol.

Insert image description here

Function analysis

(1) This should be understood based on the above picture. mpu6050_write()I won’t go into details about the same parts as functions.
<1> It is still a forced type conversion, establishing an I2C connection and sending a start signal.
<2>Two i2c_master_write_byte(), note here that although our host ESP32 wants to read data, it still writes data for the first time, because the slave needs to know that the host wants to read the data in that register next.
<3>The second i2c_master_start()sum i2c_master_write_byte(), this is to tell the slave that the host can start reading your data.
<4> i2c_master_read(), read data, the last parameter of this function I2C_MASTER_LAST_NACKmeans that the host returns an ACK every time it receives data, but the last time the host receives data, it returns NACK, telling the slave to stop sending data.
<5> Finally, the stop information is still sent, and i2c_master_cmd_begin()the buffer data is output using a function. Removed connection to 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 brief introduction

(1) Due to my limited energy, it is impossible to introduce every function completely. After the above understanding, readers should understand the important parts. For some other functions, those who are interested in the underlying implementation can check the MPU-6050_Register_Map manual and understand it by referring to the code.
(2) After understanding the above functions, there are only the following three functions that need to be understood.
<1> mpu6050_get_acce()Function to obtain the acceleration value of MPU6050.
<2> mpu6050_get_gyro()Function to obtain the gyroscope value of MPU6050.
<2> mpu6050_get_temp()Function to obtain the temperature value of MPU6050.
(4) Of these three functions, the first one uses the incoming I2C handle. (mpu6050_handle_t pointer created by the mpu6050_create() function) The second parameter is slightly different:
<1> mpu6050_get_acce()function, he needs to pass in a mpu6050_acce_value_t type structure pointer, and the acce.acce_x method is used to ultimately process the data.

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 component usage

A brief introduction to unit test functions

(1) In the official MPU6050 components, you will see many functions starting with TEST_ASSERT. This is the Unity test unit. The assertion is similar to assert. When the test result fails, the program will be terminated and reset.
(2) GitHub link: https://github.com/ThrowTheSwitch/Unity
(3) Brief introduction of the function:
<1> TEST_ASSERT_NOT_NULL_MESSAGE(), used to test whether the first parameter passed in is a null pointer. If it is a null pointer, the console will The second parameter data will be output and the program will be terminated.
<2> TEST_ASSERT_EQUAL(), determine whether the second parameter is equal to the first parameter. If not equal, the program will be terminated and a message will be printed.
<3> TEST_ASSERT_EQUAL_MESSAGE(), determine whether the second parameter is equal to the first parameter. If not equal, the third parameter data will be output.

Official routine

(1) The following is the official test routine.

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

#include <stdio.h>
#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);
}

Guess you like

Origin blog.csdn.net/qq_63922192/article/details/133457076