Jump to content
43oh

LaunchPad controlling up to 8 RC Servos


Recommended Posts

  • 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

Another example, this time the simplest single channel ADC -> servo.

It is possible to get 2 or more channels this way, but MCU with at least 3 CCRs must be used.

 

      Vcc
       ^                         Vcc GND  
       |     +---------------+    ^  ^    
      +++    | MSP430G2331   |    |  +--+ 
      | |    |               |    +-----+ Servo
  Pot | |<---+ P1.1     P1.2 +----------+
      | |    |               |              
      +++    |               |           
       |     |               |              
       v     |               |    
      GND    |               |                  
             +---------------+              

 

#include 

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

   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   P1DIR |= BIT2;      					// Bit 2 is connected to enable and will be used for PWM
   P1SEL |= BIT2;      					// P1.2 TA1 option select

   ADC10CTL0 = ADC10SHT_2 + ADC10ON + ADC10IE;
   ADC10CTL1 = INCH_1;						// P1.1 analog input
   ADC10AE0 |= BIT1;

   CCTL0 = CCIE;                           // CCR0 interrupt enabled
   CCR0 = 2500;                            // 20ms cycle
   CCTL1 = OUTMOD_7;                   	// CCR1 set/reset
   CCR1 = 180;                   			// CCR1 default position
   TACTL = TASSEL_2 + MC_1 + ID_3;         // SMCLK/8, upmode

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

// ADC10 interrupt service routine
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void) {
   CCR1 = (ADC10MEM >> 3) + 0x7F;
}

// Timer A0 interrupt service routine
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A (void) {
ADC10CTL0 |= ENC + ADC10SC;
}

Link to post
Share on other sites

Someone has asked me for a 2ch version, here it is.

 

      Vcc
       ^                         Vcc GND  
       |     +---------------+    ^  ^    
      +++    | MSP430G2331   |    |  +--+ 
      | |    |               |    +-----+ Servo1
  Pot | |<---+ P1.0     P1.5 +----------+
      | |    |               |              
      +++    |               |           
       |     |               |              
 Vcc   v     |               |    
  ^   GND    |               |   Vcc GND
  |          |               |    ^  ^      
 +++         |               |    |  +--+   
 | |         |               |    +-----+ Servo2
 | |<--------+ P1.1     P1.4 +----------+   
 | |         |               |
 +++         |               |
  |          |               |
  v          |               |
 GND         |               |
             +---------------+

 

#include 

#define SERVO_1 BIT4
#define SERVO_2 BIT5

unsigned int counter = 0;     // Servo counter
unsigned int servo1pos = 180; // Default servo position
unsigned int servo2pos = 180;
unsigned int adcValues[2] = {0,0}; // ADC buffer

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

   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   P1OUT &= ~(SERVO_1 + SERVO_2);
   P1DIR |= SERVO_1 + SERVO_2;             // Port P1.4-1.5 is out
   P1SEL &= ~(SERVO_1 + SERVO_2);

   ADC10CTL1 = INCH_1 + CONSEQ_1;
   ADC10CTL0 = ADC10SHT_2 + MSC + ADC10ON + ADC10IE;
   ADC10DTC1 = 0x02;
   ADC10AE0 |= 0x03;

   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 == 0x03)
       counter = 0;                // Counter range is 0-2, the last count is used to complete 20ms delay, otherwise analog servos might act funny

   P1OUT &= ~(SERVO_1 + SERVO_2);                       // Clear ports

   if(counter == 0) {
       P1OUT |= SERVO_1;          // Set port of the servo 1
       CCR0 = servo1pos;          // Set time for the servo 1
   } else if(counter == 1) {
       P1OUT |= SERVO_2;          // Set port of the servo 2
       CCR0 = servo2pos;          // Set time for the servo 2
   } else {
       CCR0 = 2500 - (servo1pos + servo2pos); // complete 20ms cycle
       ADC10CTL0 &= ~ENC;
       while (ADC10CTL1 & BUSY)
           ;
       ADC10SA = (unsigned int)adcValues;
       ADC10CTL0 |= ENC + ADC10SC;
   }
}

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

   servo1pos = (adcValues[0] >> 3) + 0x7F; // copy ADC buffer to servo position variables
   servo2pos = (adcValues[1] >> 3) + 0x7F;
}

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

I loaded the 1 servo code and it ran good so I then loaded the 2 servo code, it is a good program as well.

 

I am now running 2 servos on one pot with usb power through the emulator board! But be careful not to put the servos under a load with usb as this can create usb malfunction.

 

RobG Thanks for the code :thumbup: . My next addition i will attempt to code 1 pot , 2 servo out and push button for auto centering.

 

______________________________________________________________________________________________________________

 

 

 

"Reading c or c++ is like juggling 3 chain saws on full while blindfolded."

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

Hi Rob,

 

I have an MSP430G2553 and I'd like to control six RC servos independently, and simultaneously, with six pots using all six capture/compare registers of Timer_A0 and Timer_A1.

 

I'm aiming to control a robotic arm with pots and servos and, in order for it to keep its position, each servo has to always have a constantly updated PWM signal. Is this possible? If so, how would the code differ from the code that you posted for the G2331?

 

Thanks for any help!

Link to post
Share on other sites

Since you are using 2553, you do not need shift register to control 6 servos.

Using one CCR per servo is not possible (20 pin chip) due to pin function arrangement, but you can still control up to 8 servos using 8 potentiometers.

You can use P1 port for all analog inputs and P2 port for servo output.

HS-645MG and HS-485HB servos are analog, 20ms cycle, looks like they do not need any special treatment.

You can use this 4ch code as an example, just move all servo outs to P2.

Let me know if you need help with that.

 

Here's (untested) code, you may have to adjust min and max position as some servos do not take out of range values too well.

P1.0 - P1.5 is A0 to A5, P2.0 - P2.5 is S0 to S5

 

#include 

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

unsigned int counter = 0;                   // Servo counter
unsigned int servoPosition[6] = { 180, 180, 180, 180, 180, 180 }; // Default servo position
unsigned int servoOn[6] = { BIT0, BIT1, BIT2, BIT3, BIT4, BIT5 };
unsigned char valueIndex = 0;
unsigned int adcValues[6] = {0,0,0,0,0,0};

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

   BCSCTL1 = CALBC1_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;
   ADC10CTL0 = ADC10SHT_2 + MSC + ADC10ON + ADC10IE;
   ADC10DTC1 = 0x06;
   ADC10AE0 |= 0x3F;

   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;                // Counter range is 0-6, the last count is used to add 5ms delay

   P2OUT &= ~(SERVO_OUTPUTS);                       // Clear ports
   if(counter == 0x06) {
       CCR0 = 2500 - (servoPosition[0] + servoPosition[1] + servoPosition[2] + servoPosition[3] + servoPosition[4] + servoPosition[5]);
       ADC10CTL0 &= ~ENC;
       while (ADC10CTL1 & BUSY)
           ;
       ADC10SA = (unsigned int)&adcValues[0];
       ADC10CTL0 |= ENC + ADC10SC;
   } 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)
       ;
   valueIndex = 0;
   while(valueIndex < 6) {
       servoPosition[5 - valueIndex] = (adcValues[valueIndex] >> 3) + 0x7F;
       valueIndex++;
   }
}

Link to post
Share on other sites

Thanks Rob, I really appreciate your help, I can't wait to test it out this evening! If I can't use one servo per channel, would the servos still be able to maintain their positions?

 

In your code does the PWM work by using CCR0 in the timer to set the period and then the other registers are used to set the duty cycle? After talking to a friend of mine, I was wondering if the following method work if I wanted to use one servo per channel:

 

Free-up the CCRO's by setting the timers to continuous mode instead of up mode, counting up to FFFF and then rolling over.

 

Fix the period, calculate the duty cycle and then on alternate interrupt events set the timer control to autoset/reset the associated pin. The calculations would be done in the foreground [DutyISR = X, PeriodISR = (Period - X)].

 

While using the timers in compare mode, set pulse to SET on PeriodISR and to RESET on DutyISR. That way, the ISR wouldn't need to occur on the exact time to control the pin, it would occur during dead-time, only needing to flip a couple of control bits and to adjust the compare value, and the clock rolls over so wouldn't have to adjust for that.

 

I'm not sure, I may be over-complicating things a bit. I guess I'm just wondering if I'll actually NEED to use one CCR per servo if I want the robotic arm to keep its position while changing the duty cycles, or if the current setup will work?

 

Thanks again, it'll be a lot of fun to get this arm moving around.

Link to post
Share on other sites

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 of the pulse for each servo, so they are still repeated every 20ms, but occur one after another, never at the same time.

We set CCR for the first servo and turn it on, then when done, we turn it off, set CCR for servo 2 and turn it on. We repeat that 6 times and then we calculate time needed to complete 20ms and set CCR.

 

This image should make things clear

 

post-197-13513554673_thumb.jpg

Link to post
Share on other sites

The code works very well, thanks!

 

I noticed that I'm only getting about 90 degrees of motion out of the servos, do I need to adjust the code to get the full 180 degrees range out of them? At first I thought it may be a problem with the torque specs, but it also happens with the servos at the wrist and claw where very little torque is required.

 

Thanks again.

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