Introduction
Now that we have learned a lot of things about Sparki, like moving it or using its distance, light and line sensors, gripper, buzzer and display, why not put all this together in one (a bit) more complex application? In this lesson we will learn how to find objects with Sparki’s ultrasonic ranger along a line following course, grasp them with the gripper and bring them to an area behind the starting point. Some of the concepts that we are going to use here were already explained in the Maze Solving lesson. It’s strongly recommended that you read it before going on with this lesson.What You’ll Need
- A Sparki.
- Some lightweight objects, which may be cardboard boxes, cylinders, or even balls. We will show below in this lesson how to build a cylindrical object that fits well in Sparki’s gripper.
- A line path along which the objects to be retrieved will be deployed, like the one in ArcBotics’ Sparki Materials Packs:
How It Works
The object retrieval program is easier to understand if we first think about the steps that Sparki should actually do to accomplish the task. The overall idea is to program the Sparki to do the following:- The robot starts to follow the black line, searching for an object with its ultrasonic distance sensor (also called ultrasonic range finder).
- Once Sparki finds an object, the robot advances (moves forward) towards it until the object is inside its gripper’s reach.
- Sparki grasps the object with its gripper and then rotates itself until it finds the line again, so it can go back.
- The robot now follows the line again, but this time it’s moving towards the START mark, looking for it with its infrared sensors.
- Once the robot finds the START mark, it goes to the place (a few centimeters behind the mark) that the object has to be released, and releases it.
- Sparki counts the retrieved objects, so when all the objects are retrieved, the program stops.
The Objects
Before starting to program the robot, let’s take a look to the objects to be retrieved. Of course, you can use any object that fits into Sparki’s gripper, but if you don’t figure out which kind of objects to use, we will show here how to create some cheap and easy to make cylinders that will be ideal for this and other future advanced robotic lessons: 1. Dimensions: We want our objects to fit inside’s Sparki’s gripper, which has a maximum span of about 7 centimeters (cm). Our choice was to use cylinders with a diameter of about 5 cm, which resulted on a relatively easy to grip object, as you can see in the video above. Regarding the height, ideally our cylinders should be between 10 and 12 cm, to allow the Sparki’s ultrasonic ranger to detect them: 2. Flexibility: It’s convenient that the objects are a bit flexible, in order to be grasped with Sparkis gripper easily. Materials such as cardboard could be ideal for our purposes. 3. Weight: We don’t want the objects to be heavy weighted, since our little robot needs to deal with them. So, to make our objects we can grab some cardboard tubes, often easy to recycle from cleaning paper rolls (just to give an idea of a possible source for them): 4. Appearance: Of course we can use the crude cardboard cylinders for our activities. But this has two drawbacks. First, without caps, they will probably last less since they will be structurally weaker. Second, they are ugly! So let’s create the caps, also with cardboard. Then you can glue them with a glue stick, for example: Finally, we may want to cover the cylinders with colored paper, starting with the caps: Here we are: Now that we have the objects (we will use 2 of them here, but you can make some more if you want), let’s program Sparki to go for them!Coding!
Let’s start programming one step at a time in order to better understand the whole program. Please remember to check that the batteries are properly connected (and charged!). And as we are going to use the motors here, please check that the On/Off Switch is on. Another important thing to take care of when playing with the robot’s motors is to be careful not to be working over a table. A fall from that table could permanently damage your Sparki. As explained previously, the first task that Sparki should be able to do is follow a line. To do this, we will use the same algorithm used in the Line Following lesson. Please read it if you are not familiar with the concepts there before continuing. But our program here will be far more complex that the line follower, so all the line following algorithm will be encapsulated in a function, as we did before in the Maze Solving lesson:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void followLine(bool approachingObject) { state = "follow line"; if (approachingObject) { state = "approaching"; if (ping <= objectDistance) // if the object is so close, stop the robot { gripObject(); finished(); while(true); //work done => program ends here! } } if (!lineLeft) // if the black line is below left line sensor { moveLeft(); // turn left } else if (!lineRight) // if the black line is below right line sensor { moveRight(); // turn right } else if (lineLeft && !lineCenter && lineRight) // if the center line sensor is the only one reading a line { sparki.moveForward(); // move forward } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
void readSensors() { //each sensor is 1 if reading white, and 0 if reading black: lineLeft = sparki.lineLeft() > threshold; lineCenter = sparki.lineCenter() > threshold; lineRight = sparki.lineRight() > threshold; ping = sparki.ping(); } bool startMark() { return !lineLeft && !lineCenter && !lineRight; } void showSensorsAndState() { sparki.clearLCD(); // wipe the screen sparki.print("Line Left: "); // show left line sensor on screen sparki.println(lineLeft); sparki.print("Line Center: "); // show center line sensor on screen sparki.println(lineCenter); sparki.print("Line Right: "); // show right line sensor on screen sparki.println(lineRight); sparki.print("Ping: "); // ultrasonic ranger on screen sparki.print(ping); sparki.println(" cm"); sparki.println(String("state = ") + state); sparki.updateLCD(); // display all of the information written to the screen } |
1 2 3 4 5 6 7 8 |
void gripObject() { sparki.moveStop(); state = "gripping object"; sparki.gripperClose(); delay(gripTime); sparki.gripperStop(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void loop() { readSensors(); if (ping < (objectDistance * 3)) { followLine(true); // aproximates to the detected object, but always centered on the black line } else { followLine(false); } showSensorsAndState(); delay(100); // wait 0.1 seconds } |
1 2 3 4 5 6 7 |
void loop() { readSensors(); followLine(ping < (objectDistance * 3)); showSensorsAndState(); delay(100); // wait 0.1 seconds } |
- undefined: this is the initial state, when the robot has not yet even started to follow a line.
- gripping object: the robot has stopped to move, and is trying to grasp the detected object with its gripper.
- releasing object: the robot is releasing the object (this should happen only when it’s in the retrieval zone).
- approaching: an object was detected near enough, so the robot is now carefully approaching to it (while still following the line, of course) in order to grasp the object as soon as it reaches the gripper’s range.
- follow line: as its name says, the robot is following the line course.
- turn back: the robot rotates to turn back, since it has grasped an object and wants to retrieve it to the retrieving zone.
- finished: the robot has retrieved all the objects to the retrieving zone. The program has stopped at this point until the next reset or on/off cycle of the robot.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
#include <Sparki.h> // include the sparki library const int threshold = 500; // line sensors thereshold const int objectDistance = 3; // distance to the object where the gripper has to grip it [cm] const int gripTime = 1500; // time that takes to the gripper effectively grip the object [milliseconds] const int turnSpeed = 0; //used to turn the robot over an external center of rotation. bool lineLeft = false, lineCenter = false, lineRight = false; int ping = 0; String state = "undefined"; void readSensors() { //each sensor is 1 if reading white, and 0 if reading black: lineLeft = sparki.lineLeft() > threshold; lineCenter = sparki.lineCenter() > threshold; lineRight = sparki.lineRight() > threshold; ping = sparki.ping(); } bool startMark() { return !lineLeft && !lineCenter && !lineRight; } void showSensorsAndState() { sparki.clearLCD(); // wipe the screen sparki.print("Line Left: "); // show left line sensor on screen sparki.println(lineLeft); sparki.print("Line Center: "); // show center line sensor on screen sparki.println(lineCenter); sparki.print("Line Right: "); // show right line sensor on screen sparki.println(lineRight); sparki.print("Ping: "); // ultrasonic ranger on screen sparki.print(ping); sparki.println(" cm"); sparki.println(String("state = ") + state); sparki.updateLCD(); // display all of the information written to the screen } void gripObject() { sparki.moveStop(); state = "gripping object"; sparki.gripperClose(); delay(gripTime); sparki.gripperStop(); } void finished() { //tells the user that the work is done: state = "finished!"; sparki.gripperStop(); sparki.moveStop(); showSensorsAndState(); sparki.beep(880, 300); delay(300); sparki.beep(440, 300); delay(300); sparki.beep(880, 600); delay(600); } void moveLeft() { //turn left at a lower speed: sparki.motorRotate(MOTOR_LEFT, DIR_CCW, turnSpeed); sparki.motorRotate(MOTOR_RIGHT, DIR_CW, 100); } void moveRight() { //turn right at a lower speed: sparki.motorRotate(MOTOR_LEFT, DIR_CCW, 100); sparki.motorRotate(MOTOR_RIGHT, DIR_CW, turnSpeed); } void followLine(bool approachingObject) { state = "follow line"; if (approachingObject) { state = "approaching"; if (ping <= objectDistance) // if the object is so close, stop the robot { gripObject(); finished(); while(true); //work done => program ends here! } } if (!lineLeft) // if the black line is below left line sensor { moveLeft(); // turn left } else if (!lineRight) // if the black line is below right line sensor { moveRight(); // turn right } else if (lineLeft && !lineCenter && lineRight) // if the center line sensor is the only one reading a line { sparki.moveForward(); // move forward } } void setup() { sparki.servo(SERVO_CENTER); // rotate the servo to is 0 degree postion (forward) //indicates to the user that the program started: sparki.beep(440, 300); delay(300); sparki.beep(880, 500); } void loop() { readSensors(); if (ping < (objectDistance * 3)) { followLine(true); // aproximates to the detected object, but always centered on the black line } else { followLine(false); } showSensorsAndState(); delay(100); // wait 0.1 seconds } |
1 2 3 4 5 6 7 8 |
void turnBack() { state = "turn back"; sparki.moveLeft(90); //turn left a fixed angle to ensure that the center sensor does not see the line sparki.moveLeft(); // turn left until the robot is centered centerRobot(); sparki.beep(); // the line has been found! } |
1 2 3 4 5 6 7 8 9 |
void centerRobot() { readSensors(); //very important! if this is not done, lineCenter will still be false from the last reading while(lineCenter) // this cycle ends when the Center Line sensor detects the black line { readSensors(); showSensorsAndState(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
void followLine(bool approachingObject) { state = "follow line"; if (approachingObject) { state = "approaching"; if (ping <= objectDistance) // if the object is so close, stop the robot { gripObject(); turnBack(); readSensors(); while (lineLeft || lineCenter || lineRight) // when the START mark is reached the 3 sensors read false (black) { readSensors(); followLine(false); delay(100); } sparki.beep(440, 300); // make a sound to tell the START mark is there sparki.moveForward((maxObjects - objectIndex) * objectSize); // passes the mark releaseObject(); sparki.moveBackward(objectSize + 2); // the constant number is a small security margin to avoid the object when turning turnBack(); objectIndex++; if (objectIndex == maxObjects) { finished(); while(true); //work done => program ends here! } } } if (lineLeft && !lineCenter && lineRight) // if the center line sensor is the only one reading a line { sparki.moveForward(); // move forward } else if (!lineLeft) // if the black line is below left line sensor { moveLeft(); // turn left } else if (!lineRight) // if the black line is below right line sensor { moveRight(); // turn right } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
state = "approaching"; if (ping <= objectDistance) // if the object is so close, stop the robot { gripObject(); turnBack(); readSensors(); while (lineLeft || lineCenter || lineRight) // when the START mark is reached the 3 sensors read false (black) { readSensors(); followLine(false); delay(100); } sparki.beep(440, 300); // make a sound to tell the START mark is there [...] |
1 |
const int maxObjects = 2; // number of objects to retrieve |
1 |
int objectIndex = 0; |
1 |
sparki.moveForward((maxObjects - objectIndex) * objectSize); // passes the mark |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[...] sparki.moveForward((maxObjects - objectIndex) * objectSize); // passes the mark releaseObject(); sparki.moveBackward(objectSize + 2); // the constant number is a small security margin to avoid the object when turning turnBack(); objectIndex++; if (objectIndex == maxObjects) { finished(); while(true); //work done => program ends here! } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
/******************************************* Ojbect Retrieval example: The robot follows a line looking for objects that has to be retrieved to the course's START mark. ********************************************/ #include <Sparki.h> // include the sparki library const int threshold = 500; // line sensors thereshold const int objectDistance = 3; // distance to the object where the gripper has to grip it [cm] const int objectSize = 5; // diamter of the (cylindrical) objects const int maxObjects = 2; // number of objects to retrieve const int gripTime = 1500; // time that takes to the gripper effectively grip the object [milliseconds] const int turnSpeed = 0; //used to turn the robot over an external center of rotation. bool lineLeft = false, lineCenter = false, lineRight = false; int ping = 0; int objectIndex = 0; String state = "undefined"; void readSensors() { //each sensor is 1 if reading white, and 0 if reading black: lineLeft = sparki.lineLeft() > threshold; lineCenter = sparki.lineCenter() > threshold; lineRight = sparki.lineRight() > threshold; ping = sparki.ping(); } void showSensorsAndState() { sparki.clearLCD(); // wipe the screen sparki.print("Line Left: "); // show left line sensor on screen sparki.println(lineLeft); sparki.print("Line Center: "); // show center line sensor on screen sparki.println(lineCenter); sparki.print("Line Right: "); // show right line sensor on screen sparki.println(lineRight); sparki.print("Ping: "); // ultrasonic ranger on screen sparki.print(ping); sparki.println(" cm"); sparki.print(String("objects retrieved = ")); sparki.println(objectIndex); sparki.println(String("state = ") + state); sparki.updateLCD(); // display all of the information written to the screen } void gripObject() { sparki.moveStop(); state = "gripping object"; sparki.gripperClose(); delay(gripTime); sparki.gripperStop(); } void releaseObject() { sparki.moveStop(); state = "releasing object"; sparki.gripperOpen(); delay(gripTime); sparki.gripperStop(); } void finished() { //tells the user that the work is done: state = "finished"; sparki.gripperStop(); sparki.moveStop(); showSensorsAndState(); sparki.beep(880, 300); delay(300); sparki.beep(440, 300); delay(300); sparki.beep(880, 600); delay(600); } void centerRobot() { readSensors(); //very important! if this is not done, lineCenter will still be false from the last reading while(lineCenter) // this cycle ends when the Center Line sensor detects the black line { readSensors(); showSensorsAndState(); } } void turnBack() { state = "turn back"; sparki.moveLeft(90); //turn left a fixed angle to ensure that the center sensor does not see the line sparki.moveLeft(); // turn left until the robot is centered centerRobot(); sparki.beep(); // the line has been found! } void moveLeft() { //turn left at a lower speed: sparki.motorRotate(MOTOR_LEFT, DIR_CCW, turnSpeed); sparki.motorRotate(MOTOR_RIGHT, DIR_CW, 100); } void moveRight() { //turn right at a lower speed: sparki.motorRotate(MOTOR_LEFT, DIR_CCW, 100); sparki.motorRotate(MOTOR_RIGHT, DIR_CW, turnSpeed); } void followLine(bool approachingObject) { state = "follow line"; if (approachingObject) { state = "approaching"; if (ping <= objectDistance) // if the object is so close, stop the robot { gripObject(); turnBack(); readSensors(); while (lineLeft || lineCenter || lineRight) // when the START mark is reached the 3 sensors read false (black) { readSensors(); followLine(false); delay(100); } sparki.beep(440, 300); // make a sound to tell the START mark is there sparki.moveForward((maxObjects - objectIndex) * objectSize); // passes the mark releaseObject(); sparki.moveBackward(objectSize + 2); // the constant number is a small security margin to avoid the object when turning turnBack(); objectIndex++; if (objectIndex == maxObjects) { finished(); while(true); //work done => program ends here! } } } if (lineLeft && !lineCenter && lineRight) // if the center line sensor is the only one reading a line { sparki.moveForward(); // move forward } else if (!lineLeft) // if the black line is below left line sensor { moveLeft(); // turn left } else if (!lineRight) // if the black line is below right line sensor { moveRight(); // turn right } } void setup() { sparki.servo(SERVO_CENTER); // rotate the servo to is 0 degree postion (forward) //indicates to the user that the program started: sparki.beep(440, 300); delay(300); sparki.beep(880, 500); } void loop() { readSensors(); if (ping < (objectDistance * 3)) { followLine(true); // aproximates to the detected object, but always centered on the black line } else { followLine(false); } showSensorsAndState(); delay(100); // wait 0.1 seconds } |
1 2 3 4 5 6 7 |
void loop() { readSensors(); followLine(ping < (objectDistance * 3)); showSensorsAndState(); delay(100); // wait 0.1 seconds } |
Extra Activities
- There is an interesting function available in SparkiDuino software which allows us to know the exact time (in milliseconds) since the current program has started to run in the robot’s brain. Its name is millis. Using it, (and with the help of a variable), you can measure the time between the beginning and the end of the program. And as with any internal value, you can show that time in the LCD display. Why not try to distribute the objects in different parts of the course, measuring the time that takes to Sparki to retrieve the objects deployed with these different distributions? Also, you can modify the maxObjects constant in your program to make Sparki try to retrieve more objects.
- There is another improvement that can be done to the current program on this lesson. If you look carefully to the robot’s behavior, you will note that when it’s looking for the right place to release an object behind the START mark, the robot is not really following the line, but just moving forward. The program works fine anyway, since if the robot is a bit deviated from the line at this point, that doesn’t matter because it will move just for a short distance. But why not modify the program in a way that Sparki can follow the line even when it’s looking for the object retrieval place? Tip: you can change the bool parameter that the followLine function receives by an integer number, which will content a value indicating if the robot is just following a line (0), approaching an object (1), or finding the retrieval place (2).