The Ping))) Ultrasonic Distance Sensor lets your ActivityBot detect obstacles and measure the distance to them. Even though the PING))) sensor looks a bit like eyes, it is more like a mouth and an ear.
Much like a bat, the PING))) sensor emits an ultrasonic chirp when triggered, then listens for the chirp's echo and signals its return. The Propeller microcontroller can mark the time between triggering the PING))) sensor and getting a return signal. This echo return time can then be used to calculate the distance to an object.
This ability makes different ActivityBot navigation strategies possible, such as avoiding obstacles while roaming, or maintaining a set distance to an object that is moving.
In this activity, you will build the PING))) sensor circuit and use it to measure distances of a variety of objects to get familiar with what it does and does not detect. The PING))) sensor just needs power, ground, and one signal connection for the Propeller to get distance measurements.
*P14/P15 resistors are only needed for ActivityBot kits using External Encoders (#32500).
Ping))) Sensor and Servo Ports: When you are ready to design your own projects, you might want to mount the Ping))) sensor somewhere else on the robot. To make this easier, you can set the P16–P17 servo port power jumper to 5V, and then connect the sensor to one of those ports with a 3-pin extension cable. Each servo port includes a 3.9 k-ohm resistor in series with its Propeller I/O pin, so you would not need to include the 2.2 k-ohm resistor that is needed for the breadboard circuit.
The PING))) sensor's echo-location works on objects that can effectively reflect sound back at the sensor, from up to 3.3 meters away. Small objects don't reflect back enough sound. Hard-surfaced cylinders, ball-shaped obstacles, and walls if faced directly, work well. Walls that are approached at glancing angle will simply reflect the sound further away instead of back at the PING))) sensor. Soft items such as curtains and stuffed animals tend to muffle the sound and not bounce the echo.
For obstacles that do reflect sound back, the PING))) sensor and ping library provide centimeter distance measurements in the 3 cm to 3 m range. Air temperature can affect the accuracy of these distance measurements. Check out this reference article [3] about the speed of sound vs. air temperature to learn more about why this happens.
The test program will display the PING))) distance measurements in the SimpleIDE terminal. You can use it to test how well the PING))) sensor can detect various objects, and walls at various angles, to get familiar with what your ActivityBot can and cannot do with this sensor.
The PING))) sensor requires a brief high/low signal called a start pulse from the Propeller chip to start the measurement. The sensor then makes its ultrasonic click and sets its SIG pin high. When the echo comes back, it sets its SIG pin low. The Propeller chip then can measure the duration of the PING))) sensor SIG pin's high signal, which corresponds to the echo return time.
The ping library takes care of the math for converting the echo return time measurement to distance in centimeters. All your code has to do is request the distance with the ping_cm function, and pass to it the number of the Propeller I/O pin connected to the sensor. In the test code, the function's return value gets stored in a variable named distance. Then, it gets displayed in the SimpleIDE terminal with print(“%c distance = %d cm %c”, HOME, distance, CLREOL). This particular print statment puts the cursor in the top-left home position with HOME, displays the distance, and then CLREOL clears any characters to the end of the line. CLREOL is useful if the previous measurement has more digits than the current measurement. (If the program skipped that step, it would display cmm instead of cm after the measurement if it happens to be one less digit than the previous measurement. Try it if you want.)
/* Test Ping.c Test the PING))) sensor before using it to navigate with the ActivityBot. */ #include "simpletools.h" // Include simpletools header #include "ping.h" // Include ping header int distance; // Declare distance variable int main() // main function { while(1) // Repeat indefinitely { distance = ping_cm(8); // Get cm distance from Ping))) print("%c distance = %d%c cm", // Display distance HOME, distance, CLREOL); pause(200); // Wait 1/5 second } }
The ping library measures the echo pulse in terms of microseconds and then uses the fact that sound travels at 0.03448 cm/µs at room temperature. That’s 3.448 hundredths of a centimeter per millionth of a second at a temperature of (22.2 ºC). Just as a car travels distance = speed x time, so does sound, with an equation of s = ct, where s is the distance, c is the speed of sound and t is the time.
To calculate the distance of an echo, you have to remember that the time measurement is for twice the distance (there and back), so the equation would be:
Divide both sides by 2, and you’ve got an equation for distance from a time measurement.
Since 0.03448/2 ≈ 1/58, the equation for cm distance from microsecond echo time becomes:
Check out our Reference page on the "Speed of Sound in Air v. Temperature [4]" to learn how air temperature affects ultrasonic sensor readings.
You can display the raw microsecond time measurements with a call to the ping function.
Now that you can measure centimeter distances with the PING))) sensor, and know which kinds of objects it can sense, let’s put it to work in navigation.
All your code has to do is make the robot go forward until it receives a distance measurement from the PING))) sensor that’s less than some pre-defined value, say 20 cm. Then, slow down to a stop. After that, turn in place until the measured distance is more than 20 cm. At that point, it’ll be safe to go forward again. To make the ActivityBot's navigation a little more interesting, your code can also use random numbers to decide which direction to turn.
Before actually roaming, it’s important to test a smaller program to make sure your ActivityBot will stop in front of an obstacle, and turn away from it. This example program makes the ActivityBot go forward while the PING))) measured distance is greater than 20 cm. If the distance measures less than or equal to 20 cm, the robot ramps down to a stop. Then, it turns in place until it measures more than 20 cm, which means the robot turned until the object is no longer visible.
This program doesn’t use distance = ping_cm(8). Instead, it just takes the value ping_cm(8) returns, and uses it in decisions. So, it doesn’t need to declare a distance variable; however, it does still need a variable for storing a random number to decide which way to turn. So before the main function, there's a declaration for a variable named turn.
The program starts with drive_setRampStep(10), which sets the number of ticks per second that the speed is allowed to change for every 50th of a second. Since the function call passes 10, it means the speed can only change by 10 ticks per second, every 50th of a second. Next, drive_ramp(128, 128) ramps up to full speed, in steps of 10 ticks per second. With a ramp step of 10, it takes 13 50ths of a second = 0.26 seconds to ramp up to full speed. All the steps are by 10, except for the last one, which is from 120 to 128.
/* Detect and Turn from Obstacle.c Detect obstacles in the ActivityBot's path, and turn a random direction to avoid them. */ #include "simpletools.h" // Include simpletools header #include "abdrive.h" // Include abdrive header #include "ping.h" // Include ping header int turn; // Navigation variable int main() // main function { drive_setRampStep(10); // 10 ticks/sec / 20 ms drive_ramp(128, 128); // Forward 2 RPS // While disatance greater than or equal // to 20 cm, wait 5 ms & recheck. while(ping_cm(8) >= 20) pause(5); // Wait until object in range drive_ramp(0, 0); // Then stop // Turn in a random direction turn = rand() % 2; // Random val, odd = 1, even = 0 if(turn == 1) // If turn is odd drive_speed(64, -64); // rotate right else // else (if turn is even) drive_speed(-64, 64); // rotate left // Keep turning while object is in view while(ping_cm(8) < 20); // Turn till object leaves view drive_ramp(0, 0); // Stop & let program end }
Next, the program enters a one-line loop: while(ping_cm() >= 20) pause(5). This translates to "check to see if the ping_cm function returned a value equal to or greater than 20 (no object is detected within 20 cm), and if this is true, pause for 5 ms and then check again." As long as ping_cm returns greater than 20, the while loop keeps looping, the ActivityBot continues to drive straight forward.
If a ping_cm function call returns a value of 20 or less (an object is detected within 20 cm), then the while condition is no longer true and the loop stops repeating. This allows the code to move on to drive_ramp(0, 0). This slows the ActivityBot down to a stop, which also takes about 0.26 seconds.
After stopping, the ActivityBot has to decide which direction to turn to avoid the object. This example uses the math library’s rand function (included by simpletools) to randomly choose between left and right. The rand function returns a pseudo random value somewhere in the 0 to 2,147,483,648 range. Each time the code calls rand, it returns a new value in its pseudo-random sequence. The statement turn = rand() % 2 divides 2 into the random number, takes the remainder (which will be a 1 or 0), and copies it to the turn variable.
This turn variable’s random 1 or 0 is used to decide which direction to turn. If turn gets a random one, if(turn == 1) drive_speed(64, -64) statement makes the ActivityBot rotate right. If instead turn gets a random zero, drive_speed(-64, 64) makes it rotate left. After that, while(ping_cm(8) < 20) keeps repeating while the object is still in view. As soon as the ActivityBot has turned far enough, it stops.
If all that worked, most of the code in the main function can be put in a while(1) loop to make the ActivityBot go looking for new obstacles.
Library List — This application uses functions from four libraries, simpletools (pause), abdrive (anything starting with drive), ping (ping_cm) , and math (rand). The math library is not declared here because simpletools already includes it, but you could just as easily add #include <math.h> to the list. It would be enclosed in < > symbols instead of quotes by convention since it’s part of Propeller GCC and not part of the Propeller C Tutorial's custom libraries (like simpletools, abdrive, and ping).
This program just needs a while(1) loop to make it move on in search of the next obstacle and continue to repeat what it just did. The only thing that doesn’t need to be part of the while loop is the last drive_ramp(0, 0).
Here are three challenges for you.
Instead of finding and avoiding obstacles, how about making the ActivityBot follow a target instead? Same sensor system, different code. All the program has to do is check for a desired distance, and if the object is further away than that, move forward. If the object is closer than the desired distance, move backward. If the object’s distance is equal to the desired distance, stay still.
This example makes the ActivityBot try to keep a 32 cm distance between itself and an object in front of it. With no object detected, the robot will go forward until it finds one, then hone in and maintain that 32 cm distance. If you pull the object away from the ActivityBot, it will go forward to catch up. Likewise, if you push the object toward it, it will back up to restore that 32 cm distance.
The program declares five variables: distance, setPoint, errorVal, kp, and speed.
/* Follow with Ping.c Maintain a constant distance between ActivityBot and object. */ #include "simpletools.h" // Include simpletools header #include "abdrive.h" // Include abdrive header #include "ping.h" // Include ping header int distance, setPoint, errorVal, kp, speed; // Navigation variables int main() // main function { setPoint = 32; // Desired cm distance kp = -10; // Proportional control drive_setRampStep(6); // 7 ticks/sec / 20 ms while(1) // main loop { distance = ping_cm(8); // Measure distance errorVal = setPoint - distance; // Calculate error speed = kp * errorVal; // Calculate correction speed if(speed > 128) speed = 128; // Limit top speed if(speed < -128) speed = -128; drive_rampStep(speed, speed); // Use result for following } }
The program starts off by making the setPoint 32 cm and the proportionality constant kp -10. It also sets the ramp step size to 6, which will cushion sudden changes in speed. Inside the while(1) loop the program makes the ActivityBot maintain a distance between itself and the target object by repeating five steps very rapidly:
An automated system that maintains a desired physical output level is called a control system. In the case of our ActivityBot, the level it maintains is the distance between itself and the object in front of it. Many control systems use a feedback loop to maintain the level, and our ActivityBot is a prime example of this. The measured distance is fed back into the system and compared against the set point for calculating the next speed, and this happens over and over again in a process called a control loop.
Engineers use drawings like this one to think through a control loop’s design and also to convey how it works to technicians and other engineers. If the set point (distance the ActivityBot tries to maintain) is 32 and the measured distance is 38, there is an error value. This error value is the set point – measured distance. That’s 32 – 38 = -6. This error is multiplied by a proportionality constant (kp) for an output that’s the speed to correct the error this time through the loop. That’s speed = kp x error value, or 60 = -10 x -6.
The circle is called a summing junction (for addition or subtraction), and the rectangular blocks designate operations that can vary. In this example, the top one is a proportional control block that multiplies error by a proportionality constant. The System block represents things we do not necessarily have control over, like if you move the object toward or away from the ActivityBot. Other common control blocks that were not used in this example are Integral, which allows corrections to build up over loop repetitions, and Derivative, which responds to sudden system changes.
It’s really helpful to see how the values respond to different measured distances. To do this, all you have to do is add some print values to the screen.
WARNING: This modified program is just for display, not navigation. For navigation, re-open Following with Ping.
If the measured distance is 40, errorVal = setPoint – distance results in errorVal = 32 – 40 = -8. Then, speed = kp * error is speed = -10 * -8 = + 80. So, the ActivityBot will try 80 ticks per second forward. This falls in the +/- 128 range, so the ActivityBot goes a little over half speed forward to catch up with the object. Now, try this for a distance of 24. The correct answer is speed = -80 which will make the ActivityBot back up. If the distance is 28 instead, the ActivityBot will back up less quickly, with a speed of -49.
There are lots of values to experiment with here. For example, if you change the setPoint variable from 32 to 20, the ActivityBot will try to maintain a distance of 20 cm between itself and an object in front of it. The value of kp can be increased to make it try to go faster to correct with smaller error distances, or slower to correct over larger error distances.
If you decrease the drive_setRampStep value, it will cushion the changes, but could cause it to respond so slowly that it runs into the object. If you increase it, it will respond more abruptly. Too large, and it could actually change direction so fast that it could even eject the Ping))) sensor from the breadboard!
Links
[1] http://learn.parallax.com/activitybot/software-and-programming
[2] http://learn.parallax.com/propeller-c-set-simpleide/update-your-learn-folder
[3] https://learn.parallax.com/reference/speed-sound-air-vs-temperature
[4] https://learn.parallax.com/support/reference/speed-sound-air-vs-temperature