8x8x8 LED Cube Project
The finished Cube hooked up to a programmer and oscilliscope
Over the last year and a half, I designed, constructed and programmed an 8x8x8 LED Cube. This page serves as a complete description of my design and how I was able to make it all work. The goal of this page is to provide enough information so that a beginner in hardware and software could build their own cube.
If you're interested in seeing more images before reading on, take a look at the gallery.
Ready to get started? Read on!
Materials
- 512 x 5mm diffused LEDs (Any color)
- 4 x TLC59116 Constant current sink LED drivers
- 1 x PIC18LF45K22 MCU
- 8 x 2n4401 NPN type transistors
- 8 x FQD11P06 P-Channel MOSFETs
- 1 x LM350 voltage regulator
- Breadboards to put it on
High Level Design
Schematic of the cube (Power lines not shown)
The overall design is works be performing a raster scan from the top down on a 3D array of LEDs. The array is designed such that each of 8 8x8 layers are addressed one at a time. 4 constant current display drivers capable of addressing 16 LEDs each. A PIC MCU is used to coordinate the refresh cycles by writing to the drivers appropriately. Finally, a Raspberry Pi is used as an interface and frame rendering system that is capable of communicating frames to the PIC.
512 LED Array
The whole array is broken into 8 layers of 64 LEDs Each. The cathodes of all the LEDs in a layer are soldered together allowing the whole layer to be powered at one time. The anodes of the LEDs form the columns of the array. Each layer then has 1 power input, and 64 addressable columns, allowing the driver to address 64 columns with the 4 display drivers.
Display Drivers
4 Texas Instruments TLC59116 16-bit I2C LED Sink Drivers are used to address a layer of 8x8 (64) LEDs. These drivers can read in data for one layer of LEDs and latch the output so that the layer can be displayed.
Display controller
The display controller was a PIC18LF45K22 PIC Microcontroller programmed with a Mikro Electronika EasyProg. The driver was written in embedded C to act as a double buffered system capable of accepting asynchronous input through UART connection with a master controller while keeping the display refreshed.
Raspberry Pi
To give the cube the capability of almost anything, I used a Raspberry Pi as a master controller for the system. I run simple Raspian Linux on the Pi, and have developed a software API in Mono C# that abstracts away the underlying hardware interface and provides end users to easy address LEDs in a simple (x, y, z) format. The end result is a top level system that is capable of everything from simple animations to accepting network input and more!
Construction
Constructing the cube was done in parts. The hardest is the array itself.
LED Array
The array was built in layers of 8x8 LEDs. I found an old block of wood and used a drill press to place 5mm holes in a grid of 8x8, exactly 2cm (center to center) apart. I then placed the LEDs into the template and bent the cathodes down and soldered them together.
Block template and LED layers being soldered together
Each layer was made first, then they were put together. I used Lego parts as scaffolding to hold the layers in place while I soldered them.
The finished array (Upside down, in the wood template)
Display Drivers
I paired off the drivers and soldered them onto small breadboards.
Coming soon: Board schematics for the drivers.
Layer Switches
The layer switches served as a way for the PIC's 3.3V logic to control a high current 12V line. Below is a diagram of the switches.
Circuit diagram of switches (Note: only 2 are depicted here, there are 8 total. Also, the lead cut off on the right connects to +12V, and the lead on the left connects to ground).
The 8 input pins are connected to PORT B on the PIC. The PIC writes a single bit to the port to power on the layer.
The finished layer switch circuit
Power Supply
This section isn't finished yet!
Putting it together
Neither is this one!
Software
The software used to control the cube comes in to parts. The first is the embedded C code on the PIC.
Display Controller
The code on the PIC acts as a double buffered display driver, allowing outside sources to pipe in frames over UART. It manages the drivers, taking care of setting up the correct internal register values, and also includes a auto-idle animation (I.e. a screen saver) and software configurable internal registers for directing the operation of the display cycle.
The files related to the PIC code are in ledcube/LedCubeMCU/. It is a MikroElektronika C for PIC project and can only be compiled and loaded as is with the MikroE compiler and programmer.
Command/Response Protocol over UART
In the PIC code, in LedCubeMCUDriver.h, you will find the following lines,
// Enum for commands
typedef enum {
NOOP,
READFRAME = 0x91,
SETCONTROL,
IDLE,
CLEAR = 0xEE,
ENDCMD = 0xFF
} CUBECMD;
These are arbitrarily defined, but my intent was that the usable commands would satisfy the following constraints:
- Of the pairs of bits in a byte, at least one bit in the left position is set (I.e. 0b10101010 & CMD != 0x00). The reason is to not confuse commands with writing LED values, which are in the form of (0b01010101).
- Should not be easy to mistakenly send over the line. Initially, when the Raspberry Pi boots, some garbage data can be sent over the UART accidentally (I.e. the line may drop to zero for an instant). This ensures that a READFRAME isn't started unless intended.
The PIC command system acts as a state machine. Here is a quick diagram: State machine for command processing
The PIC will sit in IDLE until a byte is sent to it containing the command to start. If it is a READFRAME command, the PIC will enter the "Read to Buffer" state. This directs all further input to the back-buffer. Upon ENDCMD, the back-buffer will be swapped with the display buffer updating the frame instantly. For referance, a frame would be sent in the following format:
READFRAME, <FRAME PAYLOAD>, ENDCMD
If you wanted to send a frame setting all LEDs ON, it looks like:
9155...55FF
Where 0x55 is repeated 128 times.
Upon receiving any other command, the PIC will enter the "Read Args" state, which will read one byte at a time into an argument buffer until ENDCMD is received. A different buffer is used for this because the back-buffer is only for LED data and when swapped into the display, cannot contain any garbage from left over commands.
There is no error checking / correction done on the command system because throughput is important. As it stands, the interface is capable of about 66 FPS from the Pi to the display. Any sort of checking would only serve to add delay in reading a frame and reduce performance.
Raspberry Pi
The code for the Raspberry Pi is actually quite simple. Now that we have the PIC set up to manage the display, we can pipe frames to it over the Pi's UART asynchronously. In PyLedCube/LedCubePy, there is a Python module which serves as an interface. It simply opens the Pi's UART (/dev/ttyAMA0), and converts (x, y, z) coordinates into their corresponding bits in the buffer. When WriteBuffer() is called, it dumps the frame to the PIC to be displayed.
Support for modifying the PIC's internal variables through SETCONTROL is not supported yet. In the future, it will be possible to modify existing and future variables on the PIC through the Python interface.
There also is a C# based interface written to work with Mono on the Pi. In the future I plan to evaluate using Windows 10 IoT core on the Pi, which would require this interface.
Authors and Contributors
Michael Davies