# PIC ASM PWM. Could I be doing this better?

Status
Not open for further replies.

#### jakeselectronics

##### Member
So my current project uses PWM to control the speed of a water pump.
I want to get your thoughts on this part of the code. The part that manages the INC and DEC of the PWM DC.
I just feel it's not the best way to be going about it.

Basically I needed a way to Increment, Decrement & Display the PWM.

An online calculator told me that if I am running a 4Mhz clock, and I write (249) 0b11111001 to PR2, and asign TM2's prescaler to 1, I will have a PWM with a frequency of 4kHz and that to achieve 100% Duty, I write (D'1000') 11111010:xx00xxxx to CCPR1L:CCP1CON(5:4).
Sounds good! I like those round numbers!
So I can INC and DEC CCPR1L:CCP1CON(5:4) bit by bit and get 0.1 bits of revolution out of my PWM.

I also wanted to be able to assign a minimum PWM Duty Cycle which is user configured. (So the water does stall when being automatically controlled)

Here's how I'm doing it:

Here are my defined RAM locations:
Code:
PWM_MIN_L               ; User adjustable, stores a pre-defined "minimum" PWM duty cycle
PWM_MIN_H               ;

PWM_DC_L                ; PWM Duty Cycle L    Mirrors CCPR1L:CCP1CON(5:4) but is right justified for easy inc or dec.
PWM_DC_H                ; PWM Duty Cycle H

So the only way I could come up to easily increment and decrement the PWM Duty Cycle was to 'extract' the value from CCPR1L:CCP1CON(5:4), place it some RAM GPR's, do the math (inc/dec, check overflow, etc...) and 'replace' CCPR1L:CCP1CON(5:4) with the newly adjusted figure.
There is also a check in the INC routine that it doesn't increment past 1000, and a check in the DEC routine that it doesn't decrement past the pre-specified PWM_MIN
See below
Code:
INC_PWM
CALL    EXTRACT_PWM_DC
; Check if already 1000 (100%)
MOVLW   B'11101000'             ; Lower 8 bits of D'1000'
SUBWF   PWM_DC_L, W
BTFSS   STATUS, Z
GOTO    $+5 MOVLW B'00000011' ; Upper 8 bits of D'1000' SUBWF PWM_DC_H, W BTFSC STATUS, Z RETURN ; Already 1000 (MAX), do not increment further ; Not 1000, increment INCFSZ PWM_DC_L, F GOTO$+2
; Overflow, increment upper register
INCFSZ  PWM_DC_H, F
; Done
CALL    REPLACE_PWM_DC
RETURN

DEC_PWM
CALL    EXTRACT_PWM_DC          ;
; Check if already at MIN
MOVF    PWM_MIN_L, W            ; Lower 8 bits of PWM_MIN
SUBWF   PWM_DC_L, W             ;
BTFSS   STATUS, Z               ;
GOTO    $+5 ; MOVF PWM_MIN_H, W ; Upper 8 bits of PWM_MIN SUBWF PWM_DC_H, W ; BTFSC STATUS, Z ; RETURN ; Already at 'x' (MIN), do not decrement further ; Not at MIN, decrement DECF PWM_DC_L, F ; COMF PWM_DC_L, W ; BTFSS STATUS, Z ; GOTO$+5                     ;
; Overflow, decrement upper register
MOVF    PWM_DC_H, F             ;
BTFSC   STATUS, Z               ;
RETURN
DECF    PWM_DC_H, F             ;
; Done
CALL    REPLACE_PWM_DC          ;
RETURN

EXTRACT_PWM_DC
; Extract PWM Duty Cycle from CCPR1L:CCP1CON into PWM_DC_H:PWM_DC_L
; CCPR1L:CCP1CON holds bits 0000 0000:xx00 xxxx.
; PWM_DC_H:PWM_DC_L will then hold xxxx xx00:0000 0000
MOVF    CCP1CON, W              ;
MOVWF   PWM_DC_L                ;
RLF     PWM_DC_L, F             ;
RLF     PWM_DC_L, W             ;
MOVWF   PWM_DC_L                ;

MOVF    CCPR1L, W               ;
MOVWF   PWM_DC_H                ;
BCF     STATUS, C               ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RETURN

REPLACE_PWM_DC
; Replace PWM Duty Cycle from PWM_DC_H:PWM_DC_L into CCPR1L:CCP1CON
; PWM_DC_H:PWM_DC_L holds xxxx xx00:0000 0000
; CCPR1L:CCP1CON will now hold bits 0000 0000:xx00 xxxx.
RLF     PWM_DC_L, F             ; Rotate into C
RLF     PWM_DC_H, F             ; Rotate out of C
RLF     PWM_DC_L, F             ; Rotate into C
RLF     PWM_DC_H, F             ; Rotate out of C
RLF     PWM_DC_L, F             ; Rotate into C
RLF     PWM_DC_H, F             ; Rotate out of C
RLF     PWM_DC_L, F             ; Rotate into C
RLF     PWM_DC_H, F             ; Rotate out of C
RLF     PWM_DC_L, F             ; Rotate into C
RLF     PWM_DC_H, F             ; Rotate out of C
RLF     PWM_DC_L, F             ; Rotate into C
RLF     PWM_DC_H, F             ; Rotate out of C

MOVF    PWM_DC_H, W             ;
MOVWF   CCPR1L                  ;

MOVF    CCP1CON, W              ;
ANDLW   B'00001111'             ; Ensure lower nibble of CCP1CON remains unchanged and move bits 5&4 into place

RRF     PWM_DC_L, F             ;
RRF     PWM_DC_L, F             ; Move the 2 LSB's into bits <5:4>
IORWF   PWM_DC_L, W             ;
MOVWF   CCP1CON                 ;
RETURN
And with the 10-bit PWM right justified in my PWM_DC_H : PWM_DC_L RAM GPR's it makes it easy to do a BIN2BCD conversion for displaying the PWM.

This is working flawlessly, but can it be done better?

#### Pommie

##### Well-Known Member
I don't see why you need the extract routine. Just keep the value that you sent to Replace_pwm_dc routine.

Mike.

#### jakeselectronics

##### Member
Hm. Good point.
I think I was using that to display the PWM. I would pull it from the CCPR1L:CCP1CON(5:4) registers.
But maybe I can ditch that routine and when I want to display the PWM I can just use whats in my PWM_DC_L&PWM_DC_H GPR's....
I'll go through the code and see what is relying of it.

Oh and I think I shrunk the REPLACE_PWM_DC routine by Rotating the other direction.... Just tested it. Saved 6 instructions. 21 down to 15.
Code:
REPLACE_PWM_DC
; Replace PWM Duty Cycle from PWM_DC_H:PWM_DC_L into CCPR1L:CCP1CON
; PWM_DC_H:PWM_DC_L holds xxxx xx00:0000 0000
; CCPR1L:CCP1CON will now hold bits 0000 0000:xx00 xxxx.
BCF     STATUS, C
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;
RRF     PWM_DC_L, F             ;
RRF     PWM_DC_H, F             ;

MOVF    PWM_DC_L, W             ;
MOVWF   CCPR1L                  ;

MOVF    CCP1CON, W              ;
ANDLW   B'00001111'             ; Ensure lower nibble of CCP1CON remains unchanged and move bits 5&4 into place

RRF     PWM_DC_H, F             ;
RRF     PWM_DC_H, F             ; Move the 2 LSB's into bits <5:4>
IORWF   PWM_DC_H, W             ;
MOVWF   CCP1CON                 ;
RETURN

#### jakeselectronics

##### Member
I don't see why you need the extract routine. Just keep the value that you sent to Replace_pwm_dc routine.

Mike.
I tried stright up deleting it and the few things that call it. Program flipped out.
Then on closer look, I have a few routines that write straight to CCPR1L:CCP1CON(5:4).
For instance to turn motor on (PWM 100%) I do this.

Code:
PUMP_START
; First, set Pump Running flag bit
BSF     FLAGS, PUMPRUNNING
; Drive pump at 100% duty cycle to get it going
MOVLW   B'11111010'             ;
MOVWF   CCPR1L                  ; 100%
MOVLW   B'00001100'             ;
MOVWF   CCP1CON                 ;

CALL    DISPLAY_PWM_DC          ;
RETURN                          ;

PUMP_STOP
; First, set Pump Running flag bit
BCF     FLAGS, PUMPRUNNING
; Drive pump to 0% duty cycle to stop the pump
MOVLW   B'00000000'             ;
MOVWF   CCPR1L                  ; 0%
MOVLW   B'00001100'             ;
MOVWF   CCP1CON                 ;
CALL    DISPLAY_PWM_DC          ;

RETURN
So when it comes to INC'ing or DEC'ing, I need to know where CCPR1L:CCP1CON(5:4) is at because simply INC'ing or DEC'ing my PWM_DC_L&PWM_DC_H GPR's and writing this back to the PWM registers will be erroneous.
I guess when I write straight to CCPR1L:CCP1CON(5:4), I could update my PWM_DC_L&PWM_DC_H GPR's at the same time....

#### jakeselectronics

##### Member
I don't see why you need the extract routine. Just keep the value that you sent to Replace_pwm_dc routine.

Mike.
Deleted.

I've managed to delete it.
In my pump start and stop routines I write to PWM_DC_H & PWM_DC_L and CALL REPLACE_PWM_DC

Code:
PUMP_START
; First, set Pump Running flag bit
BSF     FLAGS, PUMPRUNNING
; Drive pump at 100% duty cycle to get it going
MOVLW   B'00000011'             ;
MOVWF   PWM_DC_H                ; 100%
MOVLW   B'11101000'             ;
MOVWF   PWM_DC_L                ;
CALL    DISPLAY_PWM_DC          ;
RETURN                          ;

PUMP_STOP
; First, set Pump Running flag bit
BCF     FLAGS, PUMPRUNNING
; Drive pump to 0% duty cycle to stop the pump
CLRF    PWM_DC_H                ; 0%
CLRF    PWM_DC_L                ;
CALL    DISPLAY_PWM_DC          ;
RETURN
I did however need to add some code to prevent problems when maniplating the data in the REPLACE_PWM_DC routine.
I created 2 new GPR's as buffers.
See first 4 lines of code.
Code:
REPLACE_PWM_DC
; Replace PWM Duty Cycle from PWM_DC_H:PWM_DC_L into CCPR1L:CCP1CON
; PWM_DC_H:PWM_DC_L holds xxxx xx00:0000 0000
; CCPR1L:CCP1CON will now hold bits 0000 0000:xx00 xxxx.
MOVF    PWM_DC_H, W             ;
MOVWF   PWM_DC_H_BUF            ; Save in buffer registers so data can be manipulated
MOVF    PWM_DC_L, W             ;
MOVWF   PWM_DC_L_BUF            ;

BCF     STATUS, C
RRF     PWM_DC_H_BUF, F         ;
RRF     PWM_DC_L_BUF, F         ;
RRF     PWM_DC_H_BUF, F         ;
RRF     PWM_DC_L_BUF, F         ;
RRF     PWM_DC_H_BUF, F         ;

MOVF    PWM_DC_L_BUF, W         ;
MOVWF   CCPR1L                  ;

MOVF    CCP1CON, W              ;
ANDLW   B'00001111'             ; Ensure lower nibble of CCP1CON remains unchanged and move bits 5&4 into place

RRF     PWM_DC_H_BUF, F         ;
RRF     PWM_DC_H_BUF, F         ; Move the 2 LSB's into bits <5:4>
IORWF   PWM_DC_H_BUF, W         ;
MOVWF   CCP1CON                 ;
RETURN
Thanks for the tips

#### Pommie

##### Well-Known Member
To write your ten bit value you could do,
Code:
WritePWM    MOVF    PWM_DC_H, W             ;
MOVWF   PWM_DC_H_BUF            ; Save in buffer registers so data can be manipulated
MOVF    PWM_DC_L, W             ;
MOVWF   PWM_DC_L_BUF            ;
MOVLW    B'00001111'
ANDWF    CCP1CON,F                ;clear LSBs
SWAPF    PWM_DC_L_BUF,W            ;move bits 0,1 to 4,5
ANDLW    B'00110000'
IORLW    CCP1CON,F
BCF     STATUS, C
RRF     PWM_DC_H_BUF, F         ;
RRF     PWM_DC_L_BUF, F         ;
BCF        STATUS,C                ;    I think this is needed
RRF     PWM_DC_H_BUF, F         ;
MOVF    PWM_DC_L_BUF, W         ;
MOVWF   CCPR1L                  ;
RETURN
I didn't understand why you were shifting right 3 times instead of two or why you didn't clear the carry on each shift.

Mike.

#### atferrari

##### Well-Known Member
Hola Jake,

Out of practice I cannot verify this: are you maintaining the frequency fixed in the same value, right? One year doing nothing is too much. Sorry.

#### jpanhalt

##### Well-Known Member
Hi Jake, This may be too little too late.

One instruction you might want to consider using is "swapf." It does not affect the STATUS bits, can be done on WREG, and may save a few steps. For example,
Code:
swapf     CCP1CON,w
incf      WREG,w
btfss     WREG,2   ;for example
etc.
would allow you to detect a "carry" from incrementing the bits <4,5> in CCP1CON. Once you know that, another swapf on WREG will get it almost ready to use movwf CCP1CON to change that register.

There are some chip-specific details about CCP1CON that one may need to know before writing specific code. For example, bits <6,7> in the 16F628 are not writable and are read as zero. So, you could determine whether there was a carry while in WREG, then swapf and movwf without fixing bit 6.

What chip are you using?
How will the increment and decrement be entered by the user?
You may need some error checking for when a register wraps from "0" to 255.

John

#### Mike - K8LH

##### Well-Known Member
If he's using a 16F628A, he doesn't have direct access to WREG... That came with the "enhanced mid-range devices" (and 18F devices)...

#### jpanhalt

##### Well-Known Member
Good point. I have become spoiled. He could still use swapf on a shadow and maybe save a few rotates.

Thanks for the correction.

John

#### jakeselectronics

##### Member
Mike I really need to get some 18f devices to play with.. I will order some.

and to pommie, I'm away OS at the minute so haven't tested your code yet, but I've analysed it.
I think it needs altering.
The first part that deals with CCP1CON looks good.
But the section that Rotates and moves the upper 8 bits of the 10 bit number and moves to CCPR1L looks like its missing a rotate.
I think its just a typo and the MOVF should've been a RRL.

Also, I think the BCF carrys can be done away with.
As only the contents of PWM_DC_L_BUF will be moved to CCPR1L, does it really matter what gets shifted into the PWM_DC_H_BUF register?
Fair enough if the data was in a non 'buffer' style regsister whe the data must be maintained...

Code:
WRITE_PWM
; Replace PWM Duty Cycle from PWM_DC_H:PWM_DC_L into CCPR1L:CCP1CON<5:4> (10 bit)
; PWM_DC_H:PWM_DC_L holds           xxxx xx00:0000 0000
; CCPR1L:CCP1CON will now hold bits 0000 0000:xx00 xxxx.
MOVF    PWM_DC_H, W             ;
MOVWF   PWM_DC_H_BUF            ; Save in buffer registers so data can be manipulated
MOVF    PWM_DC_L, W             ;
MOVWF   PWM_DC_L_BUF            ;

MOVLW   B'00001111'             ;
ANDWF   CCP1CON, F              ; Clear bits 5&4 (7&6 unused)
SWAPF   PWM_DC_L_BUF, W         ; Bits 1&0 now in 5&4 position
ANDLW   B'00110000'             ; Clear all but bits 5&4
IORLW   CCP1CON, F              ; Amend CCP1CON with new bits 5&4's state

BCF     STATUS, C               ;       Don't need ????
RRF     PWM_DC_H_BUF, F         ;
RRF     PWM_DC_L_BUF, F         ;
BCF     STATUS, C               ;       Don't need ????
RRF     PWM_DC_H_BUF, F         ;

MOVF    PWM_DC_L_BUF, W         ;       Meant to be a RRL instruction instead of MOVF????
MOVWF   CCPR1L                  ;
RETURN

#### jakeselectronics

##### Member
@
atferrari

No I am maintaining a fixed frequency. Keeping it simple for this first time using PWM.

#### atferrari

##### Well-Known Member
@
atferrari

No I am maintaining a fixed frequency. Keeping it simple for this first time using PWM.
I was afraid you were trying to vary it instead of the width of pulses. Good then.

#### Mike - K8LH

##### Well-Known Member
Perhaps slightly smaller & faster;
Code:
WRITE_PWM
;
; Replace PWM Duty Cycle from PWM_DC_H:PWM_DC_L into CCPR1L:CCP1CON<5:4> (10 bit)
; PWM_DC_H:PWM_DC_L holds           xxxx xx00:0000 0000
; CCPR1L:CCP1CON will now hold bits 0000 0000:xx00 xxxx.
;
SWAPF   PWM_DC_L, W             ; Bits 1&0 now in 5&4 position
XORWF   CCP1CON, W              ; Note CCP1CON differences
ANDLW   B'00110000'             ; Only bits 5&4, please
XORWF   CCP1CON, F              ; Amend CCP1CON with new bits 5&4's state

RRF     PWM_DC_H, W             ; CCPR1L = PWM_DC >> 2
MOVWF   PWM_DC_H_BUF            ;  "
RRF     PWM_DC_L, W             ;  "
MOVWF   PWM_DC_L_BUF            ;  "
RRF     PWM_DC_H_BUF, W         ;  "
RRF     PWM_DC_L_BUF, W         ;  "
MOVWF   CCPR1L                  ;  "
RETURN

#### Mike - K8LH

##### Well-Known Member
BTW, while the 18F chips are nice, you might take a peek at some exciting new offerings in the 16F15000 and 16F18000 series. Unfortunately, all these newer devices require a PICKIT3 programmer and MPLABX.

#### jakeselectronics

##### Member
Perhaps slightly smaller & faster;
Code:
WRITE_PWM
;
; Replace PWM Duty Cycle from PWM_DC_H:PWM_DC_L into CCPR1L:CCP1CON<5:4> (10 bit)
; PWM_DC_H:PWM_DC_L holds           xxxx xx00:0000 0000
; CCPR1L:CCP1CON will now hold bits 0000 0000:xx00 xxxx.
;
SWAPF   PWM_DC_L, W             ; Bits 1&0 now in 5&4 position
XORWF   CCP1CON, W              ; Note CCP1CON differences
ANDLW   B'00110000'             ; Only bits 5&4, please
XORWF   CCP1CON, F              ; Amend CCP1CON with new bits 5&4's state

RRF     PWM_DC_H, W             ; CCPR1L = PWM_DC >> 2
MOVWF   PWM_DC_H_BUF            ;  "
RRF     PWM_DC_L, W             ;  "
MOVWF   PWM_DC_L_BUF            ;  "
RRF     PWM_DC_H_BUF, W         ;  "
RRF     PWM_DC_L_BUF, W         ;  "
MOVWF   CCPR1L                  ;  "
RETURN
Wow mike very nice. I couldn't work out the first bit by just looking at it.
I actually got a pen and paper out, made up some dummy values for ccp1con and pwm_dc_l and wrote out the register results instruction by instruction.

And combining the saving to buffer and rotating instructions for the second part condensed well.
Great thinking.

#### Mike - K8LH

##### Well-Known Member
Jake, please tell us more about your Water Pump Speed Control project... Can we assume you're using the 4x16 LCD display you mentioned in another thread? What are the controls and functions? Does it use a rotary encoder with a switch on the shaft? Those are kind of fun...

Cheerful regards, Mike

Last edited:

#### jakeselectronics

##### Member
Jake, please tell us more about your Water Pump Speed Control project... Can we assume you're using the 4x16 LCD display you mentioned in another thread? What are the controls and functions? Does it use a rotary encoder with a switch on the shaft? Those are kind of fun...

Cheerful regards, Mike
I'll post the whole code in a week or so when I am home. I would love to get your feedback on various aspects of it. Such as how I am comparing values, and my crude PID.
A mate has been running a still. It's a very small commercially available unit from the brew shops, but it is pretty labour intensive. The condenser temperature is managed via water flow. Coolant water.
You pretty much have to sit there for 5 hours regulating the flow of water with a needle valve. I laughed and said i think i could automate that.
So in my project, the water pump is pumping the coolant water. There is a temperature sensor inline with the coolant outflow, the software reads this, compares it with the value that has been set and adjusts the speed of the pump accordingly to attempt to maintain a consistent coolant outflow temperature. Constantly reading temp and adjusting pump speed if need be.
He brought the still over the other day and we hooked up sensors and the water pump and it ran perfectly.
Looking forward to sharing how it controls the flow and getting your feedback.
Stay tuned.

#### Mike - K8LH

##### Well-Known Member
That's very cool... Have fun...

Status
Not open for further replies.