Kid-friendly PuppyDuino 0.31
OK, this is not a fully autonomous "get you a beer, open it for you and then tweet about it" robodog (yet ;) but if you're itching to get past the blinky lights on a breadboard stage with your Arduino and you're ten or know somebody that's ten this might appeal to you.
This is actually the first published part of an afterschool learning project I'm working on that demonstrates the Arduino UNO beyond flashing LEDs. Hopefully this will find its way into a lesson plan for next semester that will roughly follow the Arduino lessons on the Adafruit Learning System website.
What 0.31 does: When you pet PuppyDuino she wags her tail in gratitude. Stop petting her and tail wagging stops too.
How it works: A light sensor (made with a photoresistor) on the face reacts to the shadow of a passing hand. Less light = higher voltage on an analog pin. Once a threshold voltage is exceeded, the servo motor is signaled to wag the tail.
A similar arrangement with a trim potentiometer on another analog pin allows you to adjust that threshold voltage so to match ambient light conditions.
Concepts learned: Analog input, digital input, potentiometers, photoresistors, servo control, serial monitor, breadboarding and the satisfaction of making a real thing that reacts by doing real stuff.
Why 0.31? Because PuppyDuino is a platform that has lots of room for upgrading - LED eyes, motorized wheels, distance sensors, etc. So, keep that "get a beer for you" thing alive in the back of your mind for some future version. Also, I skipped over some introductory Arduino projects that could be taught PuppyDuino-style that I'll document later. Those can be 0.1 and 0.2.
Collect Materials
Parts
- shoe box and smaller box for head (or suitable, stable containers)
- Arduino UNO or similar 'duino
- analog micro servo (TowerPro SGR92R, SG90 or similar)
- breadboard
- 22ga solid core wire - various colors help or male - male jumper wires
- battery power adapter*
- photoresistor - I have no idea what spec mine is*
- 10K trim potentiometer (board mount type)
- 10K resistor
- tactile switch
- electrician's tape or heat shrink tubing (for photoresistor leads - in a pinch painter's tape would work)
- small cardboard to hold components or purchased project board for Arduino and breadboard
Tools
- hot glue gun
- painter's tape or other low tack tape
- scissors and / or craft knife
- paint, stickers, markers, etc for decoration
- wire stripper / cutter
- solder iron / solder (For photoresistor leads only. Option - Just twist and tape)
- Computer with the Arduino IDE installed
- USB cable to upload sketches
* 9V batteries are not the most economical choice but I had free cases of them. You can run the Arduino and a small servo attached to your computer's USB port or a 6X AA battery holder.
* I tried a few different fixed resistors in series with my photoresistor until I settled one where voltage between them was above 1V even in bright light.
We'll start building PuppyDuino on a project board and only install the 'guts' into our robodog after everything is working to satisfaction. Of course, if you are working with children you might work on the box simultaneously with the intent of finishing both about the same time. A little craft time is a great break from the electronics while still keeping on task.
Let's Wag Some Tail - Servo Motor Control
What's a servo?
The standard hobby servo motor does not go round and round. Instead it goes back and forth which is perfect for steering radio controlled model airplanes and wagging robotic dog tails. The business end of a servo is more like a small electric arm that can be positioned quickly and with some precision through about 180 degrees. You may need to check the specification of your servo either on the package or a datasheet from the manufacturer to determine how far yours rotates.
Thanks to the Servo library in the Arduino IDE we don't have to understand how this works internally, we simply need to tell the servo how many degrees to rotate and it obeys. Tell your servo to go to 0 and it will position the arm as far clockwise as it can rotate. If we tell it 180 the arm moves 180 degrees counterclockwise from zero. If we tell it 135 it positions the arm 135 degrees counterclockwise from zero. All measurements are counterclockwise from zero.
OK - Let's experiment!
Tape your servo motor to your work surface with the splined gear pointing up. Slide one of the little arms over the gear. Connect it to the Arduino as shown in the diagram. Red to 5V, Black (or Brown) to ground and the remaining wire (usually White or Orange), to pin 9.
Upload the code to your Arduino and watch it go. Try changing the numbers in the program to see what happens. DO NOT EXCEED THE MAXIMUM ROTATION OF YOUR SERVO - 0 to 180 is usually safe.
tailTest1.ino
/*tailTest1.ino your servo needs to connect to 5V to red wire, ground black wire pin 9 to control wire (white, yellow or orange) */ #include <Servo.h> // this imports the Servo library Servo servo; //create Servo void setup() { servo.attach(9); //control wire attaches to pin 9 } void loop() { // just some random moves to show servo working servo.write(0); delay(1000); servo.write(180); delay(1000); servo.write(135); delay(1000); servo.write(180); delay(1000); servo.write(135); delay(1000); servo.write(180); delay(1000); servo.write(135); delay(1000); servo.write(45); delay(1000); servo.write(0); delay(1000); }
Smooth move
You should have seen the servo arm move back and forth a few times then return to the zero position (over and over again). Cool, but kind of a jerky wag even for a robot dog. Let's smooth that wag out a bit by turning 1 degree at a time with a little 10 millisecond pause between moves.
tailTest2.ino
/*tailTest2.ino your servo needs to connect to 5V to red wire, ground black wire pin 9 to control wire (or PWM pin of your choice) */ #include <Servo.h> //this imports the Servo library Servo servo; //create a Servo int i; //counting variable int right = 135; //right extent of wag int left = 180; //left extent of wag void setup() { servo.attach(9); } void loop() { // sweep right to left one degree at at time for(i = right; i<=left; i++) { servo.write(i); delay(10); } // sweep left to right one degree at at time for(i = left; i >= right; i--) { servo.write(i); delay(10); } }
BINGO! We now have a smooth wag that says happy dog.
Obedience School
OK, now that PuppyDuino knows how to wag her tail let's train her to wag on command. We'll add the optional button circuit shown in the diagram and upload the sketch below. The tail should wag when the button is pressed and be still when it is not pressed. (If we can make her wag in response to a button press we're a hop, skip and a short jump away from making her wag in response to a petting sensor.)
tailTest3.ino
/*tailTest3.ino your servo needs to connect to 5V to red wire, ground black wire pin 9 to control wire servo tail wags only when button pressed */ #include <Servo.h> //this imports the Servo library Servo servo; //create a Servo int i; //counting variable int right = 135; //right extent of wag int left = 180; //left extent of wag int button = 8; //button input on pin 8 void setup() { servo.attach(9); pinMode(button,INPUT_PULLUP); //HIGH unless grounded } void loop() { // check if button pressed (if pin goes LOW) if(digitalRead(button) == LOW) { // sweep right to left one degree at at time for(i = right; i <= left; i++) { servo.write(i); delay(10); } // sweep left to right one degree at at time for(i = left; i >= right; i--) { servo.write(i); delay(10); } } delay(300); }
OK - let's remove that button circuit. We're done with it.
Pet Me! Making a Petting Sensor
Seeing the light
Now we need a way to detect when PuppyDuino is getting pets. We'll be making a light sensor (or dark sensor depending on your perspective) to detect the shadow of a passing hand.
Ingredient X of our sensor is a photoresistor which becomes less resistant in brighter light. We'll wire this in series with a plain ol' fixed resistor and have the Arduino measure the voltage at the point in between the two. As the resistance changes in the photoresistor so will the voltage at that midpoint. That change is how we know a hand (shadow) passed over PuppyDuino's head.
The Arduino will be testing this voltage on an analog pin. Analog pins on the Arduino interpret 0V to 5V as a number between 0 and 1023. Our sensor will return some number in full light and some other number in shadow depending on the components used and the lighting conditions in our workspace. We also need to see those numbers so we'll use the Serial Monitor in the Arduino IDE to peek at what PuppyDuino is sensing.
Let's upload the lightSensor1 sketch and click the rightmost icon on the Arduino IDE. That will open the Serial Monitor. We'll be sending values from our light sensor back from the Arduino to the serial monitor with the statement Serial.println(lightSensor).
lightSensor1.ino
/*lightSensor1.ino Let's peek at the values from our light sensor */ int lightPin = 1; //analog pin light sensor int lightSensor = 0; void setup() { Serial.begin(9600); //start serial communication (match value serial monitor) } void loop() { lightSensor = analogRead(lightPin); //get value light sensor Serial.println(lightSensor); //print sensor value to monitor delay(300); }
At the time I wrote this mine read about 215 in light and shot up to 500+ in shadow. You will need these values from your serial monitor in a minute so write them down. I'll padded mine a bit and used 400 for the dark value.
Light sensor value in light 215
Light sensor value in shadow 400
Getting a reaction
Ultimately we want PuppyDiuno to wag her tail but for now let's just blink the onboard LED connected to pin 13. Load up lightSensor2 sketch and watch the LED blink when you hand passes over the light sensor.
Our programming logic will go something like this
repeat this forever
- check light sensor value
- if light sensor value > known shadow value, then blink LED
- if not, LED off
lightSensor2.ino
/*lightSensor2.ino Let's peek at the values from our light sensor then blink an LED when it detects a shadow */ int lightPin = 1; //analog pin light sensor int lightSensor = 0; //initialize sensor value to zero int led = 13; //led pin int shadowValue = 400; //a little less than my lowest observed shadow value //change this to match your conditions void setup() { Serial.begin(9600); //start serial communication pinMode(led,OUTPUT); //set our pin led to output mode } void loop() { lightSensor = analogRead(lightPin); //read value from sensor Serial.println(lightSensor); //send sensor value to monitor if(lightSensor > shadowValue) //if shadow detected then blink { digitalWrite(led, HIGH); delay(300); digitalWrite(led, LOW); delay(300); } else //otherwise don't blink { digitalWrite(led,LOW); } }
OK, not exactly a tail wag but we are getting a response. Be patient, in the next step we'll marry our tail wag code to our light sensor code.
Tail Waggin' Bliss - PuppyDuino 0.3
Adding it all up
PuppyDuino already knows how to wag the servo tail when a button is pressed (responding to an event).
PuppyDuino already knows how to sense a petting hand from a change in light sensor value (sensing an event).
Now let's make PuppyDuino wag her tail in response to a shadow at the light sensor.
We're just going to replace blinking in the previous sketch with tail wagging. I noticed back in the previous step that petting my dog resulted in a sensor reading of about 500+. I'll pad that a bit and use 400 as my threshold so that any reading over 400 will result in a wag. (Your values may vary from mine and vary with changing ambient light conditions. Check with the serial monitor before you commit to values. We'll make this number adjustable later.)
Our programming logic will go something like this
repeat this forever
- check light sensor value
- if light sensor value > known shadow value, then wag servo tail
- if not, put tail down
Deja vu - this sounds so familiar. Let's get both the servo and the light sensor circuits on the breadboard as shown in diagram and then upload our next sketch.
puppyduino0_3.ino
/*puppyduino0_3.ino puppy wags tail when someone pets her servo tail wags when light sensor in shadow */ #include <Servo.h> //this imports the Servo library Servo servo; //create a Servo int right = 135; //right extent of wag int left = 180; //left extent of wag int lightPin = 1; //analog pin light sensor int lightSensor = 0; //initialize sensor value to zero int servoPin = 9; //servo control pin int i = left; //initialize i to left int shadowValue = 400; //put YOUR value here void setup() { servo.attach(servoPin); } void loop() { lightSensor = analogRead(lightPin); //read light sensor if(lightSensor > shadowValue) { //wag 3 times for(int j = 0; j<=3; j++) { // sweep right to left one degree at at time for(i = right; i<=left; i++) { servo.write(i); delay(10); } // sweep left to right one degree at at time for(i = left; i >= right; i--) { servo.write(i); delay(10); } } } else { servo.write(0); //tail in down position } }
If all went well your PuppyDuino wags happily when you pet her head. If not, do some troubleshooting. Is everything wired correctly? Are your circuit connections snug? Did the lighting change significantly since you determined your threshold value? Maybe try adding some Serial.println statements so you can see your actual sensor readings.
Sensitivity Training - PuppyDuino 0.31
Going to Pot
OK, we now have a puppy that wags her tail when a sensor reading exceeds a preset hard-coded threshold we determined by trial and error. That's cool! But if the ambient light conditions change very much we will probably have to change that threshold in the code and recompile again. That's not so cool.
We'll fix that in just a minute by adding an adjustment knob so we can change that threshold on the fly to match the conditions. For this we'll be using a trim potentiometer, or trim pot, to generate varied values that we can use as our wag threshold. Telling you that a trim pot is just a variable resistor is oversimplification but that definition works well enough for what we are doing here. As we turn the knob we change the resistance of the pot and change the voltage at the middle 'wiper' pin.
We need to add the last of the components to our breadboard as shown in the above diagram and load up this little demo sketch. Once you have the serial monitor open you should see values just like we saw from our light sensor in a previous step.
Turn the knob through its full extent and you should see numbers 0 to 1023. How about that? We just happen to need a value adjustable from 0 and 1023.
trimPot.ino
/* trimPot.ino Observe varying values at wiper pin on trim potentiometer */ int potPin = 0; int potValue; void setup() { Serial.begin(9600); //start serial communication } void loop() { potValue = analogRead(potPin); //read value at wiper Serial.println(potValue); //send value to monitor delay(300); }
Are we there yet?
The answer would be yes. Our circuit is complete and this is our last sketch to load kids. We'll just do the switcheroo with our hard-coded threshold value for our pot value. I added some statements that print to the serial monitor to aid in debugging and testing. These can be commented out by placing // at the beginning of the line if you don't need them.
</*puppyduino0_31.ino puppy wags tail when someone pets her servo tail wags when light sensor in shadow wag threshold determined by value on potentiometer */ #include <Servo.h> //this imports the Servo library Servo servo; //create a Servo int right = 135; //right extent of wag int left = 180; //left extent of wag int lightPin = 1; //analog pin light sensor int lightSensor = 0; //initialize sensor value to zero int servoPin = 9; //servo control pin int i = left; //initialize i to left int potPin = 0; //analog pin read pot value int potValue = 0; //value of pot init to zero void setup() { servo.attach(servoPin); //servo control pin Serial.begin(9600); //for testing } void loop() { lightSensor = analogRead(lightPin); //read light sensor potValue = analogRead(potPin); // serial output for testing and debugging Serial.print("Pot: "); Serial.println(potValue); Serial.print("Sensor: "); Serial.println(lightSensor); if(lightSensor > potValue) { Serial.println("Wagging!"); //test statement //wag 3 times for(int j = 0; j<=3; j++) { // sweep right to left one degree at at time for(i = right; i<=left; i++) { servo.write(i); delay(10); } // sweep left to right one degree at at time for(i = left; i >= right; i--) { servo.write(i); delay(10); } } } else { servo.write(0); //tail in down position } }
Boxing Day
With the electronics good to go it would be time to make our puppy proper. Drop that project board into your shoebox and reposition the servo arm so that it points up when wagging. The only rule that applies here is there are no rules except keeping your options open for future upgrades so I'll leave the crafting to you.
Resources
If you are already familiar with Arduino then this project should be a slam dunk. If you're a total noob you could just follow the instructions and make PuppyDuino but to really get the most from this you need to understand the why, not just the how. Concepts in this project are covered fairly well in the Arduino lessons on the Adafruit Learning System website. I recommend this site for beginners as it gets your hands on an Arduino from the start. I suggest completing all the lesson projects separately then bundle a few together into your own PuppyDuino.
Unless you have electronic components galore on hand already I would suggest purchasing an Arduino starter kit. Here are some kits that should have all or most of what you need to learn the basics of Arduino and complete a PuppyDuino.
http://www.adafruit.com/products/170
http://www.adafruit.com/products/68 (no servomotor)
https://www.sparkfun.com/products/13154
http://arduino.cc/en/Main/ArduinoStarterKit
If you are purchasing for a group then it may be worth your while to price individual components, negotiate volume discounts or purchase cheap knock-offs from China.
All sketches are included in embedded zip file.