BEETS Camp 2013
Hands-on Engineering Activity with Dominic Canare and Tom McGuire
It's 2013, and driverless cars are only recently becoming relevant. The automated vehicle brings challenges from multiple engineering disciplines. By building small-scale vehicles, students will identify and overcome challenges while exploring the engineering process, working as a team, and being creative.
Project Synopsis
Working in groups of two or more, students will design and build an autonomous or semi-autonomous vehicle. Each vehicle must sense one or more aspect of its environment and respond in an observable manner.
Students will also need to design a course for their vehicle to navigate. The course may consist of whatever is available to the students, but it should be easy to move and/or tear-down and rebuild.
On the final day, students will evaluate other group projects based on the the complexity/difficulty of the course they have designed for their vehicle, and the ability of their vehicle to negotiate their course. The highest scoring team will be awarded a prize!
Inspiration Videos
Tools and parts
Students will be provided with a kit, including DC motors, an Arduino, photocells, rods, tubing, wire, foam, plexiglass, glue, rubber bands, screws, etc. Students will also be provided with instruction to design pieces and parts on the foam-cutting and laser cutting machines.
Documentation
Each group will need to prepare a report on their course and vehicle. The report should include (but is not limited to) the following:
-
A list of members and their roles. E.g.:
- Project Manager
- Designer
- Mechanic
- Programmer
- Reporter
- Etc.
- Course specifications/obstacles
-
Vehicle specifications (planned) E.g.:
- Physical dimensions
- Sensors / reaction
- Response time
- Course specifications
- Min/max speed of vehicle
- Cargo bearing capacity
- Additional capabilities
- Etc
-
Description of completed project and process
- Include challenges and solutions
- Compare completed project to target specifications
- Lessons learned
- Areas for improvement
- Description of course
- How does the course play to your vehicles strengths/weaknesses?
- How can your vehicle be applied to the real world? What limits it from being used in passenger vehicles? How could those challenges be overcome?
Building blocks and samples from Tom and Dom
Controlling a DC motor with a transistor
A transistor can function like a switch which we can be controlled electronically.
In this example, the Arduino controls the speed of a motor by rapidly flipping the "switch" off and on using Pulse Width Modulation.
// DC Motor Control Example void setup() { // setup() - called once at the very beginning pinMode(A2, INPUT); // Potentiometer's value is read on Analog pin 2 pinMode(3, OUTPUT); // Motor speed is controlled using Digital pin 3 digitalWrite(3, 0); // Turn the motor off initially } void loop() { // loop() - called repeatedly int value = analogRead(A2); // Read the current value (10 bits) // NOTE: We can only send 8 bits of data to the // analogWrite function below. So we must the 2 // least significant digits from the value we // read in. value = value << 2; // Drop the 2 least significant digits analogWrite(3, value); // Flip the transistor on and off really fast // Higher values = transistor on longer // 0 = off, 128 = 50%, 255 = 100% }
Controlling a DC motor with a driver
The Pololu motor driver is a circuit designed to simplify bi-directional motor control.
In this example, the motor spins in one direction for one second, then the opposite direction for one second, and repeats this pattern continuously.
Note: because pin 9 controls the power to the motor, we can pulse a PWM signal using the analogWrite
function, instead of digitalWrite
. Remember that analogWrite
expects an integer value between 0 and 255. The closer the number is to 255, the more power the motor will receive.
// Bi-direction DC Motor Control with Pololu Driver void setup() { // setup() - called once at the very beginning pinMode(9, OUTPUT); // Pin 9 gives power to the motor pinMode(8, OUTPUT); // Pin 8 controls the direction of the motor digitalWrite(9, HIGH); // Turn the motor on } void loop() { // loop() - called repeatedly digitalWrite(8, HIGH); // Spin the motor in one direction delay(1000); // Let it spin that way for one second digitalWrite(8, LOW); // Spin the motor in the opposite direction delay(1000); // Let it spin that way for one second }
Controlling two DC motors with a driver
In the example above, pins 9 and 8 control the power and direction of the motor respecitvely. A HIGH signal on pin 9 will enable the motor and a LOW will disable it. A HIGH signal on pin 8 will spin the motor in one direction (provided that motor is on), and a LOW value will spin the motor in the opposite direction.
A second motor can easily be connected to the motor driver chip. If you look at the chip, you'll see labels marked "AEN" and "APH". "AEN" controls the power to Motor A, and "APH" controls its direction. Similarly, "BEN" and "BPH" control the power and direction of Motor B respectively.
In the example below, Motor A has power/direction connected to pins 8/9. Motor B has power/direction connected to pins 5/6.
int motor1Power = 8; int motor1Direction = 9; int motor2Power = 5; int motor2Direction = 6; void setup(){ pinMode(motor1Power, OUTPUT); pinMode(motor1Direction, OUTPUT); pinMode(motor2Power, OUTPUT); pinMode(motor2Direction, OUTPUT); Serial.begin(9600); delay(2000); } long wait = 3000; void loop(){ Serial.println("Forward 1"); digitalWrite(motor1Power, HIGH); digitalWrite(motor1Direction, LOW); delay(wait); Serial.println("Forward 2"); digitalWrite(motor2Power, HIGH); digitalWrite(motor2Direction, LOW); delay(wait); Serial.println("Reverse 1"); digitalWrite(motor1Direction, HIGH); delay(wait); Serial.println("Reverse 2"); digitalWrite(motor2Direction, HIGH); delay(wait); Serial.println("Motor 1 off"); digitalWrite(motor1Power, LOW); delay(wait); Serial.println("Motor 2 off"); digitalWrite(motor2Power, LOW); delay(wait); }
Sensing bumps with "whiskers"
This is a very simple sensor. Whiskers can be made out of simple metal wire, which can be connected to the Arduino. When a whisker touches an object, the object pushes the whisker to another metal contact which is also connected to the Arduino. This closes a circuit, just like a switch.
In this example, the Arduino will enable LEDs corresponding to the whiskers when they are activated.
// "Whisker" / Bump Sensing Example // Refer to LED pins using a name instead of a number int LED_R = 11; // Red LED is on pin 11 int LED_G = 12; // Green LED is on 12 // Refer to whisker pins using a name instead of a number int WHISKER_L = A4; // Left whisker is on analog 4 int WHISKER_R = A5; // Right whisker is on analog 5 void setup() { // setup() - called once at the beginning pinMode(LED_RED, OUTPUT); // We are outputting data to the LEDs pinMode(LED_GREEN, OUTPUT); pinMode(WHISKER_L INPUT); // And receiving data from the whiskers pinMode(WHISKER_R, INPUT); digitalWrite(WHISKER_L, HIGH); // Set a default value for the whiskers digitalWrite(WHISKER_R, HIGH); // This prevents environment noise } void loop() { // loop() - called repeatedly if(digitalRead(WHISKER_L)==LOW){ // If the left whisker is bumped digitalWrite(LED_R, HIGH); // Turn ON the red LED }else{ // If it's not bumped digitalWrite(LED_R, LOW); // Turn OFF the red LED } if (digitalRead(WHISKER_R)==LOW){ // If the right whisker is bumped digitalWrite(LED_G, HIGH); // Turn ON the green LED }else{ // If it's not bumped digitalWrite(LED_G, LOW); // Turn OFF the green LED } }
Sensing light with a photocell
The photocell is a semiconductor whose electrical resistance is dependent on the amount of light shining into it.
In this example, the photocell is connect to an analog pin, so that its value can be read. With that information, the Arduino adjusts the brightness of the LED.
// Photocell Example void setup(){ // setup() - called once at the beginning pinMode(A0, INPUT); // We'll read the photocell on Analog pin 0 digitalWrite(A0, HIGH); // Set a default value pinMode(3, OUTPUT); // We will control the LED on pin 3 digitalWrite(3, 0); // Turn the LED off } void loop(){ // loop() - called repeatedly int value = analogRead(A0); // Read the photocell's value float percent = (float)value/340; // By experimenting, we found that 340 is // about the highest possible value // So we'll calculate a number as a // percentage of 340 int output = 255 * percent; // The highest value we can send to the // LED is 255, so find the % of that if(output > 255){ // If, somehow, we've calculated a higher output = 255; // number than 255, cap it. } analogWrite(3, output); // Turn the LED on and off really fast // So fast, the LED seems dimmer/brighter // Higher values = LED on longer // 0 = off, 128 = 50%, 255 = 100% }
Sensing proximity with an IR emitter/receiver
Infrared light is invisible to the human eye, but not to this sensor. By shining IR light and detecting its reflection, the sensor can determine how close or far an object is.
In this example, the distance is measured first and a far object activates the green LED. A somewhat close object activates the yellow LED, and a very close object activates the red LED.
The servo sweeps the sensor back and forth, giving it a great field of view. This is optional.
// IR Proximity Sensing Example #include <Servo.h> // Load a library which makes it really easy to Servo myServo; // control a servo. void setup(){ // setup() - called once at the beginning pinMode(A0, INPUT); // Read the distance sensor on Analog pin 0 pinMode(2, OUTPUT); // Pin 2 controls the green LED pinMode(3, OUTPUT); // Pin 3 controls the yellow LED pinMode(4, OUTPUT); // Pin 3 controls the red LED myServo.attach(9); // Pin 9 controls the servo myServo.write(0); // Set the servo to 0 degrees } int dir = 1; // Keep track of which direction we're turning int pos = 0; // Keep track of current servo position void loop(){ // loop() - called repeatedly int led_r = 0; // Only one LED will be enabled. Assume all int led_y = 0; // will be turned off until we know which one int led_g = 0; // will be turned on int value = analogRead(A0); // Read the current distance if(value < 100){ // If the distance is small led_r = 1; // we should enable the red LED }else if(value < 500){ // If the distance is moderate led_y = 1; // we should enable the yellow LED }else{ // If the distance is large led_g = 1; // we should enable the green LED } digitalWrite(2, led_r); // Update the LED's digitalWrite(3, led_y); digitalWrite(4, led_g); if(pos > 179){ // If the servo is far right then dir = -1; // we should start moving left }else if(pos < 1){ // If the servo is far left then dir = 1; // we should start moving right } pos += dir; // Increase the position in the correct direction myServo.write(pos); // Tell the servo where it should be delay(20); // Do nothing for 20ms (gives time to move) }
Sensing IR remote signals
When you press a button on a remote control, the remote flashes an infrared LED in a specific pattern unique for each button. Using the arduino, we can decode this pattern to distinguish between different button presses.
In this example, we will control 4 LED's by listening for 4 unique remote buttons.
// IR Remote Decoding Example #include <IRremote.h> // Load a library which makes remote IRrecv irrecv(9); // codes easy. The receiver is on pin 9 typedef struct { // For each LED, we need to keep track of: long code; // Its remote code boolean value; // Its current on/off state int pin; // Which pin it's connected to } RemoteLED; State leds[4] = { { 4278200535l, false, 7 }, // Power button { 4278203085l, false, 6 }, // 1 button { 4278192885l, false, 5 }, // 2 button { 4278201045l, false, 4 } // 3 button }; void setup(){ // setup() - called once at the beginning for(int i=0; i<4; i++){ // For each RemoteLED, pinMode(leds[i].pin, OUTPUT); // Set the associated pin to output digitalWrite(leds[i].pin, LOW); // And turn the LED off } irrecv.enableIRIn(); // Start the IR receiver } void loop() { // loop() - called repeatedly decode_results results; // create a variable to hold IR results if(irrecv.decode(&results)){ // If any code is read from the remote // Check if code matches any of ours for(int i=0; i<stateCount; i++){ // For each of our codes if(results.value == leds[i].code){ // Check to see if the input matches leds[i].value = !leds[i].value; // Toggle the state of the LED digitalWrite(leds[i].pin, leds[i].value); // Update the actual LED break; // Stop looking for matches } } irrecv.resume(); // resume the IR receiver }
A simple Tachometer
A tachometer can sense the speed at which an object is sensing in - i.e., the number of rotations per minute or second. This can be accomplished by coloring half of the wheel black, and half white. Then, with a photocell (see above), the Arduino can detect when the wheel changes from white to black. This is counted as one rotation.
In this example, the tachometer senses the speed of a spinning wheel and enables a red LED if it's stopped, a yellow LED if it's slow, and a green LED if it's fast.
The Arduino will also change the speed of the motor every 5 seconds.
// Simple Tachometer Demo void setup(){ // setup() - called once at the beginning pinMode(A0, INPUT); // Analog pin 0 used for reading photocell pinMode(11, OUTPUT); // Pin 11 used to control the motor pinMode(7, OUTPUT); // Pin 7 used for red LED pinMode(8, OUTPUT); // Pin 8 used for yellow LED pinMode(9, OUTPUT); // Pin 9 used for green LED digitalWrite(A0, HIGH); // Set a default value for the photocell digitalWrite(11, HIGH); // Set the motor to full speed Serial.begin(9600); // Enable PC communications for debugging } long lastSample = 0; // Timestamp of last sensor read long lastChange = 0; // Timestamp of last motor speed change int state = 0; // Wheel state (black or white) int count = 0; // # of times the wheel has spun since last report int sampleDelay = 500; // How long to sample sensor before reporting int threshold_b = 40; // Minimum value for black detection int threshold_w = 30; // Maximum value for white detection int currentSpeed = 255; // Speed of motor void loop(){ // loop() - called repeatedly int value = analogRead(A0); // Read the photocell /* To test sensor readings, uncomment the next two lines */ //Serial.println(value); //return; if(value > threshold_b && state == 0){ // If black is detected and last one was white state = 1; // Set the current state to black count++; // Increase the count of rotations }else if(value < threshold_w){ // If white is seen state = 0; // Set the current state to white } // LED Control if(millis()-lastSample > sampleDelay){ // If enough time has elapsed Serial.print("count = "); // Send data to the PC Serial.println(count); int led_r = 0; // Assume none of the LEDs are disabled int led_y = 0; // Until we know which one to enable int led_g = 0; if(count < 1){ // If no rotations were counted led_r = 1; // Enable the red LED }else if(count < 6){ // If rotation count > 0 but < 6 led_y = 1; // Enable the yellow LED }else{ // If rotation count >= 6 led_g = 1; // Enable the green LED } digitalWrite(7, led_r); // Give the LEDs their new values digitalWrite(8, led_y); digitalWrite(9, led_g); count = 0; // Reset the rotation counter lastSample = millis(); // Reset our reporting timestamp } // Motor Control if(millis()-lastChange > 5000){ // If 5 secs since last motor change if(currentSpeed >= 255){ // If the motor is currently maxed out currentSpeed = 64; // Set the motor speed to someting slow }else{ // If it's not maxed out currentSpeed *= 2; // Double its current speed } if(currentSpeed > 255){ // Highest possible motor speed is 255 currentSpeed = 255; } analogWrite(11, currentSpeed); // Now that we know the new speed, set it lastChange = millis(); // Reset our motor change timestamp } }
Other resources
Most of the tools we're using for this course are 100% Open Source and FREE!
- Arduino IDE - for programming the Arduino
- Inkscape - for drawing vector graphics we can cut out of foam
- Arduino Tutorials
- Fritzing - the program used to make the pretty diagrams
- Dom's Facebook and Twitter
- Tom's Facebook