Jump to content
43oh

LaunchPad controlling up to 8 RC Servos


Recommended Posts

Thanks Rob,

 

I set up some conditions to try to get closer to full range. When the adc value is very close to 0 or 1023 then the servo position is set to either 113 or 263 respectively, and otherwise it calculates for any values in between.

 

#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void) {

   while (ADC10CTL1 & BUSY)
       ;
   i = 0;
   // update servo positions
   while(i < 6) {

   	// CCR from ADC is calculated
   	if ( adcValues[i] >= 0x3FC ){        // adcValue >= 1020
   		servoPosition[5 - i] = 0x107;                // 2100us / 8us = ~263 = 0x107
   	}
   	else if ( adcValues[i] <= 0x03 ){   // 0 <= adcValue <= 3
   		servoPosition[5 - i] = 0x71;                  // 900us / 8us = ~113 = 0x71
   	}
   	else {
   		servoPosition[5 - i] = (adcValues[i] >> 3) + 0x7F;      // servoPosition = adcValue / 8 + 127
   	}
       i++;
   }
}

 

I don't know if this is a very good solution; as you can imagine I get a pretty good jolt when the position jumps between the calculated values and the values that are set.

 

I'm looking into fine-tuning it by generating and flashing a look-up table to find the servo times from the ADC values. I found an example of one at the site below but I'm not familiar with LUT's or how I'd implement one... Thanks for any advice, I really appreciate your help! :thumbup:

 

http://gushh.net/blog/?cat=361

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

You can easily map any ADC range to any servo range with this formula:

 

                               // Specify the ranges
const int adc_min = xxx;        // Minimum ADC value
const int adc_max = xxx;        // Maximum ADC value
const int servo_min = xxx;      // Minimum servo value
const int servo_max = xxx;      // Maximum servo value

unsigned servo = (unsigned)((long)(adc - adc_min) * (servo_max - servo_min) / (adc_max - adc_min) + servo_min);

Link to post
Share on other sites

Thanks to you both, worked like a charm!! :D

 

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;        // Minimum ADC value
const int adc_max = 1023;     // Maximum ADC value
const int servo_min = 75;     // Minimum CCR value
const int servo_max = 320;    // Maximum CCR value

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

   while (ADC10CTL1 & BUSY)
       ;

   i = 0;
   while(i < 6) { 

   	// update servo positions
   	servoPosition[5 - i] = (unsigned)((long)(adcValue[i] - adc_min) 
   	*(servo_max - servo_min) / (adc_max - adc_min) + servo_min);

   	i++;	
   }
}

 

It made it a lot easier to work with the max and mins to get the most out of the servos, I just tweaked the assigned values in their declarations a few times and voila!

 

Here's a picture of the arm if anyone is interested: http://flic.kr/p/bNH7Br

 

The final code:

 

#include 

#define SERVO_OUTPUTS BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5 // P2

unsigned int counter = 0;                   // Servo counter
unsigned int servoPosition[6] = {187, 187, 187, 187, 187, 187}; // Default servo positions
unsigned int servoOn[6] = {BIT0, BIT1, BIT2, BIT3, BIT4, BIT5};
unsigned char i = 0;
unsigned int adcValue[6] = {0,0,0,0,0,0}; // ADC buffer (ADC values are stored in this array)

const int adc_min = 0;        // Minimum ADC value
const int adc_max = 1023;     // Maximum ADC value
const int servo_min = 75;     // Minimum CCR value
const int servo_max = 320;    // Maximum CCR value

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

   BCSCTL1 = CALBC1_1MHZ;					  // Set Digitally Controlled Oscillator at 1MHz 
   DCOCTL = CALDCO_1MHZ;					

   P2OUT &= ~(SERVO_OUTPUTS);
   P2DIR |= (SERVO_OUTPUTS);                 // Port P2.0 - P2.5 servo outs
   P2SEL &= ~(SERVO_OUTPUTS);

   ADC10CTL1 = INCH_5 + CONSEQ_1;  // Sample A5/A4/A3/A2/A1/A0, sequence of channels sampled
   ADC10CTL0 = ADC10SHT_2 + MSC + ADC10ON + ADC10IE;
   ADC10DTC1 = 0x06;               // 6 conversions
   ADC10AE0 |= 0x3F;				// Enable A5/A4/A3/A2/A1/A0

   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 = TIMER0_A0_VECTOR
__interrupt void Timer_A0(void) {
   counter++;                               // Increase counter
   if(counter == 0x07)
       counter = 0;                

   P2OUT &= ~(SERVO_OUTPUTS);                       // Clear ports
   if(counter == 0x06) { 

             // Counter range is 0-6, the last count is for the 20ms difference delay
       CCR0 = 2500 - (servoPosition[0] + servoPosition[1] + servoPosition[2] + servoPosition[3] 
       + servoPosition[4] + servoPosition[5]); 
            // [20ms/8us = 2500 ticks] <=> [2500 - total position values => 20ms - total synchronized on-time]

       ADC10CTL0 &= ~ENC;  // Stop sampling; Set ENC bit to 0

       while (ADC10CTL1 & BUSY)
           ;
       ADC10SA = (unsigned int)&adcValue[0];    // Data transfer location
       ADC10CTL0 |= ENC + ADC10SC;              // Start conversions    
   } 

   else {
       P2OUT |= 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) {

   while (ADC10CTL1 & BUSY)
       ;

   i = 0;
   while(i < 6) {    

            // update servo positions
   	servoPosition[5 - i] = (unsigned)((long)(adcValue[i] - adc_min) 
   	* (servo_max - servo_min) / (adc_max - adc_min) + servo_min);

   	i++;	
   }
}

 

Rob commented on almost everything, that was a huge help. I tried to green it up as well (bad memory), not sure if I made any mistakes there...

Link to post
Share on other sites

Hey Rob, thanks! It's been a lot of fun working on it, I'll post a video of it in action in a couple of days. :D Thanks for the input, you're right about the LUT, ideally that's what I'd like to use. However, it seems like a daunting task, mostly because I don't have any experience with them, i.e. I wasn't sure if I should just use oPossum's formula to populate the LUT or not?... or how to use it once I did. :problem: I have a tendancy to overcomplicate things...

 

 

zeke, thanks, I couldn't resist, I've been looking for a reason to use it! :thumbup: I actually just got it along with a gift last winter but I found a place online that sells them (no affiliation):

http://www.etgiftstore.com/items/all-pr ... detail.htm

Link to post
Share on other sites
I found a place online that sells them (no affiliation):

http://www.etgiftstore.com/items/all-pr ... detail.htm

Argg, I want to order one of each!

 

Me too! But, I'm not paying $4 shipping for $3 worth of stickers. Maybe I'll just get some label stock and make up my own pity warnings. :think:

Link to post
Share on other sites
Me too! But, I'm not paying $4 shipping for $3 worth of stickers. Maybe I'll just get some label stock and make up my own pity warnings. :think:

 

 

Ha, I don't blame you, good idea. You could get free shipping for $25 worth, but I have no idea what I'd do with 34 stickers. Just enough to drive me mad. :!!!:

 

Gotta love this one though :lol: :

http://www.etgiftstore.com/items/all-pr ... detail.htm

Link to post
Share on other sites

Going back to servos...

 

Here's a little utility page that will help you generate look up table. Just enter min and max, copy result to your code, and you are all done!

 

 

const unsigned int ccr[256] = {  90, 91, 92, 93, 94, 95, 96, 97, 98, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 326, 327, 328, 329 };

 

This is the only change you have to make to the code

 

// update servo positions
      servoPosition[5 - i] = ccr[adcValue[i] >>2];

 

servoLutGenerator.html.zip

Link to post
Share on other sites

There are two interrupt vectors for each TimerA unit.

 

The first interrupt vector (TIMERx_A0_VECTOR) will be called when the count matches TAxCCR0.

 

The second interrupt vector (TIMERx_A1_VECTOR) will be called when the counter is reset to 0, or the count matches any other TAxCCRn register. The timer interrupt vector register TAxIV indicates what caused the interrupt. This register must be read even if you assume what caused the interrupt.

 

The overflow interrupt is enabled by the TAIE bit of TAxCTL. The TAxCCRn interrupts are enabled by the CCIE bit of the TAxCCTLn register. Global interrupts must also be enabled.

 

All the details of timer operation are in chapter 12 of [tipdf]slau144[/tipdf]

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