In this tutorial, we'll use the MPU6050 gyroscope, a NeoPixel Ring and an Arduino to build a device that lights LEDs corresponding to the angle of inclination.
This is a simple and fun project and it's going to be assembled on a breadboard. If you'll follow the steps you'll build what you saw in the video. It's a good tutorial for learning about gyroscope and the NeoPixel Ring.
I'm building this tutorial because of the interest i saw on some other tutorial of min. In this tutorial i've replaced simple led's with a NeoPixel Ring. The ring it's simpler to use through an Adafruit library and it's definitely more spectacular.
So if you have these components lying around this is a great way to make use of them, i'll try to take you step by step through building the device and also explain how it works in the last step.
Step 1: Things RequiredParts
1. Arduino Pro Mini 328P
2. Breadboard
3. MPU6050 gyroscope
4. 24 NeoPixel LED Ring
5. 4 x AA battery pack with 4 batteries
6. U-shape jumper cables (optional). I've used these jumper cables because they look better on the breadboard, and the leds are more visible this way. You can find a box of 140 on ebay at about 4$. If you don't have these cables you can replace them with dupont wires.
Tools:
1. USB to serial FTDI adapter FT232RL to programm the arduino pro mini
2. Arduino IDE
Skills:
1. Soldering
3. Basic arduino programming
Step 2: AssemblyI've attached the fritzing schematic in fzz format and a picture of it for easy visualization of the connections.
1. You need to solder 3 male pins on the back of the neopixel ring like shown in the picture
- solder the positive pin
- solder the ground
- solder the data input pin
2. Then the 4x battery holder should have a way of a connecting to the breadboard, a easy solution is to solder two male dupont wires to it's terminals.
3. Prepare the breadboard.
- place the neopixel ring, microcontroller and gyroscope on the breadboard like in the image
- place all the negative wires: to the microcontroller, neopixel ring, gyro
- place all the positive wires: to the microcontroller, neopixel ring, gyro
- place all the data wires:
* SDA and SCL from the to the microcontroller to the gyro
* pin D6 from the microcontroller to the neopixel ring
- double check all connections before powering
- optionally using duct tape, tape the battery pack on the back of the bradboard to hold it in place and make it more portable
Step 3: The Code and CalibrationFirst you need to download and install two libraries:
1. Adafruit neopixel library fir controlling the neopixel
2. MPU6050 library for the gyroscope
They are two great libraries that will do the heavy lifting!
More details on the neopixels here
Then download and install my library from here or copy it from below:
#include "I2Cdev.h"
#include <Adafruit_NeoPixel.h>
#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;
MPU6050 mpu;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, NEOPIXED_CONTROL_PIN, NEO_RBG + NEO_KHZ800);
unsigned long lastPrintTime = 0;
bool initialization = false; // set true if DMP init was successful
uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
uint8_t devStatus; // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize; // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount; // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer
Quaternion q; // [w, x, y, z] quaternion container
VectorFloat gravity; // [x, y, z] gravity vector
float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container and gravity vector
volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high</p><p>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, -1 * MAX_ANGLE, MAX_ANGLE);
y = constrain(y, -1 * MAX_ANGLE, MAX_ANGLE);
if (y < 0 and z > 0) {
lightLeds(y, z, 0, 5, 0, 89);
} else if (y < 0 and z < 0) {
lightLeds(y, z, 6, 12, 89, 0);
} else if (y > 0 and z < 0) {
lightLeds(y, z, 13, 19, 0, 89);
} else if (y > 0 and z > 0) {
lightLeds(y, z, 20, 24, 89, 0);
}
}
void lightLeds(int x, int y, int fromLedPosition, int toLedPosition, int fromAngle, int toAngle)
{
double angle = (atan((double) abs(x) / (double) abs (y)) * 4068) / 71;
int ledNr = map(angle, fromAngle, toAngle, fromLedPosition, toLedPosition);
printDebug(x, y, 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;
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 = mpu.dmpGetFIFOPacketSize();
return true;
}
void dmpDataReady()
{
mpuInterrupt = true;
}
Upload the code:
Using the FTDI adapter upload the code to the arduino.
Connect the power supply (batteries)
Calibration:
The most important thing to calibrate here is "LED_OFFSET" constant. In my example is 12. You need to adjust this from 0 to 23 so that after powering the board the led will light in the direction you tilt the board.
If you want to find out more details about how it works check out the next step
Step 4: How It Works (optional)First a little information about the MPU6050 gyroscope. This is a MEMS gyroscope (MEMS stands for Microelectromechanical systems).
Each type of MEMs gyroscope has some form of oscillating component from where the acccleration, and hence direction change, can be detected. This is because, as per the conservation of motion law, a vibrating object likes to continue vibrating in the same plane, and any vibrational deviation can be used to derive a change in direction.
The gyro also contains a microcontroller of it's own to compute the roll, pitch and yaw through some fancy maths.
But the gyro raw data suffers from noise and drift, so we used an external library to smooth things out and give us clean usable data.
The Neopixel are RGB leds individually addressable and chained into bands and rings. They work on 5V and they contain they own circuitry so you only need to power the neopixels and to communicate with them using the data line. The communication is done with a single data line containing clock and data (more details here). Adafruit provides a clean library for interacting with the neopixel rings.
The code
Inside the loop() function the MPU6050_6Axis_MotionApps20 library is called. When the library has new data from the gyroscpe it calls redrawLeds(x, y, z) with 3 arguments representing yaw, pitch and roll
Inside redrawLeds():
- we're focusing on two axis : y, z
- we're constraining both axys from -MAX_ANGLE to +MAX_ANGLE, we defined max angle to 45 and it's changable
- we're splitting 360 degreeds into 4 quadrants and call lightLeds() functions for each as follows:
* y negative, z positive first quadrant will control led's from 0 to 5, the angle will be from 0 to 89
* y negative, z negative second quadrant controls led's from 6 to 12, the angle will be from 89 to 0
* ...etc
- inside the lightLeds function
* i'm calculating an angle based on the two axis using arctangent (check the attached picture)
* i'm calculating what led to show using the arduino map function
* i'm resetting the led strip all but two led's, the one corresponding to the led position i've calculated before and a led position before (to show a fade effect)
* i'm using a function called normalizeLedPosition() to take into account the neopixel calibration. The calibration is useful because the neopixel ring can be rotated as pleased, and should be aligned with the gyroscope
* i'm also printing the tow axis, what led has light and the angle
The math
I've attached a picture with the led ring and the trigonometric function used to determine the angle.
Comments