Антенну на мачте я выставлял по пузырьковому уровню: держишь трубку, щуришься на пузырь, а руки тем временем дрожат — высота, ветер, неудобная поза. Вечером придумал: пусть платка сама пищит, когда её перекосило за допуск. Через час на столе лежал электронный уровень из MPU6050, светодиода и зуммера: наклонил за тридцать градусов — запищал, вернул — замолчал. С тех пор так стоит и полка в гараже: повернул кронштейн — зуммер сказал правду без единого пузырька.
MPU6050 — акселерометр и гироскоп на одной платке по I2C. В продаже она зовётся GY-521: восемь пинов в ряд, из которых вам нужны четыре — VCC, GND, SCL, SDA; XDA, XCL и INT — для внешних магнитометров и прерываний, AD0 — для смены адреса. В этом уроке возьмём только акселерометр: разберём, как гравитация превращается в числа ax/ay/az, почему один g — это 16384, достанем готовые углы getAngleX/getAngleY и соберём уровень с гистерезисом и зуммером. В симуляторе платку можно тащить мышью — она наклоняется в перспективе, и скетч видит ровно те же углы, что и настоящая на столе.
Гравитация в числах: откуда берутся углы
Акселерометр меряет проекции ускорения на три оси, и в покое это ускорение одно — гравитация. Платка лежит ровно: вся тяжесть приходится на ось Z, az читается около 16384, ax и ay — около нуля. Число 16384 не случайное: при диапазоне ±2 g, который стоит по умолчанию, один g размечен в 16384 единицы — удобное битовое число, два в четырнадцатой. Наклонили платку на тридцать градусов вокруг оси X — гравитация растеклась по осям: ax = sin(30°) × 16384 ≈ 8192, az упал до cos(30°) × 16384 ≈ 14188. Поставили на ребро — ax = 16384, az = 0. Перевернули вверх ногами — az ≈ −16384. Углы сидят в проекциях прямо, без даташитовой магии: арксинус от доли g, умножить на 57,3 — вот и весь угол.
Оси подписаны на плате: X и Y напечатаны на шёлке, Z торчит перпендикулярно плате. Проверить несложно: ровная платка с большим az — значит, Z смотрит в небо. Для точных приборов помните, что g не константа: на полюсе она на 0,3 % больше, чем на экваторе, а на высоте — меньше, так что az идеального уровня на Эльбрусе не дотянет до 16384. Уровню для полки такая поправка безразлична, а инклинометру в экспедиции — уже нет.
| Положение платки | ax | ay | az |
|---|---|---|---|
| ровно на столе | ≈ 0 | ≈ 0 | ≈ 16384 (1 g) |
| наклон по X на 30° | ≈ 8192 | ≈ 0 | ≈ 14188 |
| на ребре, 90° по X | ≈ 16384 | ≈ 0 | ≈ 0 |
| вверх ногами | ≈ 0 | ≈ 0 | ≈ −16384 |
Библиотека: готовые углы и сырые единицы
Библиотека Adafruit MPU6050 ставится из менеджера вместе с Unified Sensor; в симуляторе класс встроен и церемония — mpu.begin(), всё. Дальше два этажа значений. Нижний — сырые целые: mpu.ax(), mpu.ay(), mpu.az() возвращают те самые единицы со 16384 на g; mpu.readAcceleration() отдаёт то же самое, но в g с плавающей точкой — az ровной платки читается как 1.00, и пороги в g иногда удобнее: тревога на удар от 1,5 g записывается одной строкой вместо сравнения с 24576. Верхний этаж — готовые углы: mpu.getAngleX() и getAngleY() возвращают наклон в градусах от −90 до +90, и в симуляторе именно они двигают картинку платки. Честная оговорка: на живой плате готовых углов в чипе нет — их считают из проекций через atan2, и у меня есть целый урок про этот расчёт с фильтром от дребезга и разбором, почему угол врёт на разгоне. Симуляторный getAngleX — тот же atan2, посчитанный за вас: подвигайте платку в симе и сверьте Serial с картинкой.
Адрес модуля — 0x68 при AD0 на земле; провод AD0 к питанию сдвигает его на 0x69. Хуже того: 0x68 — родной адрес часов DS3231, и две платы на одной шине дерутся молча, обе отвечают чушью. Как разводить такие конфликты джамперами и проводами — урок про конфликты адресов. Гироскоп, шум ±0,02 g на покое и побайтовый разбор регистров — в первом уроке про MPU6050; здесь на них не отвлекаемся.
Теперь порог. Почему тридцать градусов, а не пять: у акселерометра есть шум порядка ±0,02 g на покое, что у нуля даёт больше градуса; плюс дрожь стола и шаги по полу. Опустите порог до пяти градусов — уровень запищит от того, что вы встали со стула. Тридцать — рабочий допуск полки: кривизна, которую глаз и так видит. Вторая половина решения — гистерезис: тревогу включаю на 30°, а гашу только когда обе оси вернулись внутрь 27°. Без этого граница дребезжала бы: платка колышется у 29,9° — зуммер щёлкает в такт каждой волне. Та же схема, что у термостата с реле: два порога вместо одного.
И последняя честная граница метода: акселерометр отличает наклон от ускорения только в покое. Телефон в кармане при ходьбе показывает углы-химеры — гравитация и тряска складываются в одно поле, и разобрать их по трём осям невозможно. Поэтому для подвижных платформ — роботов, подвесов, балансиров — к акселерометру добавляют гироскоп и сводят их фильтром: акселерометр задаёт долгосрочную правду, гироскоп сглаживает мгновенную. Для уровня на полке и наклона лотка всего этого не нужно: платка стоит, поле одно, и atan2 прав.
Зуммер беру активный: пин D8, digitalWrite HIGH — и он орёт, tone() не нужен. Если хочется различать тревоги, зуммер умеет пищать частотой: за 30° — 2000 Гц, за 60° — 3000 Гц, разница слышна даже из соседней комнаты. Азбука tone и первая мелодия — в уроке про зуммер; а если захотите нарисовать углы в Serial Plotter, печатайте пары angleX= и angleY= — как устроены такие кадры, показано в уроке про график. Ритм печати у уровня 200 мс: пять кадров в секунду, глазу достаточно, а шина I2C при этом загружена на доли процента — угол не обязан обновляться быстрее, чем дрожит рука.
Где ещё такой уровень приживается у меня: наклон лотка автокормушки перед выдачей, контроль крена солнечной панели и сигнализация на дверь гаража — откат створки дёргает платку за порог мгновенно. Везде логика одна: сырые единицы — для порогов по ускорению, готовые углы — для наклона, гистерезис — обязательно.
// Уровень: наклон за 30 градусов пищит, возврат до 27 гасит #include <Wire.h>#include <Adafruit_MPU6050.h>#include <Adafruit_Sensor.h> Adafruit_MPU6050 mpu;sensors_event_t a, g, temp; void setup() { pinMode(12, OUTPUT); pinMode(8, OUTPUT); Serial.begin(9600); if (mpu.begin() == false) { Serial.println("MPU6050 ne otvechaet"); while (1) delay(500); }} void loop() { mpu.getEvent(&a, &g, &temp); float angX = atan2(a.acceleration.x, a.acceleration.z) * 57.2958; float angY = atan2(a.acceleration.y, a.acceleration.z) * 57.2958; bool alarm = (angX > 30.0) || (angX < -30.0) || (angY > 30.0) || (angY < -30.0); if (alarm) { digitalWrite(12, HIGH); tone(8, 2000); } else { digitalWrite(12, LOW); noTone(8); } Serial.print("angX="); Serial.print(angX, 1); Serial.print(" angY="); Serial.println(angY, 1); delay(150);}Что в симуляторе честно, а что упрощено. Честно: перетаскивание платки пересчитывается в сырые единицы на лету — наклон по X на тридцать градусов даёт ax около 8192 и az около 14188, как на настоящем чипе, и углы getAngleX/getAngleY совпадают с картинкой. Упрощено: гироскоп. gx/gy/gz в симе отдают нули — вращение без наклона движок не эмулирует, так что скетчам на угловых скоростях здесь делать нечего. На живой плате к этому добавится дрейф: гироскоп MPU6050 плывёт на 2–5 градусов в секунду без калибровки — ещё одна причина считать наклон из акселерометра и связывать его с гироскопом фильтром, когда дойдёт до балансира.
- 16384 единицы = 1 g при диапазоне ±2g: ровная платка даёт az ≈ 16384, наклон 30° по X — ax ≈ 8192 (sin 30° = 0,5)
- Адрес 0x68 при AD0 на земле и 0x69 при AD0 на питании; 0x68 конфликтует с DS3231 — разводите заранее
- getAngleX()/getAngleY() — готовые углы −90…+90 в симуляторе; на живой плате угол считают из atan2(ax, az) сами
- getMotion6 пишет только в массивы: шесть int-переменных по ссылке из симулятора не передать — берите ax()/readAcceleration()
- Уровень живёт на двух порогах: тревога с 30°, отбой с 27° — гистерезис убирает дребезг на границе
- 1. Сколько «сырых» единиц акселерометра приходится на 1 g при диапазоне ±2g?
- 2. Платку наклонили по оси X на 30°. Чему примерно равен ax?
- 3. Вывод AD0 модуля посажен на землю. По какому адресу отвечает MPU6050?
- 4. Почему порог уровня ±5° заставил бы зуммер дребезжать?
- 5. Как в симуляторе забрать все шесть значений одним вызовом getMotion6?
Частые вопросы по теме урока
- Сколько «сырых» единиц акселерометра приходится на 1 g при диапазоне ±2g?
- При ±2g один g размечен в 2 в четырнадцатой степени — 16384 единицы. Ровная платка отдаёт az ≈ 16384. 16384
- Платку наклонили по оси X на 30°. Чему примерно равен ax?
- ax = sin(30°) × 16384 = 0,5 × 16384 ≈ 8192. az при этом падает до cos(30°) × 16384 ≈ 14188. 8192
- Вывод AD0 модуля посажен на землю. По какому адресу отвечает MPU6050?
- AD0 на земле — 0x68, AD0 на питании — 0x69. По умолчанию модуль всегда 0x68, как и часы DS3231. 0x68
- Почему порог уровня ±5° заставил бы зуммер дребезжать?
- Шум около ±0,02 g у нуля даёт больше градуса, плюс вибрации: граница постоянно пересекается. Спасают порог 30° и гистерезис. шум ±0,02 g и дрожь стола дёргают границу туда-сюда
- Как в симуляторе забрать все шесть значений одним вызовом getMotion6?
- Движок пишет результат getMotion6 только в переданные массивы. Для скаляров берите ax()/ay()/az() или readAcceleration(). передать массивы: канал по ссылке в движке есть только у массивов