Jump to content
RobG

LaunchPad controlling up to 8 RC Servos

Recommended Posts

Here's my take on controlling RC servos with LaunchPad.

Again, I am using my favorite shift register, 74HC595 (later I will be adding another example which will use 74HC164.)

Since the voltage may be different in your application and the default DCO is used, you will have to work out the timing.

 

If you need only 4ch, look at this post.

Here is another 4ch example with ADC

The simplest single channel ADC -> Servo example is here

And finally, 2ch version is here

 

 

Besides LP you will need 74HC595 and 8 10k pull down resistors.

post-197-135135493665_thumb.png

 

#include 

unsigned int counter = 5;                   // Servo counter, start with 5 (see below in timer interrupt section.)
unsigned int servoPosition[8] = {180, 180, 180, 180, 180, 180, 180, 180}; // Default servo position

// DEMO counter
unsigned int demoCounter = 0;

void main(void)
{
WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT

P1OUT &= ~BIT0;                                                       // Port P1.0 will be used as serial out to set first bit
P1DIR |= BIT0 + BIT2;                                         // Port P1.0 & P1.2 out

P1SEL &= ~BIT2;                                                       // An ugly way to clear register
volatile int c;                                                       // This could be done by some sort of POR
for( c = 0; c < 8; c++ ) {
      P1OUT |= BIT2;
      P1OUT &= ~BIT2;
}

P1SEL |= BIT2;                            // P1.2 TA1 option select
CCTL0 = CCIE;                             // CCR0 interrupt enabled
CCR0 = 325;                                                           // ~2.5ms
CCTL1 = OUTMOD_3;                         // CCR1 set/reset
CCR1 = 180;                                   // CCR1 duty cycle ~1.5ms
TACTL = TASSEL_2 + MC_1 + ID_3;           // SMCLK/8, upmode

_bis_SR_register(LPM0_bits + GIE);        // Enter LPM0 w/ interrupt
}

// Timer A0 interrupt service routine
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A (void)
{
      counter++;                                                      // Increase counter
      if(counter == 0x08) counter = 0;                // Counter range 0 to 7

      if(counter == 6) {                                              // Set serial in to 1, then reset
        P1OUT |= BIT0;                                                // Both clocks of 595 are connected together, serial in must be set earlier
      } else if(counter == 7) {
        P1OUT &= ~BIT0;
      }

      // DEMO 
      demoCounter++;
      if(demoCounter == 800) servoPosition[0] = 80;
      if(demoCounter == 1600) servoPosition[0] = 280;
      if(demoCounter == 2000) servoPosition[7] = 280;
      if(demoCounter == 2400) servoPosition[7] = 180;
      if(demoCounter == 2800) servoPosition[7] = 280;
      if(demoCounter == 3200) servoPosition[0] = 130;
      if(demoCounter == 3600) servoPosition[0] = 230;
      if(demoCounter == 4000) {
              servoPosition[0] = 180;
              servoPosition[7] = 180;
              demoCounter = 0;
      }
      // END DEMO

      CCR1 = servoPosition[counter];
}

Share this post


Link to post
Share on other sites

thanks RobG,

 

i had a question. i understand u are dividing 20ms/8 and take advantage of the fact that a full servo occupies about 2ms, it's kind of like multiplexing leds (w/o the flicker). i don't understand the part when we have to "inject" the starting bit at count 6 (7 off), can u explain a bit more on that? thanks.

Share this post


Link to post
Share on other sites

Sure,

we need 595 to shift just one 1, so before first shift, we have to set input to 1, after that it's all zeros.

Every clock, we move that 1 to the next out.

Output is active when clock is 0. When clock is 1, output is disabled (Hi-Z.)

We have to set 1 on the input one clock earlier, because shift registers are one ahead of the latches when both clocks are connected together.

Share this post


Link to post
Share on other sites
thanks. i thought the setting of the 1st bit would be at count7 on count0 off.

That would be the case if we fed latch (storage clock) with inverted shift clock (register clock) as per specs.

Share this post


Link to post
Share on other sites

I was thinking that not many of us will ever need to use 8 servos so I have updated my code and here it is.

The new version controls up to 4 servos and does not require additional parts.

Just connect your servo to pins P1.4-P1.7 and voila.

 

#include 

 

unsigned int counter = 0;

Share this post


Link to post
Share on other sites

Thanks Rob. Are those servos capable of higher resolution? I've written a dozen or more high performance drivers for people over the years but I've never had a servo to play with so I don't really know what kind of feats they're capable of (LOL)...

 

Also, since you're using a constant 10-msecs to fill out the balance of the 20-msec servo period, does that imply you may not have an exact 20-msec servo period? If so, is that a concern or are servos reasonably "period" tolerant? I always assumed a more precise period was in order and so I would use an extra array element just to keep track of the end-of-period "off" time, something like this;

 

    unsigned char ndx = 0;          // servo array index, 0..4
   unsigned char select = BIT4;    // servo channel select pin mask
   unsigned int servo[5] = { 1500, 1500, 1500, 1500, 20000 };

   /******************************************************************
   *  four servos (P1.7 thru P1.4), 1-MHz clock, 1-us TACLK source  *
   *                                                                */
   #pragma vector = TIMERA0_VECTOR
   __interrupt void Timer_A (void)
   { P1OUT &= 0x0F;            // turn servo outputs off
     P1OUT |= select;          // turn next servo on
     CCR0 = servo[ndx]-1;      // servo on time, 600..2400 (usecs)
     servo[4] -= servo[ndx];   // adjust end-of-period off time
     select <<= 1;             // shift servo output select bit
     if(ndx++ = 4)             // if end of 20-ms period
     { ndx = 0;                // reset servo array index and
       select = BIT4;          // reset servo select mask and
       servo[4] = 20000;       // reset end-of-period off time
     }
   }

Share this post


Link to post
Share on other sites

You can get higher resolution by changing Timer's clock settings, just like you did in your code example.

I just felt that 8us is good enough and I was thinking of storing that value in char to save memory.

Depending on the servo, the fill period should be between 10-20ms. Newer servos will work if your total period is 10ms, but the older ones will not work properly and you have to make sure that the total period is ~20ms.

My servos were not working correctly when I had it set to <10ms, they were jerky and unreliable.

Share this post


Link to post
Share on other sites

Another iteration of servo control. 4 ADC inputs control 4 servos (thanks to aEx155 for motivating me.)

Pots are connected to Vcc and GND, center to P1.x.

P1.0 controls P1.7, P1.1 -> P1.6, P1.2 -> P1.5, P1.3 -> P1.4

 

 

 

    #include 

   #define SERVO_OUTPUTS BIT4 + BIT5 + BIT6 + BIT7

   unsigned int counter = 0;                   // Servo counter
   unsigned int servoPosition[4] = { 180, 180, 180, 180 }; // Default servo position
   unsigned int servoOn[4] = { BIT4, BIT5, BIT6, BIT7 };
   unsigned char valueIndex = 0;
   unsigned int adcValues[4] = {0,0,0,0};

   void main(void) {
       WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT

       BCSCTL1 = CALBC1_1MHZ;
       DCOCTL = CALDCO_1MHZ;

       P1OUT &= ~(SERVO_OUTPUTS);          // We could replace those BITs with define or simply 0xF0, but I left it for clarity
       P1DIR |= (SERVO_OUTPUTS);                                         // Port P1.4-1.7 is out
       P1SEL &= ~(SERVO_OUTPUTS);

       ADC10CTL1 = INCH_3 + CONSEQ_1;
       ADC10CTL0 = ADC10SHT_2 + MSC + ADC10ON + ADC10IE;
       ADC10DTC1 = 0x04;
       ADC10AE0 |= 0x0F;

       CCTL0 = CCIE;                             // CCR0 interrupt enabled
       CCR0 = 180;                                                           // ~1.5ms
       TACTL = TASSEL_2 + MC_1 + ID_3;           // SMCLK/8, upmode

       _bis_SR_register(LPM0_bits + GIE);        // Enter LPM0 w/ interrupt
   }

   // Timer A0 interrupt service routine
   #pragma vector = TIMERA0_VECTOR
   __interrupt void Timer_A (void) {
       counter++;                                                      // Increase counter
       if(counter == 0x05)
           counter = 0;                // Counter range is 0-4, the last count is used to add 10ms delay, otherwise analog servos might act funny

       P1OUT &= ~(BIT4 + BIT5 + BIT6 + BIT7);                       // Clear ports
       if(counter == 0x04) {
               CCR0 = 2500 - (servoPosition[0] + servoPosition[1] + servoPosition[2] + servoPosition[3]); // 20ms delay
           ADC10CTL0 &= ~ENC;          // disable conversion
           while (ADC10CTL1 & BUSY)  // make sure ADC is done
               ;
           ADC10SA = (unsigned int)&adcValues[0];  // set data buffer start address
           ADC10CTL0 |= ENC + ADC10SC;  // start sampling and conversion
       } else {
           P1OUT |= servoOn[counter];              // Set port of the current servo
           CCR0 = servoPosition[counter];          // Set time for the current servo
       }
   }

   // ADC10 interrupt service routine
   #pragma vector=ADC10_VECTOR
   __interrupt void ADC10_ISR(void) {

       valueIndex = 0;
       while(valueIndex < 4) {
           servoPosition[valueIndex] = (adcValues[valueIndex] >> 3) + 0x7F;
           valueIndex++;
       }
   }








Share this post


Link to post
Share on other sites

Hi Rob,

 

Can you comment these lines from the TIMER_A() interrupt, please?

 

TIA. Regards, Mike

 

            ADC10CTL0 &= ~ENC;
           while (ADC10CTL1 & BUSY)
               ;
           ADC10SA = (unsigned int)&adcValues[0];
           ADC10CTL0 |= ENC + ADC10SC;

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×