# Servo control using dsPIC33's Output Compare and Timers

I am trying to control a single servo using output compare and a couple of timers. Timer 2 provides the 20ms period and it controls output compare 1 (OC1). OC1 is running in continuous pulse mode, where it uses Timer 2 to count from 0 to anywhere from 0.8-2.2ms. In my code, I have a function called servo_pos() that calculates the needed value for this time, which is placed in the OC1's OC1RS register.

I am able to set the servo position to whatever angle I want at initialization. Currently at initialization, the servo is set to rotate to the neutral 90 degree position and it stays there. However, I want to be able to change position when I tell it to and my servo won't let me do that. For example, I am using a different timer, Timer 4, to create a 2 second delay. Each time Timer 4 reaches its period count, an ISR is called that is supposed to change the servo position using my servo_pos() function. I know the ISR works because I have an LED blinking every 2 seconds. When the OC1RS register is written a new value, my servo seems to try to move to the new position but it seems as if it is fighting to stay at the neutral position (or whatever position I assign it at initialization). My code is given below, if anyone could please try to help me figure out my problem. I've never used a servo before.

#include <p33EV256GM102.h>
#include <xc.h>
#include <stdio.h>

#pragma config ICS = PGD3
#pragma config FWDTEN = OFF

//Fosc=7.37MHz
//Fcy=Fosc/2=3.685MHz (No changes to PLL)
#define Fcy 3685000

void __attribute__((__interrupt__, __auto_psv__)) _T4Interrupt(void);

void servo_pos(int stop_time);
int pos = 1500;

int main(void)
{
TRISBbits.TRISB6 = 0; //LED output

RPOR0bits.RP35R = 0b010000;     //remap pin rp35 as OC1
ANSELBbits.ANSB3 = 0;           //sets RB3 to digital
TRISBbits.TRISB3 = 0;           //RB3 output for OC1

// set servo to neutral position at startup
OC1R = 0;                    // pulse start time
servo_pos(pos);             // move servo to neutral position
OC1CON1bits.OCM = 0b101;    // continuous pulse mode

// Configure Timer 2 (default timer for output compare)
PR2 = 9213;             // Timer 2 period (20ms) Fcy x 20ms / 8
T2CONbits.TCKPS = 0b01; // Timer 2 prescaler 1:8
T2CONbits.TON = 1;      // Enable Timer 2

// Configure Timer 4 for controlling how long the servo performs each action
IPC6bits.T4IP = 1;
IFS1bits.T4IF = 0;
IEC1bits.T4IE = 1;
PR4 = 28789;                // Timer 4 period (2s) Fcy x 2s / 256
T4CONbits.TCKPS = 0b11;     // Timer 4 prescaler 1:256
T4CONbits.TON = 1;          // Enable Timer 4

while(1)
{

}
return 0;
}

void servo_pos(int stop_time) //stop time in microseconds
{
// Configure Output Compare channel 1 (OC1)
OC1RS = ((float)stop_time/1000000.0)*Fcy/8; // pulse stop time (1.5ms) OC1RS = Fcy x 1.5ms / 8
}

void __attribute__((__interrupt__, __auto_psv__)) _T4Interrupt(void)
{
// Clear Timer 4 interrupt flag
IFS1bits.T4IF = 0;
// Toggle LED on RD1
LATBbits.LATB6 = ~LATBbits.LATB6;

servo_pos(pos += 50); //every time ISR is called, change the servo position

}


I figured out my problem: my microcontroller was apparently resetting each time the servo was activated because of too much current draw. So I got it working by supplying a separate voltage source to the servo.

• If anyone has a solution that would allow me to use just one supply that would be great. – bluemagoo92 Mar 24 '16 at 1:31
• Well, the motor itself probably draws a lot when it first starts, so a single supply would need a lot of surge capacity to keep from triggering the MCU's brownout detector. (you are using it, right? to avoid running code incorrectly and corrupting itself?) And then there's noise from the commutator and/or internal controller while it's running. – AaronD Mar 24 '16 at 2:36
• Given those two problems, I would NOT connect the servo and the MCU power directly together, but run the servo more or less directly from the power source with lots of local capacitance, then run the MCU power through a filter and maybe even a diode to keep the servo from draining the MCU's supply caps. – AaronD Mar 24 '16 at 2:37
• @AaronD, as far as I know, I am not using the brownout detector unless it's on by default. I'll try your suggestions thanks. – bluemagoo92 Mar 24 '16 at 4:08
• I don't know if that chip has it on by default or not, but I like to verify the entire chip anyway just so I know what to expect. I always use the brownout detector because low voltage can cause weird errors that can be very difficult to track down if you don't expect that cause. 2+2=5 is only the mild side of it. Imagine that kind of error when calculating the next instruction pointer... – AaronD Mar 24 '16 at 5:22

General RC motors require a servo signal update every 20ms. The behavior if this is not maintained is not well defined - and inconsistent - between manufacturers. Change your 2s timer to 20ms and you should be fine.

Generally, with the PIC24/dsPIC33 series, you can trigger one timer using the output of another. Use the 20ms timer to trigger the shorter timer in 'one-shot' mode (you probably already have it in one-shot mode).

The other option is to operate a single PWM timer with a period of 20ms and a very low duty cycle (0.8ms to 2.2ms, as you stated), but you tend to lose resolution using this method.

The two-timer method will be reasonably easy to implement from whre you are and you can keep your resolution.

• slightlynybbled, thanks for your response. Timer 2 is already controlling the 20ms period. Timer 4 is just there to switch the position every 2 seconds, rather than using a pushbutton, switch, etc. I have the OC1 set to continuous pulse mode, not one shot mode. I don't think that could be my problem though since I can set the servo position to any angle initially. – bluemagoo92 Mar 22 '16 at 19:57
• Have you checked your output signal on an oscilloscope? – slightlynybbled Mar 22 '16 at 21:02
• I've checked it with a logic analyzer. It appears to be working right with a 1.5ms duty cycle and 20ms period. – bluemagoo92 Mar 22 '16 at 21:12
• So when the above code is executed, your logic analyzer shows a steady 1.5ms? – slightlynybbled Mar 22 '16 at 23:57
• It does if I don't change the value in the OC1RS register, i.e. if I keep it at 1.5ms by repeatedly calling servo_pos(pos=1500) in the ISR. – bluemagoo92 Mar 23 '16 at 0:19

In your ISR try changing servo_pos(pos + 50) To servo_pos(pos += 50)

• Good catch, I made an edit. But as it was before, the servo still should have moved its arm by a factor of 50 ms once and stayed there infinitely. But it's fighting to stay where I initialized it at 1.5ms. And when I say "fighting", I mean the servo vibrates because it is trying to rotate the arm to the new position but it can't. – bluemagoo92 Mar 22 '16 at 20:52
• You should also put a return statement in your functions and check whether 'pos' goes beyond a certain limit. Anyway try it, maybe 50 is not enough for a proper movement and the servo and its pot struggle To keep The position..btw if pos is in millisecs, 1.5s is a bit too much for a control signal – zakkos Mar 22 '16 at 20:59
• That can't be it. If I reprogram the micro controller with pos=2000 (2ms) or pos=1000 (1ms), the servo rotates all the way left or all the way right, so I know the limit of the servo. I should be able to call the function from the ISR with any constant value between 1000 and 2000 to change the servo position. And I've tried that but no luck. Maybe my servo is broken, or maybe since it's my first time using one I'm just missing something important. – bluemagoo92 Mar 22 '16 at 21:09
• Then "pos" is not in ms but in microseconds! Anyway...I stand by my comment: add return statements and ensure that pos stays within reasonable limits (otherwise it will runaway since you're looping in an addition). Try these changes then report back ;-) – zakkos Mar 23 '16 at 0:10
• The LED you toggle in The ISR is functioning, right? Not only visually but if you check its output with The oscilloscope The timing and wave are right? – zakkos Mar 23 '16 at 9:20