1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

PIC ASM PWM. Could I be doing this better?

Discussion in 'Microcontrollers' started by jakeselectronics, Apr 18, 2017.

  1. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    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 (text):

    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 (text):

    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              ;
            ANDLW   B'00110000'             ; Mask bits
            MOVWF   PWM_DC_L                ;
            RLF     PWM_DC_L, F             ;
            RLF     PWM_DC_L, W             ;
            ANDLW   B'11000000'             ; Mask bits
            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?
     
  2. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    9,820
    Likes:
    303
    Location:
    Brisbane Australia
    I don't see why you need the extract routine. Just keep the value that you sent to Replace_pwm_dc routine.

    Mike.
     
    • Like Like x 1
  3. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    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 (text):

    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  
     
     
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia

    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 (text):

    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....
     
  6. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    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 (text):

    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    REPLACE_PWM_DC          ; ADDED
            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    REPLACE_PWM_DC          ; ADDED
            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 (text):

    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
     
  7. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    9,820
    Likes:
    303
    Location:
    Brisbane Australia
    To write your ten bit value you could do,
    Code (text):

    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.
     
  8. atferrari

    atferrari Well-Known Member

    Joined:
    Oct 8, 2003
    Messages:
    2,751
    Likes:
    118
    Location:
    Buenos Aires - Argentina
    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.
     
  9. jpanhalt

    jpanhalt Well-Known Member Most Helpful Member

    Joined:
    Jun 21, 2006
    Messages:
    5,642
    Likes:
    475
    Location:
    Cleveland, OH, USA
    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 (asm):

    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
     
  10. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,615
    Likes:
    103
    Location:
    Michigan, USA
    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)...
     
  11. jpanhalt

    jpanhalt Well-Known Member Most Helpful Member

    Joined:
    Jun 21, 2006
    Messages:
    5,642
    Likes:
    475
    Location:
    Cleveland, OH, USA
    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
     
  12. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    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 (text):

    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
     
     
    • Agree Agree x 1
  13. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    @
    atferrari

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

    atferrari Well-Known Member

    Joined:
    Oct 8, 2003
    Messages:
    2,751
    Likes:
    118
    Location:
    Buenos Aires - Argentina
    I was afraid you were trying to vary it instead of the width of pulses. Good then.
     
  15. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,615
    Likes:
    103
    Location:
    Michigan, USA
    Perhaps slightly smaller & faster;
    Code (text):
    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
     
  16. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,615
    Likes:
    103
    Location:
    Michigan, USA
    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.
     
  17. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    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.
     
  18. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,615
    Likes:
    103
    Location:
    Michigan, USA
    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: Apr 23, 2017
  19. jakeselectronics

    jakeselectronics Member

    Joined:
    May 2, 2009
    Messages:
    401
    Likes:
    4
    Location:
    Macedon Ranges, Australia
    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.
     
  20. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,615
    Likes:
    103
    Location:
    Michigan, USA
    That's very cool... Have fun...
     

Share This Page