RC Servos are pretty useful devices, they allow us to move things in a mechanical manner. They are cheap and readily available, so it only makes sense to use them!
A quick intro:
Servos are actually rather simple to interface with, they merely require a pulse of a certain width to determine the next position.
Because of this, we can use hardware PWM to generate these pulses. The frequency is typically 50Hz, which means we’ll be able to obtain a great deal of granularity with regard to the duty cycle, given our clock speed of 1MHz. Which is required since we’ll be dealing with pulses ranging ~1mS to ~2mS (typically)
Different servos will require a different minimum and maximum duty cycle, however in general the given code should just-work, in case your servo goes off range simply find the best minimum and maximum values for it.
#include "msp430g2553.h" // make sure you change the header to suit your particular device.
// Connect the servo SIGNAL wire to P1.2 through a 1K resistor.
#define MCU_CLOCK 1000000
#define PWM_FREQUENCY 46 // In Hertz, ideally 50Hz.
#define SERVO_STEPS 180 // Maximum amount of steps in degrees (180 is common)
#define SERVO_MIN 650 // The minimum duty cycle for this servo
#define SERVO_MAX 2700 // The maximum duty cycle
unsigned int PWM_Period = (MCU_CLOCK / PWM_FREQUENCY); // PWM Period
unsigned int PWM_Duty = 0; // %
void main (void){
unsigned int servo_stepval, servo_stepnow;
unsigned int servo_lut[ SERVO_STEPS+1 ];
unsigned int i;
// Calculate the step value and define the current step, defaults to minimum.
servo_stepval = ( (SERVO_MAX - SERVO_MIN) / SERVO_STEPS );
servo_stepnow = SERVO_MIN;
// Fill up the LUT
for (i = 0; i < SERVO_STEPS; i++) {
servo_stepnow += servo_stepval;
servo_lut[i] = servo_stepnow;
}
// Setup the PWM, etc.
WDTCTL = WDTPW + WDTHOLD; // Kill watchdog timer
TACCTL1 = OUTMOD_7; // TACCR1 reset/set
TACTL = TASSEL_2 + MC_1; // SMCLK, upmode
TACCR0 = PWM_Period-1; // PWM Period
TACCR1 = PWM_Duty; // TACCR1 PWM Duty Cycle
P1DIR |= BIT2; // P1.2 = output
P1SEL |= BIT2; // P1.2 = TA1 output
// Main loop
while (1){
// Go to 0°
TACCR1 = servo_lut[0];
__delay_cycles(1000000);
// Go to 45°
TACCR1 = servo_lut[45];
__delay_cycles(1000000);
// Go to 90°
TACCR1 = servo_lut[90];
__delay_cycles(1000000);
// Go to 180°
TACCR1 = servo_lut[179];
__delay_cycles(1000000);
// Move forward toward the maximum step value
for (i = 0; i < SERVO_STEPS; i++) { TACCR1 = servo_lut[i]; __delay_cycles(20000); } // Move backward toward the minimum step value for (i = SERVO_STEPS; i > 0; i--) {
TACCR1 = servo_lut[i];
__delay_cycles(20000);
}
}
}
The actual code was written for a quick and dirty servo testing unit I put together, however it could easily be expanded to other uses.
By using a LUT (Look-Up Table) we are able to position the servo on a per-degree basis, which makes the code a lot more intuitive and readable.
A bunch of notes and tips:
- Filter your servo power supply properly; servos are electrically noisy!
- Make sure your micro-controller is protected from external devices, such as the servos. For instance never connect the signal wire directly to the GPIO pins. Instead, use a buffer or at least a resistor. For this example we used a 1k Resistor, but since the “signal” interface is relatively high impedance, we could even use 4k7 or higher without much hassle.
- As long as you keep sending a signal to the servo, the servo will keep it’s position due to it’s internal feedback. However this consumes power so if you’ve got a very lightweight load or no load at all, you can always stop the PWM output for the periods of inactivity to save power.
- If you aren’t sure about your servo connections, here’s a small diagram to help you find the pinouts. In my case I had a couple cheap “Tower Pro” blue servos that use the JR pinout — These work fine at both 5v and 3.3v, however do not power them from the Launchpad power supply pins! — Servos consume a lot of current, always use an external PSU.
- Only one connection is required to interface the servo with the launchpad, find the pin P1.2 and connect it to the SIGNAL wire from your servo.
That’s it for now, enjoy!
Very helpful intro to PWM and servo control! Quick question: How are you determining the values for SERVO_MIN and SERVO_MAX? I understand this is your minimum and maximum pulse widths for the servo, but what is the conversion from a pulse width given in seconds (something shown here: http://www.servocity.com/html/how_do_servos_work_.html) to the pulse width value in your code? My servo’s pulse width min and max are 600us and 2400us, respectively.
Any help would be great. Thanks!
Hi Ray!
The values are a bit hard-coded, because we are merely testing the servo, it’s not a servo library. However we can see that the SMCLK is running at 1.000.000Hz and thus the actual SERVO_MIN and SERVO_MAX correspond to values in uS, in my case I found 650 and 2700 to be just about right, likewise because we are using the built-in oscillator we aren’t really running at exactly 1MHz, which is why my PWM_FREQUENCY is not really 50Hz either.
There’s some fudge factor involved, but hopefully you can implement the same code at different speeds by taking into account the clock speed when calculating the min and max values. Again, in this case we didn’t have to since 1MHz is a very convenient speed.
It’s worth mentioning that by running the PWM channel at 50Hz we get 20000 steps worth of duty cycle adjustment (assuming a clock of 1MHz), but since hobby servos are not precision devices, we quantize our duty cycles in favour of code size and speed.
1000000 / 50 = 20000
If we were to set TACCR1 to half of 20000 we would get 50% duty cycle.
I hope that clears things up, cheers!
Hey GuShH,
Thanks for the help! I’m up and running now.
Ray
I play with my servos in model planes and boats. I have built some servo testors and am familiar with the pulse width that runs from 1 to 2 ms with 1.5 ms as the center position. I am sure that I would want to use a separate power supply to power my servo and not the MSP430 launchpad. But doesn’t the signal pulse require a ground reference? Doesn’t the servo actually require a 2 wire connection to the microcontroller – a ground wire and a signal wire? Lynn – Austin, TX
Hi Lynn, that’s correct – You need to tie the micro-controller’s ground to your servo’s power ground, which is the same as the signal ground. There has to be a common ground. I used a separate power supply for the servo, a small 5V lab PSU I use for logic circuits. I wouldn’t want to use USB power for this, let alone the power rail of the Launchpad, which can’t handle the current whatsoever, plus the voltage level is too low for most servos. Take care and thanks for visiting!
Hi Sir, Thanks for sharing this. I am a newbie in micros and your posts are really hepful.
Iv’e been getting errors upon debugging. I’m currently using CCS5.4. The error is on this line:
for (i = 0; i < SERVO_STEPS; i++)
{
servo_stepnow += servo_stepval;
servo_lut[i] = servo_stepnow;
}
What do you think is the problem??
Thanks,
Joseph