[[McComb 04-01 Ressources Mounting Lidar on Robot|👉 Next page]] # McComb 02-02 Making McComb-Robot an Obstacle Avoiding Robot Using an Ultrasonic Sensor So we are ready to start with this tutorial: https://automaticaddison.com/how-to-make-an-obstacle-avoiding-robot-arduino/ As Automaticaddison works with the coordinate system on the breadboard, it comes a little bit unhandy that we (Thomas) put the board on the robot upside down. ==Always check for the coordinate system on the breadboard before glueing it to its place and align it correctly. This makes things easier later on, at least for some tutorials.== For now we solve the problem by using once again the McComb's manual for the hardware, comparing it to the tutorial of automaticaddison and the use the code of the later from online. ### Installing and testing the ultrasonic sensor on the robot Interestingly enough McComb's manual is missing a page, namely page 27 with instructions we would need now. Therefore we skip that and go on with automaticaddison's tutorial linked above. There it is quite easy to see where the ultrasonic sensor has to be placed. First we want to use shorter cables to save some space on the Arduino for mounting the Jetson Nano and the Lidar later. Right now they are too long, looking like this: ![[als-thn-IMG_9447-cable-long-before-short.jpg]] After exchanging the cables they are much shorter looking like this: ![[als-thn-IMG_9448-cable-short-after-long.jpg]] Now we install the sensor on the breadboard. ![[als-thn-IMG_9449-ultrasonic-sensor-breadboard.jpg]] As we think about using a smaller Lidar sensor later on first, we divert from the tutorial: Our power supply for the sensor goes first to the breadboard (the yellow cable in the picture below). From there we can later power sensor _and_ Lidar. For a bigger RPLidar A1M8 this would not be necessary as this Lidar is powered by a power bank in this case. The ultrasonic sensor after wiring it to the Arduino: ![[als-thn-IMG_9450-ultrasonic-sensor-cables.jpg]] We wonder what the `out` pin of the sensor does. It is not in use now, but we do not find information on the fly. We have other ultrasonic sensors without that pin. Now we try to compile this code: ```Cpp /** * This program tests the ultrasonic * distance sensor * * @author Addison Sears-Collins * @version 1.0 2019-05-13 */ /* Give a name to a constant value before * the program is compiled. The compiler will * replace references to Trigger and Echo with * 7 and 8, respectively, at compile time. * These defined constants don't take up * memory space on the Arduino. */ #define Trigger 7 #define Echo 8 /* * This setup code is run only once, when * Arudino is supplied with power. */ void setup(){ // Set the baud rate to 9600. 9600 means that // the serial port is capable of transferring // a maximum of 9600 bits per second. Serial.begin(9600); // Define each pin as an input or output. pinMode(Echo, INPUT); pinMode(Trigger, OUTPUT); } void loop(){ // Make the Trigger LOW (0 volts) // for 2 microseconds digitalWrite(Trigger, LOW); delayMicroseconds(2); // Emit high frequency 40kHz sound pulse // (i.e. pull the Trigger) // by making Trigger HIGH (5 volts) // for 10 microseconds digitalWrite(Trigger, HIGH); delayMicroseconds(10); digitalWrite(Trigger, LOW); // Detect a pulse on the Echo pin 8. // pulseIn() measures the time in // microseconds until the sound pulse // returns back to the sensor. int distance = pulseIn(Echo, HIGH); // Speed of sound is: // 13511.811023622 inches per second // 13511.811023622/10^6 inches per microsecond // 0.013511811 inches per microsecond // Taking the reciprocal, we have: // 74.00932414 microseconds per inch // Below, we convert microseconds to inches by // dividing by 74 and then dividing by 2 // to account for the roundtrip time. distance = distance / 74 / 2; // Print the distance in inches Serial.println(distance); // Pause for 100 milliseconds delay(100); } ``` The code works and we receive data in the serial monitor of the Arduino IDE: ![[als-thn-IMG_9451-first-data-from-ultrasonic-sensor.jpg]] Interestingly there are already two values way out of the ordinary here: `202` seems to be by far to much (this is in inch) and `-217` seems to be quite flat right impossible, would it mean that the obstacle is behind – or inside – the sensor. > Later on we ask ourselves why the program does not make a single measurement to detect an obstacle but four measurements, which are then averaged. The answer seems to be already here in the data: Because there are corrupt values coming up from time to time. The program later even has a way implemented to deal with negative values. We asked ourselves why this is necessary at first, but looking at this data, we understand the need of why the program has to be ready to receive negative values from time to time from the ultrasonic device and that there has to be a way for the program to deal with them without producing errors, unwanted behaviour of the robot or just a wrong average of measurements. The later could happen, when in a quadruple of measurement values a negative value like the one above would arise: The negative value would result in an average computed by the program that would be understood by the program as an obstacle. E.g.: the values `22`, `-217`, `22`, `22` would give back an average of `-151` back to the program (by adding all values and dividing them through the number of values which is `4`). Therefore the following program excludes negative results of averaged measurements from counting as obstacles by using this line of code: `if (distance >= 0 && distance <= 2)`. Now it is time to use the code that should make the robot drive around detecting and avoiding obstacles. ```Cpp #include <Servo.h> /** * This robot avoids obstacles * using an ultrasonic sensor. * * @author Addison Sears-Collins * @version 1.0 2019-05-13 */ // Create two servo objects, one for each wheel Servo right_servo; Servo left_servo; /* Give a name to a constant value before * the program is compiled. The compiler will * replace references to Trigger and Echo with * 7 and 8, respectively, at compile time. * These defined constants don't take up * memory space on the Arduino. */ #define Trigger 7 #define Echo 8 /* * This setup code is run only once, when * Arudino is supplied with power. */ void setup(){ // Set the baud rate to 9600. 9600 means that // the serial port is capable of transferring // a maximum of 9600 bits per second. Serial.begin(9600); right_servo.attach(9); // Right servo to pin 9 left_servo.attach(10); // Left servo to pin 10 // Define each pin as an input or output. pinMode(Echo, INPUT); pinMode(Trigger, OUTPUT); // Initializes the pseudo-random number generator // Needed for the robot to wander around the room randomSeed(analogRead(3)); delay(200); // Pause 200 milliseconds go_forward(); // Go forward } /* * This is the main code that runs again and again while * the Arduino is connected to power. */ void loop(){ int distance = doPing(); // If obstacle <= 2 inches away if (distance >= 0 && distance <= 2) { Serial.println("Obstacle detected ahead"); go_backwards(); // Move in reverse for 0.5 seconds delay(500); /* Go left or right to avoid the obstacle*/ if (random(2) == 0) { // Generates 0 or 1, randomly go_right(); // Turn right for one second } else { go_left(); // Turn left for one second } delay(1000); go_forward(); // Move forward } delay(50); // Wait 50 milliseconds before pinging again } /* * Returns the distance to the obstacle as an integer */ int doPing () { int distance = 0; int average = 0; // Grab four measurements of distance and calculate // the average. for (int i = 0; i < 4; i++) { // Make the Trigger LOW (0 volts) // for 2 microseconds digitalWrite(Trigger, LOW); delayMicroseconds(2); // Emit high frequency 40kHz sound pulse // (i.e. pull the Trigger) // by making Trigger HIGH (5 volts) // for 10 microseconds digitalWrite(Trigger, HIGH); delayMicroseconds(10); digitalWrite(Trigger, LOW); // Detect a pulse on the Echo pin 8. // pulseIn() measures the time in // microseconds until the sound pulse // returns back to the sensor. distance = pulseIn(Echo, HIGH); // Speed of sound is: // 13511.811023622 inches per second // 13511.811023622/10^6 inches per microsecond // 0.013511811 inches per microsecond // Taking the reciprocal, we have: // 74.00932414 microseconds per inch // Below, we convert microseconds to inches by // dividing by 74 and then dividing by 2 // to account for the roundtrip time. distance = distance / 74 / 2; // Compute running sum average += distance; // Wait 10 milliseconds between pings delay(10); } // Return the average of the four distance // measurements return (average / 4); } /* * Forwards, backwards, right, left, stop. */ void go_forward() { right_servo.write(0); left_servo.write(180); } void go_backwards() { right_servo.write(180); left_servo.write(0); } void go_right() { right_servo.write(180); left_servo.write(180); } void go_left() { right_servo.write(0); left_servo.write(0); } void stop_all() { right_servo.write(90); left_servo.write(90); } ``` Result: The robot does detect obstacles, but much too late. I.e. the robot collides with the object and tries to ram it down. And only then it recognises the obstacle and turns around. We stopped the video before that last part of the described behaviour took place to "rescue" the robot. ![[McComb-Roboter_3-comp.mp4]] So: Why? We experiment with the robot in its current state using a paper box as obstacle. ![[McComb-Roboter_4-comp.mp4]] So the code seems okay: The robot first drives back and then turns. We change the code: We double the distance to the obstacle that makes the robot react. So we change the according code segment like this: ```Cpp // If obstacle <= 2 inches away if (distance >= 0 && distance <= 4) { Serial.println("Obstacle detected ahead"); go_backwards(); // Move in reverse for 0.5 seconds delay(500); ``` Now the robot does not recognise obstacles at all. Go for it, buddy! ![[McComb-Roboter_5-comp.mp4]] We try the old code with distance = 2. The problems remains. We start the serial monitor and find out that the obstacle detection works but seems to react quite slow when we put the robot constantly in front of an obstacle. ![[McComb-Roboter_6-comp.mp4]] In the next attempt the serial monitor gives as `Obst` and then `ObstObstacle`… This should be because the Arduino was still running when connected to the computer and before the program was send to the Arduino again. Therefore it was already sending data to the serial monitor until it was interrupted and then started to send new data again off of the newly started program. So we experiment a little bit: Blocking only one of the two sensors does not result in an obstacle detected. Also if the obstacle is too close it does not react. There seems to be only a small band of space in which the robot detects something: ![[als-thn-IMG_9456-intervall-in-which-ultrasonic-sensor-works.jpg]] We play around with the delay given in the code to specify the duration the robot goes backwards and turns, setting them up to 5000 ms. For that, we put the robot up on a paper stand so we can observe it without having to run after it every time something goes wrong. ![[als-thn-IMG_9458-robot-on-paperstand.jpg]] This works insofar, as the wheels are now turning for a longer period of time, like the changes in our program made es expect: ![[McComb-Roboter_7-comp.mp4]] The robot itself seems to work according to the changes that we made. So we think: Is it the 9V battery? We are using a battery suited for recharging, and as it is brand new, it might be too weak, not powering the Arduino sufficiently because we did not charge it initially. We make one more test with the same battery. We put the delay for turning to its normal value of 1000 ms. We put the delay for going backwards to 3000 ms. ![[McComb-Roboter_8-comp.mp4]] Also on the floor the robot "works" again which means it again collides with the object but then turns back and makes a run for it. We think: Can it be that our servos are too strong and the device too fast? As the original parts were not available for us we use different servos than the ones recommended by McComb. But does Automaticaddison use the same motors as McComb? The robot in the video of Automaticaddison does detect every obstacle very reliable… We change the motor speed to half of its speed. Result: The robot is a little bit slower, but it does not recognise obstacles. We reduce the speed drastically. Result: The robot does not recognise obstacles. Or was it? As it moves much slower now, we are nothing but sure. But something very interesting is happening: The wheels of the robot do pause from time to time, now, i.e. they come to a full stop without any movement. This means: ==We have found the right value to stop the servos without calibrating them!== When we use `87` or `93` as value, the servos should stop. We try it out experimentally. Result: ==Our value to stop the servos both is `93`.== We put that into the code. We calibrate our values in the software now. `180` is now `113`. `0` is `73`. `90` is `93`. > This is the case, because normally `90` means that the servos stand still; `0` means the servo turns full speed in one direction; `180` means it goes full speed into the other direction (which direction is forward and which is backward depends on the position of the servo on the robot). But when `93` is the correct value where the servos do not move, we can try to adjust the other values accordingly. As we want our robot to go slower as before, we want to deviate only for a value of 20 from the servos resting position at `93`. So instead of `90 + 90 = 180` for the one direction of the servo, we calculate our new value for that like `93 + 20 = 113`. And instead of `90 - 90 = 0` for the other direction of the servo, we calculate `93 - 20 = 73`. We add a stop sequence to our code to test it. This works, the robot stands still when it should. We summarise: We think the problem of the robot ramming obstacles before reacting to them is just to the latency of the robot. To specify: It is because of the latency between measurements and not because of the latency between commands for movement that the problem occurs. To make things faster concerning measurements, we change from the average of 4 measurements as it is coded in the original program to just 2 and average them. It seemed to be a little bit better but still with collisions. We change to an average of `1`, i.e. we only make one measurement. This seems to work on our dry-dock. ![[McComb-Roboter_9-comp.mp4]] Now we give it a try in the wild (i.e. the floor of our lab): ![[McComb-Roboter_10-comp.mp4]] We do not know why averaging over 4 measurements does not work. But our solution works with only one measurement. We ask ourselves: Is there ultrasound in the house that makes our averages go wrong? Now we change some of values for the delay of movements back to the smaller values from before for more suitable movement of our robot: ```Cpp #include <Servo.h> /** * This robot avoids obstacles * using an ultrasonic sensor. * * @author Addison Sears-Collins * @version 1.0 2019-05-13 */ // Create two servo objects, one for each wheel Servo right_servo; Servo left_servo; /* Give a name to a constant value before * the program is compiled. The compiler will * replace references to Trigger and Echo with * 7 and 8, respectively, at compile time. * These defined constants don't take up * memory space on the Arduino. */ #define Trigger 7 #define Echo 8 /* * This setup code is run only once, when * Arudino is supplied with power. */ void setup(){ // Set the baud rate to 9600. 9600 means that // the serial port is capable of transferring // a maximum of 9600 bits per second. Serial.begin(9600); right_servo.attach(9); // Right servo to pin 9 left_servo.attach(10); // Left servo to pin 10 // Define each pin as an input or output. pinMode(Echo, INPUT); pinMode(Trigger, OUTPUT); // Initializes the pseudo-random number generator // Needed for the robot to wander around the room randomSeed(analogRead(3)); delay(200); // Pause 200 milliseconds go_forward(); // Go forward } /* * This is the main code that runs again and again while * the Arduino is connected to power. */ void loop(){ int distance = doPing(); // If obstacle <= 2 inches away if (distance >= 0 && distance <= 2) { Serial.println("Obstacle detected ahead"); go_backwards(); // Move in reverse for 0.5 seconds delay(1500); // WIR ERHÖHEN DIESEN WERT von 500 höher stop_all(); delay(300); /* Go left or right to avoid the obstacle*/ if (random(2) == 0) { // Generates 0 or 1, randomly go_right(); // Turn right for one second } else { go_left(); // Turn left for one second } delay(1000); go_forward(); // Move forward } delay(50); // Wait 50 milliseconds before pinging again } /* * Returns the distance to the obstacle as an integer */ int doPing () { int distance = 0; int average = 0; // Grab four measurements of distance and calculate // the average. for (int i = 0; i < 1; i++) { //changed from 4 to 1 // Make the Trigger LOW (0 volts) // for 2 microseconds digitalWrite(Trigger, LOW); delayMicroseconds(2); // Emit high frequency 40kHz sound pulse // (i.e. pull the Trigger) // by making Trigger HIGH (5 volts) // for 10 microseconds digitalWrite(Trigger, HIGH); delayMicroseconds(10); digitalWrite(Trigger, LOW); // Detect a pulse on the Echo pin 8. // pulseIn() measures the time in // microseconds until the sound pulse // returns back to the sensor. distance = pulseIn(Echo, HIGH); // Speed of sound is: // 13511.811023622 inches per second // 13511.811023622/10^6 inches per microsecond // 0.013511811 inches per microsecond // Taking the reciprocal, we have: // 74.00932414 microseconds per inch // Below, we convert microseconds to inches by // dividing by 74 and then dividing by 2 // to account for the roundtrip time. distance = distance / 74 / 2; // Compute running sum average += distance; // Wait 10 milliseconds between pings delay(10); } // Return the average of the four distance // measurements return (average / 1); //changed from 4 to 1 } /* * Forwards, backwards, right, left, stop. */ void go_forward() { // we change in the whole block 180 to 113 and 0 to 73. So we are 20 away from our new null-value 93. right_servo.write(73); left_servo.write(113); } void go_backwards() { right_servo.write(113); left_servo.write(73); } void go_right() { right_servo.write(113); left_servo.write(113); } void go_left() { right_servo.write(73); left_servo.write(73); } void stop_all() { // we use 93 because that stops our non calibrated servos. 90 was the original valuue here for both right_servo.write(93); left_servo.write(93); } ``` This code works! ![[McComb-Roboter_11-comp.mp4]] Following our first instinct (better late than never) ==we replaced the rechargeable battery with a normal battery. The robot now seems to function as it should==, at least in - 1 test - 2 tests - 3 tests So we now have a functioning McComb-robot bat with ultrasound to use with Lidar later! We will now have to figure out what exactly we want our robot to do with Lidar and find some tutorial – like the one mentioned some notes before – to learn how to do that. --- [[McComb 04-01 Ressources Mounting Lidar on Robot|👉 Next page]] # Back to: [[McComb 02-01 Setting Up the McComb-Robot Day 2 – Resources and Research for Ultrasonic Sensors]]