NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Sensors, Actuators, and Robotics » Controlling more than one servo

December 23, 2009
by Ethanal
Ethanal's Avatar

How can I control more than one servo motor? I am using code similar to the Servo Squirter code.

January 12, 2010
by BobaMosfet
BobaMosfet's Avatar

Can you post a schematic of what you've done? If you have enough free pins of the right can, you can control another. Maybe more, depending also on the above.

January 13, 2010
by mongo
mongo's Avatar

Boba, I am currently working on that aspect. Will fill you in when I get it figured out.

Mongo

January 13, 2010
by BobaMosfet
BobaMosfet's Avatar

Ethanal-

Take a look at the schematic in the Servo Squirter tutorial. It only takes a pin or two to control either the pump or the servo.

Add the necessary components, modify the code, re-upload new code to the chip and voila!

Let us know how you get on!

BM

January 22, 2010
by Phrank916
Phrank916's Avatar

I think I might have figured this one out. I'm also trying to figure out how to control 2 servos. The ATMega168 only has one 16 bit timer/counter with PWM. The output compare registers are OCR1A and OCR1B. In the servosquirter project, Mode 15 is being used which allows us to use the OCR1A register as the TOP value. Since we want to use OCR1A as an output register we can store the TOP value in a different register, the ICR1. To do this we have to set the timer to mode 14 (see page 133, table 15-4 of the spec sheet). To set mode 14 we simply remove the bit being shifted into the WGM10 bit on register TCCR1A. So, instead of this:

TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);

We change it to this:

TCCR1A = (1<<COM1B1) | (1<<WGM11);

And so then this declaration:

OCR1A = 36864

Should be changed to this:

ICR1 = 36864

Then we can make two functions that look like this:

void pwm_setx(uint16_t x){
  OCR1B = x;
}
void pwm_sety(uint16_t y){
  OCR1A = y;
}

From there we can hook up our two servos to PB2 and PB3 and then use whatever code you want to change those two values.

I haven't tried this yet, this was the very next step in my 1 joystick / 2 servos project that I hadn't yet figured out until I decided to study the spec sheet and wrap my brain around it this morning.

Please correct me if I'm going about this all wrong but this seems like the logical explanation to the problem.

Also, from this morning's study I gather that only two servos can be controlled with the ATMega168 since they require a 16 bit timer to handle the 20.0ms pulse, and the ATMega168 only has one 16 bit timer with two output compare registers.

Also, some information learned from the spec sheet as to why we would want to use OCR1A instead of ICR1: OCR1A is double buffered and would allow for dynamically changing the TOP value, this would be if we wanted to actively change the PWM frequency. Since we're keeping it at 20ms, the ICR1 register can be used since we're using a fixed TOP value.

Ted

January 22, 2010
by Phrank916
Phrank916's Avatar

Sorry, correction: the servos would need to be hooked up to PB1 and PB2, not PB3..just re-looked at the pinout.

January 22, 2010
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

Hi Ted,

You're definitely on the right track! One thing I wanted to address -- you suggest that only two servos can be controlled because a 16 bit timer is needed. However, you can also use Timer0 and Timer2 (each of which is an 8-bit counter with two Output Compare Registers each) to generate PWM for a servo.

For example, in the "Fast PWM" mode with a prescaler of 1024, the overall PWM cycle period is 1024*256/14745600 = 17.8 milliseconds. That's in the 16-20ms range that servos "prefer". You do lose some resolution, however, as one counter unit is now 1024/14745600 = 0.069 milliseconds, meaning that the entire space between 1 and 2 ms is held in just ~14 units, meaning that you're basically limited to 14 position steps. But for many applications this is perfectly fine.

There's another technique that you can use, which takes advantage of the fact that servos only care about the "on" time, and not too much about the "off" time (probably as long as they get an update within 15-100 ms or so, although the high end probably varies with different servo types). You could use the timer at a smaller prescale value to get a high resolution, but use an outside piece of code to explicitly trigger the counter to run each time.

You could even get fancy with interrupts and have the timer switch between a high-resolution mode where it is running the servo via PWM (say with prescaler 256), and then a "slower" mode with PWM disabled that just times the "off" time. Really neat idea -- I might have to do this as a video tutorial!

Mike

January 22, 2010
by Phrank916
Phrank916's Avatar

Yer blowin' my mind, Mike! Yeah, I was thinking after my post, that using the 8-bit timers and the right prescaler I'm sure it could work, but I guess I was thinking about the smoothness factor when driving the servos with a joystick. I had a feeling it would have to lose some resolution in the overall throw of the servo.

The last part about using outside code to trigger the timers has me envisioning the timer jumping all over the place just throwing pulses at a whole array of servos. Is this similar in concept to the LED array? Very cool stuff, you SHOULD do a video.

Anyway, here is a link to the code I have come up with for my 1 joystick / 2 servos project. Haven't tested it yet at all...still waiting for the other half of my soldering iron and some breakout boards for the joysticks to arrive.

2pots2servos

Ted

January 22, 2010
by Phrank916
Phrank916's Avatar

Hmm, yeah the servo only works on PB2 right now. If plugged into PB1, no worky. I am pretty sure it has something to do with setting interrupts, but I am still not grasping that. Gonna have to do more research. Stay tuned.

January 23, 2010
by Phrank916
Phrank916's Avatar

<Forehead smack> I think all I am forgetting is (1<<COM1A1) in the TCCR1A. So the line should be:

TCCR1A = (1<<COM1A1) | (1<<COM1B1) | (1<<WGM11);

Too late to try it tonight. Will test again tomorrow.

January 23, 2010
by Phrank916
Phrank916's Avatar

Yep that was it...

January 30, 2010
by mongo
mongo's Avatar

Good show! I just put it together here and after getting through the last bit, it took right off.

February 06, 2010
by icarus
icarus's Avatar

wow! you solution is perfect! i have used it on my PPM decoder for my rc plane and it functioned wonderfully. the best part is how easy it was added to my code that uses the servo squirter code. good job!

-Icarus

March 25, 2010
by mongo
mongo's Avatar

A new hitch in the workings...

I have been causing more brain damage that I like, trying to get two servos to work independently via the keyboard, (like the servosquirter does with one) I started out with the servosquirter program and began duplicating what looks like it should be but I keep getting compilation errors.

I am combining parts of the 2pots2servos concept as well. Alone by them selves, I understand their ways but for some reason, I am having trouble grasping the dual system.

Ideas?

March 25, 2010
by mongo
mongo's Avatar

OK, I got it! I can now control two individual servos AND the pump motor giving me x, y axes as well as the pump. The rest is a simple batch file to sent key sequences to the port and the servos will follow the choreography coded within.

Here's the code: I don't know why it changes fonts and sizes but here it is...

define F_CPU 14745600

include <stdio.h>

include <avr/io.h>

include <avr/interrupt.h>

include <avr/pgmspace.h>

include <inttypes.h>

include "../libnerdkits/delay.h"

include "../libnerdkits/lcd.h"

include "../libnerdkits/uart.h"

define PWM_MIN 1300

define PWM_MAX 4200

define PWM_START 2750

void pwm_setx(uint16_t x) { OCR1A = x; } void pwm_sety(uint16_t y) { OCR1B = y; }

void pwm_init() { // setup Timer1 for Fast PWM mode, 16-bit // COM1B1 -- for non-inverting output // WGM13, WGM12, WGM11 -- for Fast PWM with ICR1 as TOP value // CS11 -- for CLK/8 prescaling

// each count is 8/14745600 = 0.5425us. // so 1.0ms = 1843.2 // 1.5ms = 2764.8 // 2.0ms = 3686.4 // 20.0ms = 36864

ICR1 = 36864; // sets PWM to repeat pulse every 20.0ms

TCCR1A = (1<<COM1A1) | (1<<COM1B1) | (1<<WGM11); TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

}

int main() { // init LCD lcd_init(); FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE); lcd_write_string(PSTR("robot "));

// init serial port uart_init(); FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW); stdin = stdout = &uart_stream;

// set PB1,PB2,PB3 as output DDRB |= (1<<PB1) | (1<<PB2) | (1<<PB3);

// init PWM pwm_init();

uint16_t pos1 = PWM_START; uint16_t pos2 = PWM_START;

char tc;

while(1) { pwm_setx(pos1); pwm_sety(pos2); // Print the current servo positions to the LCD. lcd_line_two(); fprintf_P(&lcd_stream, PSTR("servo 1: %d "), pos1); lcd_line_three(); fprintf_P(&lcd_stream, PSTR("servo 2: %d "), pos2);

// Wait for a character to be sent to the serial port.
tc = uart_read();
if(tc==']') pos1+= 25;
if(tc=='[') pos1-= 25;
if(tc=='=') pos1+= 5;
if(tc=='-') pos1-= 5;
if(tc==',') pos1 = 1300;
if(tc=='.') pos1 = 4200;
if(tc=='m') pos1 = 2750;
if(tc=='}') pos2+= 25;
if(tc=='{') pos2-= 25;
if(tc=='+') pos2+= 5;
if(tc=='_') pos2-= 5;
if(tc=='<') pos2 = 1300;
if(tc=='>') pos2 = 4200;
if(tc=='M') pos2 = 2750;

if(tc=='0') PORTB &= ~(1<<PB3);
if(tc=='1') PORTB |= (1<<PB3);

// bounds checking
if(pos1 > PWM_MAX) pos1 = PWM_MAX;
if(pos1 < PWM_MIN) pos1 = PWM_MIN;
if(pos2 > PWM_MAX) pos2 = PWM_MAX;
if(pos2 < PWM_MIN) pos2 = PWM_MIN;

// Print the current servo position to the serial port.
printf_P(PSTR("%d\r\n"), pos1);
printf_P(PSTR("%d\r\n"), pos2);

}

return 0; }

March 26, 2010
by Rick_S
Rick_S's Avatar

A single Pound sign signifies Header1

Double pound Header two

Thru 6 for a header 6

That is why your code shows up that way. The pound sign at the define and include statements tell the forum software you want that text as header1.

To prevent that, precede each line of your code with 4 spaces. This will format the text as code as shown below.

#include "../libnerdkits/lcd.h"

If you follow the linke at the bottom of the page, you'll find more neat things you can do.

Rick

March 26, 2010
by mongo
mongo's Avatar

AHAH!!!

Now I get it!

The trouble I have is that the instructions get a bit wordy for my ADD to deal with so I end up skimming rather than reading.

Little chunks at a time like what you just showed me are what it takes. Thanx, Rick.

Now on to the next project...

March 31, 2010
by mongo
mongo's Avatar

Here is a short video of the 2 servos in operation on the desk. The circuit is running a script in real time from the PC.

Next step is to get the radio link to work right. (A little help Jim?)

2 servos and an LED

Post a Reply

Please log in to post a reply.

Did you know that you can impress a loved one with a digitally-controlled Valentine's Day Card with randomly twinkling LEDs? Learn more...