Introducción oficial del componente MPU6050 de ESP32

Prefacio

(1) Debido a que necesito usar componentes MPU6050, pero también necesito montar varios dispositivos en este bus I2C, planeo ajustar los componentes oficiales MPU6050 yo mismo. Cree un bus I2C y el dispositivo depende de este bus a montar.
(2) Dado que se van a realizar trabajos de trasplante, es necesario determinar cómo se debe utilizar este componente.

Introducción a la función del componente MPU6050

mpu6050_create()

Instrucciones

(1) Esta función se utiliza para crear un mpu6050_handle_tpuntero de tipo sin firmar que apunta a la información de inicialización del MPU6050. Podemos acceder a la estructura mpu6050.cde este componente a través de este puntero. (2) Esto puede lograr los efectos de alta cohesión y bajo acoplamiento que a menudo se mencionan. Si no lo entiende, está bien, recuerde que los datos que devuelve están relacionados con la información del MPU6050, siempre que podamos usarlo . (3) Cómo pasar datos: <1> Cuando su MPU6050 esté montado en I2C0 de ESP32, páselo . Si es I2C1, páselo . Si está montado en I2C de bajo consumo, páselo . Cabe señalar que no todos los ESP32 tienen I2C1 e I2C de bajo consumo, para esto es necesario consultar el manual del chip. <2> En cuanto a la dirección de MPU6050, hay dos tipos. Cuando el pin No. 9 AD0 de MPU6050 es de nivel bajo, la dirección es 0x68, es decir . Cuando este pin está alto, la dirección es 0x69, es decir .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)

Una breve descripción general de la implementación subyacente

(1) mpu6050_handle_tAlmacenado en el archivo de encabezado para exponer la interfaz.
(2) mpu6050_dev_tPara ser honesto, no entiendo la función específica de esta estructura, solo puedo hablar de las dos partes que conozco y creo que son importantes.
<1> bus, almacene el I2C bajo el cual está montado el mpu6050.
<2> dev_addr, la dirección de MPU6050.
(3) Introducción a la parte de implementación de la función. Tenga en cuenta que solo puedo hablar de las partes que entiendo:
<1> Use calloc()una función para asignar un mpu6050_dev_ttipo de espacio e inicializar todo este espacio en 0.
<2> Inicialice la variable aplicada sensory almacene la información I2C y la información de la dirección MPU6050 montada en el MPU6050 en esta variable. Algunas personas pueden sentirse confundidas en cuanto a por qué dev_addr << 1es necesario desplazar la información de la dirección hacia la derecha. Esto está relacionado con la lógica de temporización de I2C. Generalmente, la dirección de un dispositivo I2C es de 7 bits, y el último bit es responsable de almacenar información sobre si se debe leer o escribir el dispositivo. Por lo tanto, aquí se requiere un desplazamiento a la derecha.
<3>Los datos finalmente devueltos se ven obligados a convertir el tipo para mpu6050_handle_tque se puedan lograr las funciones de alta cohesión y bajo acoplamiento que mencioné anteriormente.

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

Introducción al uso

(1) Se utiliza para configurar el rango de escala completa del acelerómetro y el rango de escala completa del giroscopio del MPU6050.
(2) Cómo pasar datos:
<1> Pase el puntero mpu6050_handle_t creado por la función mpu6050_create().
<2> ACCE_FS_2G, el rango de escala completa del acelerómetro es +/-2g. ACCE_FS_4G, es +/-4g. ACCE_FS_8G, es +/-8g. ACCE_FS_16G, es +/-16g.
<3> GYRO_FS_250DPS, el rango de escala completa del giroscopio es +/- 250 grados por segundo. GYRO_FS_500DPS, es de 500 grados por segundo. GYRO_FS_1000DPS, es 1000 grados por segundo. GYRO_FS_2000DPS, es de 2000 grados por segundo.
(3) Depende de usted decidir sobre esta configuración. Si el valor es demasiado alto, la resolución se reducirá y si el valor es demasiado bajo, los datos no se medirán.

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

Una breve descripción general de la implementación subyacente

(1) Al ingresar mpu6050_config()a la función, veremos que primero crea una matriz sin signo de 8 bits, coloca la configuración del giroscopio en el frente y la configuración del acelerómetro en la parte posterior. Esto se debe a que el registro de configuración del giroscopio del MPU6050 GYRO_CONFIGestá delante del registro del acelerómetro ACCEL_CONFIGy cada registro del MPU6050 es de 8 bits.

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 escribe datos en formato I2C

(1) Sabemos mpu6050_config()cómo se implementa la configuración anteriormente, pero hay otra pregunta sobre mpu6050_write()qué se hace en ella.
(2) Antes de explicar mpu6050_write()la función, necesitamos conocer el formato de comunicación entre el host y el MPU6050.
(3) La imagen original se encuentra en la página 35 del manual MPU-6000 y MPU-6050 Revisión de especificaciones de producto 3.4, que es el Capítulo 9.3 Protocolo de comunicaciones I2C. Solo mira la imagen, no quiero explicar demasiado, la imagen aún es muy clara.

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

Análisis de funciones

(1) Después de comprender el formato de escritura de datos de MPU6050, puede comenzar a analizar la función.
<1> Primero, fuerce la conversión de tipo de sensoreste mpu6050_handle_tpuntero de tipo. Como dijimos antes, este mpu6050_handle_tes esencialmente un puntero de tipo sin signo, que puede lograr una alta cohesión y un bajo acoplamiento. Aquí solo necesita pasar los datos del puntero creado y luego convertir el tipo para acceder a este puntero.
<2> i2c_cmd_link_create()Cree un identificador para la conexión I2C. ¿No suena esto como una jerga? No lo entiendo. Para ser honesto, me sentí confundido cuando vi esto. Revisé el código fuente de esta función y, según tengo entendido personalmente, se trata de una lista enlazada bidireccional que almacena cierta información de transmisión de datos I2C. La transmisión de datos I2C posterior debe utilizar esta lista vinculada para la configuración. En cuanto a por qué se utilizan listas vinculadas, la razón es muy simple: no sabe cuántos datos se transmitirán mediante la comunicación I2C, y la transmisión de datos I2C debe ir desde el principio hasta el final, por lo que usar una lista vinculada es una buena opción. decisión. De hecho , es lo mismo que la conversión de tipo forzada de este puntero de tipo
anterior . Las funciones relacionadas con I2C están todas en i2c. c, utilizado para lograr alta cohesión y bajo acoplamiento. <3> , debido al protocolo I2C, primero debemos enviar una señal de inicio. Entonces, esta función es escribir la señal de inicio en el área del búfer. <4> , debido al protocolo I2C, después de enviar la señal de inicio, todos los esclavos en el I2C se activan. En este momento, el host necesita decirle al esclavo con quién me estoy comunicando, por lo que debe pasar la información de la dirección del esclavo, que es la información de la dirección del MPU6050. Cuando el esclavo sabe con quién está interactuando el maestro, otros esclavos en el bus I2C que no estén seleccionados entrarán en modo de suspensión. Sólo el maestro y el esclavo inician la comunicación. <5>Una vez establecida la comunicación entre el maestro y el esclavo, el maestro le dirá al esclavo qué registro quiero operar. <6> , ahora el maestro y el esclavo tienen una comprensión clara de la comunicación y el funcionamiento del registro, por lo que pueden comenzar a transmitir datos.sensormpu6050_handle_t
i2c_master_start()
i2c_master_write_byte()

i2c_master_write()
<7> i2c_master_stop(), una vez completada la transmisión de datos, el host debe decirle al esclavo que terminé de escribir los datos y que puede descansar.
<8> i2c_master_cmd_begin(), una vez completada la operación anterior, el I2C del ESP32 inicial realmente no funciona. Lo anterior es para escribir datos en el búfer. Llamar a esta función en realidad generará los datos en el búfer.
<9> i2c_cmd_link_delete(), una vez completada la comunicación, debemos llamar a esta función para eliminar la conexión con 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()

Introducción al uso

(1) Aquí solo necesita pasar el identificador de MPU6050 para activar 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)

Una breve descripción general de la implementación subyacente

(1) En realidad, esto no es difícil, simplemente borre el bit6 del registro PWR_MGMT_1 del MPU6050. Porque si el bit6 del registro PWR_MGMT_1 es 1, el MPU6050 entrará en un estado de suspensión de bajo consumo de energía.
(2) En cuanto a por qué es necesario leer primero los datos del MPU6050, es muy simple: si un dato se escribe directamente, otros bits de datos pueden dañarse.

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 lee datos en formato I2C

(1) mpu6050_wake_up()Una de las funciones mpu6050_read()no se ha introducido, así que la presentaré aquí.
(2) El MPU6050 tiene dos señales de inicio para leer datos, pero solo una señal de inicio para escribir datos. Después de que comience la segunda señal de inicio, se pueden leer los datos.
(3) La imagen original se encuentra en la página 36 del manual de Revisión 3.4 de las especificaciones de producto MPU-6000 y MPU-6050, que es el Capítulo 9.3 Protocolo de comunicaciones I2C.

Insertar descripción de la imagen aquí

Análisis de funciones

(1) Esto debe entenderse según la imagen de arriba. mpu6050_write()No entraré en detalles sobre las mismas partes que las funciones.
<1> Sigue siendo una conversión de tipo forzada, que establece una conexión I2C y envía una señal de inicio.
<2>Dos i2c_master_write_byte(), tenga en cuenta aquí que aunque nuestro host ESP32 quiere leer datos, todavía escribe datos por primera vez, porque el esclavo necesita saber que el host quiere leer los datos en ese registro a continuación.
<3>La segunda i2c_master_start()suma i2c_master_write_byte(), esto es para decirle al esclavo que el host puede comenzar a leer sus datos.
<4> i2c_master_read(), leer datos, el último parámetro de esta función I2C_MASTER_LAST_NACKsignifica que el host devuelve un ACK cada vez que recibe datos, pero la última vez que el host recibe datos, devuelve NACK, indicándole al esclavo que deje de enviar datos.
<5> Finalmente, la información de parada aún se envía y i2c_master_cmd_begin()los datos del búfer se generan mediante una función. Se eliminó la conexión a 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 breve introducción

(1) Debido a mi energía limitada, es imposible introducir todas las funciones por completo. Después de la comprensión anterior, los lectores deben comprender las partes importantes. Para algunas otras funciones, aquellos que estén interesados ​​en la implementación subyacente pueden consultar el manual MPU-6050_Register_Map y comprenderlo consultando el código.
(2) Después de comprender las funciones anteriores, solo es necesario comprender las siguientes tres funciones.
<1> mpu6050_get_acce()Función para obtener el valor de aceleración de MPU6050.
<2> mpu6050_get_gyro()Función para obtener el valor del giroscopio de MPU6050.
<2> mpu6050_get_temp()Función para obtener el valor de temperatura de MPU6050.
(4) De estas tres funciones, la primera utiliza el identificador I2C entrante. (puntero mpu6050_handle_t creado por la función mpu6050_create()) El segundo parámetro es ligeramente diferente: función
<1> mpu6050_get_acce(), necesita pasar un puntero de estructura de tipo mpu6050_acce_value_t y el método acce.acce_x se utiliza para finalmente procesar los datos.

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;

Uso del componente MPU6050

Una breve introducción a las funciones de prueba unitaria.

(1) En los componentes oficiales de MPU6050, verá muchas funciones que comienzan con TEST_ASSERT. Esta es la unidad de prueba de Unity. La afirmación es similar a afirmar: cuando el resultado de la prueba falla, el programa finalizará y se reiniciará.
(2) Enlace de GitHub: https://github.com/ThrowTheSwitch/Unity
(3) Breve introducción de la función:
<1> TEST_ASSERT_NOT_NULL_MESSAGE(), utilizada para probar si el primer parámetro pasado es un puntero nulo. Si es un puntero nulo , la consola generará los datos del segundo parámetro y finalizará el programa.
<2> TEST_ASSERT_EQUAL(), determine si el segundo parámetro es igual al primer parámetro. Si no es igual, el programa finalizará y se imprimirá un mensaje.
<3> TEST_ASSERT_EQUAL_MESSAGE(), determine si el segundo parámetro es igual al primer parámetro. Si no es igual, se generarán los datos del tercer parámetro.

rutina oficial

(1) La siguiente es la rutina de prueba oficial.

/*
 * 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);
}

Supongo que te gusta

Origin blog.csdn.net/qq_63922192/article/details/133457076
Recomendado
Clasificación