Last week, I built an LED cube - 64 LEDs that you can program to make fantastic futuristic light shows – and I hope you did too, because it’s a great project to motivate you and expand your Arduino skillset. I left you with a few basic apps to get you thinking, but today I’ll be presenting a few more bits of software that I made for the cube, along with code explanations. The purpose of this is both to give you some more pretty lightshows to run, but also to learn about some of the limitations of programming the cube, and learn some new programming concepts in the process.
This is some pretty advanced coding; you really need to have read all my previous beginner’s Arduino guide before customizing the code provided.and our
App 1: Mini Snake
Rather than running a set snake-like pattern sequence, I wanted to program a snake – an artifical one that would make it’s own random choices, and be completely unpredictable. It’s limited to 2 segments only, which I’ll explain later, and you can see the demo below. Download the full code here.
When dealing with 3D space, you need 3 co-ordinates for a single point: X, Y, and Z.
However, in our cube, the X and Z planes are represented by LED pins, while the Y is directly mapped to the cathode planes. To facilitate working with these coordinates and figuring out movement around the cube, I therefore created a new datatype (using struct) to represent a single point on the cube – which I called “xyz”. It consists of just two integers: “xz”, and “y”. With this structure, I could then also represent a direction, indicated below in our special (xz,y) coordinate system:
Y movement (up, down): (xz,y+1), (xz, y-1)
Z movement (forwards, back): (xz-1, y), (xz+1, y)
X movement (left, right): (xz+4, y), (xz-4,y)
For example, to move the LED in position (0,0) one to left, we apply (xz+4, y) and end up with (0,4).
There are certain limits that be placed on movement – namely that Y coordinates can only be a possible 0 to 3 (0 being the bottom layer, 3 being the top), and XZ coordinates could only be 0 to 15. A further limit is placed on the Z movement to prevent “jumping” from the back to the front of the cube, and vice versa. In this case, we use the modulus function to test for multiples of 4 and deny that movement attempt. This is logic is represented in the valid() function, which returns a true if the direction proposed is an acceptable move, and false otherwise. I added a further function to check for an inverse direction – that is, if the snake is heading in one direction, we don’t want it to go backwards on itself, even if it is otherwise a valid location to move to – and a move() function, which takes a coordinate, a direction, and returns the new coordinate.
The XYZ datatype, valid(), move() and inverse() functions can all be found in the xyz.h file in the downloads. If you’re wondering why this was put into a separate file instead of the main program file, it’s due to some complicated Arduino compiler rules that prevent functions from returning custom datatypes; they must be placed in their own file, then imported at the start of the main file.
Back in the main runtime file, an array of directions stores all the possible movements the snake can make; we can simply pick a random array member to get a new direction. Variables are also created to store the current location (now), the previous direction and previous location. The rest of the code should be fairly obvious to you; just for loops, and turning on and off LEDs. In the main loop, we check to see if the proposed direction is valid, and if it is then we go that way. If not, we pick a new direction.
The only thing to point out in the main loop is some checks to correct a bug I found involving multiplexing: if the new location was on the same cathode plane or same anode pin, turning off the previous LED would result in both going out. It’s also at this point that I realised going beyond a 2 segment snake was going to be impossible with my current implementation: try to light up 3 LEDs in a corner arrangement. You can’t, because with 2 layers and 2 LEDs pins actived, 4 LEDs would turn on, not 3. This is an inherent issue with our limited multiplexed cube design, but not to worry: we simply need to use the power of persistence of vision to rewrite the draw method.
Persistence of vision means that when light reaches our eyes sequentially – faster than we can process it – it appears to be a single image. In our case, rather than drawing all four layers at the same time, we should draw the first, deactivate it, draw the second and deactivate it: faster than we can tell any change is even happening. This is the principle on by which message writers work, like this one:
New Draw Method Using Persistence of Vision
First off then, a new draw routine. I’ve created a 4 x 16 two-dimensional array of bits (true, or false) to be a literal representation of the state of LED cube. The draw routine will implement persistence of vision by simply iterating over this and flushing out each layer to the cube for a brief moment. It’ll continue to draw itself in the current state until the refresh time has elapsed, at which point we’ll pass control back to the main loop(). I’ve saved this section of the code in this LED_cube_POV file, so if you want to just jump into programming your own games and such then feel free to use this as a base.
App 2: Game of Life
For now, let’s develop this into a basic version of Conway’s Game Of Life. For those of you who are unfamiliar (try Googling it to find an awesome easter egg animation), the Game of Life is an example of cellular automata that creates a fascinating pattern of emergent behaviour given only a few simple rules.
This is, for example, how ants appear to move with intelligence and a hive mind, despite the biological fact that they actual just follow very basic hormonal rules. Here’s the full code for download: press the reset button to restart. If you find yourself getting the same pattern over and over, try holding down the rest button for longer.
Here are the rules of the Game of Life:
- Any live cell with fewer than two live neighbours dies, as if caused by under-population.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overcrowding.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
Run the code. You’ll notice within 5 to 10 “generations”, the automata have probably come to a rest, stabilising on a certain position; sometimes this stable pattern will change location and shift around the board. In rare cases, they may even have completely died out. This is a limitation of only having 4x4x4 LEDs to work with, but it’s a good learning exercise anyway.
To explain the code:
- You may be unfamiliar with memcpy() function. I’ve used this to save the previous game state, as arrays can’t just be assigned to each other like normal variables – you have to actually copy across the memory space (in this case, 64 bits).
- howManyNeighbours() function should be self explanatory, but in case it isn’t – this method takes a single coordinate, and runs through each possible neighbour (the same array of directions we previously used in snake app), to check if they are valid. It then checks if those neighbour LEDs were ‘on’ in the previous game state, and counts how many there are.
- The main function of this Game of Life app is progressGame(), which applies the automata rules to the current game state.
Improvements: I’ve spent far too long on this so far, but you might want to try adding in a check that automatically resets the board after 5 or so generations of the same pattern. then please let me know! I’d also suggest trying to add the POV methodology to the snake game to hopefully make a longer snake possible.
That’s it from me today. I may revisit some more Arduino LED cube apps at a later point, but hopefully you should be in a position to modify my code and create your own game rules: let us know what you come up with in the comments, so we can all download your creations! As ever, I’ll be here to answer your questions and defend my horrendous coding abilities.
Image Credit: cartesian coordinates – Wikimedia user Sakurambo