Интерактивный гироскопический LED-дисплей на NeoPixel Ring и Arduino
Компоненты и материалы
| SparkFun Arduino Pro Mini 328 — 5V/16MHz | × | 1 шт. |
| Adafruit NeoPixel Ring: WS2812 5050 RGB LED (24 пикселя) | × | 1 шт. |
| SparkFun IMU Breakout — MPU-9250 (используется как MPU6050) | × | 1 шт. |
| Макетная плата (breadboard) | × | 1 шт. |
| Перемычки Male/Male | × | 1 комплект |
| Перемычки Male/Female | × | 1 комплект |
| Кейс для 4×AA батареек + батарейки | × | 1 шт. |
О проекте
В этом уроке мы используем гироскоп MPU6050, кольцо NeoPixel и Arduino для создания устройства, которое зажигает светодиоды, соответствующие углу наклона. Простой и зрелищный проект на макетной плате. Хороший способ изучить работу с гироскопом и NeoPixel Ring.
Автор заменил в своём раннем проекте отдельные светодиоды на NeoPixel Ring — проще через библиотеку Adafruit и выглядит впечатляюще. Если у вас есть эти компоненты — отличный способ их применить. Пошаговая инструкция сборки и объяснение принципа работы в конце.
Шаг 1: Необходимые детали и инструменты
Детали
- Arduino Pro Mini 328P
- Макетная плата
- Гироскоп MPU6050
- NeoPixel LED Ring (24 светодиода)
- Кейс 4×AA с батарейками
Инструменты
- USB-Serial адаптер FTDI (FT232RL) для прошивки Pro Mini
- Arduino IDE
Навыки
- Паяльник
- Базовое программирование Arduino
Шаг 2: Сборка
Схема в формате Fritzing (.fzz) и фото для наглядности прилагаются.
- Припаяйте 3 малевых пина на обратной стороне NeoPixel Ring (как на фото): питание (+), земля (GND), данные (Data In).
- К батарейному отсеку припаяйте два дупоновских провода для подключения к макетке.
- Разместите компоненты на макетке как на изображении:
- Все отрицательные провода: к микроконтроллеру, NeoPixel, гироскопу
- Все положительные провода: к микроконтроллеру, NeoPixel, гироскопу
- Данные: SDA и SCL от микроконтроллера к гироскопу; D6 от микроконтроллера к NeoPixel
- Дважды проверьте все соединения перед подачей питания.
- Опционально: закрепите батарейный отсек на задней стороне макетки скотчем — для переносимости.
Шаг 3: Код и калибровка
Сначала установите две библиотеки:
- Adafruit NeoPixel — управление лентой/кольцом
- MPU6050 — для гироскопе (библиотека
MPU6050_6Axis_MotionApps20.hс DMP)
Затем скачайте/скопируйте код автора (ниже или GitHub).
Загрузка кода
Через FTDI-адаптер загрузите скетч в Arduino Pro Mini.
Питание и калибровка
Подключите батарейный отсек. Главная калибровка — константа LED_OFFSET (в примере 12). Подбирайте от 0 до 23 так, чтобы после включения загорался тот светодиод, в сторону которого наклонена плата.
Подробности работы — в следующем шаге.
#include "I2Cdev.h"
#include
#include "MPU6050_6Axis_MotionApps20.h"
#include "Wire.h"
#define NEOPIXED_CONTROL_PIN 6
#define NUM_LEDS 24
const int MAX_ANGLE = 45;
const int LED_OFFSET = 12; // КАЛИБРОВКА: 0-23
MPU6050 mpu;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, NEOPIXED_CONTROL_PIN, NEO_GRB + NEO_KHZ800);
unsigned long lastPrintTime = 0;
bool initialization = false;
uint8_t mpuIntStatus;
uint8_t devStatus;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[64];
Quaternion q;
VectorFloat gravity;
float ypr[3];
volatile bool mpuInterrupt = false;
void setup() {
Serial.begin(9600);
Serial.println("Program started");
initialization = initializeGyroscope();
strip.begin();
}
void loop() {
if (!initialization) return;
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();
fifoCount = mpu.getFIFOCount();
if (hasFifoOverflown(mpuIntStatus, fifoCount)) {
mpu.resetFIFO(); return;
}
if (mpuIntStatus & 0x02) {
while (fifoCount < packetSize) {
fifoCount = mpu.getFIFOCount();
}
mpu.getFIFOBytes(fifoBuffer, packetSize);
fifoCount -= packetSize;
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
redrawLeds(ypr[0] * 180/M_PI, ypr[1] * 180/M_PI, ypr[2] * 180/M_PI);
}
}
boolean hasFifoOverflown(int mpuIntStatus, int fifoCount) {
return (mpuIntStatus & 0x10) || (fifoCount == 1024);
}
void redrawLeds(int x, int y, int z) {
x = constrain(x, -MAX_ANGLE, MAX_ANGLE);
y = constrain(y, -MAX_ANGLE, MAX_ANGLE);
if (y < 0 && z > 0) { lightLeds(y, z, 0, 5, 0, 89); }
else if (y < 0 && z < 0) { lightLeds(y, z, 6, 12, 89, 0); }
else if (y > 0 && z < 0) { lightLeds(y, z, 13, 19, 0, 89); }
else if (y > 0 && z > 0) { lightLeds(y, z, 20, 24, 89, 0); }
}
void lightLeds(int x, int y, int fromLed, int toLed, int fromAngle, int toAngle) {
double angle = (atan((double)abs(x) / (double)abs(y)) * 4068) / 71;
int ledNr = map(angle, fromAngle, toAngle, fromLed, toLed);
printDebug(y, z, ledNr, angle);
uint32_t color;
for (int i = 0; i < NUM_LEDS; i++) {
color = strip.Color(0, 0, 0);
if (i == ledNr) color = strip.Color(0, 180, 0);
else if (i == ledNr - 1) color = strip.Color(0, 5, 0);
strip.setPixelColor(normalizeLedPosition(i), color);
}
strip.show();
}
int normalizeLedPosition(int position) {
if (NUM_LEDS > position + LED_OFFSET) return position + LED_OFFSET;
return position + LED_OFFSET - NUM_LEDS;
}
void printDebug(int y, int z, int lightLed, int angle) {
if (millis() - lastPrintTime < 500) return;
Serial.print("a="); Serial.print(angle); Serial.print("; ");
Serial.print("ll="); Serial.print(lightLed); Serial.print("; ");
Serial.print("y="); Serial.print(y); Serial.print("; ");
Serial.print("z="); Serial.print(z); Serial.println("; ");
lastPrintTime = millis();
}
bool initializeGyroscope() {
Wire.begin();
TWBR = 24; // 400kHz I2C
mpu.initialize();
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// Оффсеты — подбираются индивидуально для каждого чипа!
mpu.setXGyroOffset(220);
mpu.setYGyroOffset(76);
mpu.setZGyroOffset(-85);
mpu.setZAccelOffset(1788);
if (devStatus != 0) {
Serial.print(F("DMP Initialization failed (code ")); Serial.println(devStatus);
return false;
}
mpu.setDMPEnabled(true);
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(0, dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
Serial.println(F("DMP ready! Waiting for first interrupt..."));
packetSize = mpq.dmpGetFIFOPacketSize();
return true;
}
void dmpDataReady() {
mpuInterrupt = true;
}
Шаг 4: Как это работает (опционально)
MPU6050 — MEMS-гироскоп
MEMS (Microelectromechanical systems). Внутри — колебательный элемент. По закону сохранения импульса вибрирующий объект стремится продолжать вибрацию в той же плоскости; отклонение вибрации позволяет определить изменение направления. Внутренний микроконтроллер вычисляет roll, pitch, yaw. Сырые данные шумны и дрейфуют — библиотека DMP (Digital Motion Processor) сглаживает и выдаёт чистые данные.
NeoPixel
RGB-светодиоды с индивидуальной адресацией, цепочкой в кольца/ленты. Работают от 5V, содержат свой драйвер — нужно только питание и одна линия данных (с встроенным тактированием). Библиотека Adafruit даёт удобный API.
Алгоритм кода
В loop() библиотека MPU6050_6Axis_MotionApps20 при новых данных вызывает redrawLeds(yaw, pitch, roll).
В redrawLeds():
- Работаем с двумя осями: pitch (y) и roll (z).
- Ограничиваем обе оси ±MAX_ANGLE (45°, меняется).
- Делим 360° на 4 квадранта, вызываем
lightLeds()для каждого:- y<0, z>0 — 1-й квадрант: LED 0–5, угол 0–89°
- y<0, z<0 — 2-й квадрант: LED 6–12, угол 89–0°
- y>0, z<0 — 3-й квадрант: LED 13–19, угол 0–89°
- y>0, z>0 — 4-й квадрант: LED 20–24, угол 89–0°
В lightLeds():
- Вычисляем угол через арктангенс:
angle = atan(|x|/|y|) * 4068 / 71(оптимизированная фиксированная-точка формула). - Маппим угол на номер LED через
map(). - Гасим все LED, зажигаем целевой (яркий зелёный) и предыдущий (слабый — затухание/след).
normalizeLedPosition()учитывает калибровкуLED_OFFSET— кольцо можно вращать относительно гироскопа.- Выводим отладку в Serial (каждые 500 мс).
Математика
На схеме показано кольцо и тригонометрическая функция для определения угла.
Код
GitHub репозиторий с полным кодом
Схемы
Производственный процесс
- Создание игры в реальном времени на Arduino Nano с гироскопом MPU-6050 и OLED-дисплеем
- Стильные настенные часы на четырех кольцах Adafruit Neopixel 60 LED и Arduino
- Датчик CO2 K30: мониторинг качества воздуха в реальном времени на Arduino
- Устройство для общения глухосліпых людей с помощью 1Sheeld и Arduino
- DIY USB-геймпад на Arduino: собственный высокопроизводительный контроллер
- Умная копилка: управление монетоприёмником на Arduino Nano
- Pixie: DIY-наручные часы на Arduino Pro Mini и NeoPixel — стильный светодиодный тахометр времени
- Управление светодиодом по Bluetooth с Arduino: простой DIY-гид
- Интеграция ёмкостного сканера отпечатков DFRobot с Arduino или ESP8266
- Освоение распознавания и синтеза речи на Arduino Due