В этом уроке мы создадим систему, которая измеряет:
- Тангаж - наклон вперед/назад
- Крен - наклон влево/вправо
- Рыскание - поворот вокруг оси
- Высоту - в сантиметрах относительно начальной точки
Необходимые компоненты
- Плата с ICM20948 + BMP280
- Arduino Uno/Nano/Mega
- Провода для подключения
- USB-кабель
Теоретическая часть
Как работает IMU (Inertial Measurement Unit)
ICM20948 содержит три датчика:
- Акселерометр - измеряет линейное ускорение
- Гироскоп - измеряет угловую скорость
- Магнитометр - определяет направление (в нашем коде не используется)
Как работает барометр
BMP280 измеряет:
- Атмосферное давление - уменьшается с высотой
- Температуру - для компенсации температурных эффектов
Формула расчета высоты:
h = 44330 × (1 - (P/P₀)^(1/5.255))
Схема подключения:
ICM20948+BMP280 Arduino VCC → 3.3V GND → GND SDA → A4 (SDA) SCL → A5 (SCL)
Установка библиотек
- Adafruit ICM20948 (через менеджер библиотек)
- iarduino_Pressure_BMP (через менеджер библиотек)
- Adafruit Sensor (установится автоматически)
Ключевые функции:
calibrateGyro() - Калибрует гироскоп:
<i>// Усредняет 50 измерений в неподвижном состоянии</i> <i>// Определяет нулевые смещения</i>
calibrateAlt() - Калибрует высоту:
<i>// Запоминает текущее давление как опорное</i> <i>// Устанавливает текущую позицию как 0 см</i>
processIMUData() - Обрабатывает данные IMU:
<i>// Комплементарный фильтр объединяет:</i> <i>// - Гироскоп (точность в движении)</i> <i>// - Акселерометр (точность в статике)</i>
Шаг 1: Загрузка и тестирование
- Загрузите код в Arduino
- Откройте Serial Monitor (115200 бод)
- Наблюдайте данные в формате:
Pitch Roll Yaw AltCm
Шаг 2: Калибровка
Калибровка гироскопа:
- Установите устройство неподвижно
- Отправьте команду
c - Ждите завершения калибровки
Калибровка высоты:
- Установите устройство в нулевой точке
- Отправьте команду
h - Текущая позиция = 0 см
Шаг 3: Тестирование функций
Проверьте работу:
- Наклоните устройство - наблюдайте изменение Pitch/Roll
- Повращайте устройство - наблюдайте изменение Yaw
- Поднимите/опустите - наблюдайте изменение высоты
Команды для отладки
| Команда | Описание |
|---|---|
c | Калибровать гироскоп |
h | Калибровать высоту |
r | Сбросить углы |
s | Статус системы |
d | Отладочная информация |
? | Справка по командам |
Анализ данных
Пример вывода:
Pitch Roll Yaw AltCm 12.5 -3.2 45.0 0.0 15.1 -2.8 46.2 15.3
Чтение данных:
- Pitch = 15.1° - наклон вперед
- Roll = -2.8° - небольшой крен влево
- Yaw = 46.2° - поворот на 46 градусов
- AltCm = 15.3 - высота 15.3 см от нуля
Объяснение ключевых концепций
Комплементарный фильтр
Проблема:
- Акселерометр: точен в статике, но шумит при движении
- Гироскоп: точен в движении, но дрейфует со временем
Решение:
angle = 0.98 × (angle + gyro × dt) + 0.02 × acc_angle
Барометрическая формула
Как давление преобразуется в высоту:
float pressureRatio = currentPressure / initPress; float altitude = 44330.0 * (1.0 - pow(pressureRatio, 0.1903));
Возможные проблемы и решения
- Некорректные значения высоты:
- Перекалибруйте высоту командой
h - Дайте датчику прогреться 5-10 минут
- Перекалибруйте высоту командой
- Дрейф углов:
- Перекалибруйте гироскоп командой
c - Убедитесь в неподвижности при калибровке
- Перекалибруйте гироскоп командой
- Шум в данных:
- Разместите устройство на устойчивой поверхности
- Используйте фильтрацию в обработке данных
#include <wire.h>
#include <adafruit_icm20948.h>
#include <iarduino_pressure_bmp.h>
Adafruit_ICM20948 icm;
iarduino_Pressure_BMP bmp;
// Переменные для углов ориентации
float pitch, roll, yaw;
// Переменные для высоты и калибровки
float initPress = 101325.0; // Стандартное давление (1013.25 гПа)
float initAlt = 0.0;
// Переменные времени и калибровки гироскопа
unsigned long lastTime;
float gXoff, gYoff, gZoff;
// Флаги датчиков (бит 0: IMU, бит 1: BMP)
byte sensorsOK = 0;
void setup() {
Serial.begin(115200);
Wire.begin();
Serial.println(F("=== СИСТЕМА ОРИЕНТАЦИИ И ВЫСОТЫ ==="));
// Инициализация ICM20948 (IMU)
if (icm.begin_I2C(0x69) || icm.begin_I2C(0x68)) {
sensorsOK |= 1;
Serial.println(F("ICM20948: ОК"));
calibrateGyro();
} else {
Serial.println(F("ICM20948: Ошибка подключения"));
}
// Инициализация BMP280 (Барометр)
if (bmp.begin()) {
sensorsOK |= 2;
Serial.println(F("BMP280: ОК"));
calibrateAlt();
} else {
Serial.println(F("BMP280: Ошибка подключения"));
}
Serial.println(F("========================"));
Serial.println(F("Pitch\tRoll\tYaw\tAltCm"));
Serial.println(F("========================"));
lastTime = micros();
}
void loop() {
// ОБРАБОТКА ДАННЫХ IMU
if (sensorsOK & 1) {
processIMUData();
}
// ВЫЧИСЛЕНИЕ ВЫСОТЫ
float altCm = 0;
if (sensorsOK & 2) {
altCm = calculateAltitude();
}
// ВЫВОД РЕЗУЛЬТАТОВ
printData(altCm);
// ПРОВЕРКА КОМАНД
checkSerialCommands();
delay(100); // 10 Hz update rate
}
// Обработка данных IMU
void processIMUData() {
sensors_event_t accel, gyro, mag;
if (icm.getEvent(&accel, &gyro, &mag)) {
float dt = (micros() - lastTime) / 1000000.0;
lastTime = micros();
// Преобразование данных гироскопа
float gX = (gyro.gyro.x - gXoff) * 57.2958;
float gY = (gyro.gyro.y - gYoff) * 57.2958;
float gZ = (gyro.gyro.z - gZoff) * 57.2958;
// Углы из акселерометра
float accPitch = atan2(accel.acceleration.y, accel.acceleration.z) * 57.2958;
float accRoll = atan2(-accel.acceleration.x,
sqrt(accel.acceleration.y*accel.acceleration.y +
accel.acceleration.z*accel.acceleration.z)) * 57.2958;
// Комплементарный фильтр
pitch = 0.98 * (pitch + gX * dt) + 0.02 * accPitch;
roll = 0.98 * (roll + gY * dt) + 0.02 * accRoll;
yaw += gZ * dt;
// Нормализация углов
normalizeAngles();
}
}
// Вычисление высоты
float calculateAltitude() {
bmp.read();
float currentPressure = bmp.pressure;
float pressureRatio = currentPressure / initPress;
float currentAltitude = 44330.0 * (1.0 - pow(pressureRatio, 0.1903));
return (currentAltitude - initAlt) * 100.0; // Возвращаем в см
}
// Вывод данных
void printData(float altitude) {
Serial.print(pitch, 1); // 1 знак после запятой
Serial.print('\t');
Serial.print(roll, 1);
Serial.print('\t');
Serial.print(yaw, 1);
Serial.print('\t');
Serial.println(altitude, 1);
}
// Нормализация углов
void normalizeAngles() {
pitch = constrain(pitch, -90, 90);
roll = constrain(roll, -180, 180);
if (yaw > 180) yaw -= 360;
if (yaw < -180) yaw += 360;
}
// Калибровка гироскопа
void calibrateGyro() {
Serial.println(F("Калибровка гироскопа..."));
Serial.println(F("Не двигайте устройство 2 сек"));
delay(2000);
float x = 0, y = 0, z = 0;
for (byte i = 0; i < 50; i++) {
sensors_event_t a, g, m;
icm.getEvent(&a, &g, &m);
x += g.gyro.x; y += g.gyro.y; z += g.gyro.z;
if (i % 10 == 0) Serial.print('.');
delay(20);
}
gXoff = x / 50; gYoff = y / 50; gZoff = z / 50;
Serial.println();
Serial.println(F("Гироскоп откалиброван"));
}
// Калибровка высоты
void calibrateAlt() {
Serial.println(F("Калибровка высоты..."));
Serial.println(F("Установите нулевую точку"));
delay(2000);
float sumPressure = 0;
for (byte i = 0; i < 10; i++) {
bmp.read();
sumPressure += bmp.pressure;
Serial.print('.');
delay(100);
}
initPress = sumPressure / 10;
initAlt = 0.0;
Serial.println();
Serial.print(F("Нулевое давление: "));
Serial.print(initPress / 100.0);
Serial.println(F(" гПа"));
}
// Проверка команд Serial
void checkSerialCommands() {
if (Serial.available()) {
char command = Serial.read();
switch (command) {
case 'c': if (sensorsOK & 1) calibrateGyro(); break;
case 'h': if (sensorsOK & 2) calibrateAlt(); break;
case 'r': pitch = 0; roll = 0; yaw = 0; Serial.println(F("Углы сброшены")); break;
case 's': showStatus(); break;
case 'd': showDebugInfo(); break;
case '?': showHelp(); break;
}
}
}
// Показать статус системы
void showStatus() {
Serial.println(F("\n=== СТАТУС СИСТЕМЫ ==="));
Serial.print(F("IMU: ")); Serial.println(sensorsOK & 1 ? "OK" : "ERROR");
Serial.print(F("BMP: ")); Serial.println(sensorsOK & 2 ? "OK" : "ERROR");
Serial.print(F("Опорное давление: ")); Serial.print(initPress/100.0); Serial.println(F(" гПа"));
Serial.println(F("===================="));
}
// Отладочная информация
void showDebugInfo() {
Serial.println(F("\n=== ДЕБАГ ИНФО ==="));
if (sensorsOK & 2) {
bmp.read();
Serial.print(F("Давление: ")); Serial.print(bmp.pressure); Serial.println(F(" Па"));
Serial.print(F("Температура: ")); Serial.print(bmp.temperature); Serial.println(F(" °C"));
}
Serial.print(F("Смещения гиро: "));
Serial.print(gXoff, 6); Serial.print(F(" "));
Serial.print(gYoff, 6); Serial.print(F(" "));
Serial.println(gZoff, 6);
Serial.println(F("=================="));
}
// Справка по командам
void showHelp() {
Serial.println(F("\n=== КОМАНДЫ ==="));
Serial.println(F("c - Калибровать гироскоп"));
Serial.println(F("h - Калибровать высоту"));
Serial.println(F("r - Сбросить углы"));
Serial.println(F("s - Статус системы"));
Serial.println(F("d - Дебаг информация"));
Serial.println(F("? - Эта справка"));
Serial.println(F("================"));
}</iarduino_pressure_bmp.h></adafruit_icm20948.h></wire.h>

Обсуждение