LED cubes are fascinating. The allure of blinking LEDs in various patterns is always attractive and mesmerizing. Like many Arduino fans there came a point when I decided I would try my hand at one (or two, as it turned out).
Whilst they look complex, they are, in principle, relatively straightforward from an electronics and software perspective. Making the cube can be challenging, but by using jigs and patience, this becomes more of a chore than a challenge!
As part of this learning exercise, I wanted to move away from the ‘bit-twiddling’ code that seems to drive many cubes, to a device-independent software library that could be programmed in cartesian (XYZ) coordinates. This way the same code could be used to drive many different cubes with no change at the user code end of things.
After doing some research I concluded are 2 main types of LED cube architectures:
- A scanning/refresh model, where the LEDs are refreshed by the Arduino at a rate fast enough to activate a persistence of vision effect. This puts the onus of multiplexing control in the Arduino software. An example of this type hardware is the ICStation Light Cube Kit, based on 74HC595 shift registers to control LED anodes and 4 I/O ports of Arduino control 4 common cathodes of LED. This type of hardware is the most common for LED cubes and relies on the microcontroller software opening a circuit (a path for current) for each LED in very quick succession.
- A ‘set and forget’ model, where each LED is set by the Arduino and some other hardware components ensure that they remain turned on or off. One example is a DIY version from this Arduino forum post. This cube uses a MAX7219 LED controller to drive the 64 LEDs in the cube. Refreshing the LEDs is therefore handled by the 7219, with the Arduino periodically writing new settings through the interface registers.
The software must take into account these differences and allow both to work using the same basic software pattern.
Some additional analysis and experimentation resulted in a very small set of primitives that need to be implemented to handle the differences. The final library tested with these two 4x4x4 and the JolliCube 8x8x8 cube architectures can be found here.
Basically the hardware dependent code needs to do the following:
- Initialise the hardware. This will clearly be different for the different hardware types. In the library this is implemented in the begin() method.
- Turn a LED on/off. This is THE basic function for the cube and is implemented in the setVoxel() method. Every other drawing function – clear(), drawLine(), fillPlane() – can be written using setVoxel(). The function also needs to be able to map between coordinate and a physical LED, often in a non-linear manner.
- Update the cube. Implemented in the update() method, this allows the internal buffers to be copied to the cube display, allowing for crisp animations and controlled updates, as all the changes accumulated since the previous update are shown at once.
- Animate the cube. This is the animate() method called in between updates to allow refreshing in multiplexed cubes. For ‘set and forget’ cubes, this method is ignored.
Using the extensibility inherent in C++ class inheritance makes it a breeze to create hardware specific functionality around a common core class.
So there it is. It is possible to create device independent code to run a cube. In principle, this library is also extensible to cubes of any size. Mission accomplished!
Update: See also this additional article