Загрузка данных
#include <Wire.h>
#define DEV_ADDR 0x68
// ==========================================
// СЫРЫЕ ДАННЫЕ MPU6050
// ==========================================
int16_t ax_raw, ay_raw, az_raw;
int16_t temp_raw;
int16_t gx_raw, gy_raw, gz_raw;
// ==========================================
// ИСХОДНЫЕ ФИЗИЧЕСКИЕ ДАННЫЕ
// ==========================================
// Ускорение, м/с^2
float ax, ay, az;
// Угловая скорость, рад/с
float wx, wy, wz;
// ==========================================
// МАТРИЦА ПЕРЕХОДА
//
// | 0 -1 0 |
// T = | 1 0 0 |
// | 0 0 1 |
//
// ==========================================
float T[3][3] = {
{0, -1, 0},
{1, 0, 0},
{0, 0, 1}
};
// Векторы
float aS[3];
float wS[3];
float aB[3];
float wB[3];
// После перехода в новый базис
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;
// ==========================================
// УГОЛ 0...360
// ==========================================
float angle360(float angle) {
while (angle < 0) {
angle += 360.0;
}
while (angle >= 360.0) {
angle -= 360.0;
}
return angle;
}
// ==========================================
// УМНОЖЕНИЕ КВАТЕРНИОНОВ
//
// c = a ⊗ b
// ==========================================
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
);
if (norm == 0) return;
q0 /= norm;
q1 /= norm;
q2 /= norm;
q3 /= norm;
}
// ==========================================
// ЧТЕНИЕ MPU6050
// ==========================================
void readMPU() {
Wire.beginTransmission(DEV_ADDR);
// Начинаем с ACCEL_XOUT_H
Wire.write(0x3B);
Wire.endTransmission(false);
// Читаем 14 байт
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();
// Температура
temp_raw = (Wire.read() << 8) | 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;
}
// ==========================================
// ПЕРЕХОД В НОВЫЙ БАЗИС
// МАТРИЧНОЕ УМНОЖЕНИЕ
// ==========================================
void changeBasis() {
aS[0] = ax;
aS[1] = ay;
aS[2] = az;
wS[0] = wx;
wS[1] = wy;
wS[2] = wz;
// Обнуляем результат
for (int i = 0; i < 3; i++) {
aB[i] = 0;
wB[i] = 0;
}
// aB = T * aS
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
aB[i] += T[i][j] * aS[j];
}
}
// wB = T * wS
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
wB[i] += T[i][j] * wS[j];
}
}
aBx = aB[0];
aBy = aB[1];
aBz = aB[2];
p = wB[0];
q = wB[1];
r = wB[2];
}
// ==========================================
// SETUP
// ==========================================
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();
changeBasis();
// Начальный Roll по акселерометру
float roll0 =
atan2(aBy, aBz);
// Начальный Pitch по акселерометру
float pitch0 =
atan2(
-aBx,
sqrt(aBy*aBy + aBz*aBz)
);
// Yaw из акселерометра получить нельзя,
// поэтому принимаем начальный 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();
}
// ==========================================
// LOOP
// ==========================================
void loop() {
readMPU();
changeBasis();
// ========================================
// ВРЕМЯ dt
// ========================================
unsigned long now = micros();
float dt =
(now - lastTime) / 1000000.0;
lastTime = now;
// ========================================
// ЗАДАНИЕ 3
//
// Δθ = ω * dt
// ========================================
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 sinHalf =
sin(alphaStep / 2.0);
dq0 =
cos(alphaStep / 2.0);
dq1 =
(dThetaX / alphaStep) * sinHalf;
dq2 =
(dThetaY / alphaStep) * sinHalf;
dq3 =
(dThetaZ / alphaStep) * sinHalf;
}
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;
// ========================================
// НОРМИРОВКА
// ========================================
normalizeQuaternion();
// ========================================
// КВАТЕРНИОН -> ROLL
// ========================================
float roll = atan2(
2.0 * (q0*q1 + q2*q3),
1.0 - 2.0 * (q1*q1 + q2*q2)
);
// ========================================
// КВАТЕРНИОН -> PITCH
// ========================================
float sinPitch =
2.0 * (q0*q2 - q3*q1);
// Защита от ошибок округления
if (sinPitch > 1.0) {
sinPitch = 1.0;
}
if (sinPitch < -1.0) {
sinPitch = -1.0;
}
float pitch =
asin(sinPitch);
// ========================================
// КВАТЕРНИОН -> YAW
// ========================================
float yaw = atan2(
2.0 * (q0*q3 + q1*q2),
1.0 - 2.0 * (q2*q2 + q3*q3)
);
// ========================================
// РАДИАНЫ -> ГРАДУСЫ
// 0...360
// ========================================
float roll_deg =
angle360(roll * 180.0 / PI);
float pitch_deg =
angle360(pitch * 180.0 / PI);
float yaw_deg =
angle360(yaw * 180.0 / PI);
// ========================================
// ВЫВОД
// ========================================
Serial.println("===== ЗАДАНИЕ 3 =====");
Serial.println("Кватернион:");
Serial.print("q0 = ");
Serial.print(q0, 5);
Serial.print(" q1 = ");
Serial.print(q1, 5);
Serial.print(" q2 = ");
Serial.print(q2, 5);
Serial.print(" q3 = ");
Serial.println(q3, 5);
Serial.println("Углы:");
Serial.print("Крен (Roll) = ");
Serial.print(roll_deg);
Serial.println(" град");
Serial.print("Тангаж (Pitch) = ");
Serial.print(pitch_deg);
Serial.println(" град");
Serial.print("Рыскание (Yaw) = ");
Serial.print(yaw_deg);
Serial.println(" град");
Serial.println("==============================");
delay(20);
}