Jump to content
43oh

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];
}

Link to post
Share on other sites
  • Replies 53
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular 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 b

Short answer is all servos will work at the same time.   Servo requires 1ms-2ms pulse every 20ms. Because of that, we only need one CCR to control all servos at the same time. We shift start time o

Thanks to you both, worked like a charm!!   I replaced the original code in the while loop in the ADC ISR with oPossum's formula to set the servo positions:   const int adc_min = 0; /

Posted Images

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.

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.

Link to post
Share on other sites
  • 5 months later...

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
     }
   }

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.

Link to post
Share on other sites
  • 3 weeks later...

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++;
       }
   }








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;

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...