Cube Puzzle with No Moving Parts
In Part 1, Jeff introduced a circuit that programs a string of NeoPixel LED strips to specific colors, and is controlled by push buttons. In Part 2, he amps up the fun by using that design to craft a cube puzzle that’s similar to a Rubik’s Cube, but without moving parts! Instead, the colors change using switches and colored LEDs.
In the early 1970s, a professor from Budapest, Hungary, wanted to help his students understand three-dimensional problems. Little did he know that his “Buvos Kocka” would take the world by storm, after it demo-ed at variety of international toy fairs—including London, Paris and New York. But Tom Kremer understood its potential and negotiated a worldwide distribution license with its inventor Erno Rubik. This “Magic Cube” has changed little over the past four decades. Almost everyone is familiar with a Rubik’s Cube. It’s become an icon that features championships to find the fastest solver.
According to the official website www.rubiks.com , the current record held for the fastest solve of the Rubik’s Cube is 3.47 seconds by Yusheng Du, who beat the record of Feliks Zemdegs by 0.75 seconds. Even robots—mechanical manipulators using vision input—have been used to solve the Rubik’s Cube. Such robots are incredibly fast, as shown in a typical YouTube video (posted at the end of this article) . Meanwhile, human “Speedcubers” don’t just stop at rapidly solving the traditional Cube with both hands. Zemdegs also holds the record for the fastest one-handed solve of 6.88 seconds and Jakub Kipa from Poland solved the Cube with his feet in an impressive 20.57 seconds!
Were you able to figure out what I was alluding to last month before reading this intro? There are plenty of tutorials on how to solve the Cube, but this isn’t one of them. That will be left up to you. However, I doubt you’ll find a cube anywhere that has no moving parts like the one I’ll describe here! You’ll even be able to solve it at night, thanks to NeoPixels.
Last month I used smart LEDs, which contain a 24-bit shift register, RGB LEDs and the drivers for those LEDs in a single 4-pin package. The NeoPixel is one of these creatures, and can be found on lighting strips that are sold by the meter. That circuit contained nine LEDs, which will be used to indicate the colors of the nine squares on one face of a cube.
Because my cube will have no moving parts, I added eight switches—two on each side of a face—where you would normally grab the cube to rotate a side. Instead of physically rotating a side, the two switches on that side are used to rotate it CW (Clockwise) or CCW (Counter Clockwise). Unlike the Rubik’s Cube, nothing has to physically move here. Instead, the color positions are changed to indicate their new intended positions.
An SPI interface is used to transfer information to and from this circuit. I used an Arduino to test the circuit by reading the status of the eight switches, and setting all the NeoPixels to a different color for each switch. Now it’s time to duplicate the original circuit six times—one for each cube face—and to provide some direction to all these displays from some central microcontroller (MCU). But first let’s take a quick look at how the Rubik’s Cube works, so we can get familiar with the jargon used.
HOW RUBIK’S CUBE WORKS
Many people don’t know or can’t conceptualize how a Rubik’s Cube works. It has only three main pieces—a corner, an edge and a center stub. You can take one apart by rotating any layer about 45 degrees and using a flat-blade screw driver to pry up an edge piece. Then a corner and all the other pieces can be removed (Figure 1). Each center piece has a single color, and all six are connected together to form the central hub. While each can spin independently, they all remain connected together. Their colors—Blue opposite Green, Red opposite Orange, and White opposite Yellow—always stay in the same relationship to one another. This essentially defines the six colored sides of the cube.
Twelve edge pieces have two colors. Not all possible combinations of the six colors are used, because only color combinations on adjacent sides can exist. Eight corner pieces have three colors, and again, only color combinations on adjacent sides can exist. Note how each layer forms an interlocking track that keeps the other pieces in place, while allowing opposite faces to rotate on their axes independently.
I used a drawing package to detail how a completed Cube’s face is referenced to the other faces (Figure 2), as if the cube were made of paper and then unfolded. You can see how all the colored faces are referenced to their neighbors, starting with Orange as the front, White on top, Yellow on the bottom, Blue to the left and Green to the right. The Red is in the rear or aft and connected to the right of Green. Around the edges of all the faces (within dotted lines), I’ve indicated which colors and pieces would pair up when the cube is refolded. This helps to identify 3D parts on a 2D drawing. Each face is made up of nine pieces (three rows of three), with corner pieces labeled 1, 3, 7 and 9, edge pieces 2, 4, 6 and 8, and centerpieces labeled 5. Note that each face is numbered the same way. This looks clear on the drawing, but once folded back up, paired edges are not necessarily numbered the same.
The switches for each face are also identically numbered. If you orient any face toward you, its top left switch will rotate the face CCW, and the right switch will rotate the face CW. The list down the side of Figure 2 identifies the direction in which the face rotates when a switch (diagonal face pairs) is pressed and released. When a face is rotated, the four edge pieces and four corner pieces move 90 degrees CW or CCW. Since the face pieces are, in fact, part of a layer, the whole layer moves 90 degrees CW or CCW. For this project, each move is divided into two independent routines, a face move (rotational) and an edge move (four associated edges of each adjacent face). You can see these listed as two directional rotations.
In our case, we need to move colors from one position to another (Figure 3). Without getting into some tricky math, I used a table last month to hold the three 8-bit values for each of the nine NeoPixels on the face board—that’s 27 registers. I would need six times that—or 162 registers—for the RGB values for every NeoPixel in the cube. That would span multiple banks on the Microchip PIC MCU. So, instead I’ll be using a short-hand version, 2 bits per color, to describe the 24-bit value in 6 bits. As it turns out, I can describe the colors with just four values, OFF = 00, quarter = 01, half = 10, and full ON = 11. Only Orange and Yellow need values other than Full ON or OFF for some of the 8-bit components of the RGB color. So, my table of 56 NeoPixels fits into 56 registers.
The face data is located in the table in this order: White = 0, Orange = 1, Green = 2, Red = 3, Blue= 4 and Yellow = 5. Each face color uses nine registers for their NeoPixels 1-9. Each register will be initialized to its face color. The face color × 9 gives an offset into the table for that face, where the nine NeoPixels are at additional offsets 0-8.
The 2D map enables us to determine which pieces are affected when a rotation is required. Now we need to begin the coding process based on switch releases. The main loop requires just two routines. The first samples the switch status from each of the six faces and stores the results in a short, 6-byte table. Each byte in the table holds the switch status for one face.
The second routine does most of the work, but only if it finds a switch status byte with a 0 in any bit position. The bit position is (0-7) is the switch number (1-8). We also need to know which face is involved. As previously determined, the White face is offset byte 0, Orange face is offset byte 1, and so on. I chose to branch based on bit position, followed by face color. This gets me to one of 12 conditions, rotating one of the faces (one of six colors) either CW or CCW.
We need two routines to accomplish this—one to rotate the face (Listing 1) and one to rotate the associated edges (Listing 2). Let’s take a look at this process by pressing (and releasing) switch 1 on the Blue face. If you were holding the Blue face of a Rubik’s Cube toward you and physically pushed on the top left piece, the top layer would rotate toward the left or CW.
; Rotate the top Face CW
movf Top_Data1, W
movwf Temp ; temporarily store Top_Data1
movf Top_Data4, W
movf Top_Data7, W
movf Top_Data8, W
movf Top_Data9, W
movf Top_Data6, W
movf Top_Data3, W
movf Top_Data2, W
movf Temp, W ; now we can place Top_Data1
incf Count, F
subwf Count, W
btfss STATUS, Z ; skip next if Count=2 (done)
LISTING 1 – This routine rotates the cube faces.
movf Aft_Data3, W
movf Left_Data1, W
movf Left_Data2, W
movf Left_Data3, W
movf Front_Data1, W
movf Front_Data2, W
movf Front_Data3, W
movf Right_Data1, W
movf Right_Data2, W
movf Right_Data3, W
movf Aft_Data1, W
movf Aft_Data2, W
movf Temp, W
incf Count, F
subwf Count, W
btfss STATUS, Z ; skip next if Count=3 (done)
LISTING 2 – This routine rotates the associated edges.
On the 2D map, when BS1 (or GS1) is pushed, the code finds its way to the RotateWCW (rotate White CW) routine. W1 would move to W2, W2 to W3, W3 to W6, W6 to W9 and so on, until finally W4 moves to W1. This movement must happen twice for W1 to end up at W3. To move things more smoothly, I do this backward.
Once the eight NeoPixels of a face have been rotated, the associated four edge and eight corner NeoPixels adjacent to that face also must be rotated. This means that R3 moves to R2, R2 moves to R1, R1 to G3, G3 to G2 and so on, all the way around the face’s edge. The RotateWCW routine continues as shown in Listing 2.
This time, each rotation must be done three times for R3 to get to G3. Once all the moves have been completed for a switch press, all NeoPixels of the cube must be updated by sending each face its new data. In addition, the piezo device “beeps” to give the user feedback on the move, with one beep for each completed move.
POWERING THE PORTABLE DEVICE
NeoPixels drive three internal LED chips with a constant current source. I found that each LED would draw about 13mA when fully ON. The idle state—all OFF—draws a few milliamps. White light is produced when all three are driven to their maximum output, for a total consumption of 40mA. Each face consists of nine LEDs, for a face maximum of 360mA. With six faces, that’s a maximum greater than 2A of continuous current. Since the plan is to use a Li-ion battery, this is within the capabilities of the power source. However, we can reduce this by lowering the pulse width modulation (PWM) values for every NeoPixel.
It’s actually quite simple to implement this without any math routines, if it is done at assembly time. Each color is already defined using the four shortcut values previously mentioned. They are defined as 24-bit numbers in code, as shown in Listing 3.
Color0 equ (None * 0x10000) + (None * 0x100) + None ; black
Color1 equ (All * 0x10000) + (None * 0x100) + None ; red
Color2 equ (All * 0x10000) + (Quarter * 0x100) + None ; orange
Color3 equ (All * 0x10000) + (All * 0x100) + None ; yellow
Color4 equ (None * 0x10000) + (All * 0x100) + None ; green
Color5 equ (None * 0x10000) + (None * 0x100) + All ; blue
Color6 equ (Quarter * 0x10000) + (None * 0x100) + Half ; violet
Color7 equ (All * 0x10000) + (All * 0x100) + All ; white
LISTING 3 – Each color is defined using the four shortcut values. They are shown here as 24-bit numbers in code.
The shortcut values—All, Half, Quarter and None—are predefined as:
None equ 0x00
Quarter equ 0x2F
Half equ 0x7F
All equ 0xFF
Except for None and All, the values are arbitrary and can be modified to your own tastes. To reduce the current consumption (and the intensity), just reduce all values by some percentage. When the ratios of the R, G and B stay the same, the color stays the same, but with a reduced intensity. The shortcut values can be adjusted by some fixed percentage by modifying their values with a new term “percent,” as follows:
percent equ D’10’
None equ (0x00 * percent) / D’100’
Quarter equ (0x2F * percent) / D’100’
Half equ (0x7F * percent) / D’100’
All equ (0xFF * percent) / D’100’
I’ve found that even at 10%, the NeoPixels were bright enough for daytime use indoors. This reduced the current considerably.
Since Li-ion batteries are about 3.7V and the circuit’s operating voltage is 5V, we need a little boost to raise the battery voltage. I like to use Sparkfun’s Charger/Booster to fill these requirements. It has a micro USB port for easy recharging, a 2-pin JST connector for most 3.7V Li-ion batteries and an ON/OFF switch to disconnect the battery. The only caveat is that it has no battery discharge protection, so you should use a Li-ion cell with discharge protection. Most cells do have discharge protection. This is an internal circuit that shuts off the battery output when it reaches a predetermined discharged level, normally around 2.7V, to prevent it from becoming fully discharged.
It’s always great when everything works as designed. With this project, even though I thought through all the moves ahead of time, I still was able to implement some goofs without really trying. Therefore, it was mandatory to go through each move to ensure correct operation. One incorrect move could totally destroy your ability to solve the puzzle. It’s hard enough to solve when all is working as intended, and I had to have confidence that all the rotations worked correctly.
One of the best things I added to this month’s project was a reset button. Because the first step is to initialize all the faces with their respective colors, it was much easier to detect if an error popped up, (color changed) somewhere unexpectedly, when beginning with a clean slate. Once the colors become intermixed, it is much harder to determine if an error occurs.
There are four routines that I can enable to give feedback on what operations are taking place. Two routines help debug the communication between this month’s circuit and the six face PCBs I introduced last month. These send texts on the color information that is sent to each face PCB and the switch status from each face PCB. Updating a face with the correct colors and identifying which switch on which face has been pressed is the most import operation that is being handled by this month’s circuit. The second two routines give feedback on what operations must be performed.
Once a switch change has been detected, we must determine which face needs to be rotated and in which direction, CW or CCW. The first text feedback concerns the face, and the second the rotation. If this isn’t being described correctly, there’s no hope in actually moving anything with confidence. When all moves are being described correctly, you’ve got an accurate trail to follow that points you exactly to the routine that may be misbehaving due to coding issues.
The first prototype (last month) was bread boarded using one of the common NeoPixel strips available from many vendors. This confirmed the process used to send color information to the first nine pixels of the strip. Pixel-to-pixel spacing did not work well for using the strips, so I bit the bullet and bought individual ICs to mount on a PCB. The first set of three PCBs was used to prove the exterior PCBs circuitry and allow for an inside PCB (Figure 4) to be hand-wired, to begin debugging communication, to gather switch information and update face colors.
During this process, I taped together the edges of the first three prototype faces. It was immediately obvious that I needed some way (other than the communication connectors) to hold the faces together. A lot of CNC (computer numerical control) projects use an interlocking tab and slot to make right-angle attachments. This led me to add a tab arrangement to the edges of all faces. It works perfectly for the cube’s structure. Note the close-up photos in Figure 5.
Now that the physical enclosure is coming together, there is one last issue: power. I’ve previously discussed using a Li-ion battery, but the charging port and power switch are still an issue. Prototype 2 mounts the Sparkfun Charge/Boost board to one of the faces (two screws). This places both the power switch and micro USB at the edge of a face. I didn’t like the size of the hole required to reach both the USB and the ON/OFF switch.
For the third prototype, I replicated the Charge/Boost board on the internal PCB. I added a micro USB and tiny slide switch to the face PCB. These are populated on only one face. I added a pair of pins to the 6-pin communication headers, one to disconnect the battery ground and one to connect power from the USB to the charger/boost circuit. This approach removed the need for any large holes. The added surface-mount USB connector and slide switch are very low profile and contribute to Borg-ish look of the finished cube (Figure 6). Resistance is futile!
SOLVING THE CUBE
At the onset, I said I wasn’t going to reiterate procedures for actually solving the Rubik’s Cube, since they can be found on the website www.rubiks.com . However, because we begin on reset with a solved cube, recording each move and being able to reverse it automatically seem like simple tasks to add. Figure 7 shows a video of a motorized Rubik’s Cube I found on www.mastersketchup.com. This isn’t exactly “solving” the cube, but it is kinda cool to watch it undo each move you’ve made, ending up with a solved cube again. This could even be used as demo mode, which could endlessly run through the recorded steps.
To handle this, I added two more routines. The first records the moves in a FILO (First In Last Out) buffer, and the second routine reverses each recorded move. The MoveBuffer is limited to 80 moves. This is not a max limit, but only the size of one continuous bank of available registers. There are many banks available, but this would require crossing bank boundaries, and I wanted to keep it simple for now. To store a move, we only need know which face is being rotated and in which direction. Each move requires only a nibble (12 possibilities), but I chose to use a whole byte. To keep it simple, the upper nibble indicates the face (0-5) and the lower nibble the direction (0-1).
When you use the cube, all moves will be recorded (up to 80). If at any time you wish the cube to be restored, just press switch 1 and switch 2 on any side. This special switch combo will call the RestoreCube routine. Bytes are now recalled from the MoveBuffer, and the recorded operation is reversed by calling the appropriate routine, which moves the selected cube’s face in the opposite direction. This would happen in a flash, unless I put a delay between moves. This arbitrary delay is set for 1 second. This allows each move to be followed by the user, as if the user were doing this manually. Of course, if totally frustrated, you can always push the reset button (or turn the cube OFF/ON) to reset it! Too much to do, so little time.
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • FEBRUARY 2020 #355 – Get a PDF of the issue