Arduino Balancing Game With Servos.

968

17

1

Published

Introduction: Arduino Balancing Game With Servos.

About: I love making things. I have for as long as I can remember liked to make stuff. Now days I have two kids (Thomas and Emma) and most of the things I do are safe for them! I love electronics and Microchips, I ...

Nice Arduino project. Featuring an Arduino Nano controlling 3 servos to move the tree. Firstly the "Roll" button is pressed to give you a colour and amplitude once the correct bit has been placed on the tree the "Shake" button is pressed and the tree moves around. Any bits which fall off are given to the person whos go it was! the idea of the game is to get rid of all your bits first. If when you press the "Roll" button you don't have the correct colour OR the strength is to high then if you wish you can miss your go.

Loads of nice Arduino bits...

  • Driving servos.
  • Analogue Write to fade LED's.
  • Digital inputs via Buttons.
  • Driving an array of WS2812 LED's.
  • Random numbers.

Step 1: The Wood Build.

This was a very rewarding build! i designed the parts and cut them all out and they all fitted together perfectly! (it doesn't always turn out that well!) once the bits had been cut out i mocked up the whole project so i could play with the servo program and make sure the movement was going to be enough. Again i found it was just right, but in testing i came across a rather annoying problem and that was "SERVO JITTER" i will explain about this at the end.

The plans for all the parts are included in the attached PDF, basically i print out the parts making sure that they print at 100% then stick the paper to the wood using "stick glue" i then nail the 3 pieces of ply together and cut out using a scroll saw. if you are prompt the paper should peel of the wood easily.

i haven't put dimensions on the drawings but there is always a chance your servos will be a different size, so measure first.... And the holes that need drilling are to match your servo rods, so in my case 3mm was about right. The holes shouldn't be too tight as the rods will end up bending.

Step 2: Glue the Bits Together, and a Bit of Painting.

This project came together very quickly, so i have used a couple of stills from the video.

the tree should slot together very nicely, when i glued the bits together i placed a weight on the top to keep the main body square and got some scraps of wood to go across the platforms to keep them all level (i used pegs to hold it together)

Painting.

While i was Christmas shopping i had a look out for paint, and at first found some small tubes at £1.99, but when i was walking to the till i saw tester pots for £1 so that's what i chose. And i think the paint was very good as it covered well and seems to be durable. Also its a new style domestic paint so didn't smell and could be washed off with water so it was perfect for my little girl to help with. I probably went overboard with the number of bits i cut and painted but once i had the saw set up it was just a matter of cutting them out quickly.

Two diameters 18mm and 28mm. Cut in two different lengths 40mm and 20mm

The dowels were cut as follows.

28mm dia * 40mm long 2 off each colour for each of the 4 players plus 4 jokers. 36 in total.

28mm dia * 20mm long 1 off each colour for each of the 4 players. 16 in total.

18mm dia * 40mm long 1 off each colour for each of the 4 players. 16 in total.

18mm dia * 20mm long 1 off each colour for each of the 4 players. 16 in total.

Step 3: Electronics Build.

I like to use the Arduino Nano (or clone!) its nice and small and can be purchased for a very small amount. The cost is so cheap i am happy to solder pins where i want them and not have to worry about not being able to reuse the board again! (less than £2.00 from Ebay or if you wish to pay more you can find them "on the high street" for £19.99!!) i get the board without the headers soldered on and try to avoid getting another USB lead!

So i described this part in the video, but basically i mounted the hardware, and before i started to wire everything up i spent a little time just working out how the neatest and convenient way would be. You can see in the photo i decided to use the resistors directly soldered onto the buttons, and the common part of the resistors (on both buttons) was used as the negative terminal. The positive wire was linked from one button to the other and also had the LED array joined on.

At the Nano i added headers to just the pins i wanted to use. these are as follows.

D2 Servo 1 (output)

D3 Servo 2 (output)

D4 Servo 3 (output)

D5 Shake LED (PWM Analogue output)

D6 Roll LED (PWM Analogue output)

D7 Shake Button (Digital input)

D8 Roll Button (Digital input)

D9 LED Circle 16 array of WS2812 LED's

For the POWER i used a UBEC to power the servos. Firstly i stripped of the cover and removed the input and output wires. I then added wires with a deans plug onto the input and the output (either 5 or 6 Volts) was where i connected the 3 red and 3 black wires from the servos extension cables the 3 white wires from the servos were connected to a 3 way plug. Lastly the original output wire from the UBEC was reused to connect to the input side of the UBEC and supply the Nano with the LIPO voltage to the Vin and GND. I have done it this way due to the fact that the UBEC can be set to give out 6 volts, so its safer to supply a higher voltage to the Vin and not connect the UBEC (output) straight onto the 5 volt pin and hoping i never set the UBEC to 6 Volts?

Step 4: The Program Part 1! Button Inputs and LEDs (PWM)

Basic button inputs and analogue outputs for button LED's.

When i build something like this i like to break down the project into little bits and get each bit working one at a time.

So the Buttons are an easy place to start.

const int 
/*This is a very long program to do very little! it basically 
 *reads the Roll button whilst pulseing the Roll button LED,
 *once the button is pressed, the Shake button starts pulseing, 
 *again when you press the Shake Button it swaps back to the Roll
*/
const int LEDRoll = 6;
const int LEDShake = 5;
const int BUTTONShake = 7;
const int BUTTONRoll = 8;
int brightness = 20;
int fadeAmount = 1;

void setup()//define which buttons and LED's go to which pins.
{
  pinMode(LEDRoll, OUTPUT);   //LEDs need to go on a PWM pin to
  pinMode(LEDShake, OUTPUT);  //allow fade to work (AnalogWrite)
  pinMode(BUTTONShake, INPUT);
  pinMode(BUTTONRoll, INPUT);
  TIMSK0=0;//turn off the T/C 0 interrupt (stops servo jitter)
}

void loop()
{
  readButtons(BUTTONRoll, LEDRoll);  //call the readButton fuction ROLL first
  readButtons(BUTTONShake, LEDShake);
}
void readButtons(int Button, int LED) //from above starting with Roll
{
  int Pressed = digitalRead(Button); //read the desired button
  while (Pressed == 0)//loop until pressed and do the following
  {
    Pressed = digitalRead(Button);//read the button again
    fade_LED(LED);//fade the LED
    delay02(8000);//Add a delay to allow fade to work, 8000 works well.
  }
  analogWrite(LED, 0);//Once the button is pressed turn of the LED
}
void fade_LED(int chosenLED)//copied from the examples! (Examples/Basics/Fade)
{
  analogWrite(chosenLED, brightness);
  brightness = brightness + fadeAmount;
  if (brightness <= 10 || brightness >= 200)//change vaules to get desired fade
  {
    fadeAmount = -fadeAmount;
  }
}
//The delay routine below is used because i can use the delay() command. 
void delay02(unsigned int multiplier)//maximun number is 65535
{
  for (unsigned int T = 0; T < multiplier; T = T + 1)
  {
    __asm__("nop\n\t"); //do nothing, waste a clock cycle?
  }
}

Step 5: The Program Part 2! Driving the WS2812 LED Array

Driving the WS2812 LED array.

I have used a couple of different effects. firstly i roll the 4 colours.

/* First program to rotate the 4 colours. 
 * nothing really fancy here!
 * in the final program i decreased the delay in a loop to make the display
 * speed up, then did the display again at a fast spin for a short time.
 * then displayed a solid colour.
 */

#include 
Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, 9, NEO_GRB + NEO_KHZ800);
uint32_t Blue = strip.Color(0,0,50);//define the colours
uint32_t Green = strip.Color(0,50,0);
uint32_t Red = strip.Color(50,0,0);
uint32_t Yellow = strip.Color(40,40,0);
int i = 0;

void setup()
{
  strip.begin();
  strip.show();
  TIMSK0=0;//turn off the T/C 0 interrupt (stops servo jitter)
}

void loop()
{
  rollDice();
}
void rollDice()
{
  for (int k = 0; k < 4; k = k + 1)//set 4 in a row to yellow
  {
    strip.setPixelColor(i, Yellow);//Set to yellow
    test();//check if you overflowed the 16 LED's
  }
  for (int k = 0;k < 4; k = k + 1)//set next 4 in a row to red
  {
    strip.setPixelColor(i,Red);//0
    test();//check if you overflowed the 16 LED's
  }
  for (int k = 0;k < 4; k = k + 1)//set next 4 in a row to green
  {
    strip.setPixelColor(i,Green);//0
    test();//check if you overflowed the 16 LED's
  }
  for (int k = 0;k < 4; k = k + 1)//set next 4 in a row to blue
  {
    strip.setPixelColor(i,Blue);//0
    test();//check if you overflowed the 16 LED's
  }
  strip.show();//update the display
  test();//check if you overflowed the 16 LED's and add another to rotate display
  delay02(65000);//add a couple of delays to slow things down
  delay02(65000);
}
void delay02(unsigned int multiplier)//maximun number is 65535
{
  for (unsigned int T = 0; T < multiplier; T = T + 1)
  {
    __asm__("nop\n\t"); //do nothing, waste a clock cycle?
  }
}
void test()// this is used to check if you are attemping to set the 16th LED!
{
  i = i + 1;
  if (i > 15)
  {
    i = 0;//the first LED is ZERO the last is 15...
  }
}

Next i ramp up and down the display to simulate a random strength

/* Second program to go up and down the ring of LED's. 
 * nothing really fancy here! only one colour choosen.
 * in the final program i did the pattern a couple of times then
 * went up the LED ring to show the random strength.
 */

#include 
Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, 9, NEO_GRB + NEO_KHZ800);
uint32_t Blue = strip.Color(0,0,50);//define the colours
uint32_t Green = strip.Color(0,50,0);
uint32_t Red = strip.Color(50,0,0);
uint32_t Yellow = strip.Color(40,40,0);
int i = 0;

void setup()
{
  strip.begin();
  strip.show();
  TIMSK0=0;//turn off the T/C 0 interrupt (stops servo jitter)
}

void loop()
{
  chooseIntensity();
}
void chooseIntensity()
{
  for (int X = 16;X > 0; X = X - 1)
  {
    strip.setPixelColor(X, 0);
    strip.show();
    delay02(60000);
  }
  for (int X = 0;X < 16 ; X = X + 1)
  {
    strip.setPixelColor(X, Green);
    strip.show();
    delay02(60000);
  }
}
void delay02(unsigned int multiplier)//maximun number is 65535
{
  for (unsigned int T = 0; T < multiplier; T = T + 1)
  {
    __asm__("nop\n\t"); //do nothing, waste a clock cycle?
  }
}

Step 6: The Program Part 3! Picking Random Numbers (with ODDS)

So i realised early on that just picking a random number 1-16 wasn't going to work. any number over 9 caused a lot of the pieces to fall of so i needed to limit the high number. So to achieve this i created a string with 99 numbers in it, and then chose a random number up to 99 and then picked that number from the string. What this means is i can pick how many times a number appears in the string and hence limit the possibility of it getting picked.

int RandomNumber[] = {1,1,1,1,2,2,2,2,3,3,3,3,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,8,8,9,9,10,10,11,12,13,14,15,16,15,14,13,12,11,10,10,9,9,8,8,7,7,6,6,6,6,5,5,5,5,4,4,4,4,1,1,1,1,2,2,2,2,3,3,3,3,3,3,3,3,2,2,2,2,1,1,1,1};

ODDS are as follows

1-2-3 = 16%

4-5-6 = 8%

7-8-9-10 = 4%

11-12-13-14-15 = 2%

16 = 1%

I hope that makes sense. The only other bit to add is i used "randomSeed(analogRead(3));" to aid randomness!

Step 7: The Program Part 4. the Servo Driving and SIN Pattern

/* This is the routine for driving the servos
 * Each servo drives in a sin wave pattern but 120 degress apart.
 * this gives a rotating pattern.
 * if you wanted the pattern to rotate the other direction just change
 * the order of the "Amounts" in the sin routine
 */
const int servoOne = 2;
const int servoTwo = 3;
const int servoThree = 4;
const int offset1 =  1552;
const int offset2 =  1490;
const int offset3 =  1538;
float Amount1 = 1552;
float Amount2 = 1490;
float Amount3 = 1538;

void setup()
{
  pinMode(servoOne, OUTPUT);
  pinMode(servoTwo, OUTPUT); 
  pinMode(servoThree, OUTPUT);
  TIMSK0=0;//turn off the T/C 0 interrupt (stops servo jitter)
}

void loop()
{
  sinRotate(120);
}
void sinRotate(int R)//R is the amplitude of the sin wave
{
  for (float y = 0.0; y < 12.566; y = y + 0.005) //12.566 chosen to give 4 cycles
  {
    float A = y;
    A = sin(A) * R;
    Amount1 = A + offset1;
    A = y + 2.094395;
    A = sin(A) * R;
    Amount2 = A + offset2;// change Amount2 to Amount3 to reverse 
    A = y + 4.18879;
    A = sin(A) * R;
    Amount3 = A + offset3;// change Amount3 to Amount2 to reverse
    servoOutput();
  }
}

void servoOutput()
{
  digitalWrite(servoOne, HIGH); 
  delayMicroseconds(Amount1);
  digitalWrite(servoOne, LOW);
  digitalWrite(servoTwo, HIGH); 
  delayMicroseconds(Amount2);
  digitalWrite(servoTwo, LOW);
  digitalWrite(servoThree, HIGH); 
  delayMicroseconds(Amount3);
  digitalWrite(servoThree, LOW);
}


Step 8: The Game Is On!

  1. Give out the coloured pieces to all the players.
  2. The first players pressed the green Roll button.
  3. A Random colour is chosen and a random strength.
  4. The player must then add the correct coloured peice to the tree.
  5. If the player hasn't got the correct colour then he pass by pressing the Roll button again.
  6. If the player wants to pass because the strength is too high then he can do so.
  7. Once the player has added the correct bit he presses the Red shake button to move the tree.
  8. If any bits fall off then the player has to take them.
  9. Play continues with the next person pressing the Roll button.
  10. The winner is the player who gets rid of all their bits first.

Step 9: Servo Jitter. (how to Solve It)

  1. Firstly you must understand that a Arduino Nano or Uno doesn't have vast amounts of power, so unless you are using the small slow servos you will need to supply the servos with their own supply which is why i used the UBEC.
  2. Whilst servos should be sent a pulse every 20 milliseconds, in reality once the servo is in position it will stay still if it doesn't receive any more pulses. Also it is worth pointing out that servos are happy to receive a pulse quicker than every 20mS. So if you wish send a pulse to get the servo in position then dont send anymore until you wish to move again.
  3. In my case I tried several things before turning to the internet for the solution. Basically you need to disable the interrupt facility of the timer/counter one (T/C-0) by using the following command "TIMSK0=0" this turns off three interrupt generated by T/C-0. I don't understand the whole reason why this works but from reading on the internet the Arduino software uses the T/C-0 for timing? what this means is that the T/C-0 is running in the background and generating interrupts for timing purposes. This causes other issues but they are not to hard to overcome. Basically you can't use the millis()/micros()/delay() statements and other functions which relay on T/C-0 which i believe is also the Serial.print. Thankfully i have used delayMicroseconds which uses processor cycles to perform delays.
  4. It's up to you what code you use to drive your servos, i always tend to use delayMicroseconds. But you could also use the servo library. when i had the problem with jitters i tried the servo library and it made no difference.

please see below for the program as used in the above video.

const int servoOne = 2;
const int BUTTONShake = 7;
const int BUTTONRoll = 8;
float Amount1 = 1500;

void setup()
{
  pinMode(servoOne, OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(BUTTONShake, INPUT);
  pinMode(BUTTONRoll, INPUT);  
  
  TIMSK0=0;//turn off the T/C 0 interrupt (stops servo jitter)
}
void loop()
{
  servoOutput();
  int up = digitalRead(BUTTONShake);
  if (up == 1)
  {
    Amount1 = Amount1 + 1;
  }
  int down = digitalRead(BUTTONRoll);
  if (down == 1)
  {
    Amount1 = Amount1 - 1;
  }
  delay02(40000-(2*Amount1));
}
void servoOutput()
{
  digitalWrite(6, HIGH);
  digitalWrite(servoOne, HIGH); 
  delayMicroseconds(Amount1);
  digitalWrite(6, LOW);
  digitalWrite(servoOne, LOW);
}
void delay02(unsigned int multiplier)//maximun number is 65535
{
  for (unsigned int T = 0; T < multiplier; T = T + 1)
  {
    __asm__("nop\n\t"); //do nothing, waste a clock cycle?
  }
}

Share

    Recommendations

    • Oil Contest

      Oil Contest
    • Clocks Contest

      Clocks Contest
    • Creative Misuse Contest

      Creative Misuse Contest

    Discussions

    Great Job, these project will be definitly on my todo list!