Первое знакомство с GY-521 у меня прошло обманчиво гладко: чужая библиотека, готовый скетч, числа побежали — красота. Полгода спустя тот же модуль на другой макетке молчал, и я с удивлением понял, что не имею ни малейшего представления, где у чипа двери: в моём коде не было ни одного регистра. Вечер с даташитом превратил чёрный ящик в понятную схему из одного рубильника и одного блока данных — с тех пор молчание MPU6050 для меня задача с тремя подозреваемыми, а не гадание.
MPU6050 — трёхосный акселерометр, трёхосный гироскоп и термометр в одном корпусе; на макетку он приезжает платой GY-521. Адрес — 0x68, пока ножка AD0 прижата к земле (заводское состояние платы), и 0x69, если подтянуть её к питанию: два чипа на шине живут по соседству. Данные ускорений лежат сплошным блоком с 0x3B: по два байта на ось X, Y и Z — шесть байтов, один запрос.
План урока: регистр 0x6B, которым чип будят, — без него всё остальное бессмысленно; карта первых регистров; чтение шести байтов через requestFrom и сборку 16-битных значений из пар; пересчёт в g при масштабе 16384 отсчёта на g; и в конце наклон-детектор со светодиодом, который держится на всём этом списке. Кто хочет сразу полный обзор с фильтрами и углами — есть обзорный урок по MPU6050; здесь минимальная механика, работающая без единой библиотеки.
Регистр 0x6B: чип спит из коробки
Регистр 0x6B зовётся PWR_MGMT_1, и важен в нём бит 6 — SLEEP. Заводское значение регистра 0x40: бит взведён, чип после подачи питания спит и экономит ток. При этом шина выглядит живой — сканер видит 0x68, подтверждение адреса приходит, байты читаются, — только данные мёртвые. Будим одной записью: beginTransmission(0x68), Wire.write(0x6B), Wire.write(0x00), endTransmission. Ноль здесь означает «всё включить, тактирование берём с внутреннего генератора». Откуда берутся такие адреса и как их читать в даташите — разложил урок про карту регистров.
Симптом забытого будильника я знаю на собственной шкуре: неделю винил «битый модуль с али», пока не вычитал про бит SLEEP. Вторая мелочь того же места — время подъёма: после записи в 0x6B датчикам нужно несколько десятков миллисекунд, и первый запрос сразу после пробуждения приносит нули, хотя чип уже формально жив. Дайте 30–50 мс — и только потом читайте данные.
Жив ли чип и тот ли он вообще, проверяет регистр WHO_AM_I по адресу 0x75: пишете указатель, делаете requestFrom(0x68, 1) — оригинальный MPU6050 возвращает 0x68, заводскую константу, совпадающую с базовым адресом. Родственный MPU-6500 отвечает 0x70, у безымянных клонов встречаются свои значения: не приговор, но полезно знать, с кем работаешь. По питанию расклад такой: голому чипу нужно 2,4–3,5 В, а плата GY-521 несёт свой стабилизатор и ест от 5 В — на макетке я кормлю модуль от рейки 5 В и не переживаю. И помните: сканер шины одинаково радостно показывает 0x68 и для спящего, и для наполовину мёртвого чипа — признание адреса ещё не признак жизни.
| Регистр | Что там лежит | Что делает скетч |
|---|---|---|
| 0x6B | PWR_MGMT_1: бит SLEEP взведён заводом | пишем 0x00 - будим чип |
| 0x3B / 0x3C | ускорение AX: старший и младший байты | читаем парой, собираем (hi<<8)|lo |
| 0x3D / 0x3E | ускорение AY: та же пара байтов | вторые два байта блока |
| 0x3F / 0x40 | ускорение AZ: замыкает шестёрку | последняя пара из шести |
Шесть байтов с 0x3B: пары, сдвиги, 16384 на g
Чтение двухходовкой: сначала в чип пишется адрес начала блока — beginTransmission(0x68), Wire.write(0x3B), endTransmission, — затем Wire.requestFrom(0x68, 6) и шесть Wire.read() в цикле. Механику указателя и выборку регистров по тактам разобрал урок про requestFrom и регистры. Спотыкаются на двух вещах. Байты идут старшим вперёд: первый прочитанный — верхняя половина оси. И слово лучше собирать в переменные, а не прямо в выражении: порядок вычисления операндов компилятор не гарантирует, и однажды пары молча поменяются местами.
Пересчёт в привычные единицы держится на масштабе: при заводском диапазоне ±2g один g равен 16384 отсчёта. Делите сырой int16 на 16384.0 — обязательно дробное, иначе шаг станет целочисленным. Значения знаковые: от −32768 до 32767, наклон в другую сторону приносит минус. Живая проверка: плата, лежащая плашмя, держит на AZ около +16384 — это проекция гравитации, и по ней удобно судить о жизни чипа: пробуждён, а Z нулевой — ищите обрыв SDA. Калибровку нуля и смещений разобрал урок про сырые данные, а арифметику сдвигов и масок, на которой держится сборка пары, — урок про битовые операции.
Отдельная ловушка ждёт при переносе скетча с Uno на ESP32 или другую 32-битную плату. У Uno тип int шестнадцатибитный, и (hi << 8) | lo само собой получается знаковым: перевёрнутая плата честно держит на AZ около −16384. У ESP32 int тридцатидвухбитный, те же байты 0xC0 и 0x00 складываются в 49152, знак исчезает, пороги едут мимо, детектор бунтует. Лечится явным типом: int16_t ax = (int16_t)((hi << 8) | lo). Я теперь ставлю этот каст везде, даже оставаясь на Uno: привычка дешевле часа отладки, а скетч перестаёт зависеть от разрядности платформы.
Скачущие значения из-за перепутанных половин я ловил и здесь. Один и тот же наклон то давал 4386 отсчёта, то 8721 — почти ровно вдвое больше, потом снова 4386: байты 0x11 и 0x22 менялись местами от прохода к проходу. Закономерность «в целое число раз» — подсказка: у пары 0x11 0x22 и 0x22 0x11 разница не случайная, а фиксированная, потому что старший байт умножается на 256. Если значения прыгают именно так, а не дрожат в пределах ±100–200 отсчётов шума — ищите перепутанные половины, а не датчик.
#include <Wire.h> // MPU6050 (GY-521): AD0 на земле -> 0x68. Будим 0x6B, читаем 6 байтов// с 0x3B: AX, AY, AZ по паре байтов, 16384 отсчёта на один g.const byte MPU = 0x68;const int THRESH = 5325; // 0,325 g в отсчётах: порог наклона (~19 градусов) void mpuWrite(byte reg, byte val) { Wire.beginTransmission(MPU); Wire.write(reg); Wire.write(val); Wire.endTransmission();} void setup() { Wire.begin(); Serial.begin(9600); pinMode(12, OUTPUT); mpuWrite(0x6B, 0x00); // будим: сбрасываем бит SLEEP delay(50); // датчикам нужно время на подъём Serial.println("MPU6050 разбужен, порог 0,325 g");} void loop() { Wire.beginTransmission(MPU); Wire.write(0x3B); // указатель на начало данных Wire.endTransmission(); if (Wire.requestFrom(MPU, 6) != 6) { Serial.println("Чип молчит: шина или питание"); delay(500); return; } int buf[3]; for (int i = 0; i < 3; i++) { int hi = Wire.read(); // старший байт первым int lo = Wire.read(); buf[i] = (hi << 8) | lo; } float gx = buf[0] / 16384.0; float gy = buf[1] / 16384.0; float gz = buf[2] / 16384.0; bool tilt = abs(buf[0]) > THRESH || abs(buf[1]) > THRESH; digitalWrite(12, tilt ? HIGH : LOW); Serial.print("X="); Serial.print(gx, 2); Serial.print(" g Y="); Serial.print(gy, 2); Serial.print(" g Z="); Serial.print(gz, 2); Serial.println(tilt ? " - НАКЛОН" : " - ровно"); delay(300);}В симуляторе движок подтверждает любой адрес и кладёт одинаковые демо-байты 0x2A: слово 0x2A2A — это 10794 отсчёта, или 0,66 g по каждой оси, поэтому детектор рапортует наклон на каждом проходе. Это каркас, а не показания прибора. На живой плате картина другая: AZ около +16384, X и Y болтаются в пределах пары сотен отсчётов шума, и стоит наклонить плату — ось полезет вверх. Расхождение — не баг, а договорённость: движок умеет только Wire и несколько готовых устройств, а вся логика скетча здесь чисто шинная.
Порог 5325 отсчёта — это проекция гравитации при наклоне примерно на 19 градусов: sin(19°) около 0,33, умножьте на 16384. Для сигнализации «кто-то задел шкаф» рабочая вилка 0,3–0,5 g: ниже — ловите шум ±100–200 отсчётов и ложняки от проезжающего грузовика, выше — детектор слепнет до крутых завалов. Гистерезис здесь нужен не меньше, чем авточнику из прошлого урока: включение и выключение на разных порогах спасают светодиод от мерцания на границе.
Шину под этот чип можно разогнать одной строкой: Wire.setClock(400000) после Wire.begin() — MPU6050 паспортно умеет 400 кГц. Профит заметен на чтении всей телеметрии: четырнадцать байтов с адресами и подтверждениями занимают на стандартных 100 кГц около полутора миллисекунд, на 400 кГц — меньше половины. Опрос раз в 300 мс разницы не заметит, а в плотном цикле с экраном на той же шине она уже реальна: шина освобождается втрое раньше, и кадры от LCD не толкаются с телеметрией. Подтяжки на GY-521 бортовые, и на длинах макетки 400 кГц они тянут уверенно; дальше полуметра я не проверял — честная граница моих измерений.
Где здесь гироскоп и зачем он отдельно
Акселерометр честно меряет статический наклон — проекцию гравитации на оси. Всё, что движется быстро, через него выглядит шумом. Гироскоп в том же чипе меряет угловые скорости и лежит сразу за ускорениями: байты 0x43–0x48. Поскольку блок данных сплошной, requestFrom(0x68, 14) забирает одним запросом шесть байтов ускорений, два байта термометра и шесть байтов гироскопа — так я читаю, когда нужна вся телеметрия разом.
Дальше начинается территория, где одному уроку тесно. Углы через atan2 и сглаживание — в уроке про определение наклона, дрейф гироскопа и почему угол уползает без фильтра — в уроке про фильтры. Мой минимальный набор из этого урока закрывает уровень, сигнализацию наклона и проверку живости чипа — а это девять из десяти моих задач с этим модулем.
Термометр в той же телеметрии живёт по 0x41–0x42: сырую пару делим на 340 и прибавляем 36,53 — получаем температуру кристалла. У меня плашмя лежащий чип держит 37–39 градусов: это самонагрев, а не перегрев, и пугаться минуса в мороз тоже не стоит. Гироскопу для пересчёта нужен свой масштаб: при заводском диапазоне ±250 градусов в секунду один отсчёт равен 1/131 градуса в секунду. Диапазоны задаются регистрами 0x1C для ускорений и 0x1B для гироскопа — механика та же, что у будильника 0x6B, две записи по Wire. Шесть байтов скоростей с 0x43 читаются привычным циклом: пары, старший байт первым, те же сдвиги.
- 1. Заводское значение регистра 0x6B — 0x40. Что это значит для скетча?
- 2. requestFrom(0x68, 6) вернул 0x11, 0x22, 0x33, 0x44, 0x55, 0x66. Чему равно AX?
- 3. Плата лежит плашмя на столе. Сколько примерно на AZ?
- 4. Сколько отсчётов в одном g при заводском диапазоне ±2g?
- 5. Как одним запросом забрать ускорения и гироскоп разом?
- Адрес 0x68 при AD0 на земле, 0x69 при подтяжке к питанию
- 0x6B - рубильник: заводские 0x40 значат сон; пишем 0x00 и ждём 30-50 мс
- Блок данных с 0x3B: по паре байтов на ось, старший первым, (hi<<8)|lo
- ±2g = 16384 отсчёта на g; лежащая плата держит AZ около +16384
- Порог 5325 отсчёта - наклон около 19 градусов; гистерезис спасает от мерцания
Частые вопросы по теме урока
- Заводское значение регистра 0x6B — 0x40. Что это значит для скетча?
- Бит SLEEP взведён заводом, чип спит. Запись 0x00 в 0x6B будит его, после чего нужно дать 30–50 мс на подъём. чип спит: нужен ноль в этот регистр
- requestFrom(0x68, 6) вернул 0x11, 0x22, 0x33, 0x44, 0x55, 0x66. Чему равно AX?
- Старший байт идёт первым: 0x11 — верхняя половина, 0x22 — нижняя, AX = 0x1122. Собирайте пару в переменные, а не в одном выражении. 0x1122
- Плата лежит плашмя на столе. Сколько примерно на AZ?
- Гравитация давит по оси Z: один g равен 16384 отсчёта при ±2g. Ноль на Z у пробуждённого чипа — повод искать обрыв. около +16384
- Сколько отсчётов в одном g при заводском диапазоне ±2g?
- 16384 LSB/g — паспортное значение для ±2g. Узкие диапазоны дают finer шкалу, но предел смещается: ±8g — уже 4096 на g. 16384
- Как одним запросом забрать ускорения и гироскоп разом?
- Блок данных сплошной: 6 байтов ускорений, 2 термометра, 6 гироскопа — 14 байтов с 0x3B за один запрос. requestFrom(0x68, 14) после записи указателя 0x3B