Thimble Learning Platform
Back to full view

LED Cube

Introduction

The LED Cube is Thimble's 2nd kit. This kit uses the popular WS2812B Digital RGB LED. Assemble the 6 boards to build your own LED cube. Add an optional IMU sensor to control the cube based on its orientation. Program your own patterns to control what the cube does. Add your own sensors to make the cube react to light, sound, etc.

Objectives

  • Assemble your LED Cube
  • Upload code to make the cube flash in patterns
  • Modify the code to make your own patterns
  • Theory

    How the cube works

    As you follow through our tutorial, please know that we see this as a living document. If you discover anything unclear in the instructions, let us know and we will do our best to fix it as fast as we can. Each page of every module on our learning platform has a section for discussion at the bottom of it, so please use that to let us know about anything that is unclear. We believe strongly in the philosophy of empathy-driven development, and if something is unclear to you, it is likely unclear to others.

    Our tutorials are meant to be a fun way to explore the world of electronics. We have included not only instructions, but also some theory, history, and challenges for you to explore on your own. If you only want to build the cube, you can skip over these discussions and links, but we encourage you to spend some time exploring and discussing all the resources we included here.

    WS2812B LEDs 

    The workhorse of this kit is the popular WS2812B, manufactured by Worldsemi. A single WS2812B "pixel" is pictured in the close up photo below (taken with a DSLR and a 10X macro lens).

    WS2812BWS2812B

    Each WS2812B contains an integrated red, green, and blue LED, as well as an integrated driver circuit that allows control of the LEDs through a custom one-wire interface. They can be used individually and are also chainable, meaning you can connect the output of one LED to the input of another and create chains hundreds of LEDs long.

    The photos below show the WS2812B in the 5050 package (the package size specifies the size, in this case 5mmx5mm) with the Green, Red, and Blue LEDs of the cube turned on. Each LED has 256 brightness levels, resulting in over 16 million color combinations (256x256x256=256^3=16,777,216). Click through these photos to see a close up of what happens when you turn on each of the Green, Red, and Blue diodes.

    WS2812B Green LED
    WS2812B Red LED
    WS2812B Blue LED
    WS2812B
    WS2812B Driver IC (highlighted)
    Older model WS2812B Driver IC close-up

    The driver IC is really the "magic" of the device and what makes it unique compared to analog RGB LEDs. The close-up photo of the WS2812 driver IC die shown above was taken by bunnie (CC-BY-SA license). Make sure to check out bunnie studios (his wonderful blog) for in-depth discussion of his pursuits in the world of electronics and open source hardware/software.

    The printed circuit boards (PCBs) included in your kit each contain a 4x4 matrix of WS2812Bs. The individual pixels are connected within the board as shown in the diagram below.

    Individual pixel connections within a single PCBIndividual pixel connections within a single PCB

    Controlling the WS2812B 

    There are a number of software libraries that handle the complexity of controlling the WS2812B device for you. Therefore, it is not necessary to understand the intricacies of the electrical interface in order to understand how to use the library, but this discussion is here for those that wish to take a deeper exploration of the device. The following diagram, taken from the WS2812B datasheet explains how to control the WS2812B device.

    The control scheme is certainly unique. It is a one-wire interface that is highly dependent on precise timing. A 24-bit data sequence is sent over the data line (8 bits for each color channel - green, red, and blue). A logical 0 and a logic 1 are both square pulses and are defined by the length of the pulse, as shown in the sequence chart below. "0 code" means logical 0 and "1 code" means logical 1. The 24 bits are sent, followed by a reset (RET code) to indicate the end of the sequence.

    WS2812B Timing Diagram 1WS2812B Timing Diagram 1

    The second diagram below demonstrates sending data through D1 to D4, followed by the reset code. Once the reset code is received, the pixels are latched and display their true colors. ;)

    WS2812B Timing Diagram 2WS2812B Timing Diagram 2

    A further explanation of the composition of the 24bits of data: the 3 groups of 8 bits represent the pulse width modulation (PWM) value for each of the 3 color channels - green, red, and blue. PWM is a technique that can be used to control the brightness of LEDs by varying the amount of time that power is applied to the LED. So, you can think of the PWM value to mean "brightness" of that color channel. When using PWM to control LED brightness, you are effectively turning the LED on and off at the PWM frequency. This results in a flicker. However, if the PWM frequency is fast enough, this flicker will not be detectable to the human eye, as is the case with these LEDs.

    If the value of every color is set to the maximum (255), all LEDs will be set to the maximum brightness and the colors will combine so that the WS2812B will appear white to the human eye. If the value is set to 0, the LED will be off. By controlling the brightness value of each LED, we get the resulting 256^3 (16.7 million) possible color combinations.

    Manufacturing the LED Cube PCBs 

    Once the design is completed, These boards are manufactured by robots called pick-and-place machines. The board designs are loaded onto these machines by an operator and then the bare PCBs as well as reels of components are loaded into the machine. For the purposes of running the boards through a surface mount technology (SMT) pick-and-place machine, the individual bare printed circuit boards are arranged in a panel. A fully populated panel of Thimble LED cube PCBs is shown in the photo below.

    LED Cube PCB PanelLED Cube PCB Panel

    The strips of circuit board material (FR4) along the left and right sides of the panel are called engineering or tooling edges. These edges are used to place the boards on the pick-and-place machine conveyor belt. A pick-and-place is a robotic machine that grabs electronic components (pick) and puts them in the correct position and orientation on the printed circuit board (place). The boards are then fed into a reflow oven where the solder within the solder paste melts ("reflows"), permanently connecting the joint physically and electrically.

    The temperature within the reflow oven is precisely controlled as the circuit board passes through the oven. The exact temperatures and time is referred to as a thermal profile and can vary depending on the type of board, components, and solder paste used on that design. In general, the thermal profiles have a preheat zone that is meant to get the entire assembly to the same temperature, as well as to allow any solvents in the solder paste to outgas. The second phase is thermal soak, typically 60 to 120 seconds, to remove any volatiles in the solder paste and activate the flux (a chemical agent in the solder paste that helps the solder flow). The boards then enter the reflow zone which represents the maximum temperature. It is very important to precisely control the amount of time the circuit boards are in this zone, as well as the high temperature, as any deviations above the maximum temperature can cause damage to the electronic components and any deviations below can prevent the solder paste from adequately reflowing. Finally, the boards go through the cooling zone of the thermal profile to gradually cool the boards. If the boards are cooled too quickly, thermal shock to the components can result in mechanical and/or electrical damage.

    The following chart, taken from Worldsemi's WS2812B usage instructions, explains the recommended SMT profile for these components.

    Curve description SMT Line
    The lowest preheat temperature (Tsmin) 150C
    The highest preheat temperature (Tsmax) 200C
    Preheating time (Tsmin to Tsmax) (ts) 60-180 S
    Average rate of temperature rise (Tsmax to Tp) <3C/S
    Liquid phase temperature (TL) 217C
    Holding time liquid region (tL) 60-150 S
    Peak temperature (Tp) 250C
    High temperature region (the peak temperature -5C) residence time (tp) <10 S
    Decreasing temperature rate <6C/S
    Stay time from room temperature to the peak temperature <6min

    Once the boards are cooled, they are then tested in a pin-jig test fixture. After they pass their test, they are mechanically separated to the individual PCBs and packaged as they arrived in your kit.

    Electrical Assembly

    Gather Materials 

    Gather the materials below and get ready to build the Thimble LED Cube!

    Introduction video
    This short video introduces the Thimble LED Cube.

    Parts

    All Parts x Qty
    LED Cube Top PCB x 1
    LED Cube Main PCB x 5
    Straight pin headers x 1
    Right angle pin headers x 1
    8-pin male-female dupont jumper wires x 1
    8-pin socket (optional - included) x 1
    6DOF IMU (optional - add'l purchase) x 1

    There is an optional part for this kit - an inertial measurement unit that you can purchase from our store. The IMU contains a gyroscope and accelerometer and can be used to make the cube respond to changes in orientation (ie: change colors when you move it).

    Tools you'll need

    Soldering Iron
    Solder Wire
    Flush Diagonal Cutters
    Solder Sucker (optional)
    Solder Wick (optional)
    Multimeter (optional)

    The Schematic 

    The LED Cube kit consists of two PCBs. The difference between the two is that the top PCB includes additional pin-outs on its pin-headers for an optional 6-degree-of-freedom inertial measurement unit (IMU). The IC for this IMU is the Invensense MPU-6050 and it is used in the GY-521 development board form factor. The MPU6050 contains a MEMS gyroscope and accelerometer in a single chip.

    LED Cube main PCB schematic
    LED Cube top PCB schematic

    Each of the 4x4 matrix PCBs of 16 WS2812Bs can be connected to form the LED cube. As stated in the videos, this is the intended connection diagram for this kit, however, you can connect the individual panels in a different configuration if you would like. The connection diagram below shows how to connect the individual boards to form the cube. The right angle header positions (shown in the diagram) are important as the headers have a "short" and "long" side (noted visually in the diagram) that can make the LED cube difficult to assemble if connected improperly.

    LED Cube PCB connection diagramLED Cube PCB connection diagram

    The diagram below shows a view of the cube looking from the top down and from the bottom up at the LED cube, along with the orientation of the boards around the sides of the cube.

    LED Cube PCB connection diagram 2LED Cube PCB connection diagram 2

    How to Use a Soldering Iron
    A short instructional video to learn how to use a soldering iron safely and properly.

    The PCB comes with all of the surface mount parts pre-soldered to the board, so the only parts you need to solder are the through-hole components (headers).

    Now we're ready to start soldering! As mentioned before, start by using solder to tack the headers in place (only solder one pin for each header). Grab your soldering iron and solder wire. Place the soldering iron on the top of the pad you intend to solder, also touching the pin header; allow it to heat for a couple of seconds and then slowly push solder wire into the pad and soldering iron tip. Solder should start to flow as you push it (solder should resemble a little dot or joint).

    Assembling the Cube 

    We've found most people prefer following along in the tutorial step by step, clicking on the videos and images that accompany each video to look at more detail. However, some people prefer to watch the entire assembly process first, which is why we provide the video below of the entire assembly process.

    If you'd rather watch each step by step video separately, skip to the unboxing video below.

    Complete Assembly Instructions Video
    This ~25 minute video shows the entire process of assembling the LED Cube.

    Unbox and layout your tools

    The first thing to do is un-box your kit and lay the components out on the table. We'll start by assembling the printed circuit board (PCB).

    LED Cube Unboxing
    Lay out the components from the kit that we're going to use.

    Now that everything is unboxed, it's time to grab your tools and get started!

    Gather your tools
    Gather your tools and get started!

    Start with the top PCB, which has "LED CUBE TOP INPUT SIDE" written on it. This board is different than the rest so make sure it's your centerpiece.

    Assembling the top PCB board
    Top PCB board
    CAREFULLY cut off 8 straight pin headers
    Tack headers in place
    Finish soldering all other joints and visually inspect board

    This next step is optional and only necessary if you are going to purchase and use the IMU sensor. We're going to solder the female headers on the opposite side of the board so we can add an IMU (Inertial Measurement Unit). As mentioned previously, the 6DOF IMU contains an accelerometer and gyroscope and can be combined to track the orientation of the cube, allowing you to display different patterns according to the cube's orientation.

    If you just want to build the cube and do not care about the additional sensor, you can skip this step.

    Optional IMU step 1
    Tack and solder female headers into place
    Solder rest of joints into place

    At this point, you can test the board by connecting the wires according to the schematic.

    For this part, only the +, IN, and - pins need to be connected. The + (grey wire) goes to 5V on the UNO, the IN (purple wire) goes to pin 6 on the UNO, and the - (blue wire) goes to GND on the UNO.

    LED Cube 006 Testing Main Board
    Connect jumper wires to board
    Connect three wires (grey, purple, blue) to the UNO board. Blue wire connects to Ground; Purple wire connects to pin 6; Grey wire connects to 5V
    Connect USB cable from the top PCB board to UNO board. Then upload the code displayed below.

    strandtest-1.ino

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include <Adafruit_NeoPixel.h>
    #ifdef __AVR__
      #include <avr/power.h>
    #endif
    
    // Example test code (Based on Examples > Adafruit NeoPixel > strandtest.ino)
    
    #define PIN 6
    
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, PIN, NEO_GRB + NEO_KHZ800);
    
    void setup() {
      strip.begin();
      strip.show(); // Initialize all pixels to 'off'
    }
    
    void loop() {
      // Some example procedures showing how to display to the pixels:
      colorWipe(strip.Color(255, 0, 0), 50); // Red
      colorWipe(strip.Color(0, 255, 0), 50); // Green
      colorWipe(strip.Color(0, 0, 255), 50); // Blue
    }
    
    // Fill the dots one after the other with a color
    void colorWipe(uint32_t c, uint8_t wait) {
      for(uint16_t i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, c);
        strip.show();
        delay(wait);
      }
    }
    

    Before we solder everything in place, we're going to cut our right angle headers in sections of 3. Please be careful as these headers are fragile and the amount included in the kit is close to exactly what you need. If you run out, we can send you replacements via our online store. Make sure your wire cutters are perfectly in line so it's a clean cut. Also, hold the headers down with one finger so they don't fly off when you cut them. Don't rush this step. Take your time.

    LED Cube 007 Cut Right Angle Headers
    CAREFULLY cut right angle headers in sections of 3

    If you look at any of the 5 identical PCB boards, you'll notice the word "IN" on the left and top sides of the board, and "OUT" on the right and bottom sides of the board. This is how the data travels through the boards once we've connected everything together. We've provided a diagram for you below; follow instructions on there so you can see how everything gets connected together.

    LED Cube 008 Connection diagram
    Internal Connection Diagram
    Connection Diagram between the boards
    Connection Diagram between the boards 2

    We'll now solder right angle headers to the top PCB board. If you're having a hard time soldering your first header onto your board, you can use masking tape or Kapton tape to hold the headers into place.

    Assembling the top PCB - board 1
    Place long side of right angle header through the back of the board
    Place tape on back of board to hold headers in place if you're having trouble.
    Solder remaining headers in place

    Before you get too far with building the LED Cube, feel free to number each board and label them on the back. In the video, we affixed a piece of masking tape to the back of each board and wrote its number with a marker. 1 is the main board and 6 is the bottom board. This will help you as you build out the cube. Now that board 1 is done, we'll start with board 6, the bottom.

    Cube assembly - Label Boards, Assemble Board 1 and Board 6
    Connection Diagram between the boards
    Place three right angle headers through the back of board 6 and place the board flat on table.
    Tack a joint on each header and then solder all three headers into place.

    Now we will work on board 3 and board 5. Tack the headers to board 3 according to the connection diagram above. Then, once they are at a right angle to the board, solder them in place. Do the same with board 5.

    Board 3 and Board 5
    This is how the headers will be attached to board 3.
    Tack a joint on each header and then solder all headers onto board 3.
    Here is the layout of headers for board 5; tack a joint on each header then solder all headers into place.

    Now that you have all the headers on the boards. You will solder board 1 onto board 2. Make sure to follow the connection diagram. Additionally, make sure to only tack boards 1 and 2 together, adjust the boards so they are at a right angle to each other, and then finish soldering the header in place.

    Solder Board 1 to Board 2
    Connect board 1 onto board 2; tack a joint on each header to secure them in place and then finish soldering each header onto the board.
    To keep boards 90 degrees to each other, use your fingers to keep the boards steady. This can take some time.

    Now that you have board 1 and 2 together, feel free to test the boards like we did earlier. If you're interested in the optional accelerometer, you can purchase it on our store. Keep in mind, however, it cannot be inserted once you've completed building the LED cube.

    Optional IMU step 2
    Tack a joint to the board, visually inspect, and solder the rest of header onto the IMU
    Plug IMU into LED cube
    Once IMU is secure, test it by putting SCL and SDA wires on the LED cube into SCL and SDA slots on the UNO board. INT wire will go into pin 2 on the Uno board

    The following code is meant to test the IMU. If you do not wish to use the IMU, this code will not work. Instead, modify the strandtest code to test 32 LEDs, rather than just 16.

    MPU6050-test.ino

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    // I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class
    // 10/7/2011 by Jeff Rowberg <jeff@rowberg.net>
    // Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
    //
    // Changelog:
    //      2013-05-08 - added multiple output formats
    //                 - added seamless Fastwire support
    //      2011-10-07 - initial release
    
    /* ============================================
    I2Cdev device library code is placed under the MIT license
    Copyright (c) 2011 Jeff Rowberg
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
    ===============================================
    */
    
    // I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h files
    // for both classes must be in the include path of your project
    #include "I2Cdev.h"
    
    #include "MPU6050_6Axis_MotionApps20.h"
    #include <Adafruit_NeoPixel.h>
    #include <avr/power.h>
    #include <EEPROM.h>
    
    #define PIN 6
    #define INTERRUPT_PIN 2
    #define NUMPIXELS 96
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    // class default I2C address is 0x68
    // specific I2C addresses may be passed as a parameter here
    // AD0 low = 0x68 (default for InvenSense evaluation board)
    // AD0 high = 0x69
    MPU6050 accelgyro;
    //MPU6050 accelgyro(0x69); // <-- use for AD0 high
    
    
    // MPU control/status vars
    bool dmpReady = 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
    
    // orientation/motion vars
    Quaternion q;           // [w, x, y, z]         quaternion container
    VectorInt16 aa;         // [x, y, z]            accel sensor measurements
    VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
    VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
    VectorFloat gravity;    // [x, y, z]            gravity vector
    float euler[3];         // [psi, theta, phi]    Euler angle container
    float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector
    int yawLast;
    int pitchLast;
    int rollLast;
    int yaw;
    int pitch;
    int roll;
    
    int motionMeter = 0;
    
    long int rainbowTime = 0;
    bool rainbowStart = true;
    
    // ================================================================
    // ===               INTERRUPT DETECTION ROUTINE                ===
    // ================================================================
    
    volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
    void dmpDataReady() {
        mpuInterrupt = true;
    }
    
    
    
    
    void setup() {
    
      pixels.begin();
      randomSeed(analogRead(0));
    
      // join I2C bus (I2Cdev library doesn't do this automatically)
      #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
          Wire.begin();
          Wire.setClock(400000);
      #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
          Fastwire::setup(400, true);
      #endif
    
      Serial.begin(115200);
    
      // initialize device
      Serial.println("Initializing I2C devices...");
      accelgyro.initialize();
      pinMode(INTERRUPT_PIN, INPUT);
    
      // verify connection
      Serial.println("Testing device connections...");
      Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
    
      Serial.println(F("Initializing DMP..."));
      devStatus = accelgyro.dmpInitialize();
    
      // supply your own gyro offsets here, scaled for min sensitivity
      //read MPU6050 offsets from EEPROM, constructing int from 2 single-byte memory locations
      int xAccelOff = EEPROM.read(1)<<8 | EEPROM.read(0);
      int yAccelOff = EEPROM.read(3)<<8 | EEPROM.read(2);
      int zAccelOff = EEPROM.read(5)<<8 | EEPROM.read(4);
      int xGyroOff = EEPROM.read(7)<<8 | EEPROM.read(6);
      int yGyroOff = EEPROM.read(9)<<8 | EEPROM.read(8);
      int zGyroOff = EEPROM.read(11)<<8 | EEPROM.read(10);
    
      //uncomment this to calibrate
      /*
      accelgyro.setDMPEnabled(false);
      dmpReady = false;
    
      // parameters are addresses of the 6 offsets,
      // and a boolean to determine whether the MPU is upside down or not
      accelgyro.calibrate(&xAccelOff, &yAccelOff, &zAccelOff,
                    &xGyroOff, &yGyroOff, &zGyroOff, true);
    
      accelgyro.setDMPEnabled(true);
      dmpReady = true;
    
      EEPROM.write(0, xAccelOff & 0xFF);
      EEPROM.write(1, xAccelOff>>8 & 0xFF);
      EEPROM.write(2, yAccelOff & 0xFF);
      EEPROM.write(3, yAccelOff>>8 & 0xFF);
      EEPROM.write(4, zAccelOff & 0xFF);
      EEPROM.write(5, zAccelOff>>8 & 0xFF);
      EEPROM.write(6, xGyroOff & 0xFF);
      EEPROM.write(7, xGyroOff>>8 & 0xFF);
      EEPROM.write(8, yGyroOff & 0xFF);
      EEPROM.write(9, yGyroOff>>8 & 0xFF);
      EEPROM.write(10, zGyroOff & 0xFF);
      EEPROM.write(11, zGyroOff>>8 & 0xFF);
    */
    //uncomment to calibrate
    
      accelgyro.setXAccelOffset(xAccelOff);
      accelgyro.setYAccelOffset(yAccelOff);
      accelgyro.setZAccelOffset(zAccelOff);
      accelgyro.setXGyroOffset(xGyroOff);
      accelgyro.setYGyroOffset(yGyroOff);
      accelgyro.setZGyroOffset(zGyroOff);
    
      if (devStatus == 0) {                   // make sure it worked (returns 0 if so)
          Serial.println(("Enabling DMP..."));        // turn on the DMP, now that it's ready
          accelgyro.setDMPEnabled(true);
    
          attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);        // enable Arduino interrupt detection
          mpuIntStatus = accelgyro.getIntStatus();
    
          Serial.println(("DMP ready! Waiting for first interrupt..."));  // set our DMP Ready flag so the main loop() function knows it's okay to use it
          dmpReady = true;
    
          packetSize = accelgyro.dmpGetFIFOPacketSize();    // get expected DMP packet size for later comparison
      }
    }
    
    void clear_all(){
      for(int i=0; i<pixels.numPixels(); i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
      pixels.show();
    }
    
    // Input a value 0 to 255 to get a color value.
    // The colours are a transition r - g - b - back to r.
    uint32_t Wheel(byte WheelPos) {
      WheelPos = 255 - WheelPos;
      if(WheelPos < 85) {
        return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
      }
      if(WheelPos < 170) {
        WheelPos -= 85;
        return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
      }
      WheelPos -= 170;
      return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
    }
    
    void colorSide(uint8_t startLED, uint32_t color){
      startLED *= 16;
      for(int i = startLED; i<startLED+16; i++){
        pixels.setPixelColor(i, color);
        //delay(50);
      }
      pixels.show();
    }
    
    uint32_t color;
    
    void loop() {
    
      // if programming failed, don't try to do anything
      if (!dmpReady) return;
    
      // wait for MPU interrupt or extra packet(s) available
      while (!mpuInterrupt && fifoCount < packetSize) {
      }
    
      // reset interrupt flag and get INT_STATUS byte
      mpuInterrupt = false;
      mpuIntStatus = accelgyro.getIntStatus();
    
      // get current FIFO count
      fifoCount = accelgyro.getFIFOCount();
    
      // check for overflow (this should never happen unless our code is too inefficient)
      if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
          // reset so we can continue cleanly
          accelgyro.resetFIFO();
          Serial.println(F("FIFO overflow!"));
    
      // otherwise, check for DMP data ready interrupt (this should happen frequently)
      } else if (mpuIntStatus & 0x02) {
          while (fifoCount < packetSize) fifoCount = accelgyro.getFIFOCount();
    
          // read a packet from FIFO
          accelgyro.getFIFOBytes(fifoBuffer, packetSize);
    
          // track FIFO count here in case there is > 1 packet available
          // (this lets us immediately read more without waiting for an interrupt)
          fifoCount -= packetSize;
    
          accelgyro.dmpGetQuaternion(&q, fifoBuffer);
          accelgyro.dmpGetGravity(&gravity, &q);
          accelgyro.dmpGetYawPitchRoll(ypr, &q, &gravity);
    
          yaw = 100*ypr[0];
          pitch = 100*ypr[1];
          roll = 100*ypr[2];
    
    
    
          Serial.print(yaw); Serial.print(", ");
          Serial.print(pitch); Serial.print(", ");
          Serial.println(roll);
    
    
          bool yawCheck = abs(yaw) < abs(yawLast)+1
                       && abs(yaw) > abs(yawLast)-1;
          bool pitchCheck = abs(pitch) < abs(pitchLast)+1
                       && abs(pitch) > abs(pitchLast)-1;
          bool rollCheck = abs(roll) < abs(rollLast)+1
                       && abs(roll) > abs(rollLast)-1;
    
          if(yawCheck && pitchCheck && rollCheck){
            motionMeter--;
          } else{
            motionMeter++;
          }
    
          yawLast = yaw;
          pitchLast = pitch;
          rollLast = roll;
      }
    
      if(motionMeter > 500) motionMeter = 500;
      else if(motionMeter < 0) motionMeter = 0;
    
      Serial.println(motionMeter);
    
    
      if(motionMeter > 100){
    
        rainbowStart = true;
        int yawL = map(yaw, -314, 314, 0, 255);
        int pitchL = map(pitch, -158, 158, 0, 255);
        int rollL = map(roll, -158, 158, 0, 255);
    
        for(int i=0; i < 16; i++){
          pixels.setPixelColor(i, Wheel(yawL));
        }
        for(int i=80; i < 96; i++){
          pixels.setPixelColor(i, Wheel(yawL));
        }
    
        for(int i=16; i < 32; i++){
          pixels.setPixelColor(i, Wheel(pitchL));
        }
        for(int i=48; i < 64; i++){
          pixels.setPixelColor(i, Wheel(pitchL));
        }
    
        for(int i=32; i < 48; i++){
          pixels.setPixelColor(i, Wheel(rollL));
        }
        for(int i=64; i < 80; i++){
          pixels.setPixelColor(i, Wheel(rollL));
        }
    
        pixels.show();
    
      } else{
    
        if(rainbowStart){
          rainbowStart = false;
          rainbowTime = millis();
        }
    
        long int j = (millis() - rainbowTime) / 5;
    
        for(int i=0; i< pixels.numPixels(); i++) {
          pixels.setPixelColor(i, Wheel(((i * 256 / pixels.numPixels()) + j) & 255));
        }
        pixels.show();
      }
    
    
    }
    

    Now it's time to piece all the boards together and finish building the LED cube. This may take some patience; especially with board 4. :)

    We have found it is easiest to lay out all of the boards and connect them without solder until you have the cube fully assembled in your hands. Follow the connection diagram for assistance. Start by connecting board 3 to board 2, place board 5 in position, the board 6, and finally, place board 4. Now, tack board 4 into place and tack all of the remaining joints in place to complete assembly of the cube. Attach the jumper cables to the top PCB board, program the board, and watch it work. Continue to the next section for troubleshooting tips followed by code examples, discussion, and additional challenges.

    LED Cube 014 Complete LED Cube
    Place board 3 onto board 2
    Place board 5
    Place board 6
    Place board 4
    Tack a joint on each header and then solder all boards around the board securely
    Attach jumper cable to Top PCB board
    Troubleshooting

    Tips and tricks for debugging your hardware and code.

    First checkpoint 

    If your board is not lighting up after the first checkpoint, first test to make sure you are able to program the Arduino by disconnecting the headers and uploading the Blink example.

    File > Examples -> 01.Basics -> Blink

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void setup() {
      // initialize digital pin LED_BUILTIN as an output.
      pinMode(LED_BUILTIN, OUTPUT);
    }
    
    // the loop function runs over and over again forever
    void loop() {
      digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
      delay(1000);                       // wait for a second
      digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
      delay(1000);                       // wait for a second
    }
    

    Ensure the LED on your UNO board is blinking.

    If that works, inspect your solder joints and double check your connections to the LED cube board.

    If you are still having trouble, post a photo to the forums and we or a community member will be eager to assist you.

    Second checkpoint 

    For the second checkpoint (IMU code), make sure that you have an IMU and it is plugged in. Next, inspect your solder joints and double check your connections to the LED cube board.

    If you are still having trouble, post a photo to the forums and we or a community member will be eager to assist you.

    Coding examples and challenges

    Brush up on your coding skills with some examples of what you can do with your new LED cube.

    Code examples that do not use the IMU 

    spell.ino

    spell.ino

    Modify the code below to spell out your name!

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    #include <Adafruit_NeoPixel.h>
    #include <avr/power.h>
    
    #define PIN            6
    
    // How many NeoPixels are attached to the Arduino?
    #define NUMPIXELS      96
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    
    void setup() { 
      pixels.begin(); // This initializes the NeoPixel library.
      randomSeed(analogRead(0));
    }
    
    //  0  1  2  3
    //  4  5  6  7
    //  8  9 10 11
    // 12 13 14 15
    
      int a[] = {12, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 15};
      int b[] = {11, 0, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15};
      int c[] = {10, 0, 1, 2, 3, 4, 8, 12, 13, 14, 15};
      int d[] = {10, 0, 1, 2, 4, 7, 8, 11, 12, 13, 14};
      int e[] = {12, 0, 1, 2, 3, 4, 5, 6, 8, 12, 13, 14, 15};
      int f[] = {9, 0, 1, 2, 3, 4, 8, 9, 10, 12};
      int g[] = {11, 0, 1, 2, 3, 4, 8, 11, 12, 13, 14, 15};
      int h[] = {10, 0, 3, 4, 7, 8, 9, 10, 11, 12, 15};
      int i[] = {10, 0, 1, 2, 3, 5, 9, 12, 13, 14, 15};
      int j[] = {11, 0, 1, 2, 3, 7, 8, 11, 12, 13, 14, 15};
      int k[] = {9, 0, 2, 3, 4, 5, 8, 10, 12, 15};
      int l[] = {7, 0, 4, 8, 12, 13, 14, 15};
      int m[] = {11, 0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 15};
      int n[] = {10, 0, 3, 4, 5, 7, 8, 10, 11, 12, 15};
      int o[] = {12, 0, 1, 2, 3, 4, 7, 8, 11, 12, 13, 14, 15};
      int p[] = {11, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11, 12};
      int q[] = {11, 0, 1, 2, 3, 4, 7, 8, 11, 12, 13, 15};
      int r[] = {12, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 14};
      int s[] = {8, 0, 1, 2, 5, 10, 12, 13, 14};
      int t[] = {7, 0, 1, 2, 3, 5, 9, 13};
      int u[] = {10, 0, 3, 4, 7, 8, 11, 12, 13, 14, 15};
      int v[] = {8, 0, 3, 4, 7, 8, 11, 13, 14};
      int w[] = {11, 0, 3, 4, 7, 8, 10, 11, 12, 13, 14, 15};
      int x[] = {8, 0, 3, 5, 6, 9, 10, 12, 15};
      int y[] = {9, 0, 3, 4, 7, 8, 9, 10, 11, 14};
      int z[] = {10, 0, 1, 2, 3, 6, 9, 12, 13, 14, 15};
      int plus[] = {5, 5, 8, 9, 10, 13};
      int smile[] = {6, 0, 3, 8, 11, 13, 14};
    
    
    void loop() {
      delay(1000); clear_all();
      spell(a,0);
      delay(1000); clear_all();
      spell(b,0);
      delay(1000); clear_all(); 
      spell(c,0);
      delay(1000); clear_all(); 
      spell(d,0);
      delay(1000); clear_all();
      spell(e,0);
      delay(1000); clear_all();
      spell(f,0);
      delay(1000); clear_all(); 
      spell(g,0);
      delay(1000); clear_all(); 
      spell(h,0);
      delay(1000); clear_all(); 
      spell(i,0);
      delay(1000); clear_all(); 
      spell(j,0);
      delay(1000); clear_all(); 
      spell(k,0);
      delay(1000); clear_all(); 
      spell(l,0);
      delay(1000); clear_all(); 
      spell(m,0);
      delay(1000); clear_all(); 
      spell(n,0);
      delay(1000); clear_all(); 
      spell(o,0);
      delay(1000); clear_all(); 
      spell(p,0);
      delay(1000); clear_all(); 
      spell(q,0);
      delay(1000); clear_all(); 
      spell(r,0);
      delay(1000); clear_all(); 
      spell(s,0);
      delay(1000); clear_all(); 
      spell(t,0);
      delay(1000); clear_all(); 
      spell(u,0);
      delay(1000); clear_all(); 
      spell(v,0);
      delay(1000); clear_all(); 
      spell(w,0);
      delay(1000); clear_all(); 
      spell(x,0);
      delay(1000); clear_all(); 
      spell(y,0);
      delay(1000); clear_all(); 
      spell(z,0);
      delay(1000); clear_all(); 
      spell(smile,0);
      delay(1000); clear_all();
      rainbow_spell(34);
      delay(1000); clear_all();
     }
    
    
    void clear_all(){
      for(int i=0; i<pixels.numPixels(); i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
      pixels.show();
    }
    
    void spell(int letter[], int offset){
      for(int i=1;i<=letter[0];i++){
        pixels.setPixelColor(letter[i]+offset, pixels.Color(0,0,150)); // Moderately bright green color.
      } 
      pixels.show();
    }
    
    void spell_rainbow(int letter[], int offset, int color){
      for(int i=1;i<=letter[0];i++){
        pixels.setPixelColor(letter[i]+offset, Wheel((i+color)%255)); // Moderately bright green color.
      } 
      pixels.show();
    }
    
    //Theatre-style crawling lights with rainbow effect
    void rainbow_spell(uint8_t wait) {
      for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
        spell_rainbow(t, 0, j);
        spell_rainbow(h, 16, j);
        spell_rainbow(i, 32, j);
        spell_rainbow(m, 48, j);
        spell_rainbow(b, 64, j);
        spell_rainbow(l, 80, j);
      }
    }
    
    // Input a value 0 to 255 to get a color value.
    // The colours are a transition r - g - b - back to r.
    uint32_t Wheel(byte WheelPos) {
      WheelPos = 255 - WheelPos;
      if(WheelPos < 85) {
        return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
      }
      if(WheelPos < 170) {
        WheelPos -= 85;
        return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
      }
      WheelPos -= 170;
      return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
    }
    

    Dice

    Use your LED cube and the random() function to roll a digital die.

    dice.ino

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    #include <Adafruit_NeoPixel.h>
    #include <avr/power.h>
    
    #define PIN            6
    
    // How many NeoPixels are attached to the Arduino?
    #define NUMPIXELS      96
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    void setup() { 
      pixels.begin(); // This initializes the NeoPixel library.
      randomSeed(analogRead(0));
    }
    
    // 0 0 0 0
    // 0 0 0 0
    // 0 0 0 0
    // 0 0 0 0
    
    // start dice
    // 1 = 0400
    // 2 = 8020
    // 3 = 8420
    // 4 = A0A0
    // 5 = A4A0
    // 6 = AAA0
    
    static const unsigned int dice[] = {
    /* cube face:
    
     0 0 0 0
     0 0 0 0
     0 0 0 0
     0 0 0 0
    
     for a single die, we are using the 3x3 square in the upper left corner of a single face of the cube
    
     so, 1 is represented as:
    
     0 0 0 0 = 0 in hex
     0 1 0 0 = 8 in hex (convert 0100 in binary to hex)
     0 0 0 0 = 0 in hex
     0 0 0 0 = 0 in hex
    
     and 5 is represented as:
    
     1 0 1 0 = A in hex
     0 1 0 0 = 4 in hex
     1 0 1 0 = A in hex
     0 0 0 0 = 0 in hex
     */
     
    //  0  1  2  3
    //  4  5  6  7
    //  8  9 10 11
    // 12 13 14 15
    
      0x0400, // = 1
      0x8020, // = 2
      0x8420, // = 3
      0xA0A0, // = 4
      0xA4A0, // = 5
      0xAAA0  // = 6
      
    };
    
    void display_die_face(uint8_t number, uint8_t offset, uint32_t color){
      uint8_t n = number-1;
      if(n<0) n=0;
      for(int i=3; i>=0; i--){
        for(int j=3; j>=0; j--){
          if(dice[n] & (1<<(i*4+j)) ){
            pixels.setPixelColor((3-i)*4+(3-j)+offset, color);
          }
        }
      }
      pixels.show();
    }
    
    void display_die(uint8_t number, uint32_t color){
      uint8_t n = number-1;
      if(n<0) n=0;
      switch(n%6){
          case 0://1
            display_die_face(1,0,color);
            display_die_face(2,16,color);
            display_die_face(3,32,color);
            display_die_face(5,48,color);
            display_die_face(4,64,color);
            display_die_face(6,80,color);
            break;
          case 1://2
            display_die_face(2,0,color);
            display_die_face(6,16,color);
            display_die_face(3,32,color);
            display_die_face(1,48,color);
            display_die_face(4,64,color);
            display_die_face(5,80,color);
            break;
          case 2: //3
            display_die_face(3,0,color);
            display_die_face(6,16,color);
            display_die_face(5,32,color);
            display_die_face(1,48,color);
            display_die_face(2,64,color);
            display_die_face(4,80,color);
            break;
          case 3://4
            display_die_face(4,0,color);
            display_die_face(6,16,color);
            display_die_face(2,32,color);
            display_die_face(1,48,color);
            display_die_face(5,64,color);
            display_die_face(3,80,color);
            break;
          case 4://5
            display_die_face(5,0,color);
            display_die_face(6,16,color);//5
            display_die_face(4,32,color);
            display_die_face(1,48,color);
            display_die_face(3,64,color);
            display_die_face(2,80,color);
            break;
          case 5:
            display_die_face(6,0,color);
            display_die_face(3,16,color);//5
            display_die_face(2,32,color);
            display_die_face(4,48,color);
            display_die_face(5,64,color);
            display_die_face(1,80,color);
            break;
        }
    }
    void clear_all(){
      for(int i=0; i<pixels.numPixels(); i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
      pixels.show();
    }
    void roll_die(uint32_t color){
      long r = random(60);
      for(int i=1; i<r; i++){
        clear_all();
        display_die(i%7, color);
        delay(30+10*(i));
      }
    }
    
    
    void loop() {  
      for(int i=0; i<3; i++){
        roll_die(Wheel(random(255)));
        delay(5000);
      }
    }
    
    // Input a value 0 to 255 to get a color value.
    // The colours are a transition r - g - b - back to r.
    uint32_t Wheel(byte WheelPos) {
      WheelPos = 255 - WheelPos;
      if(WheelPos < 85) {
        return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
      }
      if(WheelPos < 170) {
        WheelPos -= 85;
        return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
      }
      WheelPos -= 170;
      return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
    }
    

    Animations

    Get some inspriation for cool animations from the code below.

    animations.ino

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    #include <Adafruit_NeoPixel.h>
    #include <avr/power.h>
    
    #define PIN            6
    
    // How many NeoPixels are attached to the Arduino?
    #define NUMPIXELS      96
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    void setup() { 
      pixels.begin(); // This initializes the NeoPixel library.
      randomSeed(analogRead(0));
    }
    
    void loop() {
      uint32_t color = pixels.Color(0, 128, 0);
      go_kitt();
      colorWipe(color, 20);
      animated_clear();
     }
    
    
    void go_kitt(){
      for(int i=3; i<16; i+=4){
        knight_rider(0, i, 50);
      }
    }
    void clear_all(){
      for(int i=0; i<pixels.numPixels(); i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
      pixels.show();
    }
    
    void animated_clear(){
      for(int i=0; i<pixels.numPixels(); i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
        if(i<pixels.numPixels()-1) pixels.setPixelColor(i+1, pixels.Color(0,0,125));
        
        if(i<pixels.numPixels()-2) pixels.setPixelColor(i+2, pixels.Color(0,0,125));
        if(i<pixels.numPixels()-3) pixels.setPixelColor(i+3, pixels.Color(0,0,125));
        pixels.show();
        delay(50);
      }
    }
    
    void knight_rider(int minn, int maxn, int delayval){
      for(int i=minn; i<maxn; i++){
        pixels.setPixelColor(i, pixels.Color(150,0,0));
        pixels.setPixelColor(i+1, pixels.Color(150,0,0));
        if(i>minn){
          pixels.setPixelColor(i-1, pixels.Color(0,0,0));
        }
        delay(delayval);
        pixels.show();
      }
      delay(500);
      for(int i=maxn; i>minn; i--){
        pixels.setPixelColor(i, pixels.Color(150,0,0));
        pixels.setPixelColor(i-1, pixels.Color(150,0,0));
        if(i<maxn){
          pixels.setPixelColor(i+1, pixels.Color(0,0,0));
        }
        delay(delayval);
        pixels.show();
      }
        delay(500);
    }
    
    // Fill the dots one after the other with a color
    void colorWipe(uint32_t c, uint8_t wait) {
      for(uint16_t i=0; i<pixels.numPixels(); i++) {
        pixels.setPixelColor(i, c);
        pixels.show();
        delay(wait);
      }
    }
    

    Code examples that use the IMU 

    MPU6050-test.ino

    MPU6050-test.ino

    The code below uses the MPU6050 to control the LED Cube

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    // I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class
    // 10/7/2011 by Jeff Rowberg <jeff@rowberg.net>
    // Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
    //
    // Changelog:
    //      2013-05-08 - added multiple output formats
    //                 - added seamless Fastwire support
    //      2011-10-07 - initial release
    
    /* ============================================
    I2Cdev device library code is placed under the MIT license
    Copyright (c) 2011 Jeff Rowberg
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
    ===============================================
    */
    
    // I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h files
    // for both classes must be in the include path of your project
    #include "I2Cdev.h"
    
    #include "MPU6050_6Axis_MotionApps20.h"
    #include <Adafruit_NeoPixel.h>
    #include <avr/power.h>
    #include <EEPROM.h>
    
    #define PIN 6
    #define INTERRUPT_PIN 2
    #define NUMPIXELS 96
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    // class default I2C address is 0x68
    // specific I2C addresses may be passed as a parameter here
    // AD0 low = 0x68 (default for InvenSense evaluation board)
    // AD0 high = 0x69
    MPU6050 accelgyro;
    //MPU6050 accelgyro(0x69); // <-- use for AD0 high
    
    
    // MPU control/status vars
    bool dmpReady = 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
    
    // orientation/motion vars
    Quaternion q;           // [w, x, y, z]         quaternion container
    VectorInt16 aa;         // [x, y, z]            accel sensor measurements
    VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
    VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
    VectorFloat gravity;    // [x, y, z]            gravity vector
    float euler[3];         // [psi, theta, phi]    Euler angle container
    float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector
    int yawLast;
    int pitchLast;
    int rollLast;
    int yaw;
    int pitch;
    int roll;
    
    int motionMeter = 0;
    
    long int rainbowTime = 0;
    bool rainbowStart = true;
    
    // ================================================================
    // ===               INTERRUPT DETECTION ROUTINE                ===
    // ================================================================
    
    volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
    void dmpDataReady() {
        mpuInterrupt = true;
    }
    
    
    
    
    void setup() {
    
      pixels.begin();
      randomSeed(analogRead(0));
    
      // join I2C bus (I2Cdev library doesn't do this automatically)
      #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
          Wire.begin();
          Wire.setClock(400000);
      #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
          Fastwire::setup(400, true);
      #endif
    
      Serial.begin(115200);
    
      // initialize device
      Serial.println("Initializing I2C devices...");
      accelgyro.initialize();
      pinMode(INTERRUPT_PIN, INPUT);
    
      // verify connection
      Serial.println("Testing device connections...");
      Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
    
      Serial.println(F("Initializing DMP..."));
      devStatus = accelgyro.dmpInitialize();
    
      // supply your own gyro offsets here, scaled for min sensitivity
      //read MPU6050 offsets from EEPROM, constructing int from 2 single-byte memory locations
      int xAccelOff = EEPROM.read(1)<<8 | EEPROM.read(0);
      int yAccelOff = EEPROM.read(3)<<8 | EEPROM.read(2);
      int zAccelOff = EEPROM.read(5)<<8 | EEPROM.read(4);
      int xGyroOff = EEPROM.read(7)<<8 | EEPROM.read(6);
      int yGyroOff = EEPROM.read(9)<<8 | EEPROM.read(8);
      int zGyroOff = EEPROM.read(11)<<8 | EEPROM.read(10);
    
      //uncomment this to calibrate
      /*
      accelgyro.setDMPEnabled(false);
      dmpReady = false;
    
      // parameters are addresses of the 6 offsets,
      // and a boolean to determine whether the MPU is upside down or not
      accelgyro.calibrate(&xAccelOff, &yAccelOff, &zAccelOff,
                    &xGyroOff, &yGyroOff, &zGyroOff, true);
    
      accelgyro.setDMPEnabled(true);
      dmpReady = true;
    
      EEPROM.write(0, xAccelOff & 0xFF);
      EEPROM.write(1, xAccelOff>>8 & 0xFF);
      EEPROM.write(2, yAccelOff & 0xFF);
      EEPROM.write(3, yAccelOff>>8 & 0xFF);
      EEPROM.write(4, zAccelOff & 0xFF);
      EEPROM.write(5, zAccelOff>>8 & 0xFF);
      EEPROM.write(6, xGyroOff & 0xFF);
      EEPROM.write(7, xGyroOff>>8 & 0xFF);
      EEPROM.write(8, yGyroOff & 0xFF);
      EEPROM.write(9, yGyroOff>>8 & 0xFF);
      EEPROM.write(10, zGyroOff & 0xFF);
      EEPROM.write(11, zGyroOff>>8 & 0xFF);
    */
    //uncomment to calibrate
    
      accelgyro.setXAccelOffset(xAccelOff);
      accelgyro.setYAccelOffset(yAccelOff);
      accelgyro.setZAccelOffset(zAccelOff);
      accelgyro.setXGyroOffset(xGyroOff);
      accelgyro.setYGyroOffset(yGyroOff);
      accelgyro.setZGyroOffset(zGyroOff);
    
      if (devStatus == 0) {                   // make sure it worked (returns 0 if so)
          Serial.println(("Enabling DMP..."));        // turn on the DMP, now that it's ready
          accelgyro.setDMPEnabled(true);
    
          attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);        // enable Arduino interrupt detection
          mpuIntStatus = accelgyro.getIntStatus();
    
          Serial.println(("DMP ready! Waiting for first interrupt..."));  // set our DMP Ready flag so the main loop() function knows it's okay to use it
          dmpReady = true;
    
          packetSize = accelgyro.dmpGetFIFOPacketSize();    // get expected DMP packet size for later comparison
      }
    }
    
    void clear_all(){
      for(int i=0; i<pixels.numPixels(); i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
      pixels.show();
    }
    
    // Input a value 0 to 255 to get a color value.
    // The colours are a transition r - g - b - back to r.
    uint32_t Wheel(byte WheelPos) {
      WheelPos = 255 - WheelPos;
      if(WheelPos < 85) {
        return pixels.Color(255 - WheelPos * 3, 0, WheelPos * 3);
      }
      if(WheelPos < 170) {
        WheelPos -= 85;
        return pixels.Color(0, WheelPos * 3, 255 - WheelPos * 3);
      }
      WheelPos -= 170;
      return pixels.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
    }
    
    void colorSide(uint8_t startLED, uint32_t color){
      startLED *= 16;
      for(int i = startLED; i<startLED+16; i++){
        pixels.setPixelColor(i, color);
        //delay(50);
      }
      pixels.show();
    }
    
    uint32_t color;
    
    void loop() {
    
      // if programming failed, don't try to do anything
      if (!dmpReady) return;
    
      // wait for MPU interrupt or extra packet(s) available
      while (!mpuInterrupt && fifoCount < packetSize) {
      }
    
      // reset interrupt flag and get INT_STATUS byte
      mpuInterrupt = false;
      mpuIntStatus = accelgyro.getIntStatus();
    
      // get current FIFO count
      fifoCount = accelgyro.getFIFOCount();
    
      // check for overflow (this should never happen unless our code is too inefficient)
      if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
          // reset so we can continue cleanly
          accelgyro.resetFIFO();
          Serial.println(F("FIFO overflow!"));
    
      // otherwise, check for DMP data ready interrupt (this should happen frequently)
      } else if (mpuIntStatus & 0x02) {
          while (fifoCount < packetSize) fifoCount = accelgyro.getFIFOCount();
    
          // read a packet from FIFO
          accelgyro.getFIFOBytes(fifoBuffer, packetSize);
    
          // track FIFO count here in case there is > 1 packet available
          // (this lets us immediately read more without waiting for an interrupt)
          fifoCount -= packetSize;
    
          accelgyro.dmpGetQuaternion(&q, fifoBuffer);
          accelgyro.dmpGetGravity(&gravity, &q);
          accelgyro.dmpGetYawPitchRoll(ypr, &q, &gravity);
    
          yaw = 100*ypr[0];
          pitch = 100*ypr[1];
          roll = 100*ypr[2];
    
    
    
          Serial.print(yaw); Serial.print(", ");
          Serial.print(pitch); Serial.print(", ");
          Serial.println(roll);
    
    
          bool yawCheck = abs(yaw) < abs(yawLast)+1
                       && abs(yaw) > abs(yawLast)-1;
          bool pitchCheck = abs(pitch) < abs(pitchLast)+1
                       && abs(pitch) > abs(pitchLast)-1;
          bool rollCheck = abs(roll) < abs(rollLast)+1
                       && abs(roll) > abs(rollLast)-1;
    
          if(yawCheck && pitchCheck && rollCheck){
            motionMeter--;
          } else{
            motionMeter++;
          }
    
          yawLast = yaw;
          pitchLast = pitch;
          rollLast = roll;
      }
    
      if(motionMeter > 500) motionMeter = 500;
      else if(motionMeter < 0) motionMeter = 0;
    
      Serial.println(motionMeter);
    
    
      if(motionMeter > 100){
    
        rainbowStart = true;
        int yawL = map(yaw, -314, 314, 0, 255);
        int pitchL = map(pitch, -158, 158, 0, 255);
        int rollL = map(roll, -158, 158, 0, 255);
    
        for(int i=0; i < 16; i++){
          pixels.setPixelColor(i, Wheel(yawL));
        }
        for(int i=80; i < 96; i++){
          pixels.setPixelColor(i, Wheel(yawL));
        }
    
        for(int i=16; i < 32; i++){
          pixels.setPixelColor(i, Wheel(pitchL));
        }
        for(int i=48; i < 64; i++){
          pixels.setPixelColor(i, Wheel(pitchL));
        }
    
        for(int i=32; i < 48; i++){
          pixels.setPixelColor(i, Wheel(rollL));
        }
        for(int i=64; i < 80; i++){
          pixels.setPixelColor(i, Wheel(rollL));
        }
    
        pixels.show();
    
      } else{
    
        if(rainbowStart){
          rainbowStart = false;
          rainbowTime = millis();
        }
    
        long int j = (millis() - rainbowTime) / 5;
    
        for(int i=0; i< pixels.numPixels(); i++) {
          pixels.setPixelColor(i, Wheel(((i * 256 / pixels.numPixels()) + j) & 255));
        }
        pixels.show();
      }
    
    
    }
    
    Start Playing

    Now have some fun!

    Congratulations, you build the LED Cube!
    Now that you've build the cube, share your creation!