The HC-SR04 is a clever component. By measuring the time delay between signals from the included board one can easily calculate the distance to objects. Well, that’s the theory. Unfortunately it turns out that it does not play that well with Explorer HAT Pro board that it is connected to when using the provided explorerhat library. I’ve observed that ranges to perpendicular objects (to give the best result) are miscalculated by up to a metre (plus or minus). This seems to be a timing issue as the HC-SR04 does not return the distance to an object, but instead it sends a connected pin high for the same time as it took for a pulse of ultrasound to bounce back.
Instead, Zumo George has received an upgrade in the form of the Sharp GP2Y0A41SK0F infrared distance sensor. This has the added benefit of making him look more Goliath* and less WALL-E. This is an analog device that runs at 5V which matches perfectly with the Explorer HAT’s analog inputs. It measures distances accurately from 4cm to 30cm which, for the purpose of “don’t crash into an object” is perfect.
Wiring is straightforward with GND and Vcc to their respective 5V pins and the third cable to one of the analog pins (I use pin four). I bought the sensor on eBay for a few quid and the cable it came with did not have 0.1” pins attached at the breadboard end of things. A quick bit of soldering and heat shrinking later and we’re ready to go.
Next I needed to add to my library of tests for Zumo George to ensure that when he boots up all is A-OK. Let’s write a scenario:
@ir
Scenario: Verify infrared range finder is responding
Given the distance to an object is greater than 10 cm
When I read the infrared range finder
Then I should see a distance greater than 10 cm
Now we execute this with Lettuce. Note though that I have added a tag with the @ symbol to enable me to run just this scenario while we get it working. Hence we:
sudo lettuce -t ir
We need sudo because Pimoroni’s Explorer HAT Pro requires this, and we use the -t parameter to specify the tag to execute.
Immediately we see that our three steps have not been defined, and Lettuce helpfully returns suggested step definitions that assert False. Copying these into place and re-executing moves us from not implemented to not coded, with the three steps each going red.
At this point we need to implement some code to make everything work as it should. To do this I have decided to create a new Python module, zumo.py which will contain specific functions required by Zumo George. We are going to need a way of determining distance by using the GP2Y0A41SK0F sensor, hence in zumo.py I enter the following to create a read_distance()
function that we can call from a step definition:
import explorerhat
import time
def read_distance():
# with help from: http://bit.ly/1pxeWpJ
# and an idea from http://bit.ly/26nCkaR
v_readings = []
# read the voltage 10 times so that we can get a decent average
for i in range (0,10):
v_readings.append(explorerhat.analog.four.read())
av_voltage = sum(v_readings)/10.0
if av_voltage <= 0:
av_voltage = 0.001
distance_cm = 13 / av_voltage
return distance_cm
The GP2Y0A41SK0F works on the principle that distance to an object is inversely proportional to the voltage that can be read from the connection on analog pin four. In other words the higher the voltage the lower the distance, and our equation takes this into account. The number 13 was determined by looking at the datasheet on the Pololu product page. In the graph we can determine for a reading of 1V we should be 13cm away from our object, i.e.: 13/1 = 13cm. I got the idea to do things this way from Yoctopuce who used another Sharp IR sensor. Their magic number was 60 for the GP2Y0A02YK0F which they obtained in the same way.
Into steps.py I then add the following three step definitions as well as the import zumo
command. The Given sets up the expected result, the When is the event (i.e.: read the distance) and the Then is the comparison. I will be honest and say that in Python I don’t know if using global variables in this way is a good thing or not (note to self: must research this), but it works, so at this stage I am not too concerned:
@step(u'the distance to an object is greater than (.*?) cm')
def the_distance_to_an_object_is_greater_than(step, distance):
global minimum_expected_distance
minimum_expected_distance = distance
@step(u'I read the infrared range finder')
def i_read_the_infrared_range_finder(step):
global actual_distance
actual_distance = zumo.read_distance()
@step(u'I should see a distance greater than (.*?) cm')
def i_should_see_a_distance_greater_than(step, expected_distance):
assert float(actual_distance) > float(minimum_expected_distance), "Distance returned = %d" % actual_distance
Running Lettuce a third time now shows everything is green, meaning our scenario is passing and our code to generate distances is working. Well, sort of. We have to remember that we are now mixing a controlled software environment with real-world robotics and as the adage goes, “anything can happen in the next half hour.” Clearly this scenario will only pass if the distance from Zumo George to an object is greater than 10cm. For me this is perfect as George always starts his working day facing away from obstacles. We could of course change our code to simulate the response from the GP2Y0A41SK0F (not the easiest of components to pronounce, or spell) but then we are not demonstrating the desired real-world behaviour: that when Zumo George is not facing a nearby object he shall be a happy robot ready to drive.
You may note that something strange has happened though. We are clearly only running one scenario against a single feature by specifying the tag ir yet Lettuce is reporting that 2 features have passed (which you would think implies a minimum of two scenarios executed). I think of it this way: we have told Lettuce that only a single feature shall be run by telling it absolutely (in this case) that only a single tag containing a single scenario is within scope, therefore we have told Lettuce that the other feature passes and it counts it as such. This is on the basis that BDD should be all-or-nothing, i.e.: to demonstrate that software is fit for purpose all of our scenarios must pass and we are stating we are taking responsibility for this decision in this other case. Another way to think of it is that as the other feature contains no scenarios that we execute there is nothing that can fail, i.e.: it passes by default. It is something to be aware of though as it can make your numbers look a little strange if you are not used to seeing this outcome.
Next time: The Ryanteck 3 Line Follow sensor
* The evil truck from Knight Rider.
View more at: http://bit.ly/1XReQFr
Post a Comment