![]() |
![]() |
![]() |
|
|
|||||||
| Micro Controllers Discuss all aspects of micro controllers - building them, coding them, etc. All controllers are welcome - PIC, BASIC, Z8 Encore!, etc. |
|
|
Thread Tools | Display Modes |
|
|
(permalink) |
|
I would really like to use the 16F628A hardware PWM to run an RC servo at 60 Hz. My program is busy getting input and can’t be interrupted 60 times per second to send output. I could use a second PIC for output but would rather not. This note is in the Mid-Range MCU Family Reference Manual.
Note: The Timer2 postscaler is not used in the determination of the PWM frequency. The postscaler could be used to have a servo update rate at a different frequency than the PWM output. Googling found a lot of complaints quoting that note and saying that the postscaler has no effect. I am still looking but has anyone had success with this or is it misinformation?
__________________
C:\WHUT ? Beware the asterisk * |
|
|
|
|
|
|
(permalink) |
|
The Timer 2 postscaler is not used for determining the period when running the CCP module in PWM mode.
One method for overcoming low frequency period limitations is to break up the PWM period into smaller/faster PWM "frames" and use a small bit of interrupt code to set the duty cycle for each "frame". The algorithm and code are relatively simple, have reasonably low processor overhead, and can be used to generate a 0% to 100% duty cycle output on the CCP1 pin for very long periods; Code:
;******************************************************************
;
; // setup 250-usec PWM frames using PR2 = 249 and prescaler
; // 1 (4 MHz) or 4 (16 MHz) for 1 usec pulse width resolution
;
; int servo = 1500; // duty cycle in 1 usec ticks
; int pulse = 0; // ISR work variable
; int frame = 1; // frame number, 0..80
;
; void ISR()
; { PIR1bits.TMR2IF = 0; // clear Timer 2 interrupt flag
; if (--frame == 0) // if end of 20 msec period
; { frame = 80; // 80 frames = 20 msec period
; pulse = servo; // setup work variable
; }
; if(pulse > 250) // if pulse > 250-usecs
; { CCPR1L = 250; // do a 100% duty cycle frame
; pulse -= 250; // subtract 250-usecs
; }
; else // do a variable or 0% frame
; { CCPR1L = (pulse); // 0..250
; pulse = 0; // remaining frames are %0
; }
; }
;
isrproc
;
; save main program context
;
movwf W_ISR ; save W-reg |B?
swapf STATUS,W ; doesn't change STATUS bits |B?
movwf S_ISR ; save STATUS reg |B?
clrf STATUS ; force bank 0 |B0
;
bcf PIR1,TMR2IF ; clear Timer 2 interrupt flag |B0
;
; check for start of new 20 msec servo period
;
decfsz frame,F ; end of 20 msec period? |B0
goto doframe ; no, branch, else |B0
movlw d'80' ; reset pwm frame number |B0
movwf frame ; |B0
movf servo,W ; setup 'pulse' work variable |B0
movwf pulse ; for new 20 msec servo period |B0
movf servo+1,W ; |B0
movwf pulse+1 ; |B0
;
; setup CCP1 PWM duty cycle for next 250 usec PWM "frame"
;
doframe
movlw d'250' ; pulse = pulse - 250 |B0
subwf pulse,F ; |B0
bc setduty ; |B0
movlw 1 ; |B0
subwf pulse+1,F ; |B0
movlw d'250' ; assume 100% duty cycle frame |B0
bc setduty ; borrow? no, branch, else |B0
addwf pulse,W ; WREG = last portion of pulse |B0
clrf pulse ; force 0% duty cycle for the |B0
clrf pulse+1 ; remaining pwm frames |B0
setduty
movwf CCPR1L ; set duty cycle for next frame |B0
;
; restore main program context
;
swapf S_ISR,W ; |B0
movwf STATUS ; restore STATUS |B?
swapf W_ISR,f ; don't screw up STATUS |B?
swapf W_ISR,W ; restore W-reg |B?
retfie ; return from interrupt |B?
|
|
|
|
|
|
|
(permalink) |
|
Another method for generating a servo output uses the CCP module "compare" mode. This method would require just two interrupts per 20 msec cycle for a single Servo channel output and so might be better tolerated by your timing sensitive program.
Regards, Mike |
|
|
|
|
|
|
(permalink) |
|
Hi Mike
I was just reading your posts on this thread http://forum.microchip.com/tm.aspx?m...2&mpage=1&key= I will have to read it a few more times. That does look like the only way to do it. Thanks Mike.
__________________
C:\WHUT ? Beware the asterisk * Last edited by ClydeCrashKop; 1st December 2007 at 04:00 PM. |
|
|
|
|
|
|
(permalink) |
|
I can think of two methods of using the PWM module to control a RC servo. With this I am assuming you have 10 bits of available resolution.
The first has the PWM on all the time and only a small fraction of the period is available to control the pulse width of the servo because 90% is needed to maintain the space between pulses (18-19mS at 50Hz refresh rate). Even if you refresh at 100Hz you still spend 80% on space and 10% on mark regardless of the pulse value. This may be adequate if you only need to control one or two servos in the background and don't want to deal with (more) interrupts. The other one involves and interrupt just before the PWM period resets. At that time load a (new) value in the PWM duty register which will take care of the pulse itself. This will allow half of the available resolution for the actual value. Then immediately after the PWM begins it's new cycle, clear the register and wait for the next interrupt in another 20mS. Once the cycle is complete the PWM will maintain a low output until a non-zero value is loaded again. |
|
|
|
|
|
|
(permalink) |
|
Here is some real sloppy code for the 628A that runs a Hitec servo. Note, it uses about 90Hz, not 60 Hz as that was pretty much the limit of the prescaler. The servo code is in the first section. The sections having to do with LED's are for other controls. Bascially, the servo is used as a latch. There are two positons, high and low. You could define whatever positon(s) you want. I have had no problems running analog servos up to about 140 Hz.
Is there an easy way to provide just snippets of code with annotations? I have seen it done, but am not sure how to do it. John |
|
|
|
|
|
|
(permalink) |
|
Thanks John
That looks interesting. I have kind of been thinking along those lines. I am glad to hear that I have more tolerance on the frequency.
__________________
C:\WHUT ? Beware the asterisk * |
|
|
|
|
|
|
(permalink) | |
|
Quote:
|
||
|
|
|
|
|
(permalink) | |
|
Quote:
Mike. |
||
|
|
|
|
|
(permalink) |
|
I couldn’t use CCP because timer1 is busy being a very stable frequency counter for a dual tachometer. (2 engines) I will pull out the stops on the servo for continuous rotation. It will have a tiny model boat propeller on it, mounted inside a gauge to indicate synchronization. It will rotate at varying speeds to indicate which engine is slower and by how much. When the prop stops, it is in sync.
You guys got me thinking in the right direction. Thanks a lot. If you see any way to make the interrupts shorter or faster, please let me know. Any other refinements are welcome. Code:
;This program uses Timer0 interrupts and prescalers to send RC servo compatible PWM at 55Hz
;to 57Hz depending on the length of your high pulse. At 4 mhz and 1:64 prescale, timer0 will
;roll over and interrupt at about 61 Hz for the low part of the pulse. The high pulse uses a
; 1:8 prescale which would roll over every 2048us. You want a range from 1000us to 2000us so
;preload timer1 with a value that will let it roll over sooner to get the pulse width you want.
;2000 / 8 =250 counts for the longest pulse. 256-250=6 so if you preload timer1 with 6, it
; will roll over after 250 counts. 1000 / 8 = 125 counts for the shortest pulse. 256-125=131
; so if you preload timer1 with 131, it will roll over after 125 counts.
; Center is approx. 68 so if you put 68 +/- up to 62 in the variable “Position”, you should
; get a full range of motion or speed. This is low resolution. You only get 125 positions or
;62 speeds left and right but it only interrupts your program about 20us, 120 times per minute.
;Thanks to Nigel Goodwin and his tutorials for delays and initializing Timer0 and interrupts.
;Clyde Crashkop
LIST p=16F628A ;tell assembler what chip we are using
include "P16F628A.inc" ;include the defaults for the chip
ERRORLEVEL 0, -302 ;suppress bank selection messages
__config 0x3D18 ;sets the configuration settings (oscillator type etc.)
cblock 0x20 ;start of general purpose registers
count ;used in looping routines
count1 ;used in delay routine
counta ;used in delay routine
countb ;used in delay routine
toggle ;flag to alternate between PWM high & low
Position ;Center is 68 +/- 62 for extremes
W_TEMP
STATUS_TEMP
endc
org 0x0000
goto Start
; Timer0 interrupt.
ORG 0x0004
MOVWF W_TEMP
SWAPF STATUS,W
MOVWF STATUS_TEMP
BCF INTCON, T0IF
MOVF toggle, f ;Move it back to its self for zero flag
SKPNZ
GOTO LowOut
clrf toggle ;To toggle for next interrupt.
MOVF Position, w ;Get Position for length of high pulse
MOVWF TMR0 ;Center is 68 +/- 60 for extremes
bsf STATUS, RP0 ;select bank 1
MOVLW b'00000010' ;prescaler = 1:8 for high time
MOVWF OPTION_REG
bcf STATUS, RP0 ;select bank 0
BSF PORTB, 3 ;Physically farthest from B6 T1CKI frequency input.
SWAPF STATUS_TEMP,W
MOVWF STATUS
SWAPF W_TEMP,F
SWAPF W_TEMP,W
RETFIE
LowOut
BSF toggle, 0 ;To toggle for next interrupt.
bsf STATUS, RP0 ;select bank 1
MOVLW b'00000101' ;prescaler = 1:64 for low time
MOVWF OPTION_REG
bcf STATUS, RP0 ;select bank 0
BCF PORTB, 3 ;Physically farthest from B6 T1CKI frequency input.
SWAPF STATUS_TEMP,W
MOVWF STATUS
SWAPF W_TEMP,F
SWAPF W_TEMP,W
RETFIE
Start movlw 0x07
movwf CMCON ;turn comparators off (make it like a 16F84)
Initialise
clrf count
clrf PORTA
clrf PORTB
SetPorts
bsf STATUS, RP0 ;select bank 1
movlw 0x00 ;make all pins outputs
movwf TRISA
movlw 0x00 ;make all pins outputs
movwf TRISB
MOVLW b'00000101' ;prescaler = 1:64 for low time
MOVWF OPTION_REG
bcf STATUS, RP0 ;select bank 0
CLRF INTCON
BSF INTCON , T0IE ;enable timer interrupts
clrf TMR0
bcf INTCON, T0IF ;start timer
bsf INTCON, GIE ;start interrupts
Main ;Everything from here on is just to exercise the servo
MOVLW d'68' ;Center
MOVWF Position
CALL Delay255
CALL Delay255
CALL Delay255
CALL Delay255
MOVLW d'6' ;Long pulse
MOVWF Position
CALL Delay255
CALL Delay255
CALL Delay255
CALL Delay255
MOVLW d'125' ;Short pulse
MOVWF Position
CALL Delay255
CALL Delay255
CALL Delay255
CALL Delay255
goto Main ;loop for ever
;Delay routines
Delay255 movlw 0xff ;delay 255 mS
goto d0
Delay100 movlw d'100' ;delay 100mS
goto d0
Delay50 movlw d'50' ;delay 50mS
goto d0
Delay20 movlw d'20' ;delay 20mS
goto d0
Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)
d0 movwf count1
d1 movlw 0xC7 ;delay 1mS
movwf counta
movlw 0x01
movwf countb
Delay_0
decfsz counta, f
goto $+2
decfsz countb, f
goto Delay_0
decfsz count1 ,f
goto d1
retlw 0x00
;end of Delay routines
end
__________________
C:\WHUT ? Beware the asterisk * |
|
|
|
|
|
|
(permalink) |
|
That is hard to read so here is asm file.
__________________
C:\WHUT ? Beware the asterisk * |
|
|
|
|
|
|
(permalink) | |
|
Quote:
|
||
|
|
|
|
|
(permalink) |
|
From a post here on Oct 3 2006
My best customer wants an indicator to help manually synchronize the RPMs on his 2 boat engines. He has an airplane with a sync gauge that has a propeller that spins one way or the other and stops when the engines are in sync. He really likes that and I thought I could put it on his boat. I can’t seem to find it or any others online. The Yanmar diesels put out a hall effect tach signal at about 200 pulses per rev from the flywheel ring gear. That is the main reason I got started with PICs. While just trying to make a whirly gig, I now have a perfect dual digital tach on a 4 X 40 LCD with the difference in RPM, port or stbd. I almost had it working with a gear reduced motor, a DPDT relay and PWM at about 2 kHz but the output was messing up the tach input. This was just before they took the boats to the Bahamas for 2 months. I did manage make and install a 10 LED bar graph before they left. It serves the purpose for now and I told him the prop version was in progress. I learned a whole lot in the process and there is no way I could charge him for all the time spent so I told him “No charge. Merry Christmas”. Besides, a little Karma goes a long way. Now I am on vacation for 2 months. (hehehe)
__________________
C:\WHUT ? Beware the asterisk * |
|
|
|
|
|
|
(permalink) | |
|
Quote:
I can't quite figure out what you're doing from your brief description and code but I suspect you could reallocate PIC resources to come up with a simpler cleaner tighter solution than what you have now. Good luck with your project. Mike |
||
|
|
|
|
|
(permalink) |
|
Tell us how you're implementing the Timer 1 frequency counter? How long and how often are you counting the hall effect signal on each motor? How to you switch the T1CKI counter input pin between motors?
Last edited by Mike, K8LH; 3rd December 2007 at 04:02 AM. |
|
|
|
|
| Bookmarks |
| Thread Tools | |
| Display Modes | |
|
|
|
|
||||
| Thread | Thread Starter | Forum | Replies | Latest |
| Driving a servo motor | Wingmax | Robotics Chat | 26 | 11th February 2008 09:56 PM |
| Servo problems | bananasiong | Micro Controllers | 0 | 16th September 2007 02:15 AM |
| Servo Controll... In reverse? | savage | Micro Controllers | 9 | 1st December 2006 03:10 PM |
| Replacing RC Servo w/ Custom PCB (Open Servo) | dknguyen | Electronic Projects Design/Ideas/Reviews | 3 | 29th May 2006 02:48 AM |
| Help required with servo motor controller | mayhem | Robotics Chat | 3 | 26th May 2006 04:21 PM |