Hardware components | ||||||
| × | 1 | ||||
| × | 3 | ||||
| × | 12 | ||||
| × | 1 | ||||
Hand tools and fabrication machines | ||||||
| ||||||
|
The idea was to build a Rubik's cube solver robot, as simple as possible, easy to implement at the hardware and software levels. I was checking a lot of designs, ideas, concepts and finally, in a casual manner, find a video posted by a girl from Korea, it was the idea I was looking for : As simple as possible.
AdvantagesThis model have advantages about:
- Price of the material : total budget is low US$30, easy to find all the items of the BoM
- Implementation time - one (1)day
- Connections - one for each servo that is all - 6- jumpers for servos-2 jumpers for power source
- Calibration : the arm design helps to the realignment of the cube's faces when these get some small errors originated in the rotation-Time to calibrate one day as maximum.
- Calculation of distances and elements of the robot is quick and easy
- Arduino code is reduced. clean code and the time to upload the stickers status could be optimized
1. Build Arm
Find a piece of wood flat to use as base of the model
With the popsicles sticks build the arm according to the photo, or I wish easily better than this one.
With the cube as reference built the rotating box- tolerance between the wall and the cube should be 3-mm
For the rotation box you need to incline it parallel to the arm at the point where the arm HOLD the cube.
2. Connections
Connect the servos to Arduino using PWM input/output
Use a separate 5V DC power supply dedicated to the servos
Unify all the grounds - Servos, Arduino-Source
3. Software
Upload the software and call the serial monitor to check movements report, one by one. Previously you need to call python IDE, call kociemba module enter the cube status and solve it, then paste the solution in the arduino program.
Obviously if you have more budget, you can use a camera and one program to do this in an automated way, using python and opencv. I developed this one for other model based on stepper motors:
This is my T-shirt design, I am really a fan of the Rubik´s cube.
Please Watch the video.
/*
* Developed and integrated by: Hernando Bolanos
*
* code lines and sOurces:
* 1.Sweep by BARRAGAN <http://barraganstudio.com>
* modified 8 Nov 2013
* 2. by Scott Fitzgerald
* http://www.arduino.cc/en/Tutorial/Sweep
* 3. Instructables -Matt2Yu -ALGORTIHM, CONCEPT FOR CFOP METHOD
* Version 1- 16-AGOSTO-2020
*
* Best calibration settings: -move speed 6-push 146- take into account the servos orientation- if it is not the same you have to adapt all the calibration angles and timing
* Is must to use PWM outputs- This was implmented with a nodemcu
*/
#include <Servo.h>
//#include <Solver.h>
// servo objects
Servo rotate_servo;
Servo push_servo;
int move_speed = 6 ; //6 - CALIBRATION -PUSH 146
int buffer_time = 105; // time between moves
int rotate_pos = 90;
int push_pos = 110;
int hold_progress = 3;
int offset_degrees = 3;
bool slow_push = false;
String kociemba_sol = "";
//////// cube move variables:
bool sim_only = false;
// test function:
bool test_ongoing = true;
////////////////////// Serial Communication (receive arrays or kociemba solution from any .py script)
void accept_string()
{
char ready_signal = 'ready';
char received_signal = 'received';
for (int piece_num = 0; piece_num <5; piece_num++)
{
// send ready signal
Serial.println(ready_signal);
delay(100);
}
// receive string
while(kociemba_sol == "")
{
char character;
while(Serial.available())
{
character = Serial.read();
kociemba_sol.concat(character);
}
}
delay(10);
Serial.print("String Aceptado: ");
Serial.print(kociemba_sol);
// send color confirmed signal
Serial.println("arduino dice:");
Serial.println(received_signal);
Serial.println(kociemba_sol);
delay(10);
}
///////// Cube movement functions: ////////////
int move_servo(int start, int finish, int servo_pin)
{
int pos;
if (start - finish < 0)
{
for(pos = start; pos <= finish; pos += 1)
{
if (servo_pin == 6)
{
push_servo.write(pos);
delay(move_speed);
}
else if (servo_pin == 9)
{
rotate_servo.write(pos);
delay(move_speed);
}
}
}
else
{
for(pos = start; pos >= finish; pos -= 1)
{
if (servo_pin == 6)
{
push_servo.write(pos);
delay(move_speed);
}
else if (servo_pin == 9)
{
rotate_servo.write(pos);
delay(move_speed);
}
}
}
// use a swich case next time
if (servo_pin == 9)
{
rotate_pos = pos;
}
if (servo_pin == 6)
{
push_pos = pos;
}
delay(buffer_time);
}
///////// Cube movement functions: ////////////
void push_cube(int num_of_pushes = 1)
{
if (num_of_pushes == 1)
{
if (slow_push == false)
{
move_servo(push_pos, 146, 6);//72
delay(buffer_time);
release_cube();
delay(buffer_time);
}
else // on rotate one
{
move_servo(push_pos, 146, 6);//72
delay(buffer_time+200);
release_cube();
delay(buffer_time);
}
}
else
{
while (num_of_pushes != 0)
{
if (slow_push == false)
{
move_servo(push_pos, 146, 6);//72
delay(buffer_time+50);
move_servo(push_pos, 110, 6);//original 120
delay(buffer_time);
num_of_pushes--;
}
else // on rotate one
{
move_servo(push_pos, 110, 6);//72
delay(buffer_time+200);
move_servo(push_pos, 146, 6);//original 130
delay(buffer_time);
num_of_pushes--;
}
}
release_cube();
}
}
void hold_cube()
{
move_servo(push_pos, 133, 6);//
hold_progress = 1;
}
void release_cube()
{
move_servo(push_pos, 105, 6);
hold_progress = 3;
}
void rotate_one()
{
slow_push = true;
int rotate_finish = -5;//11-4
if (hold_progress == 1) // hold progress 1 = hold
{
// from rotate_two
if (rotate_pos < 140)
{
// initial turn
move_servo(rotate_pos, rotate_finish-2, 9);
move_servo(rotate_pos, rotate_finish+5, 9);
// release and turn some more
release_cube();
move_servo(rotate_pos, 101, 9);
hold_cube();
move_servo(rotate_pos, 75, 9);//82original/72 /75
move_servo(rotate_pos, 92, 9); // prevent pulling
release_cube();
move_servo(rotate_pos, rotate_finish, 9);
}
// from rotate_three
else if (rotate_pos > 140)
{
// initial turn
move_servo(rotate_pos, rotate_finish-5, 9);
move_servo(rotate_pos, rotate_finish+10, 9);
// release and turn some more
release_cube();
move_servo(rotate_pos, 108, 9);
hold_cube();
move_servo(rotate_pos, 83, 9);//83 origial
move_servo(rotate_pos, 93, 9); // prevent pulling
release_cube();
move_servo(rotate_pos, rotate_finish, 9);
}
hold_progress = 2;
}
else if (hold_progress == 2) // hold progress 2 = release, but offset still there
{
hold_progress = 3;
move_servo(rotate_pos, rotate_finish, 9);
}
else if (hold_progress == 3) // hold progress 3 = release, offsets reconciled
{
// do nothing
move_servo(rotate_pos, rotate_finish, 9);
}
}
void rotate_two()
{
slow_push = false;
int rotate_finish = 90;
if (hold_progress == 1) // hold progress 1 = hold
{
// rotate from rotate_one
if (rotate_pos < 50)
{
// initial turn
move_servo(rotate_pos, rotate_finish+3, 9);
move_servo(rotate_pos, rotate_finish-5, 9);
// release and turn some more
release_cube();
move_servo(rotate_pos, 0, 9);
hold_cube();
move_servo(rotate_pos, 18, 9);//original 18/25/20
move_servo(rotate_pos, 12, 9); // prevent pulling/original 8/2
release_cube();
move_servo(rotate_pos, rotate_finish, 9);
}
// rotate from rotate_three
else if (rotate_pos > 150)
{
move_servo(rotate_pos, rotate_finish-1, 9);
move_servo(rotate_pos, rotate_finish+1, 9);
// release and turn some more
release_cube();
move_servo(rotate_pos, 179, 9);
hold_cube();
move_servo(rotate_pos, 170, 9);//orignal 170
move_servo(rotate_pos, 178, 9); // prevent pulling
release_cube();
move_servo(rotate_pos, rotate_finish, 9);
}
hold_progress = 2;
}
else if (hold_progress == 2) // hold progress 2 = release, but offset still there
{
hold_progress = 3;
move_servo(rotate_pos, rotate_finish, 9);
}
else if (hold_progress == 3) // hold progress 3 = release, offsets reconciled
{
// do nothing
move_servo(rotate_pos, rotate_finish, 9);
}
}
void rotate_three()
{
slow_push = false;
int rotate_finish = 179;
if (hold_progress == 1) // hold progress 1 = hold
{
// from rotate_two
if (rotate_pos > 40)
{
move_servo(rotate_pos, rotate_finish+10, 9);
move_servo(rotate_pos, rotate_finish-1, 9); // prevent pulling
// fix: cube not fully turned
release_cube();
move_servo(rotate_pos, 90, 9);
hold_cube();
move_servo(rotate_pos, 105, 9);// original 110
move_servo(rotate_pos, 106, 9); // prevent pulling-100
release_cube();
move_servo(rotate_pos, rotate_finish, 9);
}
// from rotate_one
if (rotate_pos < 40)
{
move_servo(rotate_pos, rotate_finish+5, 9);
move_servo(rotate_pos, rotate_finish-3, 9); // prevent pulling
// fix: cube not fully turned
release_cube();
move_servo(rotate_pos, 80, 9);
hold_cube();
move_servo(rotate_pos, 110, 9);//original 100/110
move_servo(rotate_pos, 90, 9); // prevent pulling
release_cube();
move_servo(rotate_pos, rotate_finish, 9);
}
hold_progress = 2;
}
else if (hold_progress == 2) // hold progress 2 = release, but offset still there
{
hold_progress = 3;
move_servo(rotate_pos, rotate_finish, 9);
}
else if (hold_progress == 3) // hold progress 3 = release, offsets reconciled
{
// do nothing
move_servo(rotate_pos, rotate_finish, 9);
}
}
///////////////////// Cube Move Notation ///////////////////////////
// They print, simulate and call the physical functions
void right_inverted()
{
Serial.println("R', ");
if (sim_only == false)
{
rotate_three();
push_cube();
hold_cube();
rotate_two();
release_cube();
rotate_one();
push_cube();
rotate_two();
push_cube(3);
}
}
void right()
{
Serial.println("R, ");
if (sim_only == false)
{
rotate_three();
push_cube();
rotate_two();
hold_cube();
rotate_three();
release_cube();
rotate_one();
push_cube();
rotate_two();
push_cube();
}
}
void left_inverted()
{
Serial.println("L', ");
if (sim_only == false)
{
rotate_one();
push_cube();
rotate_two();
hold_cube();
rotate_one();
release_cube();
rotate_three();
push_cube();
rotate_two();
push_cube();
}
}
void left()
{
Serial.println("L, ");
if(sim_only == false)
{
rotate_one();
push_cube();
hold_cube();
rotate_two();
release_cube();
rotate_three();
push_cube();
rotate_two();
push_cube(3);
}
}
void down_inverted()
{
Serial.println("D', ");
if (sim_only == false)
{
hold_cube();
rotate_one();
release_cube();
rotate_two();
push_cube();
rotate_one();
push_cube();
rotate_two();
push_cube(3);
}
}
void down()
{
Serial.println("D, ");
if (sim_only == false)
{
hold_cube();
rotate_three();
release_cube();
rotate_two();
push_cube();
rotate_three();
push_cube();
rotate_two();
push_cube(3);
}
}
void up_inverted()
{
Serial.println("U', ");
if (sim_only == false)
{
push_cube(2);
hold_cube();
rotate_one();
release_cube();
rotate_two();
push_cube();
rotate_one();
push_cube();
rotate_two();
push_cube();
}
}
void up()
{
Serial.println("U, ");
if (sim_only == false)
{
push_cube(2);
hold_cube();
rotate_three();
release_cube();
rotate_two();
push_cube();
rotate_three();
push_cube();
rotate_two();
push_cube();
}
}
void front_inverted()
{
Serial.println("F', ");
if (sim_only == false)
{
push_cube(3);
hold_cube();
rotate_one();
release_cube();
rotate_two();
push_cube();
rotate_one();
push_cube();
rotate_two();
}
}
void front()
{
Serial.println("F, ");
if (sim_only == false)
{
push_cube(3);
hold_cube();
rotate_three();
release_cube();
rotate_two();
push_cube();
rotate_three();
push_cube();
rotate_two();
}
}
void back_inverted()
{
Serial.println("B', ");
if (sim_only == false)
{
push_cube();
hold_cube();
rotate_one(); // ccw
release_cube();
rotate_two();
push_cube(3);
rotate_three(); //cw
push_cube();
rotate_two();
}
}
void back()
{
Serial.println("B, ");
if (sim_only == false)
{
push_cube();
hold_cube();
rotate_three();
release_cube();
rotate_two();
push_cube(3);
rotate_one();
push_cube();
rotate_two();
}
}
// insert top layer edges
// miscellaneous algorithms
void warm_up() // do it six times to get back to the original position
{
Serial.println();
Serial.print("Warmup: ");
Serial.print("R', D', R, D");
//r'
rotate_one();
push_cube();
hold_cube();//
rotate_two();
release_cube();
rotate_three();
push_cube();
rotate_two();
push_cube(3);
//d'
hold_cube();//
rotate_three();
release_cube();
//r start here
rotate_two();
push_cube();
rotate_three();
hold_cube();
rotate_two();
release_cube();
// d
rotate_three();
push_cube();
hold_cube();
rotate_two();
release_cube();
push_cube();
rotate_one();
push_cube();
rotate_two();
push_cube(3);
}
void superflip() // all edges are opposite (checkered pattern)
{
Serial.println();
Serial.println("Superflip: ");
up();
up();
down();
down();
left();
left();
right();
right();
front();
front();
back();
back();
}
// test it
void scramble() // random 25 moves - if you need continuos solve-scramble and resolution this part of code would be usefull
{
Serial.println();
Serial.println("Scramble: ");
int move;
for(int j = 0; j < 25; j++)
{
move = random(1, 12);
//Serial.println(move);
switch(move)
{
case 1:
right();
break;
case 2:
right_inverted();
break;
case 3:
left();
break;
case 4:
left_inverted();
break;
case 5:
up();
break;
case 6:
up_inverted();
break;
case 7:
down();
break;
case 8:
down_inverted();
break;
case 9:
front();
break;
case 10:
front_inverted();
break;
case 11:
back();
break;
case 12:
back_inverted();
break;
}
}
}
void run_kociemba(){
//"L2 D' R' L2 D2 R F' D2 F2 R' B' L2 D R2 L2 D L2 B2 U' B2 U2"
kociemba_sol= "B L' F' B R F2 U2 D R2 F D F2 U R2 F2 L2 B2 R2 D2 R2";
//"L U' L B2 U2 F' D R2 F L F2 R2 F2 U2 D B2 L2 D2 B2 U'";
// Length (with one extra character for the null terminator)
int str_len = kociemba_sol.length() + 1;
//Serial.println("arduino : STRINGS RECEIVED:");
//Serial.println((str_len-1));
for(int i = 0; i <= (str_len-1); i++){ //recorre
//Serial.print(i);
if ((kociemba_sol.charAt(i)) =='R'){
if ((kociemba_sol.charAt(i+1)) == '2') {
right();
right();}
if ((kociemba_sol.charAt(i+1)) == '\'') {
right_inverted();}
if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
right();}
}
else { // DO NOTHING
}
if ((kociemba_sol.charAt(i)) =='L'){
if ((kociemba_sol.charAt(i+1)) == '2') {
left();
left();}
if ((kociemba_sol.charAt(i+1)) == '\'') {
left_inverted();}
if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
left();}
}
else { //DO NOTHING
}
if ((kociemba_sol.charAt(i)) =='U'){
if ((kociemba_sol.charAt(i+1)) == '2') {
up();
up();}
if ((kociemba_sol.charAt(i+1)) == '\'') {
up_inverted();}
if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
up();}
}
else { //DO NOTHING
}
if ((kociemba_sol.charAt(i)) =='D'){
if ((kociemba_sol.charAt(i+1)) == '2') {
down();
down();}
if ((kociemba_sol.charAt(i+1)) == '\'') {
down_inverted();}
if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
down();}
}
else { //DO NOTHNIG
}
if ((kociemba_sol.charAt(i)) =='F'){
if ((kociemba_sol.charAt(i+1)) == '2') {
front();
front();}
if ((kociemba_sol.charAt(i+1)) == '\'') {
front_inverted();}
if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
front();}
}
else { //DO NOTHING
}
if ((kociemba_sol.charAt(i)) =='B'){
if ((kociemba_sol.charAt(i+1)) == '2') {
back();
back();}
if ((kociemba_sol.charAt(i+1)) == '\'') {
back_inverted();}
if (((kociemba_sol.charAt(i+1))!= '2') and((kociemba_sol.charAt(i+1)) != '\'')) {
back();}
}
else { //DO NOTHING
}
}
//warm_up();
//scramble();
}
void show_off_cube()
{
rotate_one();
rotate_three();
push_cube(2);
rotate_one();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////// PROGRAM START ///////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void setup()
{
rotate_servo.attach(12); // attaches the servo on pin 9 to the servo object
push_servo.attach(14); // attaches the servo on pin 6 to the servo object
push_servo.write(push_pos);
rotate_servo.write(rotate_pos);
delay(1000);
Serial.begin(9600);
while (! Serial);
}
/////////////// Se recibe por puerto serial la solucion //////////////////
void loop()
{
//activate accept string when you want to receive solution from PY script
//accept_string();
//Warming up
Serial.println("Warming Up: will back to the original position and start ");
push_cube();
push_cube();
push_cube();
push_cube();
delay(2000);
Serial.println("Arduino dice:Inicia a correr la solucion:");
run_kociemba(); //corre el string recibido
Serial.println("Arduino : Finish-check all the cube face solved");
//show all the cube solved
show_off_cube();
Serial.println("Arduino :Finish- send a new kocoemba solution");
while(true){}
}
Comments