RobG 1,892 Posted December 23, 2010 Share Posted December 23, 2010 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. #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]; } MarkoeZ, tech_juggernaut, godisdie and 3 others 6 Quote Link to post Share on other sites
jsolarski 94 Posted December 23, 2010 Share Posted December 23, 2010 thats awesome, you saved me some work from porting my WDT PWM code over to timer A good work Quote Link to post Share on other sites
bluehash 1,581 Posted December 23, 2010 Share Posted December 23, 2010 Sweet! I can donate a servo if you want to test out some nice stuff. Why don't you put one of your projects op for the Contest? Thanks for sharing! Quote Link to post Share on other sites
simpleavr 399 Posted December 23, 2010 Share Posted December 23, 2010 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. Quote Link to post Share on other sites
RobG 1,892 Posted December 23, 2010 Author Share Posted December 23, 2010 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. Quote Link to post Share on other sites
simpleavr 399 Posted December 23, 2010 Share Posted December 23, 2010 thanks. i thought the setting of the 1st bit would be at count7 on count0 off. Quote Link to post Share on other sites
RobG 1,892 Posted December 23, 2010 Author Share Posted December 23, 2010 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. Quote Link to post Share on other sites
RobG 1,892 Posted December 24, 2010 Author Share Posted December 24, 2010 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; GeekDoc 1 Quote Link to post Share on other sites
simpleavr 399 Posted December 24, 2010 Share Posted December 24, 2010 great thanks, i like less parts and i had only two servo (which i don't know what to do w/ them yet). Quote Link to post Share on other sites
Mac 67 Posted June 6, 2011 Share Posted June 6, 2011 Hi Rob, May I ask how many servo steps between 1 and 2 ms, please? Is it 125 steps with 8-us resolution? TIA, Mike... tech_juggernaut 1 Quote Link to post Share on other sites
RobG 1,892 Posted June 6, 2011 Author Share Posted June 6, 2011 Your assumptions are correct Mike. Quote Link to post Share on other sites
Mac 67 Posted June 6, 2011 Share Posted June 6, 2011 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 } } Quote Link to post Share on other sites
RobG 1,892 Posted June 6, 2011 Author Share Posted June 6, 2011 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. Quote Link to post Share on other sites
RobG 1,892 Posted June 24, 2011 Author Share Posted June 24, 2011 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++; } } Gyula84 and tech_juggernaut 2 Quote Link to post Share on other sites
Mac 67 Posted June 24, 2011 Share Posted June 24, 2011 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; Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.