Things are about to take a very bizarre turn (at some improbable angles). Why? For no other reason than: because it can. But we'll try our best to explain our thinking. You see, our plan is to make our superhero more lovable. More approachable. Because us humans - we have a long history of not being understanding of things that we've never come across before. We are suspicious of others that don't look like us. That don't act like us. We fear the unknown. And a plastic box with wheels, sensors and a light... just isn't going to be very trustable. Even if it has a cute and harmless name like Rosie.
Clearly we should do something about this. And quite frankly, we have no better ideas than to give Rosie Patrol a... face. Because robots with faces; they can just blend in. Like a new member of our extended family.
Additionally, logic dictates that for robots to have a human-like face, they need to have a head, and a neck also. Preferably ones that move around, so she no longer relies on her wheels to keep careful watch on the delinquents causing mischief in town. But wait. That sounds like even more stuff requiring robot motion. Do we need more motors? Different types of motors, perhaps?
Yep. Like breeds of fish, there are many different motor species. And we couldn't call ourselves accidental roboticists if we didn't experiment (badly) with some of these. After all, motors that simply go round and round are boring. And of course, we already use them for Rosie Patrol's wheels. Nope. What we want is a different type of motor. One that does more interesting stuff. Like move into position as it's told, and stay there.
So perhaps it's not that bizarre a request after all. We simply want our (friendly) superhero to look like those superstar robots saving mankind in movies. With a likeable face. A moving, likeable face. One that will hopefully reassure the entire family that Rosie Patrol is indeed here to enforce law and order (and not dismantle our kitchen when we are simply not looking).
All superheroes need:
- Raspberry Pi 3 (kitted out with wheels, sensors, lights and stuff), and Raspbian running on SDHC card
- Computer from which you are connecting to the Raspberry Pi remotely
- 3 × servo motors. We used widely available SG90 servo motors, which handily came with the Monk Makes Servo Six board. But don't let your ambitions get in your way. Much later, we end up using HiTEC servo motors.
- Several plastic boxes, not only for the face and neck, but to make all the bits that we need to attach them and the motors together
- ...And to attach these things together - clumsily - you're likely to need nuts and bolts, glue, scissors, craft knife, etc, etc. Most likely, whatever you can get your hands on.
Already completed these missions?You'll need to have completed the Rosie series. Then:
Your mission, should you accept it, is to:
- Be creative. Create a robot face and neck, out of any material that you can find. We used cheap plastic boxes, as they are very
- Somehow attach your servo motors. Attach them in a way that allows your robot head and neck to move in the way you want them to.
- Attach the servo motors to a dedicated board (if using one), and to Pi's GPIO pins
- Yep, you'll need some Python code to move these motors
- Laugh at the results
The brief:We'll be honest with you up-front. This mission can either go well. Or become very, very messy. And it won't all be your fault, for a change. Instead, it's likely to be because of the materials you have - or have not - to work with. And because we are now working with physical motion again, physics and maths will gatecrash your party, and bring it to an ugly conclusion (like a broken motor, or a flattened house).
But enough of the negativeness. We learn by trying (and failing) right? Let's see how far we can get with giving Rosie Patrol a working head.
We'll start by building a neck for Rosie Patrol. Then, we'll attach an equally plastic face to it (a smily, doodle is optional). And all these bits need to neatly come together, because we'll - rather ambitiously - insert three Servo Motors into our messy heap of plastic. Porque? Because we want to make Rosie Patrol's face move - both sideways, and up and down. Didn't we tell you that this was heading for a complete disaster?
Now, cast your mind back to when we were working on Rosie's wheels. We used, Direct Current (DC) Motors. They (rather boringly) just go round and round. You can adjust the speed. And direction. But that's about it. Perfect for wheels. Rubbish for conventional necks.
That's why we need to use Servo Motors. They tend to only have around 180 degrees of movement, but have a built-in Potentiometer to tell themselves what positions they are currently in. This is useful. As we can instruct servo motors to move to certain angles, and hold that position (as long as they can handle the force they're coming up against). Think joints in robot arms. Think heads of affable robot sidekicks.
All the whats, whens and whys can be answered later. Like, we don't quite know why Rosie Patrol might want to turn her head.
But, for now, we would like nothing more than a robotic head. Even if it's only to prove that we can use some servo motors, plastic boxes and the obligatory Python code to make something that might possibly make it into a Jurassic Park out-take. Ok, maybe not...
The devil is in the detail:
What range of movement does a robot head need?It's not a trick question. We'd actually really like to know.
But let's have a guess. We think Rosie Patrol would like to be able to turn her head horizontally, left and right. Clearly, this is useful when tracking henchmen making their escape on roller skates (as they do). We think she would also like to be able to look up. Because, in the end, evil masterminds inevitably escape in rockets. That probably means we need a minimum* of two servo motors: one used for horizontal movement, the other for vertical movement.
*Yes, we did say earlier that we'll be using three motors. We found this necessary, as physics - specifically gravity - really tries to spoil our fun. More on this later.
So what exactly are we moving? Good question. Time to source some more plastic boxes from the local supermarket. Three plastic boxes, to be exact. Although unlike the torches in our previous episode, we don't think they are worthy of names. Especially since one perishes in front of our eyes, quite quickly.
Here's our line-up:
- Box 1: it's plastic, it's large and it's square
- Box 2: it's plastic, it's small, and it's square
- Box 3: it's plastic, it's not as large as box 1 but not as small as box 2, and it's rectangular (it won't be for long)
We can tell you're still overwhelmed by the cryptic riddle, so let's continue to use what's left of our brain cells to construct our robot head.
First of all, the big, square plastic box looks like a suitable candidate for a robot face. There's plenty of space here to mount more random gadgetry (weight permitting). The little box, on the other hand, looks perfect for the neck. It can sit neatly on Rosie Patrol's body. Of course - being plastic boxes - all these have handy lids, which makes it possible to access the insides where we can pack even more electronics.
So far, it sounds like we know what we're doing.
So what's the third box for, I hear you ask? Clearly, we're finding plastic boxes easy to get along with. They can be chopped, and drilled through, making them cheap and flexible material for robot body parts (just don't expect them to last). And this is exactly what we're doing with this third box. After all, we need a way to connect the head to the neck, using some sort of lever than can be moved up and down using one or two servo motors tasked with vertical movement. We'll tuck away the motors(s) in the neck, so we also need a "secure" compartment to house the motor. With a craft knife, scissors, drill, glue, and whatever destructive tools you can get hold of, we'll make all these bits and more.
Here's a picture of the final "masterpiece". No attempt was made to make this look professional.
But we have to be honest. It wasn't an easy journey reaching this design. Because we thought science would always be on our side. Be helpful to us. As it turned out, it really didn't like us, one bit.
So what was the problem? You see - we assumed a single SG90 servo motor could lift up Rosie Patrol's head, especially considering that it was made of plastic, and it was empty. It couldn't. And one broken motor later, we realised that we needed to take science seriously.
We quickly learnt; that it's all to do with something called Torque (τ).
Let's begin with what our SG90 servo motor tells us it can lift. The specifications in the datasheet tells us this:
Lots of pretty pictures. And some numbers. But hidden away is one figure that we really should have noted down before embarking on our our mission.
Stall torque: 2.5 kg-cm
Torque (τ) is a measure of rotational force, calculated using distance (r) to where the force is being applied, and the force (F) itself. Practically speaking, in our design, r is the length of Rosie Patrol's neck and F is the weight of her head.
τ = r × F
So SG90's stall torque of 2.5 kg-cm means that it should be able to hold up Rosie Patrol's face up to a weight of 2.5 kg, when the length of her neck is 1 cm. Great! That should allow her to carry cameras, screens, satellite dishes, ice cream dispensers and all sorts of other gizmos on her head, no? Except, originally, she had a neck that was 10 cm long. Following the formula, we could now only lift a tenth of the 2.5 kg, a measly 250 g. Basically, longer the neck, lighter her head has to be. Thanks, torque!
So in true investigative spirit, we did three things:
- Googled a bunch of stuff, including totally unrelated videos of cats doing silly things
- Chopped Rosie Patrol's neck in half, 18th Century France style - at 5 cm, it should mean that we can now lift 500 g. Vive la révolution!
- Added another SG90 servo motor in parallel to do the vertical lift. This should mean we can now lift 1 kg, as we have doubled the total torque available to 5.0 kg-cm. Sounds great when we say it like that. But we'll regret this later.
It's also very much advised to test your rig before you connect your motors to the moving parts. Just from looking at the plastic horns on your motors moving around, you can judge how they will perform. You should also test your Python code this way.
Once the motors are all attached to our unsightly bits of pink plastic, they require power, and some sort of control signals from the Pi's GPIO outputs. We used a Monk Makes Servo Six board, as it tidies up all the connections that are required. It acts a hub so that a single battery pack can power up to 6 servo motors. We can also connect up to 6 GPIO connections to it (one per servo motor) which we then use to control the angles of the servos.
Here's how the servos are probably cabled up.
But before we proceed, we need to know what our servo motors expect for their control signals. It's a bit more complicated than your traditional on / off signals, and relies on the use of something called Pulse Width Modulation (PWM). Using PWM, you can send electrical signals at a certain frequency, and change the proportion of the time that the signal is on, compared to when it's off. This fraction is known as the Duty Cycle (the time / percentage the signal is on for), and is what the circuitry in the servo motor uses to set, and maintain, an angle. There's lots of great resources on the web explaining the workings of PWM with diagrams. For now, let's just assume that we know just about enough.
In order to understand specifically what the SG90 expects, we return to its technical specifications. There's a couple of useful specs about what the PWM signal should look like.
PWM Period: 50Hz / 20ms
- 1.0ms (5%) for 0 degrees
- 1.5ms (7.5%) for 90 degrees
- 2ms (10%) for 180 degrees
In short, we need to configure our GPIO pins as PWM outputs, then configure them for a frequency of 50Hz, and alter the duty cycles between 1.0ms (5%) and 2ms (10%) to control the angles of the servo motors between 0 and 180 degrees.
As always, let's start with a class, as we already know there will be a few of these servos. We'll create a low-level class called Servo which will allow us to interact with one servo object. Let's allow it to be instantiated with a few basic parameters, such as description, GPIO pin being used, minimum and maximum angles, and the initial angle. Specifying minimum and maximum angles is particularly important, as there will always be physical limits on how far the servo can actually move an attached object in a given space. For example, while Rosie Patrol's head might spin sideways happily up to 180 degrees, it's not likely to rotate upwards beyond 90 degrees vertically. Back-to-front, upside-down robot faces are known to look quite disturbing.
class Servo: PWM_FREQ_HZ = 50 PERIOD_MS = 1 / PWM_FREQ_HZ * 1000 def __init__(self, description = None, gpio_pin = None, min_angle = 80, max_angle = 100, initial_angle = 90): self.description = description self.min_angle = min_angle self.max_angle = max_angle gpio.setmode(gpio.BCM) gpio.setwarnings(False) gpio.setup(gpio_pin, gpio.OUT) self.pwm = gpio.PWM(gpio_pin, self.PWM_FREQ_HZ) self.pwm.start(self._calculate_duty_cycle(initial_angle)) self.current_angle = initial_angle
To translate angles (that we humans understand) to PWM duty cycles (that them servo motors understand), we create an internal method: _calculate_duty_cycle(). All we want to do here is take angles in the range of 0 - 180 degrees, and translate them into duty cycles in the range of 5-10%. These can then be sent via GPIO to the servo motors using the control lines.
def _calculate_duty_cycle(self, angle): _duty_cycle = (float(angle) / 180 + 1) * 5 return _duty_cycle
All that's left for the class to do is to actually set the duty cycle to the desired value using a method. We'll call this method move() and using _calculate_duty_cycle() and GPIO's pwm.ChangeDutyCycle(), we'll set the duty cycle of the PWM to the desired value. This has the immediate effect of moving the servo motor, and holding that position. Notice we've also capped the angles at minimum and maximum values in this method, so that it can never be requested to move beyond these limits.
def move(self, next_angle): if next_angle < self.min_angle: print("Min angle capped at", self.min_angle) next_angle = self.min_angle elif next_angle > self.max_angle: print("Max angle capped at", self.max_angle) next_angle = self.max_angle self.pwm.ChangeDutyCycle(self._calculate_duty_cycle(next_angle))
Now, do you remember we said we'd regret having two vertical motors (we'll call them s1 and s2) installed in parallel? It gives us a small headache, as we have to move both s1 and s2 at the same time, by the same amount. This is a physical constraint, that we need to reflect in our code. Not doing so, will probably mean one motor fighting against the other. Not good! This is one reason why we'll create a HeadController class, and give it responsibility to coordinate the movement of our three servos. We can also make these movements gradual, as by default, servo motors spring into action rapidly, and cause robot whiplash.
class HeadController: def __init__(self): self.s1 = Servo("Vertical neck servo 1", 9, V_ANGLE_MIN, V_ANGLE_MAX, V_ANGLE_INITIAL) self.s2 = Servo("Vertical neck servo 2", 11, V_ANGLE_MIN, V_ANGLE_MAX, V_ANGLE_INITIAL) self.s3 = Servo("Horizontal neck servo", 7, H_ANGLE_MIN, H_ANGLE_MAX, H_ANGLE_INITIAL)
During instantiation of our HeadController class, we instantiate our three servo objects - s1, s2 and s3. Notice the different GPIO pins being used, and also the use of global variables for our minimum, maximum and initial angles.
We then have two methods, v_move() for vertical movement, using the two servos, and h_move() for horizontal movement, using the one remaining servo. The workings are identical, except v_move() moves two servos, instead of the one.
def v_move(self, next_angle, gradual = False, transition_duration_s = 1, transition_steps = 50): if gradual == False: self.s1.move(next_angle) self.s2.move(next_angle) elif gradual == True: # ...Code for gradual movement of s1 and s2
The technique used to implement gradual movement of the servos is near-identical to what we used previously for our wheels. We'll calculate the incremental angle changes necessary, store these in a list, and send control signals from our GPIO at specific time intervals.
_v_step_time = float(transition_duration_s) / float(transition_steps) _v_control_sequence =  _v_count = 0 _v_change_angle = float(next_angle - self.s1.current_angle) / transition_steps print(self.s1.description, "and", self.s2.description, "moving to", next_angle, "degrees over", transition_duration_s, "seconds") while _v_count < transition_steps: _v_control_sequence.append((self.s1.current_angle + _v_change_angle * (_v_count + 1), self.s2.current_angle + _v_change_angle * (_v_count + 1))) _v_count += 1 _v_step = 0 while _v_step < len(_v_control_sequence): self.s1.move(_v_control_sequence[_v_step]) self.s2.move(_v_control_sequence[_v_step]) _v_step += 1 time.sleep(_v_step_time)
To test, we'll use IPython interactively to generate a while loop to randomly generate transition times, vertical and horizontal angles. The final trick we'll use is to carry out the vertical and horizontal movements using threads. This way, both vertical and horizontal movements can happen at the same time.
while True: transition_duration_s = randint(TRANSITION_TIME_MIN_S, TRANSITION_TIME_MAX_S) t3 = Thread(target = hc1.v_move, args = (randint(V_ANGLE_MIN, V_ANGLE_MAX), True, transition_duration_s)) t3.daemon = True t3.start() t4 = Thread(target = hc1.h_move, args = (randint(H_ANGLE_MIN, H_ANGLE_MAX), True, transition_duration_s)) t4.daemon = True t4.start() t3.join() t4.join() time.sleep(transition_duration_s)
The entire code looks something like this:
You can copy and paste everything up to and including the classes in IPython and execute the if __name__ == "__main__" loop manually. Or you can copy the entire code into a new file - rosie-servo.py - and execute it from the Linux shell.
Either way, you should see the servos spring into life, and adjust their angles accordingly.
Of course, all we're doing here is moving Rosie Patrol's head around randomly, in an endless loop. Unless we want a very dizzy superhero, this seems like a pointless exercise. Except, we have learnt to use some servo motors, and have something that resembles more of a robot (...possibly). Clearly, we're lacking some logic to determine why her head moves the way it does (perhaps she has spotted something, or she's just looking around?)
But all that can come later, right?