
Привет, Хабр!
Недавно, вдохновившись моим опытом, мой товарищ решил приобщиться к моноколёсному комьюнити и купил моноколесо Inmotion V12 Pro. В целом колесо достаточно хорошее, но первая совместная поездка выявила одну проблему, а именно: практически невидимые задние сигналы. Этот недостаток не добавляет безопасности поездкам, поэтому, как всегда, я спешу исправить ситуацию. А что из этого вышло — читайте далее.
❯ Начало
Да, совместная поездка на моноколёсах без сигналов добавляет немного экстрима, но всё же лучше исправить ситуацию и наслаждаться безопасными поездками с чётко читаемыми сигналами. Так как временные рамки не позволяли купить готовый вариант умного стоп-сигнала, я решил собрать свой DIY-вариант, потратив на разработку и сборку менее двух часов. По традиции проект будет состоять из компонентов, которые уже есть у меня в наличии.
❯ Я его слепил из того, что было
Итак, так как было решено собирать умный сигнал, нам понадобятся следующие компоненты:
Красные яркие светодиоды 5 мм.
Как правило, они всегда лежат в запасе, купленные ещё в «нормальные времена». А если нет, то можно купить по цене семечек на Алиэкспрессе:

Микроконтроллер;
Здесь нужно компактное решение. У меня пару месяцев валялась без дела одна из вариаций платы ESP32-C3-Zero — будем использовать её:

Датчик положения в пространстве;
В данном проекте я буду использовать трёхосевой гироскоп MPU6050. У меня их «горка и маленькая кучка» — остались ещё с прошлых экспериментов:

Дополнительные компоненты.
N-канальный мосфет P0603BDG для управления матрицей светодиодов и несколько резисторов на 100 Ом.
❯ Как это работает
Ниже представлена принципиальная схема моего стоп-сигнала:

Принцип работы очень прост: для работы сигнала нам нужно отслеживать данные с акселерометра MPU6050 и при наличии отрицательного ускорения по оси Y зажигать матрицу светодиодов.
❯ Прошивка и отладка
Для дальнейших экспериментов собираем минимальный сет из датчика и отладочной платы. Затем, «абы как» припаиваем датчик и начинаем эксперименты:

И загрузив первую попавшуюся библиотеку MPU6050 начинаем эксперименты:

После установки библиотеки загружаем из примеров скетч MPU6050_Raw и закомментируем в нём следующие строки:
/*Print the obtained data on the defined format*/ #ifdef OUTPUT_READABLE_ACCELGYRO //Serial.print("a/g:\t"); //Serial.print(ax); Serial.print("\t"); //Serial.print(ay); Serial.print("\t"); //Serial.print(az); Serial.print("\t"); //Serial.print(gx); Serial.print("\t"); //Serial.print(gy); Serial.print("\t"); //Serial.println(gz); Serial.print(gy); // Выводим в консоль значения ускорения по оси Y #endif
Далее загружаем код в отладочную плату и запускаем в среде вывод значений в виде графика. Затем мы сможем наблюдать следующий результат, если подвигаем датчик по направлению оси Y вперед-назад:

Здесь мы наблюдаем положительное и отрицательное ускорение по оси Y. Данные при этом достаточно шумные, поэтому далее нам нужно их сгладить. Обычно это делается с помощью фильтра Калмана. После обработки сигнала датчика данным фильтром мы сможем наблюдать следующее:

Теперь мы можем использовать далее данные в нашей логике управления зажигания матрицы:
int target = 600; // Значение, за которым включается стоп-сигнал (отрицательное ускорение) int fgy = (int)filter(gy); Serial.println(fgy); // Выводим в порт данные ускорения по оси Y if (fgy > target){ analogWrite(LED_pin, 255); // Зажикаем на максимум } else{ analogWrite(LED_pin, 30); // Режим габарита }
По моей задумке, стоп-сигнал должен работать в двух режимах: габаритного огня (яркость 20%) и сигнала торможения (яркость 100%). Для этой цели и используется ШИМ-регулирование в представленном коде, а сам порог зажигания стоп-сигнала определяется переменной target, значение которой зависит от необходимой границы отрицательного ускорения.
Не буду ходить вокруг да около — ниже представлен итоговый код прошивки:
Код прошивки
/* CYBEREX TECH, 2026 Стоп-сигнал с гироскопом MPU6050 */ #include "I2Cdev.h" #include "MPU6050.h" /* MPU6050 default I2C address is 0x68*/ MPU6050 mpu; //--------------------------------------------------------------------------------------------------------------*/ #define OUTPUT_READABLE_ACCELGYRO // Пины подключения к датчику #define sda_pin 12 // SDA 12 #define scl_pin 11 // SCL 11 #define LED_pin 5 // LED array MOSFET 5 int16_t ax, ay, az; // Переменные скорости по осям int16_t gx, gy, gz; // Переменные ускорения по осям // переменные фильтра Калмана float varSt = 100; // среднее отклонение float varProcess = 0.5; // скорость реакции на изменение (подбирается вручную) float Pc = 0.0, G = 0.0, P = 1.0, Xp = 0.0, Zp = 0.0, Xe = 0.0; // переменные фильтра Калмана // Функция фильтрации float filter(float val) { Pc = P + varProcess; G = Pc / (Pc + varSt); P = (1 - G) * Pc; Xp = Xe; Zp = Xp; Xe = G * (val - Zp) + Xp; // "фильтрованное" значение return (Xe); } int target = 600; // Значение, за которым включается стоп-сигнал (отрицательное ускорение) void setup() { Wire.begin(sda_pin, scl_pin); Serial.begin(115200); /*Initialize device and check connection*/ Serial.println("Initializing MPU..."); mpu.initialize(); Serial.println("Testing MPU6050 connection..."); if(mpu.testConnection() == false){ Serial.println("MPU6050 connection failed"); while(true); } else{ Serial.println("MPU6050 connection successful"); } /* Use the code below to change accel/gyro offset values. Use MPU6050_Zero to obtain the recommended offsets */ Serial.println("Updating internal sensor offsets...\n"); mpu.setXAccelOffset(0); //Set your accelerometer offset for axis X mpu.setYAccelOffset(0); //Set your accelerometer offset for axis Y mpu.setZAccelOffset(0); //Set your accelerometer offset for axis Z mpu.setXGyroOffset(0); //Set your gyro offset for axis X mpu.setYGyroOffset(0); //Set your gyro offset for axis Y mpu.setZGyroOffset(0); //Set your gyro offset for axis Z /*Print the defined offsets*/ Serial.print("\t"); Serial.print(mpu.getXAccelOffset()); Serial.print("\t"); Serial.print(mpu.getYAccelOffset()); Serial.print("\t"); Serial.print(mpu.getZAccelOffset()); Serial.print("\t"); Serial.print(mpu.getXGyroOffset()); Serial.print("\t"); Serial.print(mpu.getYGyroOffset()); Serial.print("\t"); Serial.print(mpu.getZGyroOffset()); Serial.print("\n"); /*Configure board LED pin for output*/ // pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_pin, OUTPUT); } void loop() { /* Read raw accel/gyro data from the module. Other methods commented*/ mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); #ifdef OUTPUT_READABLE_ACCELGYRO int fgy = (int)filter(gy); Serial.println(fgy); // Выводим в порт данные ускорения по оси Y if (fgy > target){ analogWrite(LED_pin, 255); // Зажикаем на максимум } else{ analogWrite(LED_pin, 30); // Режим габарита } #endif delay(100); }
❯ Корпус
Для разработки корпуса я, как всегда, использую FreeCAD. Сам корпус имеет довольно простую конструкцию: сборка выполняется без единого болта, по принципу матрёшки. Ниже представлен скриншот проекта:

Печать корпуса выполнялась на моём 3D принтере Flashforge Adventurer 5M и заняла не более двадцати минут.
❯ Сборка стоп-сигнала
Самый интересный момент — когда твоя задумка воплощается в жизнь. Вставляем светодиоды в распечатанный корпус и припаиваем элементы согласно представленной выше схеме:

В состав корпуса входит разделяющая панель, на которую устанавливается датчик и отладочная плата:

Я очень торопился с реализацией и не предусмотрел в панели отверстия для болтов, поэтому фиксирующие датчик болты были просто вплавлены в пластик. Примерно так же было реализовано крепление платы, но в этот раз уже вплавлялись небольшие отрезки выводов, оставшиеся от ножек светодиодов, к которым позднее припаивалась отладочная плата:

Аналогичным способом крепится и мосфет. А так выглядит вся электроника в сборе:

Подключаем питающие провода и в обратной крышке герметизируем соединение:

Всё, стоп-сигнал собран:

Осталось разобраться с подключением: здесь всё просто. Моноколесо имеет USB-порт с функцией зарядки мобильных устройств, обеспечивающий достаточную мощность для работы стоп-сигнала. Его и будем использовать для питания. Для подключения используется разъем USB-A («папа»), который нужно максимально укоротить, чтобы он влез в отсек с портами при закрытой заглушке. Сам фонарь крепится на резиновую заглушку отсека моноколеса с помощью наноскотча, перед этим нужно обязательно обезжирить поверхность заглушки.

В итоге, после нескольких тестовых поездок, крепление стоп-сигнала на наноскотч оказалось очень надёжным решением.
❯ Итоги
Итоговым результатом я остался очень доволен! У меня были сомнения насчёт видимости фонаря в дневное время при прямом солнечном свете, но они не подтвердились. Стоп-сигнал очень хорошо видно даже на ярком солнце. Теперь поездки на моноколесе стали значительно комфортнее и безопаснее.

Само собой, этот стоп-сигнал можно применить не только на моноколесе, но и, например, на велосипеде. В этом случае стоит позаботиться о внешнем источнике питания. Да и данное решение будет экономически более целесообразным, чем покупка готовых решений, а удовольствие от сборки — бесценно!
На этой позитивной ноте можно и закончить статью! Спасибо за уделенное время! Интересных проектов и всех благ! А если остались вопросы или есть что добавить — добро пожаловать в комментарии.
Ссылки к статье:
Может быть интересно:

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
