robotic rubik's cube

36
Robotic Rubik's Cube Final Report Brian Winkelmann, Hieu Bui, Jiawei Jiang T.A. Ben Cahill Group # 7 ECE 445 - Senior Design 6th May 2015

Upload: others

Post on 18-Dec-2021

24 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Robotic Rubik's Cube

 

Robotic Rubik's Cube

Final Report

Brian Winkelmann, Hieu Bui, Jiawei Jiang

T.A. Ben Cahill

Group # 7

ECE 445 - Senior Design

6th May 2015

 

Page 2: Robotic Rubik's Cube

Abstract

Our project is a giant Rubik’s Cube®[1] that is mechanized via robotics . We installed six

stepper motors to turn each side of the cube. These motors are controlled via a Raspberry Pi 2[2]

that is sending signals to separate stepper motor drivers for each motor. The Raspberry Pi

handles the solving and scrambling of the cube. It also provides three simple control buttons

(Scramble, Solve, Stop) for users to interact with the cube. The entire project is all powered by a

standard North American wall outlet.

Page 3: Robotic Rubik's Cube

Table of Contents

1. Introduction 3

1.1. Objectives

1.1.1. Goals

1.1.2. Benefits

1.1.3. Functions

1.1.4. Features

2. Design 5

2.1. Design Procedure

2.1.1. Microcontroller

2.1.2. Power Supply

2.1.3. Motor Drivers

2.1.4. Stepper Motors

2.1.5. Buttons

2.1.6. Display

2.2. Design Details

2.2.1. Microcontroller

2.2.2. Power Supply

2.2.3. Motor Drivers

2.2.4. Stepper Motors

3. Design Verification 14

3.1. Microcontroller

3.2. Power Supply

3.3. Motor Drivers

3.4. Stepper Motors

3.5. Buttons

3.6. Display

4. Cost 16

4.1. Parts

4.2. Labor

4.3. Grand Total

5. Conclusion 18

5.1. Accomplishments

5.2. Problems and Solutions

5.3. Ethical Considerations

5.4. Safety Concerns

5.5. Future Work

5.5.1. User Interface

5.5.2. Color detection

6. References 20

7. Appendix 21

Page 4: Robotic Rubik's Cube

1. Introduction

Rubik’s Cube® solvers are fun and interesting, but they all currently obstruct the view of

the cube. This makes it hard to grasp what the machine is doing. Our aim is to have a live

tutorial that a user can interact with the cube. We do this by having a large cube mounted on a

pedestal that is visible on all sides. This provides a more user-friendly environment for the

spectators. The cube will have motors embedded inside that can scramble itself, solve itself,

and teach people how to solve it. The cube is able to do this independently of other devices

because of a microcontroller inside. But there will also be a companion computer program that

runs a visual guide to a step-by-step solution of the cube.

Figure 1 - Block Diagram (26 April 2015)

Page 5: Robotic Rubik's Cube

1.1. Objectives

1.1.1. Goals

● Motorized cube that can turn each face

● Drivers created to control all 6 motors

● Controller that is the brain behind the cube

1.1.2. Benefits

● A live tutorial for the user

● Show the entire cube while still turning the faces

1.1.3. Functions

● It can scramble itself

● It can solve itself

● It can go step by step towards the solution

1.1.4. Features

● Companion program on a computer to describe the turns

● Easy controls

● Continuous scramble and solve mode

Page 6: Robotic Rubik's Cube

2. Design

2.1. Design Procedure

2.1.1. Microcontroller

The microcontroller Raspberry Pi 2 and is coded in Python. It takes button inputs to

decide which part of the code to run. It uses this code to determines which motor to be turned

on and in which direction. It sends the generated sequence one turn at a time to the motor

drivers. It will also can connect to a display. If connected, it can show the scramble used and

print what turns it is doing.

2.1.2. Power Supply

The power supply provides power for the microcontroller and the motor driver . It

consists of a transformer, an ON/OFF switch, a 5V voltage regulator and a 12V voltage

regulator. It provides power to the microcontroller via a USB port and to the motor drivers via a

molex connector, with a total power of 11.7 W. We know this because at any time, only one

motor will be activated. Thus the total power requirement of the system is:

V .5A 2V .35A  1.7WP = 5 × 1 + 1 × 0 = 1

Figure 2 - Power Supply Diagram (26 April 2015)

2.1.3. Motor Drivers

The motor driver takes input from the microcontroller and uses that information to turn

the motors. It will be able to turn each motor clockwise and counterclockwise independently.

Page 7: Robotic Rubik's Cube

Figure 3 - Motor Drivers Diagram (6 May 2015)

2.1.4. Stepper Motors

There are six motors to turn each face. Five of these motors are inside the cube, and the

last one is in the base (See the appendix for motor placement). They are directly connected to

the motor drivers, which will power them. They will be able to have full rotation in the

clockwise and counterclockwise direction.

The motor are rated for a 1 kg*cm = 10 N*cm holding torque per phase[6]. The radius of

the cube is the distance from the center to an outside edge or about 127.3 mm

    rτ max =   × F 27.3 mm F 100 N m1 ×   <   *m   .785 NF < 0

The torque to turn a face can vary based on how inaccurate the adjacent face is

positioned. We position the adjacent face to different angle and measure the turning torque.

By using a force gauge attached to the outside edge of the face and pulling perpendicular we

will be able to measure the force and relate that to the torque. We increase the angle until we

measure a force that exceed 0.785N.

Page 8: Robotic Rubik's Cube

2.1.5. Buttons

This is simply a set of three push buttons available to the user. These are accessible in

the front of the pedestal and are the way the user interacts with the cube when a computer is

not connected. “Scramble” will scramble the cube. “Solve” will solve the cube. “Stop” will halt

all motor movement until another button is pressed.

2.1.6. Display

This is an optional component to the project. The cube does not need a display to turn,

but the display is needed to view the program. Most HDMI compatible displays will work

because the raspberry pi will automatically adjust to a display. The display is very useful if the

user wants to follow along with the cube, because the program can print out the scramble

sequence and the solve sequence.

Page 9: Robotic Rubik's Cube

2.2. Design Details

2.2.1. Microcontroller

The program is made of two main parts. The first part is called Solve.py and it is

responsible for a whole bunch of things. It takes the cube state, loads and picks a random

scramble, updates cube for each turn, solves the cube, and prints out the cube representation.

The key to doing all of this is the cube object. The cube object store the cube state in to list, one

for the twelve edges and one for the eight corners. The object is also where we modify the two

list when a turn is executed. The cube object also have a a few had functions like printing the

current state, checking if the cube is solved , and resetting to a solved cube.

The remaining code of Solve.py is dedicated to the solving algorithm. The solve is not a

reversing of all the previous moves, but rather a process of algorithms to return the cube to a

solved state. The process we used is very closely related to the solve guide on the Rubik’s

website[1]. It is a five step process. The hard thing about making this into code is recognizing

where the piece that is next is and what orientation it is in. This is accomplished by a large set

of if statements that were hand coded.

The second part of the program is called Stepper.py and it is responsible for the input

and output of the Raspberry Pi. The input it takes are from the three buttons. The output is the

six enables for the six motors, a step signal, and a direction signal. Stepper.py uses the buttons

to call the solve.py function. those functions return a turn sequence. This turn sequence is

transformed by Stepper.py in to motor signals. For example, if the sequence says to turn the

orange face one turn counterclockwise, the output will enable motor 1, set direction to high,

and the pulses step 50 time to do a quarter turn.

To understand button and their callback functions, we looked at the sample code on

Make[8]. When a button is press it calls it specific callback function. The callback event is

triggered on the rising edge of the button. There is also a delay before accepting another rising

edge so that we do not call the callback function twice on accident. In addition, the buttons are

the way to stop the whole program on the Raspberry Pi. If the user presses all three buttons at

the same time it will stop the motors and halt the code.

Page 10: Robotic Rubik's Cube

Figure 4 - Microcontroller Program Flowchart with Solve Subroutine (6 May 2015)

For demonstration purposes MagicCube[10] was used to show a visual representation of

the cube on the display. If more work is done on the project, we hope to link this to the rest of

the code.

2.2.2. Power Supply

The power supply contains three major parts, the transformer, the rectifier and two

linear regulators. Considering that the total power requirement is 11.7W, we chose a

transformer (Stancor P8130) that takes 120V/AC input and provides 12V/AC output with 2A

current limit, so that the power supply can provide at most 24W output to meet the

requirement.

We chose two linear regulator from the TI µA7800 series to stabilize the rectified DC

voltage to meet the voltage requirement of the microcontroller (5V) and motor driver (12V).

Page 11: Robotic Rubik's Cube

The input voltage of the linear regulator may contain AC with frequency at 60Hz. In

order to filter out the oscillation, we use a bypass capacitor with 0.33 µf capacitance.

  0, 05 ΩZ capacitor 1 = 5 5

Likewise, we also need to filter out the oscillations introduced by the linear regulator.

According to the datasheet, we find out that the maximum frequency is 100Hz, so we choose a

capacitor with a slightly smaller capacitance, which is 0.1 µf .

  00, 00 ΩZ capacitor 2 = 1 0

Figure 5 - Power Supply Circuit (26 April 2015)

2.2.3. Motor Drivers

The motor driver circuit contains six drivers to control the six motors we need for the

system. The driver circuit receives signals from the microcontroller to control the motors.

At the beginning of the project, we select DRV8860, a serial input driver, to work with.

However, this type of driver can only drive unipolar stepper motors while the motors that we

have are bipolar. As a result, we need to switch to another type of driver that can work with our

10 

Page 12: Robotic Rubik's Cube

motors. We then select the A4988 driver to work with. The A4988 also accepts a motor supply

voltage ranging from 8 to 35V, which includes the voltage rating of our motor (10V). In addition

to its ability to drive our motor, it is a Step/Direction control driver which is easier to implement

than the serial input driver.

The driver circuit gets the input voltage from the power supply circuit for the motors.

We decide to have a 12V supply instead of 10V (the voltage rating of the NEMA 14) to have

more power for the motor. The supply voltage is within tolerance and does not damage the

motors.

In order to drive a motor, a low signal needs to be sent to the Enable pin of the driver. Each

rising edge to the Step pin will indicate a step for the motor. The number of pulses determine

the number of steps that we want the motor to move. For instance, a signal with 50 pulses will

make the motor complete a 90o turn. Since only one motor needs to be enabled at a given time,

the Step and Direction pins can be connected to the same output from the microcontroller,

while the Enable pin of each driver are wired to separate output pin from the microcontroller.

In our design, we only need the motors to be in full-step mode. Therefore, MS0, MS2 and MS3

pins are wired to GND. Also, we only have high inputs for the the Sleep and Reset pins since we

do not need to use these features in our design.

To prevent the motors from being damaged due to large current, we need to set the

output current limit of the driver.According to the datasheet of the driver carrier:

Current Limit = VREF x 2.5

Since the current rating of the motor is 500mA/phase, we can calculate VREF = 200mV.

We then adjust the potentiometer on the IC to get a value of 200mV for the reference voltage.

11 

Page 13: Robotic Rubik's Cube

Figure 6 - Microcontroller - Motor Driver Circuit (6 May 2015)

12 

Page 14: Robotic Rubik's Cube

2.2.4. Stepper Motors

We need six motors to turn the six faces of the cube. Our design is to have the motor

mounted inside the cube and have the center pieces attached to the motors so that turning the

center piece will make the whole face to turn. Initially, we selected the NEMA 17, a bipolar

stepper motor, to work with. We decided to choose this type of motor since we can use it to

precisely turn the face of the cube to a proper angle by determining the number of steps the

motor needs to move. However, when assembling the cube, since we did not take the corner of

the motors into account, we found out that the motors were unable to fit inside the cubes.

We then selected the NEMA 14. This is the same type of motor as the NEMA 17. It has

less power and torque, but it is smaller and can fit inside the cube as we desire. However, this

creates a problem for us as we progress towards the end of the project. With the driver circuit

implemented, the motor does not have enough power and torque to turn the whole face of the

cube. We do not have a good method and equipments to accurately determine the required

torque to turn a whole face of the cube. We overlook the importance of the torque needed. As

a result, we fail to complete the project as proposed.

13 

Page 15: Robotic Rubik's Cube

3. Design Verification

3.1. Microcontroller

The microcontroller has plenty of memory space to store the two Python codes and the

1000 scrambles. By opening the scramble file and choosing a random line, we can have a

pseudo random scramble each time. The solving function has 5 steps and after each step we

verified the cube was turning the correct sequence and progressed to the correct state. We

have verified that for each of the 1000 scrambles the cube returns to a solved state after

running the solving function. In fact it does this 1000 scramble test in under 16 seconds. If a

users chooses to they can run the program in interactive mode and run and sequence they

choose.

The output to the stepper motor drivers was verified using an oscilloscope. The

direction and step behaved as expected. This includes 50 steps for a single turn and 100 steps

for a double turn. The motor enables also work well and the code does limit to only having one

motor enabled at any one time.

The inputs are able to detected and run in parallel with the motors turning. The turn

sequence and display cube state can also be shown on the display without interrupting the

motor driver signaling.

3.2. Power Supply

The power supply needs to meet the voltage and power requirements of the

microcontroller and motor driver. We used multimeter to verify that the voltage across the USB

port is 5V ± 0.25V and the voltage across the molex connector is 12V ± 0.5V.

3.3. Motor Driver

The motor driver circuit works well with the microcontroller and the power supply. It

receives the 12V input from the power supply circuit to supply the motors. Also, it receives

signals from the microcontroller to properly drive the motors. At a given time, only one motor

is enabled with the signals from the microcontroller. Once a motor is done with its rotation, it

stops and another motor is enabled. The 6 motors’ rotation accurately corresponds with the

sequence provided by the microcontroller. However, when translating pulses into steps, the

driver sometimes miss a few steps. For example, when the microcontroller outputs 50 pulses,

14 

Page 16: Robotic Rubik's Cube

the driver sometimes only drive the motor to turn 48 or 49 steps. This prevents the center

piece to have a perfect 90o turn. However, this is still within tolerance and does not affect other

faces’ rotation.

3.4. Stepper Motors

As mentioned earlier, we have to switch to a new set of motors since the initial one

does not fit inside the cube. With the new motors, we are able to assemble the cube with the

motors inside and have the faces rotate. In addition, these motors have bidirectional turning.

The center pieces attached to them can turn clockwise and counterclockwise. However, when it

comes to turning a whole face of the cube, the motors fail to do so. More torque and power are

required to turn the face.

3.5. Buttons

Each button sends a desired signal to the microcontroller in order to execute or halt the

program. All the buttons function accurately. The blue “Scramble” button runs the scramble

function to generate scrambling sequence for the driver, the green “Solve” button runs the

solve function to generate solving sequence for the driver, while the red “Stop” button pauses

the motor movement until another “Stop” is pushed again. Pushing all 3 buttons result in the

exit of the program.

3.6. Display

This is the main change to the project from the initial design review. Instead of a

separate computer and display, it is just a display connected to the Raspberry Pi. The display

can show the scramble used and the cube’s state and the end of each step in the solving

process. It is also able to display the step-by-step turns required to solve the cube. A visual

representation of the cube can be displayed, but it is not currently linked to the main program

and therefore is not following along with the motor movements.

15 

Page 17: Robotic Rubik's Cube

4. Cost and Schedule

4.1. Parts

Item Quantity Price Cost

Giant 3x3x3 Cube 1 $37.94 $37.94

Raspberry Pi 2 1 $44.95 $44.95

RaspPi 2 Case 1 $7.95 $7.95

Voltage Regulator µA7805, µA7812[4]

2 $1 $2.00

Rectifier 1 $3 $3.00

Transformer Stancor P8130

1 $15 $15.00

USB type-A jack 1 $0.75 $0.75

A4988 Motor Driver[5] 6 $4.95 $29.70

Stepper Motor NEMA 14[6] 6 $12.95 $77.70

½” CPVC to PVC Bushing 1 $0.98 $0.98

3Pack #8 x 3” Screw and Bolt 2 $1.18 $2.36

⅛” x 2” Cotter Pins 1 $0.78 $0.78

Super Glue 1 $5.77 $5.77

3D Printed Parts 5 $5 $25.00

Push Buttons 3 $2.95 $8.85

GPIO Breakout for RasPi 2 1 $7.95 $7.95

Micro USB cable 1 $2.95 $2.95

HDMI Cable 1 Borrowed $0

Mouse and Keyboard 1 Borrowed $0

Solder and Wires N/A Class Provided $0

Total $273.63

16 

Page 18: Robotic Rubik's Cube

4.2. Labor

Name Hourly Rate Total Time Cost

(2.5 × Hourly Rate × Total Time)

Brian Winkelmann $30.00 225 $16,875

Hieu Bui $30.00 225 $16,875

Jiawei Jiang $30.00 225 $16,875

Total $50,625

4.3. Grand Total

Section Total

Labor $50,625

Parts $273.63

Grand Total $50,898.63

17 

Page 19: Robotic Rubik's Cube

5. Conclusion

5.1. Accomplishment

We completed the basic functionality of the power supply, the microcontroller and the

motor driver circuit. The power supply is able to provide power to the microcontroller and the

motor driver. When we press the buttons, the microcontroller is able to respond and run the

corresponding program to scramble, solve or stop to cube and send the correct combination of

output signals to the motor driver. The motor driver is able to respond to the signals from the

microcontroller and drive the motors to turn accordingly.

5.2. Problems and Solutions

The motors do not have enough torque to turn the faces of the cube when it is fully

assembled. We are considering to use customized motors that can be operated under a higher

voltage and still have the same size. Another solution to this problem is to design a gearing

system that can help the motors overcome the friction to turn the faces.

Having exposed wires on the PCB is unsafe. Therefore, better PCB design and soldering

skill are necessary to ensure safety for the user. Also, having loose wires is another problem

which needs to be addressed. It causes unreliable connection between parts and can affect the

functionality of the circuit. A PCB design for the driver circuit is necessary to ensure proper

functionality of the circuit.

5.3. Ethical Considerations

The purpose of this project is to provide an interactive environment for the user to learn

how to solve a Rubik’s cube. Therefore, the product needs to be safe for the user to interact

with it, which correlates to the first item of the IEEE Code of Ethics[7]:

Item 1: to accept responsibility in making decisions consistent with the safety, health,

and welfare of the public, and to disclose promptly factors that might endanger the public or the

environment;

Besides the above item, we also followed other items listed in the IEEE Code of Ethics:

Item 3: to be honest and realistic in stating claims or estimates based on available data;

Item 5: to improve the understanding of technology; its appropriate application, and

potential consequences;

18 

Page 20: Robotic Rubik's Cube

Item 6: to maintain and improve our technical competence and to undertake

technological tasks for others only if qualified by training or experience, or after full disclosure of

pertinent limitations;

Item 7: to seek, accept, and offer honest criticism of technical work, to acknowledge and

correct errors, and to credit properly the contributions of others;

Item 8: to treat fairly all persons and to not engage in acts of discrimination based on

race, religion, gender, disability, age, national origin, sexual orientation, gender identity, or

gender expression;

Item 9: to avoid injuring others, their property, reputation, or employment by false or

malicious action;

Item 10: to assist colleagues and co-workers in their professional development and to

support them in following this code of ethics.

5.4. Safety Concerns

The main safety concern is that our project has moving parts. These moving parts are a

potential pinch hazard. We avoid excessive risk by having all the metal moving parts enclosed in

plastic housing and not openly accessible. There is also the “Stop” button to halt all motor

movement.

Another safety concern is the power supply is a potential shock hazard. We will reduce

this risk by having the power supply board not openly accessible and also by having a ground

plane on our board to safely handle any arcs.

Our last safety concern is the risk of personal injury on sharp corners. We eliminate this

risk by making the corners of the pedestal all smooth. We also use a cube that has larger

rounded edges on each piece. That way there is no risk of someone being poked or hit by a

sharp corner when the cube is moving.

5.5. Future Work

5.5.1. User Interface

So far, the program only has basic functionalities such as scramble and solve. It does not

explain to the user how the program solves the cube as we proposed at the start of the project.

In the future, we will build a user interface to create more interactions between the user and

19 

Page 21: Robotic Rubik's Cube

the program. The user interface will be able to show the steps to solve the cube, and explain

the concept and algorithm behind them.

5.5.2. Color Detection

Currently, our system can not resume from a power outage during execution. We will

use cameras to detect the color and position of each face, and let the program to determine

the current state of the cube and solve from it.

6. References

[1] Rubik’s Cube® used with permission of Rubiks Brand Ltd. https://www.rubiks.com/

[2] Raspberry Pi Foundation http://www.raspberrypi.org/

[3] World Cubing Association (WCA) https://www.worldcubeassociation.org/

[4] LM805 Voltage Regulator https://www.sparkfun.com/datasheets/Components/LM7805.pdf

[5] A4988 Motor Driver https://www.pololu.com/file/download/A4988.pdf?file_id=0J450

[6] NEMA 14 https://www.pololu.com/file/download/SY35ST28-0504A.pdf?file_id=0J688

[7] IEEE Code of Ethics http://www.ieee.org/about/corporate/governance/p7-8.html

[8] Tutorial: Raspberry Pi GPIO Pins and Python

http://makezine.com/projects/tutorial-raspberry-pi-gpio-pins-and-python/

[9] MagicCube by Jake Vanderplas

https://jakevdp.github.io/blog/2012/11/26/3d-interactive-rubiks-cube-in-python/

[10] Gear Bearing by Emmett Lalish http://www.thingiverse.com/thing:53451

All websites last accessed 6th May 2015

20 

Page 22: Robotic Rubik's Cube

7. Appendix

Oriental Motor - Speed vs Torque Curves for Stepper Motors

http://www.orientalmotor.com/technology/articles/pdfs/SpeedTorqueCurves.pdf

Last accessed: 24th February 2015

Top View of Motor Placement

21 

Page 23: Robotic Rubik's Cube

Front View of Motor Placement

22 

Page 24: Robotic Rubik's Cube

Motor Mount for 5 Internal Motors

Pedestal and Gearing

23 

Page 25: Robotic Rubik's Cube

Code: Scrambles.txt is omitted. It contains 1000 unique scrambles generated by WCA scramble program (TNoodle-WCA-0.9.0.jar). # ­*­ coding: utf­8 ­*­ # UIUC ECE 445  # Spring 2015 # # Winkelmann # Robotic Rubik's Cube # # Solve.py # from numpy.random import randint  file_name = "Scrambles.txt" #file with 1000 scrambles temp = open(file_name,'r') scrambles = [] #store 1000 scrambles for line in temp:     scrambles.append(line.strip()) temp.close()  p = False #When True functions print step labels printstep = False #print verbose steps (i.e. print cube per turn)  seq = [] #Queue to store motor moves  #Cube object that stores current state class Cube_obj(object):     def __init__(self):         #corners         self.c = [['Y','G','R'],                   ['Y','R','B'],                   ['Y','B','O'],                   ['Y','O','G'],                   ['W','G','R'],                   ['W','R','B'],                   ['W','B','O'],                   ['W','O','G']]         #edges         self.e = [['Y','R'],                   ['Y','B'],                   ['Y','O'],                   ['Y','G'],                   ['G','R'],                   ['R','B'],                   ['B','O'],                   ['O','G'],                   ['W','R'],                   ['W','B'],                   ['W','O'],                   ['W','G']]         #centers         #not included      def solved(self): #check if cube is solved         return (self.c == [['Y','G','R'],['Y','R','B'],                             ['Y','B','O'],['Y','O','G'],                             ['W','G','R'],['W','R','B'],                             ['W','B','O'],['W','O','G']] and                  self.e == [['Y','R'],['Y','B'],['Y','O'],['Y','G'],                             ['G','R'],['R','B'],['B','O'],['O','G'],                             ['W','R'],['W','B'],['W','O'],['W','G']])      def __repr__(self): 

24 

Page 26: Robotic Rubik's Cube

        if self.solved():             return "<Cube_obj: current state is SOLVED>"         else:             return "<Cube_obj: current state is SCRAMBLED>"      def __str__(self):         s = "    {}{}{}\n".format(self.c[1][0],self.e[1][0],self.c[2][0])         s +="    {}Y{}\n".format(self.e[0][0],self.e[2][0])         s +="    {}{}{}\n".format(self.c[0][0],self.e[3][0],self.c[3][0])         s +="{}{}{} ".format(self.c[1][1],self.e[0][1],self.c[0][2])         s +="{}{}{} ".format(self.c[0][1],self.e[3][1],self.c[3][2])         s +="{}{}{} ".format(self.c[3][1],self.e[2][1],self.c[2][2])         s +="{}{}{}\n".format(self.c[2][1],self.e[1][1],self.c[1][2])         s += "{}R{} ".format(self.e[5][0],self.e[4][1])         s += "{}G{} ".format(self.e[4][0],self.e[7][1])         s += "{}O{} ".format(self.e[7][0],self.e[6][1])         s += "{}B{}\n".format(self.e[6][0],self.e[5][1])         s +="{}{}{} ".format(self.c[5][1],self.e[8][1],self.c[4][2])         s +="{}{}{} ".format(self.c[4][1],self.e[11][1],self.c[7][2])         s +="{}{}{} ".format(self.c[7][1],self.e[10][1],self.c[6][2])         s +="{}{}{}\n".format(self.c[6][1],self.e[9][1],self.c[5][2])         s +="    {}{}{}\n".format(self.c[4][0],self.e[11][0],self.c[7][0])         s +="    {}W{}\n".format(self.e[8][0],self.e[10][0])         s +="    {}{}{}".format(self.c[5][0],self.e[9][0],self.c[6][0])         return s      def reset(self):         self.__init__()      def turn(self, side):         face = side[0]         if len(side) < 2:             val = 1         elif side[1] == '2':             val = 2         else:             val = 3         for i in range(val):             if face == 'Y':                 #corner cycle                 temp = list(self.c[0])                 self.c[0] = self.c[3]                 self.c[3] = self.c[2]                 self.c[2] = self.c[1]                 self.c[1] = temp                 #edge cycle                 temp = list(self.e[0])                 self.e[0] = self.e[3]                 self.e[3] = self.e[2]                 self.e[2] = self.e[1]                 self.e[1] = temp             elif face == 'W':                 #corner cycle                 temp = list(self.c[4])                 self.c[4] = self.c[5]                 self.c[5] = self.c[6]                 self.c[6] = self.c[7]                 self.c[7] = temp                 #edge cycle                 temp = list(self.e[8])                 self.e[8] = self.e[9]                 self.e[9] = self.e[10]                 self.e[10] = self.e[11]                 self.e[11] = temp             elif face == 'G':                 #corner cycle 

25 

Page 27: Robotic Rubik's Cube

                temp = list(self.c[0])                 self.c[0][0] = self.c[4][2]                 self.c[0][1] = self.c[4][1]                 self.c[0][2] = self.c[4][0]                 self.c[4][0] = self.c[7][1]                 self.c[4][1] = self.c[7][2]                 self.c[4][2] = self.c[7][0]                 self.c[7][0] = self.c[3][1]                 self.c[7][1] = self.c[3][0]                 self.c[7][2] = self.c[3][2]                 self.c[3][0] = temp[2]                 self.c[3][1] = temp[0]                 self.c[3][2] = temp[1]                 #edge cycle 

temp = list(self.e[3])                 self.e[3][0] = self.e[4][1]                 self.e[3][1] = self.e[4][0]                 self.e[4][0] = self.e[11][1]                 self.e[4][1] = self.e[11][0]                 self.e[11] = self.e[7]                 self.e[7] = temp             elif face == 'B':                 #corner cycle                 temp = list(self.c[1])                 self.c[1][0] = self.c[2][2]                 self.c[1][1] = self.c[2][0]                 self.c[1][2] = self.c[2][1]                 self.c[2][0] = self.c[6][2]                 self.c[2][1] = self.c[6][1]                 self.c[2][2] = self.c[6][0]                 self.c[6][0] = self.c[5][1]                 self.c[6][1] = self.c[5][2]                 self.c[6][2] = self.c[5][0]                 self.c[5][0] = temp[1]                 self.c[5][1] = temp[0]                 self.c[5][2] = temp[2]                 #edge cycle                 temp = list(self.e[1])                 self.e[1][0] = self.e[6][1]                 self.e[1][1] = self.e[6][0]                 self.e[6][0] = self.e[9][1]                 self.e[6][1] = self.e[9][0]                 self.e[9] = self.e[5]                 self.e[5] = temp             elif face == 'O':                 #corner cycle                 temp = list(self.c[3])                 self.c[3][0] = self.c[7][2]                 self.c[3][1] = self.c[7][1]                 self.c[3][2] = self.c[7][0]                 self.c[7][0] = self.c[6][1]                 self.c[7][1] = self.c[6][2]                 self.c[7][2] = self.c[6][0]                 self.c[6][0] = self.c[2][1]                 self.c[6][1] = self.c[2][0]                 self.c[6][2] = self.c[2][2]                 self.c[2][0] = temp[2]                 self.c[2][1] = temp[0]                 self.c[2][2] = temp[1]                 #edge cycle                 temp = list(self.e[2])                 self.e[2][0] = self.e[7][1]                 self.e[2][1] = self.e[7][0]                 self.e[7][0] = self.e[10][1]                 self.e[7][1] = self.e[10][0]                 self.e[10] = self.e[6] 

26 

Page 28: Robotic Rubik's Cube

                self.e[6] = temp             elif face == 'R':                 #corner cycle                 temp = list(self.c[0])                 self.c[0][0] = self.c[1][2]                 self.c[0][1] = self.c[1][0]                 self.c[0][2] = self.c[1][1]                 self.c[1][0] = self.c[5][2]                 self.c[1][1] = self.c[5][1]                 self.c[1][2] = self.c[5][0]                 self.c[5][0] = self.c[4][1]                 self.c[5][1] = self.c[4][2]                 self.c[5][2] = self.c[4][0]                 self.c[4][0] = temp[1]                 self.c[4][1] = temp[0]                 self.c[4][2] = temp[2]                 #edge cycle                 temp = list(self.e[0])                 self.e[0][0] = self.e[5][1]                 self.e[0][1] = self.e[5][0]                 self.e[5][0] = self.e[8][1]                 self.e[5][1] = self.e[8][0]                 self.e[8] = self.e[4]                 self.e[4] = temp  def turn(cube, sequence, front = 'G'):     "Takes cube notation and executes the turns"     global seq     inst = reassign(sequence, front).split()     for i in inst:         cube.turn(i)         seq.append(i)         if printstep:              print i             print cube  def next_turn():     "return the next motor move"     global seq     if len(seq) > 0:         return seq.pop(0)     else:         return ' '  def clear_turn(cube):     "Undo moves in seq and clear it"     global seq     while len(seq) > 0:         move = seq.pop() #undo move         if move == "R":             cube.turn("R'")         elif move == "R'":             cube.turn("R")         elif move == "O":             cube.turn("O'")         elif move == "O'":             cube.turn("O")         elif move == "G":             cube.turn("G'")         elif move == "G'":             cube.turn("G")         elif move == "B":             cube.turn("B'")         elif move == "B'":             cube.turn("B")         elif move == "W": 

27 

Page 29: Robotic Rubik's Cube

            cube.turn("W'")         elif move == "W'":             cube.turn("W")         elif move == "Y":             cube.turn("Y'")         elif move == "Y'":             cube.turn("Y")         else: #two move             cube.turn(move)  def reassign(string, front = 'G'):     "Make normal cube notation to motor notation"     string = string.replace('U','Y')     string = string.replace('D','W')     if front == 'G':         string = string.replace('F','G')         #string = string.replace('B','B')         string = string.replace('R','O')         string = string.replace('L','R')     elif front == 'R':         string = string.replace('R','G')         string = string.replace('F','R')         string = string.replace('B','O')         string = string.replace('L','B')     elif front == 'O':         string = string.replace('F','O')         string = string.replace('L','G')         string = string.replace('B','t')         string = string.replace('R','B')         string = string.replace('t','R')     elif front == 'B':         string = string.replace('B','G')         string = string.replace('F','B')         #string = string.replace('R','R')         string = string.replace('L','O')     return string  def scramble(cube, printlabel = False, seed = ­1):     "Chooses a random scramble"     global p     p = printlabel     if p: print "Scrambling Cube"     if seed < 0 or seed > 1000:         index = randint(len(scrambles)) #random num [0­999]     else:         index = seed     if p: print scrambles[index]     turn(cube, scrambles[index])  def white_cross(cube):     if p: print "Solving White Cross"     ey = {'B':1,'O':2,'G':3,'R':0}     es = {'RB':5,'BO':6,'OG':7,'GR':4}     ew = {'B':9,'O':10,'G':11,'R':8}     for f in ['B','O','G','R']:         if cube.e[ew[f]] == ['W',f]: #already right             continue          for i in ew: #move bottom to top             if cube.e[ew[i]] == [f,'W'] or cube.e[ew[i]] == ['W',f]:                 turn(cube, "F2",i)          for i in es: #move side to top             if cube.e[es[i]] == ['W',f]:                 turn(cube, "F' U F",i[1])             elif cube.e[es[i]] == [f,'W']: 

28 

Page 30: Robotic Rubik's Cube

                turn(cube, "F U F'",i[0])          for i in ey: #allign             if cube.e[ey[i]] == ['W',f] or cube.e[ey[i]] == [f,'W']:                 diff = (ey[f] ­ ey[i])%4                 if diff == 1:                     turn(cube,"U")                 elif diff == 2:                     turn(cube,"U2")                 elif diff == 3:                     turn(cube,"U'")                 break          if cube.e[ey[f]][0] == 'W': #match             turn(cube, "F2",f)         else:             turn(cube, "F D R' D'",f)     if p: print "DONE"  def white_corners(cube):     if p: print "Solving White Corner"     cy = {'RB':1,'BO':2,'OG':3,'GR':0}     cw = {'RB':5,'BO':6,'OG':7,'GR':4}     for f in ['RB','BO','OG','GR']:         if cube.c[cw[f]] == ['W',f[0],f[1]]: #already right             continue          for i in cw: #move bottom to top             if (cube.c[cw[i]] == ['W',f[0],f[1]] or                 cube.c[cw[i]] == [f[1],'W',f[0]] or                 cube.c[cw[i]] == [f[0],f[1],'W']):                 turn(cube, "F U F'",i[0])          for i in cy: #allign             if (cube.c[cy[i]] == ['W',f[1],f[0]] or                 cube.c[cy[i]] == [f[0],'W',f[1]] or                 cube.c[cy[i]] == [f[1],f[0],'W']):                 diff = (cy[f] ­ cy[i])%4                 if diff == 1:                     turn(cube,"U")                 elif diff == 2:                     turn(cube,"U2")                 elif diff == 3:                     turn(cube,"U'")                 break          while cube.c[cw[f]] != ['W',f[0],f[1]]: #match             turn(cube, "R U R' U'", f[1])     if p: print "DONE"  def middle_layer(cube):     if p: print "Solving Middle Layer"     ey = {'B':1,'O':2,'G':3,'R':0}     es = {'RB':5,'BO':6,'OG':7,'GR':4}     for f in ['RB','BO','OG','GR']:         if cube.e[es[f]] == [f[0],f[1]]: #already right             continue          for i in es: #move side to top             if cube.e[es[i]] == [f[0],f[1]] or cube.e[es[i]] == [f[1],f[0]]:                 turn(cube,"R U' R' U' F' U F",i[1])          for i in ey: #allign and match             if cube.e[ey[i]] == [f[0],f[1]]: #allign to f[1]                 diff = (ey[f[1]] ­ ey[i])%4                 if diff == 1: 

29 

Page 31: Robotic Rubik's Cube

                    turn(cube,"U")                 elif diff == 2:                     turn(cube,"U2")                 elif diff == 3:                     turn(cube,"U'")                 turn(cube,"U R U' R' U' F' U F",f[1])#right turn                 break             if cube.e[ey[i]] == [f[1],f[0]]: #allign to f[0]                 diff = (ey[f[0]] ­ ey[i])%4                 if diff == 1:                     turn(cube,"U")                 elif diff == 2:                     turn(cube,"U2")                 elif diff == 3:                     turn(cube,"U'")                 turn(cube,"U' L' U L U F U' F'",f[0])#left turn                 break     if p: print "DONE"  def top_face(cube):     if p: print "Solving Top Face"     #yellow cross     ey = {'B':1,'O':2,'G':3,'R':0}     if (cube.e[ey['B']][1] == 'Y' and         cube.e[ey['O']][1] == 'Y' and         cube.e[ey['G']][1] == 'Y' and         cube.e[ey['R']][1] == 'Y'): #dot shape         turn(cube,"F U R U' R' F'")     if (cube.e[ey['B']][0] == 'Y' and         cube.e[ey['O']][1] == 'Y' and         cube.e[ey['G']][0] == 'Y' and         cube.e[ey['R']][1] == 'Y'): #BG line         turn(cube,"F R U R' U' F'", 'R')     elif (cube.e[ey['B']][1] == 'Y' and           cube.e[ey['O']][0] == 'Y' and           cube.e[ey['G']][1] == 'Y' and           cube.e[ey['R']][0] == 'Y'): #RO line         turn(cube,"F R U R' U' F'", 'G')     elif (cube.e[ey['B']][0] == 'Y' and           cube.e[ey['O']][1] == 'Y' and           cube.e[ey['G']][1] == 'Y' and           cube.e[ey['R']][0] == 'Y'): #RB L shape         turn(cube,"F U R U' R' F'", 'G')     elif (cube.e[ey['B']][0] == 'Y' and           cube.e[ey['O']][0] == 'Y' and           cube.e[ey['G']][1] == 'Y' and           cube.e[ey['R']][1] == 'Y'): #BO L shape         turn(cube,"F U R U' R' F'", 'R')     elif (cube.e[ey['B']][1] == 'Y' and           cube.e[ey['O']][0] == 'Y' and           cube.e[ey['G']][0] == 'Y' and           cube.e[ey['R']][1] == 'Y'): #OG L shape         turn(cube,"F U R U' R' F'", 'B')     elif (cube.e[ey['B']][1] == 'Y' and           cube.e[ey['O']][1] == 'Y' and           cube.e[ey['G']][0] == 'Y' and           cube.e[ey['R']][0] == 'Y'): #GR L shape         turn(cube,"F U R U' R' F'", 'O')      #yellow corners     cy = {'RB':1,'BO':2,'OG':3,'GR':0}     up = (int(cube.c[cy['RB']][0] == 'Y') +           int(cube.c[cy['BO']][0] == 'Y') +           int(cube.c[cy['OG']][0] == 'Y') +           int(cube.c[cy['GR']][0] == 'Y'))     while up < 4: 

30 

Page 32: Robotic Rubik's Cube

        if up == 0: #state 1             for i in cy:                 if cube.c[cy[i]][2] == 'Y':                     f = i[0]                     break         elif up == 1: #state 2             for i in cy:                 if cube.c[cy[i]][0] == 'Y':                     f = i[0]                     break         elif up == 2: #state 3             for i in cy:                 if cube.c[cy[i]][1] == 'Y':                     f = i[0]                     break         else:             if p: print "~~CUBE ERROR: CORNERS~~"             break         turn(cube,"R U R' U R U2 R'", f)         up = (int(cube.c[cy['RB']][0] == 'Y') +               int(cube.c[cy['BO']][0] == 'Y') +               int(cube.c[cy['OG']][0] == 'Y') +               int(cube.c[cy['GR']][0] == 'Y'))     if p: print "DONE"  def final_layer(cube):     if p: print "Solving Final Layer"     #corners position     cy = {'RB':1,'BO':2,'OG':3,'GR':0}     match = (int(cube.c[cy['RB']] == ['Y','R','B']) +              int(cube.c[cy['BO']] == ['Y','B','O']) +              int(cube.c[cy['OG']] == ['Y','O','G']) +              int(cube.c[cy['GR']] == ['Y','G','R']))     while match < 2:         turn(cube,"U")         match = (int(cube.c[cy['RB']] == ['Y','R','B']) +                 int(cube.c[cy['BO']] == ['Y','B','O']) +                 int(cube.c[cy['OG']] == ['Y','O','G']) +                 int(cube.c[cy['GR']] == ['Y','G','R']))     if match == 4:          pass #matched     elif (cube.c[cy['RB']] == ['Y','R','B'] and            cube.c[cy['BO']] == ['Y','B','O']):         turn(cube,"R' F R' B2 R F' R' B2 R2 U'", 'G')     elif (cube.c[cy['BO']] == ['Y','B','O'] and            cube.c[cy['OG']] == ['Y','O','G']):         turn(cube,"R' F R' B2 R F' R' B2 R2 U'", 'R')     elif (cube.c[cy['OG']] == ['Y','O','G'] and            cube.c[cy['GR']] == ['Y','G','R']):         turn(cube,"R' F R' B2 R F' R' B2 R2 U'", 'B')     elif (cube.c[cy['GR']] == ['Y','G','R'] and            cube.c[cy['RB']] == ['Y','R','B']):         turn(cube,"R' F R' B2 R F' R' B2 R2 U'", 'O')     else: #cross         if cube.c[cy['GR']] == ['Y','G','R']:             turn(cube,"U2")         turn(cube,"R' F R' B2 R F' R' B2 R2 U2", 'G')         turn(cube,"R' F R' B2 R F' R' B2 R2 U'", 'R')      #edge position     ey = {'B':1,'O':2,'G':3,'R':0}     match = (int(cube.e[ey['B']][1] == 'B') +              int(cube.e[ey['O']][1] == 'O') +              int(cube.e[ey['G']][1] == 'G') +              int(cube.e[ey['R']][1] == 'R'))     if match == 0: #no matches 

31 

Page 33: Robotic Rubik's Cube

        turn(cube,"F2 U L R' F2 L' R U F2") #makes a match     if match == 4: #all done         pass     elif match == 2:         if p: print "~~CUBE ERROR: EDGES~~"     else: #1 match         if cube.e[ey['B']][1] == 'B':             f = 'G'             r = 'R'         elif cube.e[ey['O']][1] == 'O':             f = 'R'             r = 'B'         elif cube.e[ey['G']][1] == 'G':             f = 'B'             r = 'O'         elif cube.e[ey['R']][1] == 'R':             f = 'O'             r = 'G'         if cube.e[ey[f]][1] != r:             turn(cube,"F2 U' L R' F2 L' R U' F2", f) #counterclockwise         else:             turn(cube,"F2 U L R' F2 L' R U F2", f) #clockwise       if p: print "DONE"  def solve(cube, printlabel = False, printfig = False):     "Solve the cube based on rubiks.com guide"     global p      p = printlabel     if p: print "Solving Cube"     if printfig: print cube     white_cross(cube)     if printfig: print cube     white_corners(cube)     if printfig: print cube     middle_layer(cube)     if printfig: print cube     top_face(cube)     if printfig: print cube     final_layer(cube)     if printfig: print cube     if p: print "SOLVED"  def testing(cube):     global seq     total = 0     nseq = []     for i in range(1000):         scramble(cube, seed = i)         solve(cube)         total += cube.solved() #increment is correctly solved         nseq.append(len(seq))         seq = []     print "Solved {} cubes".format(total)     print "{} turns on average".format(sum(nseq)/float(len(nseq)))  def print_details(value = True):     global printstep     printstep = value  if __name__ == "__main__": #main function     cube = Cube_obj()     testing(cube)     

32 

Page 34: Robotic Rubik's Cube

# ­*­ coding: utf­8 ­*­ # UIUC ECE 445  # Spring 2015 # # Winkelmann # Robotic Rubik's Cube # # Stepper.py # # For Raspberry Pi 2  import RPi.GPIO as GPIO import time import Solve  #GPIO.setmode(GPIO.BOARD) #use pin numbers GPIO.setmode(GPIO.BCM) #use GPIO name  #Pin assignment orange_face = 18 #motor 1 green_face = 23 #motor 2 blue_face = 24 #motor 3 red_face = 25 #motor 4 yellow_face = 12 #motor 5 white_face = 16 #motor 6  step = 20 direct = 21  stop_button = 22 #red button scramble_button = 17 #blue button solve_button = 27 #green button   GPIO.setwarnings(False) #disable warnings #Setup pins GPIO.setup(white_face, GPIO.OUT) GPIO.setup(yellow_face, GPIO.OUT) GPIO.setup(green_face, GPIO.OUT) GPIO.setup(blue_face, GPIO.OUT) GPIO.setup(red_face, GPIO.OUT) GPIO.setup(orange_face, GPIO.OUT)  GPIO.setup(step, GPIO.OUT) GPIO.setup(direct, GPIO.OUT)  GPIO.setup(stop_button, GPIO.IN, pull_up_down=GPIO.PUD_UP) #connect to GND GPIO.setup(scramble_button, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(solve_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)  #Global variables cube = Solve.Cube_obj() #Cube object  command = "" #string to receive commands  printl = False #print the labels printf = False #print the figures prints = False #print the steps  stopped = False #toggle movement  quarter_turn = 50 #50 steps  step_delay = 0.02 #secondss turn_delay = 1.0 #secondss button_delay = 300 #milliseconds  turns = {"W": (white_face,1),"W'": (white_face,­1), "W2": (white_face,2), 

33 

Page 35: Robotic Rubik's Cube

         "Y": (yellow_face,1),'Y\'': (yellow_face,­1), "Y2": (yellow_face,2),          "G": (green_face,1), 'G\'': (green_face,­1), "G2": (green_face,2),          "B": (blue_face,1), 'B\'': (blue_face,­1), 'B2': (blue_face,2),          "R": (red_face,1), 'R\'': (red_face,­1), 'R2': (red_face,2),          "O": (orange_face,1), 'O\'': (orange_face,­1), 'O2': (orange_face,2)}  def moving():     move = Solve.next_turn()     while move != ' ':         turning(move)         move = Solve.next_turn()  def turning(instructions):     "Executes turn instructions"     seq = instructions.split()     for i in seq:         val = turns[i]         if val[1] == 1:             set_direction(0)             for i in range(quarter_turn):                 motor_step(val[0])             motor_off()         elif val[1] == ­1:             set_direction(1)             for i in range(quarter_turn):                 motor_step(val[0])             motor_off()         elif val[1] == 2:             set_direction(0)             for i in range(2*quarter_turn):                 motor_step(val[0])             motor_off()         time.sleep(turn_delay)  def motor_step(pin):     "Turn motor one step"     while stopped: #busy loop         time.sleep(0.1)     motor_on(pin)      GPIO.output(step, GPIO.HIGH)     time.sleep(step_delay/2.0)     GPIO.output(step, GPIO.LOW)     time.sleep(step_delay/2.0)     def set_direction(direction = 0):     if direction == 0:         GPIO.output(direct, GPIO.LOW)     else:         GPIO.output(direct, GPIO.HIGH)  def motor_on(pin):     "Turns motor on"     GPIO.output(pin, GPIO.LOW)  def motor_off():     "Turns all motors off"     GPIO.output(white_face, GPIO.HIGH)     GPIO.output(yellow_face, GPIO.HIGH)     GPIO.output(green_face, GPIO.HIGH)     GPIO.output(blue_face, GPIO.HIGH)     GPIO.output(red_face, GPIO.HIGH)     GPIO.output(orange_face, GPIO.HIGH)  def stop_callback(channel): 

34 

Page 36: Robotic Rubik's Cube

    "Toggles motor movement on button push"     global stopped     stopped = not stopped        if stopped:         if printl:             print "Stopped"         motor_off()     else:         if printl:             print "Running"  def solve_callback(channel):     "Call solve function on button push"     if printl:         print "Solve Button Pressed"     if not stopped:         Solve.clear_turn(cube) #clear queue         Solve.solve(cube, printl, printf) #new task  def scramble_callback(channel):     "Call scramble function on button push"     if printl:         print "Scramble Button Pressed"     if not stopped:         Solve.clear_turn(cube) #clear queue         Solve.scramble(cube, printl) #new task  #Button Callback Event GPIO.add_event_detect(stop_button, GPIO.FALLING, callback = stop_callback, bouncetime = button_delay) GPIO.add_event_detect(solve_button, GPIO.FALLING, callback = solve_callback, bouncetime = button_delay) GPIO.add_event_detect(scramble_button, GPIO.FALLING, callback = scramble_callback, bouncetime = button_delay)  def set_print(printlabel = False, printfig = False, printstep = False):     global printl, printf, prints     printl = printlabel     printf = printfig     Solve.print_details(printstep)  motor_off()  if __name__ == "__main__": #main function     set_print(True, True)     while True:         try:             if len(command) > 0:                 pass #add code to listen to gui commands             if ((not GPIO.input(stop_button)) and                  (not GPIO.input(scramble_button)) and                  (not GPIO.input(solve_button))):                 print "Program Finished"                 break #stop running if all buttons pressed             #moving()         except KeyboardInterrupt:             print             if printl:                 print "Program  Halted"             break  motor_off() #GPIO.cleanup()  

35