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:

  1. 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).
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. The next two lines set both PWM channels to zero, so both motors are off when it starts up.
  7. 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