|
@@ -6,4 +6,276 @@
|
|
|
|
|
|
## 一、理论介绍
|
|
|
|
|
|
-欧拉角转四元数
|
|
|
+### 1.1.欧拉角转四元数
|
|
|
+
|
|
|
+欧拉角转四元数的公式我们在第六章入门篇第三节有介绍,这里回顾一下
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+根据公式我们可以写出代码
|
|
|
+
|
|
|
+```c++
|
|
|
+typedef struct
|
|
|
+{
|
|
|
+ float w;
|
|
|
+ float x;
|
|
|
+ float y;
|
|
|
+ float z;
|
|
|
+} quaternion_t;
|
|
|
+
|
|
|
+Euler2Quaternion(float roll, float pitch, float yaw, quaternion_t &q)
|
|
|
+{
|
|
|
+ double cr = cos(roll * 0.5);
|
|
|
+ double sr = sin(roll * 0.5);
|
|
|
+ double cy = cos(yaw * 0.5);
|
|
|
+ double sy = sin(yaw * 0.5);
|
|
|
+ double cp = cos(pitch * 0.5);
|
|
|
+ double sp = sin(pitch * 0.5);
|
|
|
+ q.w = cy * cp * cr + sy * sp * sr;
|
|
|
+ q.x = cy * cp * sr - sy * sp * cr;
|
|
|
+ q.y = sy * cp * sr + cy * sp * cr;
|
|
|
+ q.z = sy * cp * cr - cy * sp * sr;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 1.2 坐标系校准
|
|
|
+
|
|
|
+我们采用右手坐标系,接着我们依次来校准角度数据的方向。
|
|
|
+
|
|
|
+打开终端,点击RST,查看IMU数据。
|
|
|
+
|
|
|
+首先是X轴,我们让开发板上爱神丘比特的剑头指向自己,然后从右侧往左侧倾斜。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+可以看到此时X轴为正值,符合右手坐标系法则。
|
|
|
+
|
|
|
+接着是Y轴,平放,将箭头朝向自己的胸口,接着抬高板子,让箭头指向自己的头部,观察Y轴的变化。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+Y轴为负值,不符合右手坐标系法则,所以Y的值应该取一次负,使其为正。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+接着是Z轴,平放,将箭头朝向自己的胸口,然后逆时针旋转板子,观察数值变化。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+值为正,表示符合右手坐标系法则。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+> 你可以会问小鱼怎么确认怎么旋转是正,怎么旋转是负,首先要确认轴向,我们开发板的Z轴朝上,X轴朝前,此时Y轴应该朝左。接着摊开右手手掌,用大拇指朝向轴的方向,比如朝向X轴,然后握起手掌,那么你握的方向就是正方向。
|
|
|
+
|
|
|
+## 二、开始写代码
|
|
|
+
|
|
|
+新建工程`example07_mpu6050_oop`
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+接着为其添加依赖
|
|
|
+
|
|
|
+修改`platformio.ini`
|
|
|
+
|
|
|
+```ini
|
|
|
+[env:featheresp32]
|
|
|
+platform = espressif32
|
|
|
+board = featheresp32
|
|
|
+framework = arduino
|
|
|
+lib_deps =
|
|
|
+ https://ghproxy.com/https://github.com/rfetick/MPU6050_light.git
|
|
|
+```
|
|
|
+
|
|
|
+接着在`lib`下新建`IMU`文件夹,并在文件夹下新建`IMU.h`和`IMU.cpp`
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+IMU.h
|
|
|
+
|
|
|
+```c++
|
|
|
+#ifndef __IMU_H__
|
|
|
+#define __IMU_H__
|
|
|
+#include "Wire.h"
|
|
|
+#include "MPU6050_light.h"
|
|
|
+
|
|
|
+typedef struct
|
|
|
+{
|
|
|
+ float w;
|
|
|
+ float x;
|
|
|
+ float y;
|
|
|
+ float z;
|
|
|
+} quaternion_t; // 四元数结构体
|
|
|
+
|
|
|
+typedef struct
|
|
|
+{
|
|
|
+ float x;
|
|
|
+ float y;
|
|
|
+ float z;
|
|
|
+} vector_3d_t; // 通用3D点结构体
|
|
|
+
|
|
|
+typedef struct
|
|
|
+{
|
|
|
+ quaternion_t orientation;
|
|
|
+ vector_3d_t angle_euler;
|
|
|
+ vector_3d_t angular_velocity;
|
|
|
+ vector_3d_t linear_acceleration;
|
|
|
+} imu_t; // IMU数据结构体
|
|
|
+
|
|
|
+class IMU
|
|
|
+{
|
|
|
+private:
|
|
|
+ MPU6050 *mpu_; // mpu6050指针
|
|
|
+
|
|
|
+public:
|
|
|
+ /**
|
|
|
+ * @brief 同u哦NPU6050构造一个新的IMU对象
|
|
|
+ *
|
|
|
+ * @param mpu
|
|
|
+ */
|
|
|
+ IMU(MPU6050 &mpu);
|
|
|
+
|
|
|
+ ~IMU() = default;
|
|
|
+ /**
|
|
|
+ * @brief 初始化函数
|
|
|
+ *
|
|
|
+ * @param sda 引脚编号
|
|
|
+ * @param scl 引脚编号
|
|
|
+ * @return true
|
|
|
+ * @return false
|
|
|
+ */
|
|
|
+ bool begin(int sda, int scl);
|
|
|
+ /**
|
|
|
+ * @brief 欧拉角转四元数
|
|
|
+ *
|
|
|
+ * @param roll 输入X
|
|
|
+ * @param pitch 输入y
|
|
|
+ * @param yaw 输入Z
|
|
|
+ * @param q 返回的四元数引用
|
|
|
+ */
|
|
|
+ static void Euler2Quaternion(float roll, float pitch, float yaw, quaternion_t &q);
|
|
|
+ /**
|
|
|
+ * @brief 获取IMU数据函数
|
|
|
+ *
|
|
|
+ * @param imu
|
|
|
+ */
|
|
|
+ void getImuData(imu_t &imu);
|
|
|
+ /**
|
|
|
+ * @brief 更新IMU数据,同上一节中的mou.update
|
|
|
+ *
|
|
|
+ */
|
|
|
+ void update();
|
|
|
+};
|
|
|
+
|
|
|
+#endif // __IMU_H__
|
|
|
+```
|
|
|
+
|
|
|
+IMU.cpp
|
|
|
+
|
|
|
+```c++
|
|
|
+#include "IMU.h"
|
|
|
+
|
|
|
+IMU::IMU(MPU6050 &mpu)
|
|
|
+{
|
|
|
+ mpu_ = &mpu;
|
|
|
+};
|
|
|
+
|
|
|
+bool IMU::begin(int sda, int scl)
|
|
|
+{
|
|
|
+ Wire.begin(sda, scl);
|
|
|
+ byte status = mpu_->begin();
|
|
|
+ Serial.print(F("MPU6050 status: "));
|
|
|
+ Serial.println(status);
|
|
|
+ if (status != 0)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ } // stop everything if could not connect to MPU6050
|
|
|
+
|
|
|
+ Serial.println(F("Calculating offsets, do not move MPU6050"));
|
|
|
+ delay(1000);
|
|
|
+ // mpu.upsideDownMounting = true; // uncomment this line if the MPU6050 is mounted upside-down
|
|
|
+ mpu_->calcOffsets(); // gyro and accelero
|
|
|
+ Serial.println("Done!\n");
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void IMU::Euler2Quaternion(float roll, float pitch, float yaw, quaternion_t &q)
|
|
|
+{
|
|
|
+ double cr = cos(roll * 0.5);
|
|
|
+ double sr = sin(roll * 0.5);
|
|
|
+ double cy = cos(yaw * 0.5);
|
|
|
+ double sy = sin(yaw * 0.5);
|
|
|
+ double cp = cos(pitch * 0.5);
|
|
|
+ double sp = sin(pitch * 0.5);
|
|
|
+ q.w = cy * cp * cr + sy * sp * sr;
|
|
|
+ q.x = cy * cp * sr - sy * sp * cr;
|
|
|
+ q.y = sy * cp * sr + cy * sp * cr;
|
|
|
+ q.z = sy * cp * cr - cy * sp * sr;
|
|
|
+}
|
|
|
+
|
|
|
+void IMU::getImuData(imu_t &imu)
|
|
|
+{
|
|
|
+ imu.angle_euler.x = mpu_->getAngleX();
|
|
|
+ imu.angle_euler.y = -mpu_->getAngleY();
|
|
|
+ imu.angle_euler.z = mpu_->getAngleZ();
|
|
|
+
|
|
|
+ imu.angular_velocity.x = mpu_->getAccAngleX();
|
|
|
+ imu.angular_velocity.y = -mpu_->getAccAngleY();
|
|
|
+ imu.angular_velocity.z = mpu_->getGyroZ();
|
|
|
+
|
|
|
+ imu.linear_acceleration.x = mpu_->getAccX();
|
|
|
+ imu.linear_acceleration.y = mpu_->getAccY();
|
|
|
+ imu.linear_acceleration.z = mpu_->getAccZ();
|
|
|
+
|
|
|
+ IMU::Euler2Quaternion(imu.angle_euler.x, imu.angle_euler.y, imu.angle_euler.z,
|
|
|
+ imu.orientation);
|
|
|
+}
|
|
|
+
|
|
|
+void IMU::update()
|
|
|
+{
|
|
|
+ mpu_->update();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+main.cpp
|
|
|
+
|
|
|
+```c++
|
|
|
+#include <Arduino.h>
|
|
|
+#include "IMU.h"
|
|
|
+
|
|
|
+MPU6050 mpu(Wire); // 初始化MPU6050对象
|
|
|
+IMU imu(mpu); // 初始化IMU对象
|
|
|
+
|
|
|
+imu_t imu_data;
|
|
|
+unsigned long timer = 0;
|
|
|
+
|
|
|
+void setup()
|
|
|
+{
|
|
|
+ Serial.begin(115200);
|
|
|
+ imu.begin(18, 19); // 初始化IMU,使用18,19引脚
|
|
|
+}
|
|
|
+
|
|
|
+void loop()
|
|
|
+{
|
|
|
+ imu.update();
|
|
|
+ if ((millis() - timer) > 100)
|
|
|
+ {
|
|
|
+ imu.getImuData(imu_data); // 获取IMU数据结构体
|
|
|
+ Serial.printf("imu:\teuler(%f,%f,%f)\n",
|
|
|
+ imu_data.angle_euler.x, imu_data.angle_euler.y, imu_data.angle_euler.z);
|
|
|
+ Serial.printf("imu:\torientation(%f,%f,%f,%f)\n",
|
|
|
+ imu_data.orientation.w, imu_data.orientation.x, imu_data.orientation.y, imu_data.orientation.z);
|
|
|
+ timer = millis();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+对于代码的解释已经放到了注释之中。
|
|
|
+
|
|
|
+编译下载后,你将看到
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 三、总结
|
|
|
+
|
|
|
+本节我们通过对MPU6050驱动的封装,学习了如何在嵌入式上使用面向对象编程的方法,下一节我们继续尝试使用开源库来驱动OLED模块,让我们的显示器亮起来。.
|