View previous topic :: View next topic |
Author |
Message |
yann p Guest
|
Driving lots of servos for a robot |
Posted: Wed Oct 12, 2005 1:30 pm |
|
|
Hi,
Please be gentle, I'm new here.
I have done lots of programming before but am new to the world of PICs. I have some 16C74As and a programmer and I want to control 18 servos... yes 18. I've been looking for some samples that I can use and expand but none of them are layed out that simply... can anyone suggest a good starting place.
Thanks.
|
|
|
kender
Joined: 09 Aug 2004 Posts: 768 Location: Silicon Valley
|
|
Posted: Wed Oct 12, 2005 1:41 pm |
|
|
Once upon a time I have used servo. Here's what I remember:
Each servo needs an update of its position every 20ms, or it will go to one of the extremes. The update is a positive pulse, whose duration sets the position of a servo: 1ms is one extreme, 2ms is another extreme and 1.5ms is the middle.
You can hook-up the servo to any I/O pin.
Timer interrupt is the best way of generating these waveforms. |
|
|
yann p Guest
|
|
Posted: Wed Oct 12, 2005 3:57 pm |
|
|
Is there any nice "This is how you drive two servos with a timer" code out there that doesn't make things cmplicated with serial comms in it? |
|
|
kender
Joined: 09 Aug 2004 Posts: 768 Location: Silicon Valley
|
|
Posted: Wed Oct 12, 2005 4:51 pm |
|
|
yann p wrote: | Is there any nice "This is how you drive two servos with a timer" code out there... |
I thought that there would be such code in this forum, but I couldn't find it. So, here's a $0.02 code, which you might be able to evolve into your servo driver, if you want. The heart of it is the timer interrupt service routine (ISR). This routine is called when the timer register overflows thus causing an interrupt. The code is just a simple state machine
Code: |
// constants
#define SERVOS 18 // number of servos
#define ONE_MS 100 // a number of timer cycles corresponding to 1ms
// global declarations
int8 g_arrServos[SERVOS]; // states for each servo
// timer ISR
#int_timer0
void isr_timer0
{
int i; // loop counter
if (g_arrServos[0] > 0)
{
output_high(PIN_SERVO0);
--g_arrServos[0];
}
else
{
output_low(PIN_SERVO0);
}
if (g_arrServos[1] > 0)
{
...
}
// main
void main()
{
// initialize the timer
...
// initialize the state variables
for (i = 0; i < SERVOS; ++i) g_arrServos[i] = ONE_MS;
while (1) // your main loop. probably all of your intelligence resides here
{
g_arrServos[x] = ...
}
}
|
yann p wrote: | ...with serial comms in it? |
That's a separate can of worms. You'll need to make your own or adopt an existing serial communication protocol. (Frankly, I haven't solved this problem for myself.) Again, there should be codes for this in this forum.
Last edited by kender on Thu Oct 13, 2005 5:53 pm; edited 1 time in total |
|
|
Ttelmah Guest
|
|
Posted: Thu Oct 13, 2005 2:18 am |
|
|
One idea that might be worth considering, is taking advantage of how servos are driven in normal RC configurations. Basically, the data sent over the 'air', comprises a 'packet', that is normally 20mSec long (this is not quite as critical as one of the other posts makes it sound, and some sysems use a 28mSec packet, where a lot of servos exist).
The packet, comprises a pause, which marks the start, and then a signal for the pulse for the first servo, and then sequentially the signals for each servo in turn. At the receiver, a monostable multivibrator, with a period of just over 3mSec, is triggered from the edge of each pulse, and it's output will only go inactive, when the 'gap' is seen. When it goes inactive, this resets a counter, connected to a multiplexer. The multiplexer feeds the pulse train to a sequence of outputs, and is clocked on each falling edge in the train. So what happens is that the first pulse after the gap, is fed to the first output, then the second pulse to the second, working through the pulses in the packet. In old RC systems, this was all done in discrete logic, but there are decoder IC's, that contain this whole system, readily available (or it is easy to do with a processor).
Now if such a decoder IC, was connected to a single PIC output pin, and then this pin generated the signals for each servo in turn, using the same packet format, it'd make the code really easy to do, even allowing really accurate pulses from the CTC output, without problems if other jobs have to be done at the same time. A circuit to do this for six channels, using a 4017, and an OP-AMP to form the gap detector, is at:
<http://web.telia.com/~u85920178/use/rc-prop.htm>
The decoder chips, used to be Harris parts, and should still exist from sombody.
An alternative woul be a device like:
http://www.sourceresearch.com/elab/EDEFT639.cfm
This is a simple chip, that controls five servos from a command on a 2400bps serial link. Add a couple of these to a PIC, and using the software serial ports, you can control ten servos, very simply indeed.
Best Wishes |
|
|
Sophi
Joined: 14 Jun 2005 Posts: 64
|
|
|
yannp
Joined: 12 Oct 2005 Posts: 13
|
|
Posted: Thu Oct 13, 2005 12:45 pm |
|
|
I think the bit that's confusing me is a nice way to update the servos regularly AND for a certain time depending on the position. I know there is a way of using the timer but I'm not sure of how to go about it. Is there any simple code using a timer for sending a set position to a servo on one pin? |
|
|
tophe59
Joined: 13 Oct 2005 Posts: 32 Location: France
|
|
Posted: Thu Oct 13, 2005 1:37 pm |
|
|
my english is too bad
in French:
Je l'ai déja fais pour8 servo mais c'est modifiable pour plus !!!
Il faut utiliser les timer mais bien souvent ils sont incrémenté trop vite pour génerer les 20ms. L'idée de base est de découper le temps en tranches par exemple 100us.
Tu regles ton timer pour qu'il génère une interruption toutes les 100us et dans ton sous programme d'interruption:
- tu incrémente un compteur.
- tu fais une mise à "1" de toutes tes sortie servo quand le compteur atteint 200.
- tu fais une mise à "0" du ou des servo qui t'interessent en fonction de la position du compteur.
Voilà j'espere que tu parviendra à traduir |
|
|
yannp
Joined: 12 Oct 2005 Posts: 13
|
|
Posted: Thu Oct 13, 2005 1:42 pm |
|
|
My freanch is not too good but did you mean that you have got some code for 8 servos....but it's not that good. If it uses the timer then it would be a useful starting point for me..... |
|
|
tophe59
Joined: 13 Oct 2005 Posts: 32 Location: France
|
|
Posted: Thu Oct 13, 2005 1:59 pm |
|
|
Quote: | #int_timer1
void timer1()
{// 5< servo <23
SET_TIMER1(0xfe0b+get_timer1()); //inter toutes les 200us (ck timer 0.2us)
tps_servo++;
if (tps_servo==190) {output_b(0xFF);tps_servo=0;}
if (tps_servo==servo[0]) {output_low(pin_b0);}
if (tps_servo==servo[1]) {output_low(pin_b1);}
if (tps_servo==servo[2]) {output_low(pin_b2);}
if (tps_servo==servo[3]) {output_low(pin_b3);}
if (tps_servo==servo[4]) {output_low(pin_b4);}
// if (tps_servo==servo[5]) {output_low(pin_b5);}
// if (tps_servo==servo[6]) {output_low(pin_b6);}
// if (tps_servo==servo[7]) {output_low(pin_b7);}
} |
|
|
|
hillcraft
Joined: 22 Sep 2003 Posts: 101 Location: Cape Town (South africa)
|
Driving servos in parallel |
Posted: Thu Oct 13, 2005 8:57 pm |
|
|
I do not think that driving the servos serially is efficient where many servos need to be driven. A better way to do it would be in parallel.
What do we know about servos:
-------------------------------------
1. They must be updated every 20ms.
2. They generally have a pulse width of 1ms to 2ms.
The way I would go about the hardware is a follows:
------------------------------------------------------------
1. Use 3 X 74595 shift registers in series so as to limit the number of io pins required from the pic. (This will give you 3 X 8 = 24 servo outputs using 3 pic pins)
The way I would go about the software is a follows:
-----------------------------------------------------------
1. Create a 24 element array to hold the servo positions. Each position has a range of 0-200 (Where 0 = 1ms pulse width and 200 = 2ms pulse width)
2. Create a timer that will roll over every 1ms (The reason for this is that the frame rate is 20ms, but the minimum servo pulse width is 1ms). Update a counter within the interrupt.
3. Create timer that will roll over every 5us.
4. Put the entire program in a loop.
5. Look at the 1ms counter. When the counter hits 20 then it is time to raise the 24 parallel servo outputs.
6. Clear the 1ms counter.
7. When the counter hits 1, then the pulse width is at 1ms.
8. Clear the 1ms counter.
9. Start counting the 5us pulses.
10. If the 5us pulse == servo[x] position in the array (then you have output the right pulse width for that servo. Lower the servo output for that servo.
11. Do point 10 until all the servos have been handled.
This strategy will allow you to have the other 18ms of the frame time to run other aspects of the program
The program can be refined in the following ways:
---------------------------------------------------------
1. Place the servo update code within the 5us interrupt. Then you would not need to waste 2ms out of every 20ms. You just need to be sure that the clock speed is high enough to support this.
2. Change the 5us interrupt to be a 1us interrupt and store servo positios from 0-2000 within the array so as to give you better resolution. And apply point 1. |
|
|
Guest
|
|
Posted: Fri Oct 14, 2005 9:48 am |
|
|
I doubt that you'd be able to shift all 24 bits within the 5uS time slot without latency issues. This might work ok for fewer channels but worst case would be shifting the entire servo update, along with the array check overhead, every 5 uS, no? |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Fri Oct 14, 2005 6:08 pm |
|
|
A few more enhancements to Kender's algorithm:
- Multiple 74595's in parallel?
- Use the SPI port for shifting out the data. A PIC running at 40MHz can clock out the data at a 10MHz rate. While SPI is busy shifting out the bits the processor can load the next data from memory. Running at 40MHz updating 24 servo's should be possible in just under 3us. Maximum clock frequency for the 74595 is 20MHz so the high speed serial clocking is no problem.
- Forget about interrupts when running loops in microsecond resolutions. Way too much overhead.
Can someone tell me what the effective resolution of a typical servo is? I mean, how many discrete steps can be distinguished? Most servo's work with a potentiometer for position feedback, how accurate are those?
Are the 200 proposed steps a realistical value, too small or way better than most servo's capabilities? |
|
|
hillcraft
Joined: 22 Sep 2003 Posts: 101 Location: Cape Town (South africa)
|
200 Steps |
Posted: Sat Oct 15, 2005 12:58 am |
|
|
One of my customers uses a servo to control the fire button on a digital camera. We set the servo travel endpoints in software and the pc the just send a command to the pic to move the servo from one extreme to the other. 200 points is fine for this application.
Digital servos can accurately travel in 1024 points some even in 2048 steps. Another thing to note is that a typical servo has a sweep range of about 120 degrees. I think that 200 steps is way too low for an analog servo where accuracy is important. I would move the number of steps up to at least 1000.
Another thing to remember is that the quality of the servo is extremely important. Cheaper servos have a fair bit of slop and therefore are inaccurate by there very own nature. You also find that the cheap servos are fairly slow and cannot move from endstop to endstop very quickly.
I like the 74595s in parallel idea. |
|
|
yannp
Joined: 12 Oct 2005 Posts: 13
|
|
Posted: Sun Oct 16, 2005 3:01 pm |
|
|
My original problem was driving a large number of servos following a sequence stored in an array etc. I wanted a simple hardware config and was hoping to use some PIC16C74As I've got lying around. As this is for robot control I'd like to be able to use ultra sonic detectors in the future but for now a walking sequnce would be good. I have programmed in c before but don't know of a nice way of being able to update a large number of servos in timers... any ideas? |
|
|
|