Wednesday, December 12, 2012

PIC 16F628 PWM setup

As the primary detector sensors I wanted to use IR detectors as I had in previous robots.  Key to this is the generation of a 38khz square wave (50% duty cycle) which the IR detectors are tuned to.  In other robots I had used the circuit described in David Cook's Intermediate Robot Building book.  There are others based on 555 timer chips to.  However the pic16f628 has a PWM module and can output a PWM signal on one pin (RB3).  This meant one less extra IC chip and associated parts (resistors/capacitors).  However the setting up of the PWM was a little more esoteric.

I recommend reading the Microchip "PICmicro Mid-Range MCU Family Reference Manual".  The section on PWM makes the setup of PWM a lot clearer as there are some worked examples of the formulas used.  Without these I would have been pretty lost in time conversion hell.

Setting up PWM requires two main steps.  First is setting the frequency of the PWM.  The second is setting the duty cycle of the PWM.  Both the pic16f628 reference manual the the Mid-Range reference manual describe the formulas to do this.  Where things get interesting is that the PWM duty cycle is 10 bits and thus spread across two registers (CCPR1L and CCP1CON<5:4>).  Below is a worked example showing this.

First I set the PWM frequency to 38khz (ish).  For the TMR2 prescale I looked at the examples in the reference manual as a guide of what to set.  Like most of technology it's a case of seeing what someone else did and changing it to fit your specific case.  Via the equation with a 4mhz clock and a TMR2 prescale of 1

PWM period = (PR2 +1) * 4 * TOSC * TMR2 prescale
1/38kHz = (PR2 +1) * 4 * 1/4MHz * 1
26us = (PR2+1) * 4 * 0.25
26us = (PR2+1) * 1
25 = PR2

and the code with comments

; set pwm period
; 0x19 == 25 decimal.  So via equation
; 25+1 * 4 * 0.25 * 1 = 26
; 1/26 == 38khz ish
    banksel PR2
    movlw   0x19
    movwf   PR2

Then I set the duty cycle to 50%.  This means that the frequency will be twice that of the pwm.  Ie for each PWM period the duty cycle has to change twice (on, then off).  Thus the value to be put into the registers will be 0x34 (decimal 52 - 26*2).  This is split across the two registers.  The easiest way I found to do this was write out 0x34 as binary

0011 0100

Now this needs to be 10 bits, so the left most bits will be '00'

0000 1101 00

So CCPR1L will be '0000 1101' (0x0d) and CCP1CON<5:4> will be '00' (CCP1CON = 0x0c).  Code and comments below

; set pwm duty cycle to 50%
; so need duty cycle to be twice pwm period - ie 52 = 0x34
; since Tosc is in both pwm and duty cycle equation they cancel out
; remember split across bits 5/4 of ccp1con and ccpr1l
; ie 0000 1101 00
;  0x   0    d  0
    banksel CCP1CON
    movlw   0x0c
    movwf   CCP1CON
    movlw   0x0d
    movwf   CCPR1L  ; duty cycle 0x34 - ie 52 decimal
; set tmr2 prescaler 
    banksel T2CON
    movlw   0x00
    movwf   T2CON
; turn on timer 2
    bsf T2CON, TMR2ON

On the first attempt I got the duty cycle numbers wrong and saw no frequency output.  Stumped I ending up measuring the voltage on pin RB3.  It was 5V.  So the voltage was at VDD which is 5V which means I had a duty cycle of over 100%.  Reworking the numbers I spotted my error and fixed things up.  With a duty cycle of 50% the voltage was half VDD as expected.

On a side note I used a Casio FX-100d calculator to do the number crunching.  It's over 20 years old (I had it in high school) and is still running on the original battery.  Not sure what battery technology it has but I'm assuming it's an arc reactor subtype.