Program PWM 16F876
http://www.winpicprog.co.uk/pic_tutorial8.htm
PIC Tutorial Eight - Using the PWM hardware
This tutorial is a little different, it's based on the 16F876 processor 2 board, but is designed specifically as a basic framework for a small differential drive robot, this means simply that it has two motors, one for left and one for right, with steering controlled in the same way as a tank. There seems a lot of confusion about how to use the hardware PWM, hence this simple tutorial. I've used the 16F876 as it has two PWM channels, the 16F628 only has one, the principals are exactly the same though, and the code could very simply be moved to the 16F628 if you only wanted a single PWM channel.
The PWM hardware has up to 10 bit resolution, which means you can have 1024 different steps from zero to full power, for our purposes this is a little excessive, and I've decided on 128 steps forward (0-127), and 128 steps in reverse (128-255), using a single byte for the speed and direction, with the highest bit signifying reverse.
It's actually very easy to use, once you've set the PWM up all you need to do is write to the CCPR1L and CCPR2L registers to set the speed, the listed routine uses an initialise subroutine which sets everything up, then subroutines to set the left and right motor speeds.
The initialise subroutine sets various registers, they are commented in the code, but I'll explain them here as well:
- First we turn off the analogue to digital converters for PortA, they default to analogue, so it's good practice to set them as digital I/O if they are not being used, if we need them later we can turn them back on (or simply remove the code which turns them off).
- Secondly we set all the pins of PortC as outputs, we'll be using six of the pins, pins 1 and 2 are the PWM outputs, and pins 0, 3, 4 and 5 will be used for direction switching.
- Next we set the CCP1CON and CCP2CON registers to operate as PWM, CCP1 and CCP2 can operate in various modes, so we need to specifically set them as PWM.
- Then we set the PR2 register, this is a step which often causes confusion, it basically sets the value of a comparison register which the actual PWM value will be compared against, in this case we set it to 126 which means the highest PWM value will be 126, if the PWM is 127 the comparator will never reach that value and the output will stay permanently high - just as we need for full power!. If the PWM value is zero, the comparator will always equal that value as it starts, so the output will remain permanently low - again, just as we need for zero power.
- The next step is to set T2CON, this sets the frequency of the PWM, as it's derived from the 20MHz system clock it runs too at too high a frequency, there are two possibilities here - setting the prescaler divides the frequency before the PWM section, and the postscaler afterwards. For this example we set the prescaler to divide by 16, this gives us a PWM frequency of 2500Hz.
- The next two lines set both PWM channels to zero, so both motors are off when it starts up.
- The last line actually starts the PWM system by turning Timer2 on, once this line runs the PWM is independent of the rest of the code, we can do pretty well whatever we like (unless we alter the register settings) and the PWM will carry on running regardless.
The main program itself is just a demonstration of how to use the PWM subroutines, it simply sets four different PWM and direction settings with 5 second delays in between them. It should be pretty self evident how to use it from your own programming. I've included various delay routines, including a new one called 'Delay100W', this delays 100mS multiplied by the value in W when the routine is called - in this example we load W with 50 to give a 5 second delay.
; 16F876 PWM example code ; ; Device 16F876 LIST P=16F876, W=2, X=ON, R=DEC #INCLUDE P16F876.INC __CONFIG 0x393A cblock 0x20 ;start of general purpose registers count ;used in delay routine count1 ;used in delay routine counta ;used in delay routine countb ;used in delay routine temp ;temp storage endc RL Equ 0x00 ;pin for left motor reverse FL Equ 0x03 ;pin for left motor forward RR Equ 0x04 ;pin for right motor reverse FR Equ 0x05 ;pin for right motor forward ;pins 1 and 2 are the 2 PWM channels ORG 0x0000 NOP ;for bootloader compatibility NOP NOP GOTO START ORG 0x0010 START CALL Initialise MainLoop: MOVLW d'64' CALL SpeedL ;both half speed forwards CALL SpeedR CALL Long_Delay MOVLW d'64' CALL SpeedL ;left half speed forwards MOVLW d'192' CALL SpeedR ;right half speed reverse CALL Long_Delay MOVLW d'10' CALL SpeedL ;slow speed forwards MOVLW d'228' CALL SpeedR ;fast speed reverse CALL Long_Delay MOVLW d'228' CALL SpeedL ;fast speed reverse MOVLW d'10' CALL SpeedR ;slow speed forwards CALL Long_Delay GOTO MainLoop Initialise: BANKSEL ADCON1 ;turn off A2D MOVLW 0x06 MOVWF ADCON1 BANKSEL PORTA BANKSEL TRISC MOVLW 0 ;set PORTC as all outputs MOVWF TRISC BANKSEL PORTC MOVF CCP1CON,W ;set CCP1 as PWM ANDLW 0xF0 IORLW 0x0C MOVWF CCP1CON MOVF CCP2CON,W ;set CCP2 as PWM ANDLW 0xF0 IORLW 0x0C MOVWF CCP2CON MOVLW 126 ;set highest PWM value BANKSEL PR2 ;over this (127) is permanently on MOVWF PR2 BANKSEL TMR2 MOVF T2CON,W ;set prescaler to 16 ANDLW 0xF8 ;PWM at 2500HZ IORLW 0x02 MOVWF T2CON MOVF T2CON,W ;set postscaler to 1 ANDLW 0x07 IORLW 0x00 MOVWF T2CON CLRF CCPR1L ;set PWM to zero CLRF CCPR2L BSF T2CON, TMR2ON ;and start the timer running RETURN SpeedL: ;use value in W to set speed (0-127) MOVWF temp BTFSC temp, 7 ;if more than 128 set speed in reverse CALL ReverseL ;so '1' is very slow forward BTFSS temp, 7 ;and '129' is very slow reverse CALL ForwardL ANDLW 0x7F MOVWF CCPR1L RETURN SpeedR: MOVWF temp BTFSC temp, 7 CALL ReverseR BTFSS temp, 7 CALL ForwardR ANDLW 0x7F MOVWF CCPR2L RETURN ReverseL: BSF PORTC, RL ;set pins for reverse BCF PORTC, FL RETURN ReverseR: BSF PORTC, RR BCF PORTC, FR RETURN ForwardL: BCF PORTC, RL ;set pins for forward BSF PORTC, FL RETURN ForwardR: BCF PORTC, RR BSF PORTC, FR RETURN ;Delay routines Long_Delay movlw d'50' ;delay 5 seconds call Delay100W return Delay100W movwf count ;delay W x 100mS d2 call Delay100 ;maximum delay 25.5 seconds decfsz count ,f goto d2 return 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 Delay10 movlw d'10' ;delay 10mS goto d0 Delay1 movlw d'1' ;delay 1mS goto d0 Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock) d0 movwf count1 d1 movlw 0xE7 movwf counta movlw 0x04 movwf countb Delay_0 decfsz counta, f goto $+2 decfsz countb, f goto Delay_0 decfsz count1 ,f goto d1 return ;end of Delay routines END