View Full Version : Discussion Servo routines & parallel processes...
meteor
Jun 29, 2006, 12:29 PM
One of the minor caveats to interrupt-driven servo routines is the usual NOP looping to get the correct timing for the PMW pulse width, and the subsuquent loss of any other processes.
The one thing you can do is only execute the interrupt routine when you actually need to move the servo!
If the servo control line is held low, the servo will simply hold it's last known position. (sort of a "don't move" command, useful in continuous-rotation servos, as they can sometimes creep around the neutral point, as the pot can move by a hair, or just be a tiny bit erratic...)
In most cases, the required frequency of position change is very low compared to the processing power available.
The tricky part is knowing whether or not the servo actually managed to reach the desired position before you shut off the signal.
Just a thought.
xorcise
Jun 29, 2006, 12:57 PM
If you use interrupts you only have to lock in for the first 2 to 2.4ms. Once that is completed you should have nearly 18ms to do what you want until the 20ms period (50Hz) restarts for the next PWM cycle. I have done this with 8 and 16 channels of servos running off a single PIC. It's very accurate and has decent resolution. Examples of the mikroBASIC source-code are here for free:
MultiServo Code (http://www.circuit-ed.com/forums/viewforum.php?f=8)
xtal
Jun 29, 2006, 04:36 PM
if capturing servo pulse .... :rolleyes:
set up to interrupt on Leading edge
on Leading edge int start a timer [ that is properly scaled ]
set interrupt to capture trailing edge
on trailing edge stop and retrive timer value
.....pass to main routine for what ever processing
repeat
if driving servo ..... :cool:
load and start a timer [that is properly scaled]
set servo bit
on timer int stop timer reset servo bit
only a very small amount of time spent in ISR... :D
jperch
Jun 29, 2006, 04:42 PM
Holding the servo signal line low unless you want the servo to move will also cause the servo to not hold its position should there be a load on it. The constant updating of hte pulse is partly what gives a servo its holding power. This is one reason digital servos have so much holding power. The pulse from the receiver is still only 50Hz but the internal controller is updating the servo driver much faster.
Joe
xorcise
Jun 29, 2006, 05:57 PM
The "lock in" is not absolutely necessary except for certain PIC's (sorry I was thinking about PIC interrupts) because they have only one timer, such as the 16F84A, which is very popular with some users. A dual timer setup can create a very nice system and is available on most other PIC's. One is set for 20ms periods and the other for the on-time which initiates at the beginning of each period. The timers are relatively invisible to other code processes until their flags are set.
On the other hand I don't see the problem associated with dedicating 2ms of a 20ms period. On a slow PIC (4MHz) I can actually manage up to 4 servos in that short period inside an interrupt routine and still have 18ms to do a whole lot else not inside an interrupt routine.
Comatose
Jun 29, 2006, 07:53 PM
xtal's got it. There's absolutely no reason to sit in your interrupt routine NOPing.
A basic pseudo-C servo generation routine goes something like this
//globals
int servo1;
int servo2;
int servoselect = 10;
T0 interrupt: // set this up to interrupt every 2ms
{
switch (servoselect)
{
case 0:
set_timer0(servo1);
output_high(servo1_pin);
output_low(servo2_pin);
servoselect = 10;
break;
case 1:
set_timer0(servo2);
output_high(servo2_pin);
servoselect--;
break;
default:
output_low(servo1_pin);
output_low(servo2_pin);
servoselect--;
break;
}
}
Then servo1 and servo2 are set to 0 to 128 for a 2ms to 1ms pulse. NOPing inside an interrupt is a really, really bad practice.
The above can be pushed out to 10 servos just by copy/pasting more cases. It only requires one timer. It takes at most a couple percent of your processor cycles on a PIC, and significantly less on something like an AVR that has more than one register.
meteor
Jul 04, 2006, 09:23 AM
T0 interrupt: // set this up to interrupt every 2ms...
I think I see how this works, but I can't see the efficiency gain...
You have a 2ms "hearbeat", and then you set-up another timer to determine the period of each servo pulsewidth?
Do you somehow count nine 2ms "off" pulses and one variable "on"?
Could you describe what's happening at the root level (no specific code), just a description of how the timers and the output lines interact...
I'm sure it's easy, but I can't quite figure it out!
Thanks.
Comatose
Jul 04, 2006, 12:47 PM
Two efficiency gains. First, it only uses only one 8 bit timer and no other system resources. Second, there is no NOPing at all, so most of the overhead is getting into and out of the interrupt. That's about 25 instructions per cycle, or 250 instructions per 20 ms. After that its about 10 instructions per servo. So figure ~300 instructions per 20 ms period, or 15000 instructions per second. On a 1 mips processor like a pic runing at 4mhz, thats 1.5% of processor resources. On something like an AVR where you can be in and out of the interrupts in under 10 instructions, its well under 1%.
I'll run through one cycle of the code execution. Lets say we want servo1 and servo2 to be 1.5ms, so we set them both to 64.
Servoselect starts at 10.
T0 starts at 0 and counts up to 255. When it overflows to 0, the interrupt fires.
2 ms elapse. T0 interrupt fires. Seroselect decrements to 9. Total time: 2ms
4 ms elapse. T0 interrupt fires. Servoselect decrements to 8. Total time: 4ms
6ms elapse. T0 interrupt fires. Servoselect decrements to 7. Total time: 6ms
8ms elapse. T0 interrupt fires. Servoselect decrements to 6. Total time: 8ms
10 ms elapse. T0 interrupt fires. Servoselect decrements to 5. Total time: 10ms
12ms elapse. T0 interrupt fires. Servoselect decrements to 4. Total time: 12ms
14ms elapse. T0 interrupt fires. Servoselect decrements to 3. Total time: 14ms
16ms elapse. T0 interrupt fires. Servoselect decrements to 2. Total time: 16ms
18ms elapse. T0 interrupt fires. Servoselect decrements to 1. Total time: 18ms
Now the good stuff starts. At 20ms before the interrupt routine starts, Servoselect is 1. At the beginning of the case 1 interrupt we preload timer0 (currently zero, because it jsut overflowed) to 64. So this time its only going to take 1.5ms to overflow instead of 2ms. Servo2 pin is driven high, then servoselect is decremented to 0 and we exit the interrupt routine.
At 21.5ms (inportant to notice its not 22ms) the interrupt fires again. This time servoselect is 0, so we'll run the code in case 0. The timer is preloaded to 64, which will overflow in 1.5ms. servo2 pin is driven low (1.5ms after it was driven high, giving it a high time of 1.5ms and a refresh rate of 23ms) servo1pin is driven high. Then servoselect is set to 10 and we exit the routine.
At 23ms, servoselect is 10, so we run the default case. This drives servo1 pin low, so it has a high time of 1.5ms and a refresh rate of 23ms.
Thats the basic gist of how it works. The frame rate is flexible (it increases if the servo pulses are long and decreases if the servo pulses are short) but real transmitter signals do the exact same thing, so thats not a problem. If it is a problem its easy to pad out to exactly 20ms by having some cases (say, 8 and 9) that set the timer to 255-servo1 and 255-servo2.
Did that help any?
meteor
Jul 04, 2006, 02:11 PM
Ah!
The variable framerate is the key. I had assumed that a tight 20ms timing was required...
I'm a bit confused why you get a potentially 24ms framerate (all servos "off"), with ten 2ms interrupt calls. (shouldn't it be a maximum of 20ms before it "loops around" to servo0?)
Also, I'm a bit puzzled as to why a Timer0 value of zero gives a 2ms interrupt. Is this a "C" feature, where the timer function sets the timer to 256 minus of the called value?
Anyhoo, I roughly see how it works, all I need to do is port it to JAL! (and get the correct Timer0 prescaler values for a 2ms period on a 20Mhz 16F876A...)
On that note, is it really possible to get an exact 2.000ms "full duration" (modulo 256) Timer0 interrupt, or is it a bit of a fudge? (there are only 8 prescaler choices, which are in pretty rough steps...)
Is the 4Mhz implementation a convenient coincidence?
Markz
Jul 04, 2006, 03:22 PM
Did that help any?
Yes, me, among others ;)
It's the most efficient servo generation code I've seen so far. It almost deserves a sticky!
Marc
vBulletin® Copyright ©2000-2009, Jelsoft Enterprises Ltd.