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
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
No comments:
Post a Comment