If we're honest, we've been feeling a little exhausted lately. It's been a bit too hectic, with all the stuff that Rosie's been up to. Don't get us wrong. We want her to be happy, energetic and excitable... but also to be a lot more careful. You know, not to destroy our house from top to bottom.
But it's also sort of our own fault too. Rosie is... erm... (how can we be polite about this..?) a little... wide. After all, let's not kid ourselves. She is (for now) still just a plastic box from the local supermarket. Of the cheapest variety. So while she has one ultrasonic distance sensor attached to her front - being positioned in the middle of her very un-aerodynamic, flat body - its coverage isn't exactly great. In fact, we've concluded: it's actually terrible. And that's why we think she has table legs for breakfast, or wrestles gnomes in the garden, or chooses to play tag with the toilet brush (yuck!). All very silly robot pastimes. So silly, in fact, that she needs to be upgraded.
So we've decided. Like cheeseburgers. Chocolate bars. Scoops of ice cream. Two of something is almost always better than one (just ask your dentist). With two HC-SR04 ultrasonic distance sensors, Rosie should have more information about the world around her. She should know a lot more. She might even be able to detect incoming objects, better.
Therefore, let's double up on our ultrasonic-ness. Make our ultrasound code, well... more sound. And dial up the volume of our sound waves in the name of Pi-ence. And just maybe, it'll be just what prompts Rosie to find a better hobby. One that doesn't involve broken tables, angry garden gnomes, and a dangerous liaison with a very filthy toilet brush.
You will need to have these:
- Raspberry Pi 3 (kitted out with wheels), and Raspbian running on SDHC card
- Computer from which you are connecting to the Raspberry Pi remotely
- 2 × HC-SR04 ultrasonic distance sensors. How many? Dos. Deux. 二. 두. Oh, did we mention we need two of these?
- Some resistors, soldering iron, solder, cables and heat shrink if you want to take this seriously. Very seriously. More on these mysterious items later.
You will need to have done these things:
- I’ve got Pi(3) brain
- Why-Fi-ght the Wi-Fi?
- Hurrah-ndom inventions
- Don't reinvent the eel
- Achoo! Crash-choo! Episode I
- Achoo! Crash-choo! Episode II
- Web of Pies
- Hello supervision, goodbye supervision
You will need to do this:
- Attach a second HC-SR04 ultrasonic distance sensor to the Pi's GPIO pins, using a voltage divider circuit if required (it's probably the case unless you are using a dedicated module)
- Modify the Python code to create a class for the ultrasonic distance sensor
- Adapt the code to make use of the class, and the second sensor
- Challenge the robot to crash into something
What do you get after all this?
In this episode, we briefly return to the mystical land of electronics. Mainly, it's because unlike our first HC-SR04 ultrasonic distance sensor that plugged directly into designated GPIO pins on the RasPiRobot V3 motor controller board, with the second one, we're very much on our own. Oh yes, and it's also an excuse to solder some stuff together using a soldering iron.We can learn here, that a HC-SR04 ultrasonic distance sensor outputs a 5 volt electrical signal on its "echo" pin when the sensor detects that a pulse sent via the "trigger" pin was returned. But we also learn that Raspberry Pi GPIO pins - when configured as an input pin - expects electrical signals back at 3.3 volts. That doesn't sound very good? No, it's the perfect recipe for some zapped circuitry (or so we're told and afraid to find out). And that's why we need to use two Resistors to create a Voltage Divider Circuit to convert the 5V output from the sensor (echo) into 3.3V. Time to solder some resistors to wires and pretend to know a whole lot more about electronics.
Then, once we've connected our sensor (together with the home-made voltage divider circuit) to our spare GPIO pins, we want to modify Rosie's Python code to make use of it. But before we do, we want to abandon RasPiRobot V3 motor controller board libraries' own function to interact with the sensor, and create our own. And when we do, we'll use a dash of Object Orientated Programming in Python, using Classes. This way, whether we have 1 distance sensor on our Rosie, or 1 million on Rosie v2100 Mark III Mars Explorer Edition, we can reuse our single class for each Instance of our sensor.
So that's how the theory goes. Two ultrasonic distance sensors + more Python code = ∞ of Rosie happiness. Let's put this into practice and see what happens.
This is simply too much detail:
Who wants more cables? Better still, who wants random bits of electronic circuity attached to their robots? If your answers to these questions are emphatic yeses, it's time to proceed to our next stage.Thankfully for us, back when people were still wearing top hats and waistcoats, some very clever people worked out what the relationship is between very scientific stuff; like electrical power (measured in Watts - W), voltage (measured in Volts - V), current (measured in Amps - A) and resistance (measured in Ohms - Ω*).
*Yes, that is actually the symbol for a measure of electrical resistance. We didn't make this one up.
You may have heard of some equations, like Voltage (V) = Current (I) × Resistance (R), otherwise known as Ohm's Law. Georg Ohm's important discovery pretty much underpins all of electronics, so we might as well know about it.
V = I × R
How does this help us with Rosie? It doesn't (not right now). So let's move onto another boring equation. We want to reduce our 5V "echo" output down to 3.3V. There's an equation that helps us with this too:
Vout = Vin × R2 / (R1 + R2)
The principles are helpfully explained here (and yes, Ohm's Law makes an appearance yet again). In short, we want two resistors (a R1 and a R2) that if we connect together, provide us with a voltage of 3.3V at the point at which they connect. As explained here, a 1kΩ resistor for R1 and a 2kΩ resistor for R2, provide us with a Vout value which is exactly two thirds of Vin. So the 5V Vin, helpfully becomes, approximately, 3.3V Vout when this sensor output is "high". Just what we need. Thanks Mr. Ohms. You really are the reason why we're getting on grandly with our Raspberry Pi.
Now we could create this voltage divider circuit in lots of different ways. It could be built in to the circuit board, like it is for the dedicated ultrasonic distance sensor pins on the RasPiRobot V3 motor controller board. Or we could use, for example, a breadbord (nothing to do with bread, of the wheat variety). But you wanted to solder some stuff, right? Sure. We did too. So we simply soldered one end of the 1kΩ resistor to the cable attached to the HC-SR04 ultrasonic distance sensor's echo pin, then soldered the 2kΩ resistor to the other end of the 1kΩ resistor. The remaining end of the 2kΩ resistor was then soldered to the ground (0V) cable. Crude. Yes. But quite effective, we found.
Here are the pins in question:
Pi GPIO | HC-SR04 |
---|---|
Any 5V (e.g. 2, 4) | Vcc |
33 (BCM 19) | Trigger |
37 (BCM 26) | Echo (via voltage divider circuit) |
Any 0V Ground (e.g. 6, 9, 14, 20, 25, 30, 34, 39) | 0V (Ground) |
Where's the 3.3V output then, I hear you ask? It's where the two resistors meet, and this is where we need to solder a new cable our for our "actual" echo pin that we connect down to the Pi's GPIO pin. All the joints were finally covered in heat shrink, and come to think of it, so were the resistors too.
And here's an important public service announcement:
Soldering can be dangerous. Like many things that are dangerous, it involves heat, and some tools (but not stunt bikes, or lethal animals... except later, a Python). So don't do it by yourself if you've never done it before, or don't know what you're doing.
And if you're what others consider a child, always have an adult there to help you. This way, you can laugh at their mistakes, rather than be in trouble for making them.
And if you're what others consider a child, always have an adult there to help you. This way, you can laugh at their mistakes, rather than be in trouble for making them.
Here's is a not a very exciting photo of our test rig.
...And more professional looking schematics relating to this additional ultrasonic sensor.
Great. We now have random electronic bits hanging out of our plastic box. And we think we've learnt some useful things about electricity. What else do we need to do?
We did promise you some Python action in this post. So here it is.
So far, we used the RasPiRobot V3 motor controller board's own library for our distance checking. This made sense, as we were using the GPIO pins on the motor controller board for the HC-SR04, and we only had one of it. That way, we were able to use the rrb3 module's get_distance() function to obtain our distance readings from the connected sensor. Simples.
...But now that we need to use GPIO pins that are not on the motor controller board, and on the Pi itself, we ought to create our own bit of code to do the same thing. We also now intend to have two sensors, but we may decide to have more in the future. We might even stop using the RasPiRobot V3 motor controller board someday. So creating reusable Python code that represents what a HC-SR04 sensor is and does seems like something that will save us time in the future. We could then keep using the same bit of code, for each sensor we attach, using any supported GPIO pin of our choosing.
Aha! We must be talking Object Orientated Programming? We sure are. Many programming languages support it. Python does too. And, surprisingly, we were actually already using it when we were doing this with our code to create specific instances of a RasPiRobot V3 motor controller board, or a RGB LED (aka 'the squid'). Only, we were using code that others had created for us.
rr = RRB3(9.0, 6.0) rgb = Squid(16, 20, 21)
Object Orientated Programming (OOP) allows the coder - yes, that's now you - to define and use... guess what... Objects. That's why it's called Object Orientated Programming, and not Meerkat Orientated Programming, as there are no meerkats to be seen during programming. And how do we describe an object? An object probably does something (OOP calls these functions, Methods). Or it has probably got some attributes (Instance Variables). These blueprints for Objects are called Classes. And using a class, we can create many Instances of that object. Confused? Let's call on some furry friends to help out.
...And for some reason, we still have meerkats in our heads. So here's an example of a meerkat class:
class Meerkat: def __init__(self, name, age, fur_colour): self.name = name self.age = age self.fur_colour = fur_colour def grow_older(self): self.age += 1 print(self.name, "is now aged", self.age)
During initialisation of a specific instance of a meerkat (using this class as a template), we give our meerkat a name, age and fur colour. These will be unique to each instance of a meerkat. Meerkats can also grow old (really, they do - they don't stay cute forever), so we have a method (basically a function in the class) that adds one year to that meerkat's age. We can now create many imaginary meerkats, and make them grow older, together. Pretty pointless, we realise.
m1 = Meerkat("Bob", 2, "Brown") m2 = Meerkat("Charleen", 5, "Golden") m1.grow_older() m2.grow_older() print(m1.age) print(m2.age)
We made some imaginary meerkats - christened Bob and Charleen - grow one year older using Python code. Let's move swiftly on *move on people, nothing to see here* and actually do something useful with OOP and classes.
So what does a distance sensor need to be able to do? Take temperature? No - check distance, of course. And all distance sensors need to do just that, so let's make those bits of Python code methods. What else do we need to know about our distance sensors to use them? The Raspberry Pi GPIO pins each uses for "echo" and "trigger" signals are pretty important, so that we know which pins to send our 'trigger' signal to, and from which pins we expect back the 'echo' signal. Like the meerkats, perhaps we can provide a meaningful description too for our instances of the sensor, like about where they are positioned. These can be our Instance Variables.
Let's give this a go. First, we'll create a DistanceSensor class, which has been largely
*For those that might find this fact interesting, speed of sound through air (i.e. your robot hasn't been launched into space yet) actually differs depending on environmental factors such as temperature. You can use an online calculator such as the one here, but if you want to take this experiment further, you could temporarily skip forward to a later project of ours where we use a temperature sensor connected to the Pi. From its reading, you can (allegedly) use the following equation if the air is dry like our humour:
v = 331 + 0.6 × T
...where v is the (speed of sound), and T the temperature in Celsius (c).
Now back to our class (quite literally).
class DistanceSensor: #... def __init__(self, sensor_description = None, GPIO_PIN_TRIGGER = None, GPIO_PIN_ECHO = None): self.GPIO_PIN_TRIGGER = GPIO_PIN_TRIGGER self.GPIO_PIN_ECHO = GPIO_PIN_ECHO self.sensor_description = sensor_description if (self.GPIO_PIN_TRIGGER == None) or (self.GPIO_PIN_ECHO == None): raise ValueError("Values must be provided for both Trigger and Echo GPIO pins") gpio.setmode(gpio.BCM) gpio.setwarnings(False) gpio.setup(self.GPIO_PIN_TRIGGER, gpio.OUT) gpio.setup(self.GPIO_PIN_ECHO, gpio.IN) #...then all our other methods to do stuff with the sensor
So like our friendly meerkats Bob (m1) and Charleen (m2), in our existing check_distance() function, we now create two instances of a DistanceSensor class - _ds1 and _ds2. For each, during instantiation, we set their unique descriptions (no, we don't actually have to call them Bob and Charleen), and GPIO pins they use for echo and trigger signals. We've also wrapped the object instantiations using the try / except clauses. It should allow us to capture any exceptions (errors) raised by our class. In fact, from the code above, you'll notice that if no GPIO pin numbers are provided, the class will raise a ValueError. Our bit of exception handling will exit the program, as we don't want Rosie travelling around the world without her distance sensors working correctly.
try: _ds1 = DistanceSensor("Front left HC-SR04", 18, 23) _ds2 = DistanceSensor("Front right HC-SR04", 19, 26) except Exception as e: print(e) sys.exit(1)
Whenever we want to get our distances from our ultrasonic sensors (_d1 and _d2), we can now do this to assign their recordings to our two variables, _distance_fl_cm and _distance_fr_cm:
_distance_fl_cm = _ds1.get_distance() _distance_fr_cm = _ds2.get_distance()
...And because we want to change Rosie's alert level when either of the two sensors notice something interesting (and not necessarily both), we have changed the if statements to take into consideration this logic.
if ((_distance_fl_cm < _DISTANCE_DANGER_CM) and (_distance_fl_cm_last < _DISTANCE_DANGER_CM)) or ((_distance_fr_cm < _DISTANCE_DANGER_CM) and (_distance_fr_cm_last < _DISTANCE_DANGER_CM)): #...code to change alert level to critical, if not already elif ((_distance_fl_cm < _DISTANCE_WARNING_CM) and (_distance_fl_cm_last < _DISTANCE_WARNING_CM)) or ((_distance_fr_cm < _DISTANCE_WARNING_CM) and (_distance_fr_cm_last < _DISTANCE_WARNING_CM)): #...code to change alert level to warning, if not already elif ((_distance_fl_cm >= _DISTANCE_WARNING_CM) and (_distance_fl_cm_last >= _DISTANCE_WARNING_CM)) or ((_distance_fr_cm >= _DISTANCE_WARNING_CM) and (_distance_fr_cm_last >= _DISTANCE_WARNING_CM)): #...code to change alert level to normal, if not already
We've incorporated all this into our existing code, and named it "rosie-web-multisensor.py". It looks a little something like this:
And that's about it. We're not changing any of our Flask code for web access, or the HTML, or CSS. The end result is subtle, but we've actually done a whole load of things behind scenes to make our code more reusable. And using Python Object Orientated Programming, we can make Rosie's application a lot more powerful.
Now, let's return to name some more of our imaginary meerkats...
Even more reading:
There is excellent information on the technical specs of the HC-SR04 sensor, and the voltage divider circuit required, on the ModMyPi website. Highly recommended reading if you are intending to do some soldering of your own.Official Raspberry Pi information on GPIO pins can be found here:
Python's official documentation on classes can be found here:
We're continuing to make use of the Monk Makes' RasPiRobot V3 motor controller board, and its libraries written by Simon Monk:
Comments
Post a Comment