Article Index

Controlling a Servo

Hobby servos, the sort used in radio control models, are very cheap and easy to use and the micro:bit has enough PWM lines to control three of them without much in the way of extras and without losing any other facilities. You can of course control more if you are prepared to sacrifice using the LED display.

A basic servo has just three connections - usually ground and power line and a signal line. The colors used vary but the power is usually red and the ground line is usually black or brown. The signal line is white, yellow or orange. 

 

servosg90

The power wire has to be connected to 5V supply capable of providing enough current to run the motor - anything up to 500mA or more depending on the servo.  As the micro:bit only has a 3V supply you will need an additional power source. 

The good news is that the servo signal line generally needs very little current, although it does need to be switched between 0 and 5V using a PWM signal.

You can assume that the signal line needs to be driven as a voltage load and so the appropriate way to drive the servo is:

 

The 10K resistor R1 can be a lot larger for most servos - 47K often works. The 5.6K resistor limits the base current to slightly less than 0.5mA.

Now all we have to do is set the PWM line to produce 20ms pulses with pulse widths ranging from 0.5 to 2.5 ms.  You can do this using the PWM functions we have been using but the framework has a set of functions that are specifically for servo PWM control:

int setServoValue( int  value)

int setServoValue( int  value,int range)

int setServoValue( int  value,int range,int center)

These set the duty cycle using a default pulse rate of 20ms. If for any reason you need a different pulse rate you can set it using:

int setServoPulseUs(int  pulseWidth)

The three forms of ServoValue function cater for different degrees of customization the servo needs. The simple setServoValue simply produces pulses with widths ranging from 500 microseconds to 2.5 milliseconds which is what most servos work with - specified as a value from 0 to 180 corresponding to the normal 180 degree rotation of a servo.

The other two functions allow you to set the range and center value. If you specify a range and center then the angle selects a value in the range center-range/2 and center+range/2. If you only specify a range ten the default center of 1.5ms is used. 

So the simplest servo program you can write is something like:

#include "MicroBit.h"
MicroBit uBit;

int main() {
    uBit.init();
    for (;;) {
        uBit.io.P0.setServoValue(0);
        uBit.sleep(1000);
        uBit.io.P0.setServoValue(90);
        uBit.sleep(1000);
        uBit.io.P0.setServoValue(180);
        uBit.sleep(1000);
    }
    release_fiber();
    return 0;
}

This moves the servo to three positions and pauses. If you run the program using the circuit given earlier  you will discover that the servo does nothing at all - apart perhaps from vibrating. 

The reason is that the transistor voltage driver is an inverter. When the PWM line is high the transistor is fully on and the servo's pulse line is effectively grounded. When the PWM line is low the transistor is fully off and the servo's pulse line is pulled high by the resistor. 

A common solution to this problem is to drive the servo using an emitter follower configuration, but in this case this isn't possible because the maximum voltage an emitter follower configuration would generate is 3.3-0.6=2.7V, which is too low to drive most servos. 

The standard solution in this case is to use two transistors to generate a non-inverted pulse but it is possible to use a single transistor in a non-inverting configuration.

The simplest solution of all is to ignore the problem in hardware and solve the problem in software. 

Instead of generating 20ms pulses with pulse widths 0.5 to 2.5ms, you can generate an inverted pulse with 20ms pulses with widths in the range 17.5 to 19 ms. To do this we have to go back to using the non servo PWM functions. The principle is that if the servo needs a 10% duty cycle we supply it with a 90% duty cycle which the inverter converts back to a 10% duty cycle.

The range of duty cycles we need goes from 17.5 to 19.5.ms which in percentages is 87.5% to 97.5% or as values 895 to  997.

Simple math can convert an angle T to a value in the range 895 to 997:

value = (997-895)/180*T+895
          =   138*T/180+895

To make sure this works with all servos it is a good to restrict the range to 1ms to 2ms or inverted 18ms to 19ms and hence values from  920 to 972

value=52*T/190+920

So we can write the same testing program as:

#include "MicroBit.h"
MicroBit uBit;

int main() {
    uBit.init();
    uBit.io.P0.setAnalogValue(0);
    uBit.io.P0.setAnalogPeriod(20);

   for (;;) {
    uBit.io.P0.setAnalogValue(52 * 0 / 180 + 920);
    uBit.sleep(1000);
    uBit.io.P0.setAnalogValue(52 * 90 / 180 + 920);
    uBit.sleep(1000);
    uBit.io.P0.setAnalogValue(52 * 180 / 180 + 920);
        uBit.sleep(1000);
  }
  release_fiber();
  return 0;
}

 

If you run this program you should find that the servo moves as promised - however it might not reach its limits of movement.

Servos differ in how they respond to the input signal and you might need to calibrate the pulse widths. Many robot implementations, for example, calibrate the servos to find their maximum movement using either mechanical switches to detect when the servo is at the end of its range or a vision sensor. 

You can see from the logic analyzer plot that the PWM pulse train at the GPIO pin  is "inverted" as desired.

 

You can also see that the values used for the period and for the pulse width could do with some adjustment to bring them closer to the target values. In practice, however, servo calibration is the better answer.