Модуль GY-521 я купил ради одной идеи — повесить на штору датчик, который поймает кота в момент покушения. Подключил за пять минут, залил первый попавшийся скетч и упёрся в стену: Serial писал шесть нулей в строке, сколько я ни тряс плату в руке. Пересобрал схему трижды, прозвонил каждый провод и уже грешить начал на подделку с маркетплейса. Виноват оказался не датчик, а одна строчка: чип MPU6050 после подачи питания спит, и без записи нуля в регистр 0x6B он не отдаёт ничего, кроме нулей.
С тех пор я не начинаю разговор с этим датчиком без будильника. В этом уроке пройдём весь путь данных за один сеанс: как разбудить чип, откуда читать ускорения и угловые скорости, как склеить из сырых байтов нормальные g и градусы в секунду и что делать с дрожью, из-за которой числа на экране пляшут. Симулятор уже собрал шину — датчик висит на адресе 0x68 и ждёт запросов, панель I2C покажет каждый байт. Дальше по этому фундаменту в следующем уроке соберём настоящий угол наклона, а в третьем — курс сторон света на компасе: всё это одна и та же пара проводов.
Две микросхемы в одном корпусе
MPU6050 — это две микросхемы в одном корпусе: трёхосевой акселерометр, чувствующий тяготение и всякое дёрганье платы, и трёхосевой гироскоп, меряющий скорость вращения вокруг каждой оси. Обе половины висят на шине I2C — той самой, что живёт на двух проводах SDA и SCL; как шина устроена, я разбирал в уроке про два провода на все устройства. На Uno это по-прежнему A4 и A5 — ни одного отдельного пина датчик не занимает.
Адрес на шине выбирает вывод AD0: посажен на землю — 0x68, подан на плюс — 0x69. На модуле GY-521 стоит собственный стабилизатор, так что VCC смело сажаем на 5 В. Логика чипа трёхвольтовая, и хотя с Uno его обычно гонят напрямую без согласования уровней и это работает годами, длинные провода при таком раскладе — плохая идея. И ещё: магнитометра в MPU6050 нет, как бы ни обещали продавцы; компас — это MPU9250 или отдельный чип, до него дойдём в третьем уроке этой тройки.
Если после подключения Serial молчит, а на шине тишина — не гадайте. Прогоните сканер I2C-адресов: за пару секунд он скажет, видит ли мастер что-нибудь на 0x68. У меня один модуль из пяти отвечал только когда плату подкладывала пара книг — плохая пайка контакта VCC, и сканер выдал вердикт раньше паяльника.
Карта регистров: где лежат ускорения
Карта регистров у MPU6050 короткая, и её стоит выучить один раз. Регистр 0x6B — управление питанием: после подачи питания там стоит бит сна, ноль его снимает. Регистр 0x75 — who-am-i: живой чип отвечает числом 0x68, по нему удобно проверять связь. С 0x3B по 0x48 лежат данные — четырнадцать байтов подряд: шесть на ускорения, два на температуру, шесть на угловые скорости.
Порядок байтов старый, сетевой: сначала старший, потом младший. Ускорение по X — это регистры 0x3B и 0x3C, и склеиваются они сдвигом: старший уезжает на восемь разрядов влево, младший накрывает его снизу. Температура сидит в середине пакета, на 0x41, и пересчитывается с двумя константами: сырой отсчёт делим на 340 и прибавляем 36,53. Это внутренний датчик корпуса, он на пару градусов теплее комнаты — не путайте его с уличным термометром.
Читать все четырнадцать байтов я советую одним запросом: Wire.requestFrom(0x68, 14) — шина отрабатывает одну транзакцию вместо трёх. Когда я в первый вечер тянул ускорение и гироскоп двумя отдельными запросами, значения рассыпались: половина кадра из одного измерения, половина из другого. Один пакет — одна фотография состояния чипа; этому же приёму учат в уроке про обмен между двумя платами.
#include <Wire.h> const byte MPU = 0x68; // AD0 на GND -> 0x68, на VCC -> 0x69 void setup() { Serial.begin(9600); Wire.begin(); Wire.beginTransmission(MPU); Wire.write(0x6B); // регистр управления питанием Wire.write(0); // ноль выводит чип из сна Wire.endTransmission(true);} void loop() { Wire.beginTransmission(MPU); Wire.write(0x3B); // первый регистр данных: ускорение X Wire.endTransmission(false); // повторный старт: шину не отпускаем Wire.requestFrom(MPU, 14); // 6 акселя + 2 температуры + 6 гироскопа int16_t axr = 0, azr = 0, gzr = 0; if (Wire.available() == 14) { axr = Wire.read() << 8 | Wire.read(); // старший байт вперёд Wire.read(); Wire.read(); // ось Y — сейчас не нужна azr = Wire.read() << 8 | Wire.read(); Wire.read(); Wire.read(); // температура пропускаем gzr = Wire.read() << 8 | Wire.read(); // гироскоп Z } float ax = axr / 16384.0; // ускорение в g при диапазоне плюс-минус 2g float gz = gzr / 131.0; // скорость поворота, градусы в секунду Serial.print("axg="); Serial.print(ax, 3); Serial.print(" gzps="); Serial.println(gz, 1); delay(100);}Сердце скетча — строка вида Wire.read() << 8 | Wire.read(). Первый read() отдаёт старший байт, второй — младший, сдвиг и побитовое ИЛИ склеивают их в число от минус 32768 до 32767. Дальше перевод в физику: при диапазоне плюс-минус 2g один отсчёт стоит 1/16384 g — делим на 16384.0; у гироскопа при плюс-минус 250 градусов в секунду цена деления 1/131. Нули после точки в делителях не опечатка, это плавающая точка: целочисленное деление сожрёт дробь и всегда оставит ноль.
Про знак отдельная песня. Если старший байт больше 127, число выходит отрицательным само — но только пока переменная знаковая. Я однажды сложил байты в беззнаковую переменную и два вечера не мог понять, почему покой читается как 60000 с хвостом: это 16-битный минус в беззнаковой обёртке. Обычный int — и вопрос закрыт навсегда.
| Диапазон | Цена отсчёта | Когда я его выбираю |
|---|---|---|
| ±2g / ±250 град/с | 16384 / 131 | Наклоны, уровень, медленный робот — по умолчанию |
| ±4g / ±500 град/с | 8192 / 65,5 | Качели, маятник, наклоны рукой |
| ±8g / ±1000 град/с | 4096 / 32,8 | Робот с моторами, тряска на ходу |
| ±16g / ±2000 град/с | 2048 / 16,4 | Удары и дроны; на покое точность заметно падает |
Шум: почему числа пляшут
Живой датчик на столе — это не прямая линия, а лента с дрожью: на покое ускорение гуляет на плюс-минус пары сотых g, при вибрации от мотора — на десятые. Гироскоп шуршит ещё заметнее — единицы градусов в секунду мусора на ровном месте. Это не брак, так работает любой MEMS-датчик: с шумом не борются, его сглаживают.
Самый дешёвый фильтр, который я знаю, — экспоненциальный: filt = filt * 0.8 + новое * 0.2. Восемьдесят процентов памяти и двадцать процентов свежего отсчёта: дрожь уходит, а реакция на реальный наклон остаётся. Коэффициент 0.8 сглаживает сильно, но тупит; 0.5 почти не трогает шум. В Serial Plotter из урока про график данных разница видна сразу: сырая линия дёргается, фильтрованная ползёт плавно.
Второй инструмент — скользящее среднее: держим массив из последних десяти отсчётов и печатаем их среднее. По шумодаву они с экспоненциальным близнецы, но у окна есть заметная задержка: при опросе каждые 100 мс свежий наклон доберётся до вывода через полсекунды. Для уровня на столе это неважно, для робота — фатально, поэтому в движущихся проектах я беру экспоненциальный. Третий вариант — медиана из трёх отсчётов: она выбрасывает одиночный выброс, которым часто бьёт проводка рядом, и при этом не тормозит; когда аксель «глотнул» помеху и показал минус два g на ровном месте, медиана это молча исправит, а среднее — размажет.
Тот же трюк спасёт экран, когда будете выводить значения на дисплей: печатать сырые отсчёты — значит заставить последнюю цифру мигать десятки раз в секунду. Я печатаю сглаженное значение раз в 200 мс — приём подробно разобран в уроке про термометр на LCD без мерцания.
Что видно в прогоне: в панели I2C сначала уходит запись в 0x6B, затем серия запросов по адресу 0x68 с чтением четырнадцати байтов, а Serial держит пару axg= и fil= — сырую ось и фильтр. Числа стоят на месте, потому что заглушка честно отвечает константой; на живой плате наклоните модуль — и оба значения поедут, а фильтр останется позади сырого на полсекунды.
Задачи на закрепление: поменяйте адрес на 0x69 и найдите его глазами в панели I2C; попросите вместо четырнадцати байтов два и посмотрите, что покажет Wire.available(); допишите в скетч чтение регистра 0x75 — в симуляторе придёт 42, а на живой плате ровно 0x68, и это готовый тест живости модуля. Когда сырые оси и фильтр перестанут пугать — переходите к следующему уроку про угол наклона: из этих же чисел мы соберём настоящий угол.
- 1. Сколько байтов приносит один полный запрос данных MPU6050?
- 2. Что нужно записать в регистр 0x6B, чтобы вывести MPU6050 из спячки?
- 3. Чему равна цена одного отсчёта акселерометра при диапазоне ±2g?
- 4. В каком порядке MPU6050 отдаёт байты каждого измерения?
- 5. Датчик лежит ровно на столе. Что покажет ось Z акселерометра?
- MPU6050 после питания спит: запись нуля в регистр 0x6B будит его — с этой строки начинается любой скетч
- Данные лежат с 0x3B по 0x48: читайте 14 байтов одним запросом requestFrom(0x68, 14)
- Байты склеиваются сдвигом Wire.read() << 8 | Wire.read(), старший вперёд; переменная — знаковый int
- Перевод в физику: ±2g — делить на 16384.0, ±250 град/с — на 131.0; точка в делителе обязательна
- Дрожь лечится экспоненциальным сглаживанием filt = filt * 0.8 + x * 0.2
Частые вопросы по теме урока
- Сколько байтов приносит один полный запрос данных MPU6050?
- С 0x3B по 0x48 идут 14 байтов подряд. Температура сидит в середине пакета, между акселерометром и гироскопом, и чаще всего её просто пропускают. Четырнадцать — 6 ускорений, 2 температуры, 6 гироскопа
- Что нужно записать в регистр 0x6B, чтобы вывести MPU6050 из спячки?
- После подачи питания чип спит; запись нуля в регистр управления питанием будит его. Это первая строка, которую я пишу в setup(). Ноль — бит сна в нём снимается
- Чему равна цена одного отсчёта акселерометра при диапазоне ±2g?
- Шкалу в 2g тянут 32768 отсчётов в одну сторону, поэтому 1 g — это 16384 отсчёта. Делим сырой отсчёт на 16384.0 и получаем g. 1/16384 g
- В каком порядке MPU6050 отдаёт байты каждого измерения?
- Старший байт сдвигают на восемь разрядов влево и накрывают младшим. У компаса QMC5883L, о котором пойдёт речь дальше, всё наоборот. Сначала старший байт, потом младший
- Датчик лежит ровно на столе. Что покажет ось Z акселерометра?
- В покое по вертикали акселерометр показывает примерно 1 g. На этом и построено вычисление наклона — тема следующего урока. Около 1 g — ось держит реакцию опоры