PDA

View Full Version : The best way to control a servo with a PIC



The Master
- 14th October 2008, 12:18
Hi, I already know how a servo works and what kind of pulse to send to it. I can do that fine with just a PIC and a servo. Recently ive been working on a PWM circuit as some of you know. I wanted to try and control 2 servos with the same circuit but its not going so well. As far as i know TMR0 will overflow every 51.2us. The servo i have takes a pulse from about 500 to 2500us. That only gives me about 39 positions and i was hoping for 256.

Ive thought about a few things but the best way i can think of is to have 2 seperate chips. One will do the PWM as normal and the other will control the servos. I just want to know if theres a better way. I would like to keep everything on one chip but i dont think its going to happen not even if i upgraded to a 18F with a 40MHz oscillator.

Acetronics2
- 14th October 2008, 13:55
Hi,

Reading your beloved manual, you will find :

PULSOUT command

Which :

@ 4Mhz Clock gives you ... 200 steps from 500 to 2500 µs

@ 20Mhz Clock gives you ... 1000 steps from 500 to 2500 µs

Why look for complicated softs ???

Alain

The Master
- 14th October 2008, 14:33
Is PULSOUT a blocking command? I always thought it stopped your code like a pause command but im guessing not now youve mentioned it.

Edit: Just had a quick read of the online manual (im not at home with my proper one). It doesnt mention anything about it blocking/not blocking. Im sure ive read about it before though because i remember the resolution bit

rmteo
- 14th October 2008, 15:29
A single PIC18 @ 20MHz can control 24 servos with 256 steps:
http://www.mikroe.com/forum/viewtopic.php?t=6192

The Master
- 14th October 2008, 15:53
Im using a 16F (PIC16F87 to be more specific) @ 20MHz. Im sure it could handle 24 servos even with the way i did it. The problem is that the way i was doing it would have caused problems with the PWM for the LEDs. If PULSOUT allows the next few lines of code to run before it toggles back off then this should be really simple. I dont have anything to test on at the moment but when i get home ill give it a go

skimask
- 14th October 2008, 16:18
Is PULSOUT a blocking command? I always thought it stopped your code like a pause command but im guessing not now youve mentioned it.
Edit: Just had a quick read of the online manual (im not at home with my proper one). It doesnt mention anything about it blocking/not blocking. Im sure ive read about it before though because i remember the resolution bit

Yes, PULSOUT is a blocking command. As are PWM, SERIN(2)/SEROUT(2), POT, and so on.
Basically, anything that 'bit-bangs' a function is a blocking command. Nothing else can happen until that command is done.

I'm with you on this one... Send out your servo position data to a 2nd PIC via serial (or whatever) and have that 2nd PIC handle the servo positioning. There are a bunch of boards out there that do exactly that...can't think of any off hand, but I know I've seen them a bunch of times...

T.Jackson
- 14th October 2008, 16:34
Yes, PULSOUT is a blocking command. As are PWM, SERIN(2)/SEROUT(2), POT, and so on.
Basically, anything that 'bit-bangs' a function is a blocking command. Nothing else can happen until that command is done.


That's called a "synchronous" operation.

Asynchronous is a "do together at the same time" with something else operation.

The Master
- 14th October 2008, 16:41
Oh, I thought it might have been simple for a moment there. Since im using 16F i only get 1 hardware UART to play with but im using a MAX485 chip and in the lights the PIC controls the master/slave pins (the 555 method is only used in the main controller). So i can send data to a second chip without it being sent outside the light. I also have 2 spare IO pins so if i use a PIC chip to controll it i can tell it to only listen for serial data if an input pin goes high.

Now i just have to think of something to do with all those extra IO pins on the second chip. I dont think you can get anything with less than 12 and A/E/USART

skimask
- 14th October 2008, 16:55
That's called a "synchronous" operation.
Asynchronous is a "do together at the same time" with something else operation.

Thanks for clearing that up there Sparky...
I was kinda wondering when you'd get in there with the proper terminology and all...

skimask
- 14th October 2008, 16:58
Oh, I thought it might have been simple for a moment there. Since im using 16F i only get 1 hardware UART to play with but im using a MAX485 chip and in the lights the PIC controls the master/slave pins (the 555 method is only used in the main controller). So i can send data to a second chip without it being sent outside the light. I also have 2 spare IO pins so if i use a PIC chip to controll it i can tell it to only listen for serial data if an input pin goes high.

Now i just have to think of something to do with all those extra IO pins on the second chip. I dont think you can get anything with less than 12 and A/E/USART

I've used the 16F688 quite a bit, smallest PIC you can get with a built-in USART as far as I know. And if you set it up right, you should be able to run a load of servo's with it...If you set it up right...
Just a really simple idea off the top of my head...some pseudo-code if you will...


servo1 var porta.whatever...a lot of servo's...
...serial port setup...

loop:
....check RCREG for new data which is in this format...
bit7 = 1 = new data actually here, =0 nothing really here :)
bit6-bit4 = servo select, one of eight
bit3 = direction
bit2-bit0 = 3 bits to define the amount to move...

...now it gets a bit tricky...depending on clock speed of the PIC

...do the same thing with the servo's that you did with the LEDs, except the servos are on for XXX microseconds, and off for about 18ms or so...

goto loop

The Master
- 14th October 2008, 19:35
I was planning to do something similar to the LED code but i was going to use a whole byte for the position to simplify all of the code. The first chip can then pass a byte right from the buffer i already have for the LEDs and the second can turn the output on for an amount of time that is directly proportional to the value of the byte.

I think the best thing to do is have the servo PIC make one of its pins high during the 18ms of waiting. The LED PIC can only send data while that pin is high. I can use TMR0 to get the 18ms and a simple pauseus or pulsout command for the short pulse.

Ive checked both Rapid and Maplin for a 16F688 but they dont stock it. Can you buy directly from Microchip? It might be worth buying from elsewhere if the chips are going to be that much cheaper than the next best thing that Rapid stock.

I only have the need for 2 servos at the moment but i may need 3 for some other projects. I think thats about it though

skimask
- 14th October 2008, 20:44
Quick random thought...If you're used to using the 16F87/88, why not stick with them? They're only 4 pins more than the 'F688 discussed earlier...

Here's my addendum to the earlier post...
Say you've got a 'PWM counter' that runs from 0-2000 (actually a 20ms counter incrementing every 10 microseconds), you want desired_position '0' on the servo. Actual_Servo_Position = desired_position + 50 (0 in this case)...
if actual_servo_position < pwmcounter then servo-pin = 1, servo-pin will go high for .5ms (bare minimum for a servo probably), then be low for the rest of PWMcount until it resets itself.
or you want desired_position 2000 on the servo...
Actual_Servo_position = desired_position + 50 (2050 in this case)....same thing except servo-position will go high for 2.5ms (probably the absolute most), then be low for the rest of the count just like the other case...

You could easily put this in a tight little loop handling everything for you, reading the RCREG for any servo commands that come thru (or packets if you want to go that way) and still keep decent resolution on the servo itself.

The Master
- 14th October 2008, 21:08
Im not really used to the 16F87. I used to use 18F877As for testing everything and since looking at Matt's boxes a 16F84A. I found the 16F87 by using the search at Microchip.com. I think this project is the first time ive used it. I guess i might aswell stick with it. I might save more from bulk buying just that 1 chip than from buyng a smaller chip.

Im not completely sure i understand your last explanation. I dont think i can use the normal PWM code for servos because TMR0 overflows every 256 instruction cycles. 1 instruction cycle takes 200ns. That means TMR0 overflows every 51200ns or 51.2us. The pulse for the servo has a range of 2000us. If i divide 2000 by 51.2 then i get 39.0625. Thats the resolution i will get for the servo. I was hoping for 256.

I know i can do what i want by using pauseus. I tested that out before. I guess pulsout would work the same way. Idealy i dont want any serial data arriving during the short pulse. Just a few microseconds difference causes the servo to move out of place. The best thing i think is to do all the receiving and processing during the 18ms where the timing doesnt have to be so accurate. I think anything between 18 and 25ms is fine. If i use a 2 byte protocol as described above then there shouldnt be a problem. The PIC can buffer those 2 bytes and wait until the short pulse has finished. The main PIC can be set not to send data faster than that so hopefully everything should work fine.

I will breadboard all of this out first ofcourse. I just have 1 servo at the moment for testing. These lights arnt the same ones that ive been making but they work in pretty much the same way. There just bigger, brighter and move:D lol.

skimask
- 14th October 2008, 21:13
Im not really used to the 16F87....
Well, time to get used to something eh?


Im not completely sure i understand your last explanation. I dont think i can use the normal PWM code for servos because TMR0 overflows every 256 instruction cycles
It doesn't have to overflow every 256 instruction cycles... It can, in theory, overflow once every 8 instruction cycles if you want it to.
Now how would you accomplish that?


Idealy i dont want any serial data arriving during the short pulse.
If you're going thru that loop fast enough, grabbing a byte out of the serial port won't jack up the timing that much.
And like you said, the 18-25ms frame time is fine, as long as you're in the vicinity.
The critical thing would be the high pulse. Confine the critical code and you're in there...

The Master
- 14th October 2008, 22:06
Hmm, ive read the datasheet again. Aparently there is a register called TMR0. I cant seem to find any more information on that though. There is the prescaler but that does the oposite of what i want. I have heard that you can count how long something takes using the timer. Would you just have a loop and keep checking the current value? Is that value in TMR0? Ive not seen anything else about making the timer overflow at less than 256.

skimask
- 14th October 2008, 22:34
Hmm, ive read the datasheet again. Aparently there is a register called TMR0. I cant seem to find any more information on that though. There is the prescaler but that does the oposite of what i want. I have heard that you can count how long something takes using the timer. Would you just have a loop and keep checking the current value? Is that value in TMR0? Ive not seen anything else about making the timer overflow at less than 256.

TMR0 is a register... Is this TMR0 a read-only register? Is it a write-only register? Is it a read/write register?
If you can write to this TMR0 register as it's called, when can you write to it?
Can you write to it while it's running? Or do you have to write to it while it's stopped?
(I'm not dogging you at all...there's water around here somewhere...You just happen to be the horse :) )
And no, for this particular item, there's not a lot of DIRECT information in the datasheets, but there is quite a bit of 'read-between-the-lines' stuff that applies to all registers and applies here...

The Master
- 14th October 2008, 23:41
I didnt see the answer to any of those questions when i read through the datasheet. Ill do some more reading tomorrow and probably some more google searches.

skimask
- 14th October 2008, 23:53
I didnt see the answer to any of those questions when i read through the datasheet. Ill do some more reading tomorrow and probably some more google searches.

We don't need no steenkin' Google :D
TMR0 is a register just like almost any other register, like any other byte of ram in the PIC. It just so happens that this register is increment on every instruction cycle (or every other one, or every 4th one, or whatever you've set it up to do).
Does that help you out any?

The Master
- 15th October 2008, 00:18
I think so. I know the timer counts up to 256. Im guessing that register tells you its current count. I have read before that you can do that so i think i would keep checking it in a loop to see if it is above a certain value. If it is then there must be a way to reset it most likely disable it and enable it again.

Just 'reading between the lines' of your post. I havnt checked yet but i wouldnt be supprised if you could set the value of the register to whatever you want after an interrupt so instead of counting 0-128 and checking in a loop you can start at 128 and count upto 255 which will allow the interrupt to work still

skimask
- 15th October 2008, 00:23
Just 'reading between the lines' of your post. I havnt checked yet but i wouldnt be supprised if you could set the value of the register to whatever you want after an interrupt so instead of counting 0-128 and checking in a loop you can start at 128 and count upto 255 which will allow the interrupt to work still

BAM!
So I think I can infer that you figured out that you can both read AND write the TMR0 register like any other R/W register out there. There's only a couple of 'catches' to writing the TMR0 register...and that item IS in the datasheet...
Reload TMR0 with 128 (plus a small correction value depending on your code) and you're 51.2us can become 25.6us, or more, or less, depending on whatever you want to do...
Works the same with the rest of the timers too...

rmteo
- 15th October 2008, 00:35
Master, locate and download the MID RANGE MCU FAMILY REFERENCE MANUAL here:
http://ww1.microchip.com/downloads/en/DeviceDoc/33023a.pdf
Read Section 11 for detailed info on TIMER0.

skimask
- 15th October 2008, 01:49
Ok, did some thinking, even easier plan to work with...

Set up an interrupt routine to trigger a TRM1 overflow. PIC16F628A (or whatever), TMR1 prescale 1:2, you get an overflow interrupt 38.14 times per second, giving you about 26.21ms between interrupts. A servo needs at most a 2.5ms pulse to work right, and as you've figured out, 38 times per second should be good enough to hold position.
So, you've got 26.21ms to work with, and 2.5ms for each servo. Should be able to handle 10 servo's here.
So here's what some code might look like:


....set up....
....Clear the pins, set up the variables, set up TMR1, set up the serial port, turn on TMR1 interrupts, turn on serial port receive interrupts
inthandler: 'each time TMR1 overflows
If TMR1 overflow interrupt
...clear the overflow flag
...put all servo pins low (even though they should be already)
...send out the pulse for servo 1 (all 2.5ms of it, high and low chunks included)
...send out the pulse for servo 2 thru 10 each in turn...
...check the RXIF flag in between each servo pulse, if there is one there, handle it in the main loop in the spare time after sending out all those pulses which probably won't total up to the total time between TMR1 interrupts...
...if the byte received by the serial port is an $FF, it'll mean that the next byte received is for servo 1, after that servo 2, after that servo 3, and so on... This will also mean that a servo cannot have a position of $FF, which is okay, you're only losing one position.
...if it's not an $FF, it's for the next servo position in turn...
RESUME
mainloop: 'handle the received serial bytes and save them in the respective servo position variables...

You get good enough framing for the individual servo pulses (about 38 per second), each servo gets the required 'high' time depending on the desired position, the rest of the time the pin is low (until the next TMR1 interrupt), and if you take care of the high and low time for each servo, in other words allotting 2.5ms per servo, each servo will get the right pulses at the same time in the TMR1 interrupt 'frame'...
Of course, you'll have to 'convert' your received byte value to a value that'll fit inside that 2.5ms window, not really that hard, just multiply it by 10 will get you the full window plus a bit, but that might be too much for the servo's themselves.
And if you're not using all 10 servo's, you could reload TMR1 with a 'correction value' and bring the refresh time for all the servo's up to 50hz. Theoretically, you should be able to drive 8 servo's at 50hz, but in a real world, probably only 7 because you'll have to have a bit of time left over for 'housekeeping' chores.
If I get ambitious, I'll write up some actual code...if I get ambitious :)

The Master
- 15th October 2008, 09:28
Ive just done a quick bit of maths. I think if i set the timer to overflow when it reaches 39 (or start 39 before 255 ofcourse) then i should be able to get the resolution i want. I could in theory control quite a few servos by using almost identical code to the LED code. All i need to do is add that long pause in.

Im always really ambitious so i might even try the origional plan again and control the servos from the same PIC as the LEDs. If i make the timer overflow every 39 instruction cycles that means it will run the PWM code every 7800ns. I can setup my own counter to only run the PWM code for the LEDs every 6 or 7 times the timer overflows. That should make the LED code run pretty much as normal. Now i can simply turn the servo output on then on each timer overflow count upto the servo value (plus a bit). The servo pulse range is 2000us so an overflow every 7.8us gives me a resolution of 256.41. If im not bothered about the 41 (and im not) then it matches the byte perfectly.

I cant say if this method will work without trying it. It all depends on how many instruction cycles i have spare. I think its worth a try and i always have the seperate chip method if it fails.

Edit: The type of servo im using seems happy to accept any length pulse i give it but if its outside of its range then it tries to turn too far and ends up buzzing at me

T.Jackson
- 17th October 2008, 16:03
We don't need no steenkin' Google :D


I think someone got that from Sean Connery (The Untouchables)

skimask
- 17th October 2008, 16:12
I think someone got that from Sean Connery (The Untouchables)
Wrong again...

T.Jackson
- 17th October 2008, 16:26
Straight away I thought of Sean Connery for some reason.

Pic_User
- 17th October 2008, 17:52
In 2005, it was chosen as #36 on the American Film Institute list (AFI's 100 Years... 100 Movie Quotes)

Archangel
- 18th October 2008, 00:01
We're not going to Hart jew, all we want is your guns . . . No it wasn't the Aussie govt. either :D

skimask
- 18th October 2008, 03:10
All references to great movies of the past aside, at that moment I was thinking of some Mel Brooks back in '74... It's in my top 10...
(and nobody's got ]PR#6 yet)

Darrel Taylor
- 18th October 2008, 04:19
(and nobody's got ]PR#6 yet)

PBP doesn't run on Apple's.

T.Jackson
- 18th October 2008, 04:30
(and nobody's got ]PR#6 yet)



First thought that comes to my mind is a pair of scissors.




PBP doesn't run on Apple's.


Lose the apostrophe.

skimask
- 18th October 2008, 04:32
(and nobody's got ]PR#6 yet)
PBP doesn't run on Apple's.

Oh man... You GEEK! :D
I wish I had a .wav of that drive clattering at startup...that would be geek-ish.

http://www.youtube.com/watch?v=TQoA9Ul-YS0

T.Jackson
- 18th October 2008, 12:17
Oh man... You GEEK! :D
I wish I had a .wav of that drive clattering at startup...that would be geek-ish.

http://www.youtube.com/watch?v=TQoA9Ul-YS0

You reckon Leonard Kleinrock (the father of the Internet) -- is a geek?

T.Jackson
- 18th October 2008, 12:48
How about John Barden & Walter Bratton (the two that invented the transistor) -- just another couple of geeks eh?

Pic_User
- 18th October 2008, 14:13
How about John Barden & Walter Bratton (the two that invented the transistor) -- just another couple of geeks eh?"Germanium? We don't need no steenkin' germanium!" - Bill Shockley, Walter Brattain
"Yes we do." - John Bardeen (poetic history license)

T.Jackson
- 18th October 2008, 14:39
"Germanium? We don't need no steenkin' germanium!" - Bill Shockley, Walter Brattain
"Yes we do." - John Bardeen (poetic history license)

Definitely not a suspect.

skimask
- 18th October 2008, 15:38
You reckon Leonard Kleinrock (the father of the Internet) -- is a geek?
You just don't get it, do ya?

T.Jackson
- 18th October 2008, 23:57
You just don't get it, do ya?

Oh believe me I do.

That question really wasn't for you.

T.Jackson
- 19th October 2008, 00:20
My definition of geek: "a person who gets a buzz from anything intellectual, and thrives off gratitude for promoting it"

Darrel Taylor
- 19th October 2008, 01:35
My Sweetie's a Geek, and so am I.
You can't make us change, so don't even try.

We know how to Tivo and Google.
And Ebay will keep us quite Frugal.

But the best kind of Geek,
you don't see every week.

Because he's busy fixing somebody else's problem.

T.Jackson
- 19th October 2008, 02:14
Lol Darrel :D

Conclusion: the next time someone calls you a geek -- take it as a "compliment" I seriously urge you to, because being a geek means that: "you're educated, passionate and moral".

Oh!, and be sure to reference to me if you use my definition, it's very original :)

T.Jackson
- 19th October 2008, 02:44
Because he's busy fixing somebody else's problem.

Correct apostrophe usage.