Привет, Хабр!

Недавно, вдохновившись моим опытом, мой товарищ решил приобщиться к моноколёсному комьюнити и купил моноколесо Inmotion V12 Pro. В целом колесо достаточно хорошее, но первая совместная поездка выявила одну проблему, а именно: практически невидимые задние сигналы. Этот недостаток не добавляет безопасности поездкам, поэтому, как всегда, я спешу исправить ситуацию. А что из этого вышло — читайте далее.

❯ Начало

Да, совместная поездка на моноколёсах без сигналов добавляет немного экстрима, но всё же лучше исправить ситуацию и наслаждаться безопасными поездками с чётко читаемыми сигналами. Так как временные рамки не позволяли купить готовый вариант умного стоп-сигнала, я решил собрать свой DIY-вариант, потратив на разработку и сборку менее двух часов. По традиции проект будет состоять из компонентов, которые уже есть у меня в наличии.

❯ Я его слепил из того, что было

Итак, так как было решено собирать умный сигнал, нам понадобятся следующие компоненты:

  • Красные яркие светодиоды 5 мм.

    Как правило, они всегда лежат в запасе, купленные ещё в «нормальные времена». А если нет, то можно купить по цене семечек на Алиэкспрессе:

Скриншот
Скриншот
  • Микроконтроллер;

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

ESP-32-С3-SuperMini
ESP-32-С3-SuperMini
  • Датчик положения в пространстве;

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

Трёхосевой гироскоп MPU6050
Трёхосевой гироскоп MPU6050
  • Дополнительные компоненты.

    N-канальный мосфет P0603BDG для управления матрицей светодиодов и несколько резисторов на 100 Ом.

❯ Как это работает

Ниже представлена принципиальная схема моего стоп-сигнала:

Принципиальная схема стоп-сигнала
Принципиальная схема стоп-сигнала

Принцип работы очень прост: для работы сигнала нам нужно отслеживать данные с акселерометра MPU6050 и при наличии отрицательного ускорения по оси Y зажигать матрицу светодиодов.

❯ Прошивка и отладка

Для дальнейших экспериментов собираем минимальный сет из датчика и отладочной платы. Затем, «абы как» припаиваем датчик и начинаем эксперименты:

Подключение акселерометра MPU6050
Подключение акселерометра MPU6050

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

Библиотека MPU6050 в Arduino IDE
Библиотека MPU6050 в Arduino IDE

После установки библиотеки загружаем из примеров скетч 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
Вывод сырых данных ускорения по оси 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-канале