Загрузка данных
#include <Wire.h>
#define DEV_ADDR 0x68
// Сырые данные MPU6050
int16_t ax_raw, ay_raw, az_raw;
int16_t gx_raw, gy_raw, gz_raw;
// Данные в базисе датчика
float ax, ay, az;
float wx, wy, wz;
// Данные после перехода в базис B
float aBx, aBy, aBz;
float p, q, r;
// Кватернион ориентации
float q0 = 1.0;
float q1 = 0.0;
float q2 = 0.0;
float q3 = 0.0;
unsigned long lastTime;
// Умножение двух кватернионов
void quatMultiply(
float a0, float a1, float a2, float a3,
float b0, float b1, float b2, float b3,
float &c0, float &c1, float &c2, float &c3
) {
c0 = a0*b0 - a1*b1 - a2*b2 - a3*b3;
c1 = a0*b1 + a1*b0 + a2*b3 - a3*b2;
c2 = a0*b2 - a1*b3 + a2*b0 + a3*b1;
c3 = a0*b3 + a1*b2 - a2*b1 + a3*b0;
}
// Нормировка кватерниона
void normalizeQuaternion() {
float norm = sqrt(
q0*q0 +
q1*q1 +
q2*q2 +
q3*q3
);
q0 /= norm;
q1 /= norm;
q2 /= norm;
q3 /= norm;
}
// Чтение MPU6050
void readMPU() {
Wire.beginTransmission(DEV_ADDR);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(DEV_ADDR, 14, true);
ax_raw = (Wire.read() << 8) | Wire.read();
ay_raw = (Wire.read() << 8) | Wire.read();
az_raw = (Wire.read() << 8) | Wire.read();
// Температуру здесь не используем
Wire.read();
Wire.read();
gx_raw = (Wire.read() << 8) | Wire.read();
gy_raw = (Wire.read() << 8) | Wire.read();
gz_raw = (Wire.read() << 8) | Wire.read();
// Ускорение в м/с^2
ax = ax_raw / 16384.0 * 9.81;
ay = ay_raw / 16384.0 * 9.81;
az = az_raw / 16384.0 * 9.81;
// Гироскоп: сырые данные -> град/с -> рад/с
wx = gx_raw / 131.0 * PI / 180.0;
wy = gy_raw / 131.0 * PI / 180.0;
wz = gz_raw / 131.0 * PI / 180.0;
// ==================================
// Переход в базис из задания 1
// ==================================
aBx = -ay;
aBy = ax;
aBz = az;
p = -wy;
q = wx;
r = wz;
}
void setup() {
Serial.begin(115200);
Wire.begin();
// Разбудить MPU6050
Wire.beginTransmission(DEV_ADDR);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission(true);
// Диапазон гироскопа ±250 град/с
Wire.beginTransmission(DEV_ADDR);
Wire.write(0x1B);
Wire.write(0);
Wire.endTransmission(true);
// Диапазон акселерометра ±2g
Wire.beginTransmission(DEV_ADDR);
Wire.write(0x1C);
Wire.write(0);
Wire.endTransmission(true);
delay(100);
// ==================================
// Начальная ориентация
// ==================================
readMPU();
// Начальные Roll и Pitch по акселерометру
float roll0 = atan2(aBy, aBz);
float pitch0 = atan2(
-aBx,
sqrt(aBy*aBy + aBz*aBz)
);
// Начальный Yaw принимаем равным 0
float yaw0 = 0;
// ==================================
// Перевод Roll, Pitch, Yaw
// в начальный кватернион
// ==================================
float cr = cos(roll0 / 2.0);
float sr = sin(roll0 / 2.0);
float cp = cos(pitch0 / 2.0);
float sp = sin(pitch0 / 2.0);
float cy = cos(yaw0 / 2.0);
float sy = sin(yaw0 / 2.0);
q0 = cr*cp*cy + sr*sp*sy;
q1 = sr*cp*cy - cr*sp*sy;
q2 = cr*sp*cy + sr*cp*sy;
q3 = cr*cp*sy - sr*sp*cy;
normalizeQuaternion();
lastTime = micros();
}
void loop() {
readMPU();
// ==================================
// Время между измерениями
// ==================================
unsigned long now = micros();
float dt =
(now - lastTime) / 1000000.0;
lastTime = now;
// ==================================
// ЗАДАНИЕ 3
//
// Δθ = ω * Δt
// ==================================
float dThetaX = p * dt;
float dThetaY = q * dt;
float dThetaZ = r * dt;
// Модуль вектора малого поворота:
// alpha = |Δθ|
float alphaStep = sqrt(
dThetaX*dThetaX +
dThetaY*dThetaY +
dThetaZ*dThetaZ
);
// Кватернион малого поворота Δq
float dq0;
float dq1;
float dq2;
float dq3;
if (alphaStep > 0.000001) {
float s = sin(alphaStep / 2.0);
dq0 = cos(alphaStep / 2.0);
dq1 = (dThetaX / alphaStep) * s;
dq2 = (dThetaY / alphaStep) * s;
dq3 = (dThetaZ / alphaStep) * s;
}
else {
// Если поворота практически нет
dq0 = 1.0;
dq1 = 0.0;
dq2 = 0.0;
dq3 = 0.0;
}
// ==================================
// q(k+1) = q(k) ⊗ Δq
// ==================================
float newQ0;
float newQ1;
float newQ2;
float newQ3;
quatMultiply(
q0, q1, q2, q3,
dq0, dq1, dq2, dq3,
newQ0, newQ1, newQ2, newQ3
);
q0 = newQ0;
q1 = newQ1;
q2 = newQ2;
q3 = newQ3;
// q = q / |q|
normalizeQuaternion();
// ==================================
// Получаем Roll, Pitch, Yaw из q
// ==================================
float roll = atan2(
2.0 * (q0*q1 + q2*q3),
1.0 - 2.0 * (q1*q1 + q2*q2)
);
float sinPitch =
2.0 * (q0*q2 - q3*q1);
// Защита asin от ошибок округления
if (sinPitch > 1.0)
sinPitch = 1.0;
if (sinPitch < -1.0)
sinPitch = -1.0;
float pitch = asin(sinPitch);
float yaw = atan2(
2.0 * (q0*q3 + q1*q2),
1.0 - 2.0 * (q2*q2 + q3*q3)
);
// Радианы -> градусы
float roll_deg = roll * 180.0 / PI;
float pitch_deg = pitch * 180.0 / PI;
float yaw_deg = yaw * 180.0 / PI;
// ==================================
// ВЫВОД
// ==================================
Serial.println("===== QUATERNION =====");
Serial.print("q0 = ");
Serial.print(q0);
Serial.print(" q1 = ");
Serial.print(q1);
Serial.print(" q2 = ");
Serial.print(q2);
Serial.print(" q3 = ");
Serial.println(q3);
Serial.println("===== ANGLES =====");
Serial.print("Roll = ");
Serial.print(roll_deg);
Serial.print(" deg ");
Serial.print("Pitch = ");
Serial.print(pitch_deg);
Serial.print(" deg ");
Serial.print("Yaw = ");
Serial.print(yaw_deg);
Serial.println(" deg");
Serial.println("-----------------------------");
delay(10);
}