序文
(1) MPU6050 コンポーネントを使用する必要があるだけでなく、この I2C バスに複数のデバイスを搭載する必要があるため、公式の MPU6050 コンポーネントを自分で微調整する予定です。I2C バスを作成すると、デバイスはこのバスに依存してマウントされます。
(2) 移植作業を行うため、この部品をどのように使用するかを考える必要があります。
MPU6050コンポーネントの機能紹介
mpu6050_create()
説明書
(1) この関数は、MPU6050 初期化情報を指す
mpu6050_handle_t
unsigned 型ポインタを作成するために使用されます。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のアドレスは2種類あります。MPU6050 の 9 番ピン AD0 が Low レベルのとき、アドレスは 0x68、つまり ですMPU6050_I2C_ADDRESS
。このピンが High の場合、アドレスは 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
正直に言うと、この構造の具体的な機能は理解していませんが、私が知っていて重要だと思う 2 つの部分についてのみお話します。
<1> はbus
、mpu6050 が実装されている I2C を格納します。
<2>dev_addr
、MPU6050のアドレス。
(3) 関数実装部分の紹介 ただし、理解している部分のみになりますので、ご了承ください:
<1>calloc()
関数を使用して空間の種類を割り当てmpu6050_dev_t
、この空間をすべて 0 に初期化します。
②適用したsensor
変数を初期化し、MPU6050に搭載されているI2C情報とMPU6050のアドレス情報をこの変数に格納します。なぜ住所情報をdev_addr << 1
右にずらす必要があるのか疑問に思う人もいるかもしれません。これは I2C のタイミング ロジックに関連しており、一般に I2C デバイスのアドレスは 7 ビットで、最後の 1 ビットはデバイスを読み書きするかどうかに関する情報を格納する役割を果たします。したがって、ここでは右シフトが必要です。<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) データの渡し方
① 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
、ジャイロスコープのフルスケール範囲は 1 秒あたり +/- 250 度です。GYRO_FS_500DPS
、1秒あたり500度です。GYRO_FS_1000DPS
、1秒あたり1000度です。GYRO_FS_2000DPS
、1秒あたり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()
関数に入ると、最初に 8 ビットの符号なし配列を作成し、ジャイロスコープの構成を前に、加速度計の構成を後ろに配置することがわかります。これは、MPU6050 のジャイロスコープ設定レジスタがGYRO_CONFIG
加速度レジスタの前にありACCEL_CONFIG
、MPU6050 の各レジスタが 8 ビットであるためです。
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形式で書き込みます
mpu6050_config()
(1)構成がどのように実装されるかはすでにわかっていますが、mpu6050_write()
そこで何が行われるかについては別の疑問があります。(2)機能
を説明する前に、ホストと MPU6050 間の通信フォーマットを知る必要があります。(3) 元の画像は、MPU-6000 および MPU-6050 製品仕様改訂 3.4 マニュアルの 35 ページにあります。これは、第 9.3 章 I2C 通信プロトコルです。写真を見てください。あまり説明したくありませんが、写真はまだ非常に鮮明です。mpu6050_write()
機能分析
(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 レジスタのビット 6 をクリアするだけです。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 にはデータの読み出し用のスタート信号が 2 つありますが、データの書き込み用のスタート信号は 1 つだけです。2 回目のスタート信号が開始すると、データの読み出しが可能になります。
(3) 元の画像は、MPU-6000 および MPU-6050 製品仕様改訂 3.4 マニュアルの 36 ページにあります。これは、第 9.3 章 I2C 通信プロトコルです。
機能分析
(1) これは上の図に基づいて理解する必要があります。機能と同じ部分については
mpu6050_write()
説明を省略します。
<1> これは依然として強制的な型変換であり、I2C 接続を確立し、開始信号を送信します。
<2>2 つ目i2c_master_write_byte()
は、ホスト ESP32 がデータを読み出したいにもかかわらず、初めてデータを書き込むことに注意してください。これは、ホストが次にそのレジスタ内のデータを読み出したいことをスレーブが知る必要があるためです。
<3>2 番目のi2c_master_start()
合計i2c_master_write_byte()
。これは、ホストがデータの読み取りを開始できることをスレーブに伝えます。
<4>i2c_master_read()
、データの読み取り、この関数の最後のパラメータは、I2C_MASTER_LAST_NACK
ホストがデータを受信するたびに ACK を返しますが、ホストが最後にデータを受信したときに NACK を返し、スレーブにデータの送信を停止するように指示することを意味します。
⑤ 最後に停止情報を送信し、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) 上記の機能を理解した上で、理解する必要がある機能は次の 3 つだけです。
<1>mpu6050_get_acce()
MPU6050の加速度値を取得する関数です。
<2>mpu6050_get_gyro()
MPU6050のジャイロ値を取得する機能。
<2>mpu6050_get_temp()
MPU6050の温度値を取得する機能です。
(4) これら 3 つの関数のうち、最初の関数は受信 I2C ハンドルを使用します。(mpu6050_create() 関数によって作成された mpu6050_handle_t ポインタ) 2 番目のパラメータは少し異なります:
<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 のテストユニットです。アサーションはアサートと似ており、テスト結果が失敗した場合、プログラムは終了してリセットされます。
(2) GitHub リンク: https://github.com/ThrowTheSwitch/Unity
(3) 関数の簡単な紹介:
<1>TEST_ASSERT_NOT_NULL_MESSAGE()
、渡された最初のパラメーターが null ポインターかどうかをテストするために使用されます。 、コンソールは 2 番目のパラメータのデータを出力し、プログラムは終了します。
<2>TEST_ASSERT_EQUAL()
、2 番目のパラメータが最初のパラメータと等しいかどうかを判断します。等しくない場合、プログラムは終了し、メッセージが出力されます。
<3>TEST_ASSERT_EQUAL_MESSAGE()
、2 番目のパラメータが最初のパラメータと等しいかどうかを判断します。等しくない場合は、3 番目のパラメータのデータが出力されます。
公式ルーティン
(1) 以下は公式のテスト ルーチンです。
/*
* 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);
}