How to slow PWM period with fast clock speed

Status
Not open for further replies.
I'm using a PIC16F1934 running at 32MHz. I'd like to maintain the current clock speed, but I need to get a 20ms period from the PWM to run a servo.

PWM period = (PRx +1) × 4 × Tosc × TMRx Prescale Value

PRx is the Timer Period Register
Tosc = 1/Fosc
TMRx Prescale Value maxes out at 1:64.

2.048ms = (255+1) × 4 × (1 / 32MHz) × 64

With the prescaler set to 1:64 and the period register loaded to 0xFF the max period I'm figuring is 2.048 milli seconds. If I'm correct in all of this, I will need a 2 Mhz clock speed just to achieve this.

It's my understanding that a standard servo needs one pulse of every 20 ms, so I was just going to vary the duty cycle on a 20ms period to achieve the 1ms-2ms pulse widths needed to position the servo.

I'm doing a good bit of calculation and displaying, so I'm pretty set on keeping my clock speed.

any suggestions for a work around?
 
PWM alone is useless for controlling servos, it's much too coarse - either use software routines, or interrupts called from the PWM module (reprogram the PWM every half cycle) - it's ben discussed onnhere many times.
 
Thanks for the reply and the advice on the PWM module. I did a search but I didn't find much on my specific issue.

I'm not fully understanding what you mean by "interrupts called from the PWM module (reprogram the PWM every half cycle)." It was my undertanding that the PWM module couldn't generate interrupts.

I looked into using the on board 16 bit timer. At 32Mhz that means 1 tick every 0.0003125 ms. With the 1:8 prescaler on the timer (unfortunately, no post-scaler), that gives me 1 increment every 0.00025 ms. Once the 16 bit timer overflows from that I'm left with 16.38375 ms between overflow interrupts. Given a 1 ms minimum signal pulse, I'm left with a refresh rate of 57.52 Hz (1000ms/17.38375ms). I'm reading that most servos expect a 50hz refresh rate. I'm wondering if I could get away with this.

If that would work, I could calculate my required position, translate that into a delay time, and then in my ISR I could set the signal pin, call the delay based on the position requirements, clear the signal pin and interrupt flag, and exit the ISR. If that makes sense I'll give it a try. This way my would only have to spend a maximum of ~11% or so worrying about controlling the servo(s) and the rest of the time just doing it's regular thing.
 
Timer 2 sets the TMR2IF interrupt flag each time there is a PR2 period match. That's what you use for the PWM interrupt.

Setup the PWM module with a much smaller PWM period and link several of these smaller PWM period "frames" together using an interrupt helper to form the much longer 20-msec servo period.

With a 32-MHz clock (8 cycles per microsecond), you could easily get 2000 steps of resolution between 1 and 2 msecs (500-nsec steps) using something like the following interrupt "helper". Please note that the Servo duty cycle value in the example below is set for the Servo center position (1500-usecs).
Code:
 unsigned char frame = 1;        // pwm "frame" counter
 unsigned char dutycycle = 0;    // isr work variable
 unsigned int servo = 3000;      // 0..40000, 500-nsec duty cycle steps
Code:
/* 
 *  setup PWM module for a 125-usec period (pr2 = 250, prescale 4)
 *
 *  © 2010, Michael McLaren, Micro Application Consultants
 */
 void interrupt()                // 125 usec (1000 cycle) interrupts
  { pir1.TMR2IF = 0;             // clear TMR2 interrupt flag
    if(--frame == 0)             // if end of 20-msec Servo period
    { dutycycle = servo;         // refresh duty cycle work variable
      frame = 160;               // reset 20-msec "frame" counter
    }
   /*
    *  set duty cycle for next 125-usec PWM period "frame"
    */
    if(dutycycle > 250)          // if duty cycle > 125-usecs then
      ccpr1l = 250;              // do a 100% duty cycle frame
    else                         // else
      ccpr1l = dutycycle;        // do a 0..100% duty cycle frame
    dutycycle -= ccpr1l;         // adjust dutycycle
  }
 
Last edited:
Thanks the the help guys.

I'm still not sure that I totally understand the concept, but I'm going to try and use my VERY limited knowledge of C to walk through the example. My big problem here is that I don't understand the program flow of what you just gave me.

It looks like you are setting the timer up to interrupt every 125 micro seconds. If frame, decremented, equals 0, set the duty cycle variable equal to the servo variable value. If duty cycle is greater than 250, set ccpr1l to 250, else set it equal to duty cycle and make duty cycle equal duty cycle - ccpr1l. Is the duty cycle just setting to 0 whenever the frame is not 0?
And I'm not seeing how it translates into timing. I do appreciate it though. Pardon my ignorance of C.

Does anyone think that my idea that I outlined in post #3 will work?
 
Last edited:
You need 160 of the 125-usec PWM period "frames" to build a 20-msec Servo period.

I'm not sure how to describe the method any better. You get the TMR2 interrupt at the beginning of each 125-usec PWM period after the PR2 period match. The value you stuff into the CCPR1L duty cycle register is loaded into the PWM module at the end of the current PWM period and used as the duty cycle value for the next PWM period (CCPR1L is double buffered). So we're always setting up the duty cycle for the next 125-usec PWM period.

The PWM module uses a TMR2 prescaler of 4 which produces 0.5-usec TMR2 "ticks". Then we setup PR2 to 250 (minus 1) for 125-usec interrupts.



If you write the code in assembler (for 16F1934) you end up with about 2.3% ISR overhead (23 cycles out of every 1000 cycle interval for 159 interrupts and 28 cycles for the end-of-period interrupt).

Code:
;
;  void interrupt()             // 125-usec (1000 cycle) interrupts
;  { pir1.TMR2IF = 0;           // clear TMR2 interrupt flag
;    if(--frame == 0)           // if 20-msec servo period
;    { duty = servo;            // refresh duty cycle work variable
;      frame = 160;             // reset 20-msec frame counter
;    }
;    if(duty > 250)             // if duty cycle > 125 usecs
;      ccpr1l = 250;            // do a 100% duty cycle frame
;    else                       // else
;      ccpr1l = duty;           // do a 0..100% duty cycle frame
;    duty -= ccpr1l;            // adjust duty cycle variable
;  }
;
        org     0x004
        radix   dec
v_int
        banksel PIR1            ; bank 0                       |B0
        bcf     PIR1,TMR2IF     ; clear TMR2 interrupt flag    |B0
        decfsz  frame,F         ; 20-msec interval?            |B0
        bra     prep            ; no, branch, else             |B0
        movf    ServoLo,W       ; refresh "duty" variable      |B0
        movwf   DutyLo          ;                              |B0
        movf    ServoHi,W       ;                              |B0
        movwf   DutyHi          ;                              |B0
        movlw   160             ; reset "frame" counter        |B0
        movwf   frame           ;                              |B0
prep
        movlw   250             ;                              |B0
        subwf   DutyLo,W        ;                              |B0
        movlw   0               ;                              |B0
        subwfb  DutyHi,W        ;                              |B0
        movlw   250             ; assume 100% duty cycle       |B0
        skpc                    ; skip if duty >= 250          |B0
        movf    DutyLo,W        ; else use "duty"              |B0
        banksel CCPR1L          ; bank 5                       |B5
        movwf   CCPR1L          ; duty cycle for next period   |B5
        banksel DutyLo          ; bank 0                       |B0
        subwf   DutyLo,F        ;                              |B0
        movlw   0               ;                              |B0
        subwfb  DutyHi,F        ;                              |B0
        retfie                  ;                              |B0
;
 

Attachments

  • pwm frames.png
    3.6 KB · Views: 498
Last edited:
Thanks for that. I'm going to study it a bit more and I think I'll have it. The ASM code is something that I can definately follow.

For what it's worth, I fouled up in post #3 because I forgot that timers are driven by FOSC/4, so its 8 million timer ticks per second (with 0 prescale and 0 postscale) instead of 32million, which yields a max time to overflow of 65.535 milli seconds. But anyway, I don't want to try and re-invent the wheel when I don't understand the wheel and I have some one going out of their way to build me a wheel.
 
Last edited:
There's a few other methods you can use with that PIC. The CCP1 module has a "compare" mode that toggles the output on compare register match. You could use that mode with Timer 1 prescaler = 8 for 1-usec timer 'ticks' and then just set the 16-bit CCPR1 "compare" register pair to the next compare value in the ISR. Depending on the output being on or off, you'll add either the servo on time or servo off time to the value in CCPR1. This method, like the PWM method, produces a zero jitter servo pulse output.

Code:
void interrupt()               //
{ pir1.CCP1IF = 0;             // clear CCP1 interrupt flag
  CCPR1H += 0x80;              // prevent false trigger
  if(portc.CCP1)               // if CCP1 output "on"
    CCPR1 += servo;            // add servo "on" time
  else                         // else
    CCPR1 += (20000 - servo);  // add servo "off" time
  CCPR1H += 0x80;              // prevent false trigger
}
 
Last edited:
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…