Pulse Width Modulator using PIC16F84a

Discussion in 'Embedded Systems and Microcontrollers' started by farscape, May 30, 2010.

  1. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    Hi,

    I'm trying to build a Pulse Width Modulator using a PIC16F84a.

    If possible, I want one of my inputs to increase the duty factor of the LED every time it is asserted and the other input to decrease the duty factor.

    I think I've got a decent schematic drawn up and a rough start on the code, but I'm having trouble coming up with an effecient way to program the inputs so that they continually increase or decrease the duty factor each time one is asserted.

    Any help would be appreciated! Thanks


    Code ( (Unknown Language)):
    1. ;    cpu equates (memory map)
    2. portB    equ    0x06        
    3. portA    equ    0x05
    4. duty    equ    0x0c
    5.  
    6.  
    7.     org    0x000
    8.  
    9. start    movlw    0x00        
    10. tris      portB        
    11. clrf      portB        
    12. movlw 0x1F        
    13. tris      portA        
    14. clrf      portA
    15.  
    16. test0   btfsc   portA, '0'
    17.            call    test1
    18.            call    upLED
    19.  
    20. test1    btfsc    portA, '1'
    21.             goto    test0
    22.             call    downLED
    23.  
    24.  
    25. upLED   movlw     d'64'
    26.             movwf     duty
    27.             bsf          portB, '0'
    28. light     decfsz     duty
    29.             goto     light
    30.             clrf     portB
    31.             movlw     d'255
    32.             movwf     duty
    33. blank     decfsz     duty
    34.              goto     blank
    35.              goto     upLED
     
  2. jordan22

    New Member

    May 30, 2010
    2
    0
    not sure yet how you did the whole duty cycle part so i don't know how to get the downLED part but i think this is what I meant about using a loop to make it repeat the process every 250ms

    correct me if I'm misunderstanding how this works.

    Code ( (Unknown Language)):
    1. ;    cpu equates (memory map)
    2. portB    equ    0x06        
    3. portA    equ    0x05
    4. duty    equ    0x0c
    5.  
    6.  
    7.     org    0x000
    8.  
    9. start    movlw    0x00        
    10. tris      portB        
    11. clrf      portB        
    12. movlw 0x1F        
    13. tris      portA        
    14. clrf      portA
    15.  
    16. loop     goto pause(250ms)
    17.           test0   btfsc   portA, '0'
    18.                call    test1
    19.                call    upLED
    20.           test1    btfsc    portA, '1'
    21.                goto    test0
    22.                call    downLED
    23.           goto loop
    24.  
    25. upLED   movlw     d'64'
    26.             movwf     duty
    27.             bsf          portB, '0'
    28. light     decfsz     duty
    29.             goto     light
    30.             clrf     portB
    31.             movlw     d'255
    32.             movwf     duty
    33. blank     decfsz     duty
    34.              goto     blank
    35.              goto     upLED
    36.              return
    37.  
    38. downLED ...
    39.              return
    40.  
    41. pause(25ms) ...
    42.              return
    43.  
    44. end
    45.  
    46.  
     
  3. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    For phase 2 of the project it would be useful to have a ADC on the PIC. Might be worth considering the 12F675. That has enough pins for what you need, an ADC and an internal oscillator. Should be cheaper than the 84a as well.
     
  4. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    Okay thanks. I'm not sure whether or not we have access to the 12F675 in our lab... Would the programming for that be much different than with the 84a?
     
  5. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    There aren't too many differences, RAM starts at a different location, there are a few extra things to do setting up the oscillator and the ADC. Best to check that the programming hardware supports it before you order one (it probably does).
     
  6. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    The 12F683 might be even better because it has PWM built in.
     
  7. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    Thanks Mark. Unfortunately, due to my poor planning, we (Jordan22 and I) need to have this done by Thursday so I think we may need to stick with the 84a... We'll be sure to plan this a little better in the future. Hopefully it wont be too much of a headache this way...

    I updated what I had for the code and added the pause sequence you suggested Jordan.

    In the new code, the inputs will be checked every .25 sec so were hoping we can increase the duty cycle every time the program sees that RA0 is asserted and alternatively, decrease it every time is sees that RA1 is asserted.
    -I'm having difficulty seeing how we can program this feature. The way it is right now, if RA0 is asserted it sends the LED to a 25% duty cycle. Is it possible to increase it to 50%, then 75% if RA0 is still asserted when the inputs are checked?

    Thanks

    Code ( (Unknown Language)):
    1. cpu equates (memory map)
    2.  
    3. portB    equ    0x06        
    4. portA    equ    0x05
    5. duty      equ    0x0c
    6. duty1    equ    0x0e
    7.  
    8. org    0x000
    9.  
    10. start    movlw    0x00        
    11. [INDENT]     tris    portB        
    12.     clrf    portB        
    13.     movlw    0x1F        
    14.     tris    portA      
    15.     clrf    portA
    16. [/INDENT]
    17. test0    call    pause
    18.            btfsc    portA, '0'
    19.             call    test1
    20.            call    upLED
    21.  
    22. test1    btfsc    portA, '1'
    23.            goto    test0
    24.            call    downLED
    25.  
    26.  
    27. upLED     bsf     portB, '0'
    28.               movlw   d'64'
    29.               movwf    duty
    30.              decfsz    duty
    31.              goto    upLED
    32.              bcf    portB, '0'
    33.              movlw    d'192'
    34.              movwf    duty
    35. dim       decfsz    duty
    36.              goto    dim
    37.              goto    upLED
    38.  
    39. pause    movlw    0xD3    ;pause is ~.25SEC
    40.             movwf    duty
    41.             movlw    0x31
    42.             movwf    duty1
    43. Delay_0 decfsz    d1, f
    44.              goto    $+2
    45.             decfsz    d2, f
    46.             goto    Delay_0
    47.             goto    $+1
    48.  
    49. return
    50.  
     
    Last edited: May 30, 2010
  8. upand_at_them

    Active Member

    May 15, 2010
    246
    29
    It's been so long since I messed with the F84, but I remember that the TRIS "command" is not the preferred way of doing it. It'll generate warnings, for one thing. The proper way is to BANKSEL TRISA and then write the value to TRISA/TRISB. If you don't want to use BANKSEL you can just set the RP bits in STATUS; BANKSEL just makes it easier.

    You also need to debounce the switches.

    Before you try to throw everything together, I would get each sub-step working:
    * Light an LED.
    * Read a switch with debounce, have it just toggle the LED: push on, push off.
    * Read a switch every .25 seconds (with debounce).
    * Pulse an LED when you press a switch.

    Then write the final program. And it would be best to write separate routines for things: UpLED, DownLED, .25s pause, switch debounce pause. You call then and they all have return statements.
     
  9. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    Can you post the whole code? I don't see the downLED subroutine.
    I think checking every 0.25 seconds means that debouncing is unnecessary.
     
  10. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    No need to debounce the switches if you're sampling them at intervals greater than 10-20 msecs.

    You might consider something like a simple isochronous loop. Use something like 256 pwm steps (8-bit resolution) and 50-usecs per step for an overall 12.8-msec period and 78.125-Hz refresh rate. Setup another timer within your 50-usec pwm "step" loop to detect 250-msec intervals and sample the switches and adjust the LED PWM duty cycle then. This would provide a 250-msec repeat rate when you press and hold the push button switches. Do you need to see a "cycle accurate" DelayCy() routine?

    Cheerful regards, Mike

    Code ( (Unknown Language)):
    1.  
    2. led     equ     0x28            ; led duty cycle, 0..255
    3. dcy     equ     0x29            ; duty cycle counter, 0..255
    4. tmrl    equ     0x2A            ; 250-msec switch timer
    5. tmrh    equ     0x2B            ;
    6. shadow  equ     0x2C            ; PORTB shadow reg'
    7.  
    8.         radix   dec
    9. ;
    10. ;
    11. ;
    12. Start
    13.         movlw   low(250000/50)  ; setup 250-msec timer            |B0
    14.         movwf   tmrl            ;                                 |B0
    15.         movlw   high(250000/50) ;                                 |B0
    16.         movwf   tmrh            ;                                 |B0
    17.         clrf    dcy             ; clear duty cycle counter        |B0
    18.         movlw   0x80            ;                                 |B0
    19.         movwf   led             ; led duty cycle = 128 (50%)      |B0
    20. ;
    21. ;  256 50-usec steps -> 12.8-msec period, 78.125-Hz refresh rate
    22. ;
    23. loop0
    24.         DelayCy(50*usecs-15)    ; maintain 50-usec "step" time    |B0
    25. loop1
    26.         clrf    shadow          ;                                 |B0
    27.         movf    led,W           ; led duty cycle, 0..255          |B0
    28.         subwf   dcy,W           ; C = (dcy >= led)                |B0
    29.         rlf     shadow,W        ; file = 0000000R (active lo)     |B0
    30. ;       xorlw   1               ; invert for active hi output     |B0
    31.         movwf   PORTB           ; update led                      |B0
    32.         incf    dcy,F           ; bump duty cycle counter         |B0 <---
    33.         movf    tmrl,W          ;                                 |B0
    34.         skpnz                   ;                                 |B0
    35.         decf    tmrh,F          ;                                 |B0
    36.         decf    tmrl,F          ;                                 |B0
    37.         movf    tmrl,W          ;                                 |B0
    38.         iorwf   tmrh,W          ; 250-msec interval?              |B0
    39.         skpz                    ; yes, skip, else                 |B0
    40.         goto    loop0           ; branch (loop)                   |B0
    41. ;
    42. ;  sample active hi "up" and "dn" switches and modify duty cycle
    43. ;
    44.         movlw   low(250000/50)  ; reset 250-msec timer            |B0
    45.         movwf   tmrl            ;                                 |B0
    46.         movlw   high(250000/50) ;                                 |B0
    47.         movwf   tmrh            ;                                 |B0
    48.         movf    led,W           ; duty cycle == 0?                |B0
    49.         skpz                    ; yes, skip, else                 |B0
    50.         decf    led,W           ; wreg = led - 1                  |B0
    51.         btfsc   PORTA,1         ; Dn sw pressed? no, skip, else   |B0
    52.         movwf   led             ; update duty cycle               |B0
    53.         incf    led,W           ; duty cycle == 255?              |B0
    54.         skpnz                   ; no, skip, else                  |B0
    55.         movf    led,W           ; wreg = 255                      |B0
    56.         btfsc   PORTA,0         ; Up sw pressed? no, skip, else   |B0
    57.         movwf   led             ; update duty cycle               |B0
    58.         DelayCy(50*usecs-30)    ; maintain 50-usec "step" time    |B0
    59.         goto    loop1           ;                                 |B0
    60. ;
     
    Last edited: May 31, 2010
  11. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    I didn't include the downLED loop because that is where I am kind of lost. I'm stumped on how to go about adjusting the duty cycle when additional inputs are asserted after an initial state is already in place.
    -I guess, my upLED code is probably totally wrong since it has no associated adjustment to an already existing duty cycle.
    -Ill keep trying to figure this part out


    MMcLaren, I'm trying to understand your code, but there are a few things I'm struggling with.
    I don't want to be a pain, but would you mind briefly walking me through what is happening in loop1?
    -Where does the adjustment in duty cycle take place?
    Is it here? "subwf dcy,W"
     
  12. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    Loop1/loop0 is supposed to count up or down "dcy" until it matches "led" which is the duty cycle set in the last part of the program which happens every quarter of a second.
    Some of it deals with updateing the 16 bit timer (trml and tmrh).
    I have a feeling there should be a decf dcy, or incf dcy somewhere.
     
  13. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    Is it possible to use a table to read off values for another register I create that records what level the duty should be at? I'm going to try to write the code, but I wanted to check and see if I am on the right track.

    I'm thinking that every time the program sees one of the inputs is asserted, it either adds "1" or subtracts "1" from an address I can call "level" and if this address has a value of 2 for instance, the output will be at a 50% duty cycle.

    If the program then recognizes that the opposite input has been asserted, then it subtracts "1" from the "level" address and reads from the table that it should now be at a 25% duty.

    Can table reads be used in this way? I'll work on it and try to get some code up as soon as I can.

    Thanks
     
  14. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    Good catch! I added the errant incf dcy,F instruction...

    farscape,

    I used the "led" variable for the duty cycle value in that example program. It holds an 8-bit duty cycle value of 0 (off or 0%) through 255 (100%). Each time we go through the 50-usec "step" loop we compare the duty cycle counter value with the "led" duty cycle value and we turn off the led output when the counter value is equal to or greater than the "led" duty cycle. If the "led" variable contains a value of 2 the led will be lighted for 2 50-usec "steps" and turned 'off' for 254 50-usec "steps". The "dcy" duty cycle counter variable is incremented at the end of each 50-usec "step" loop and rolls over from 255 to 0.

    I use the "tmrl" and "tmrh" variables to count off 250000 usecs worth of 50-usec loops. So, every 250-msecs we venture outside of the 50-usec PWM "step" loop to sample the switches and increment or decrement the "led" duty cycle value accordingly.

    The code may look a bit funky because it's been written to be isochronous. That is, it uses the same number of instruction cycles to get through the code wether the switches are pressed or released. Isochronous code is a pain if you need to add another code feature or function but it can be very useful for some time critical applications, for example, a Sony SIRC controlled (8-bit PWM) RGB Accent Lighting controller running on a 6-pin 10F200.

    Regards, Mike
     
    Last edited: May 31, 2010
  15. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    Thanks Mike, Ill take a closer look and see if I can follow now.

    I rewrote my code this afternoon using the table idea I had above... I hope its not too tedious to follow but maybe if I layout my basic ideas it will be less of a pain.

    This is what I hope the program does so far...

    -When the program starts, it loops through "test0" and "test1" until one input is asserted and it then jumps to "upLED" (if input 0 is asserted) or "downLED" (if input 1 is asserted).
    -In "upLED", the "level" address is increased by "1" which is then read from a table which at the moment has 5 levels corresponding to 0%, 20%,40%,... duty cycles. If "level" is at "1", the LED is turned on for 51 clock cycles and off for 204 cycles which is ~20% duty.
    -I included a pause sequence within upLED so that this repeats for ~.25 sec and then jumps back to checking the inputs once again.
    -The downLED routine does the same thing but subtracts "1" from the level address.

    Code ( (Unknown Language)):
    1. cpu equates (memory map)
    2.  
    3. portB    equ    0x06        
    4. portA    equ    0x05
    5. duty    equ    0x0c
    6. timer    equ    0x0e
    7. timer1    equ    0x0f
    8. level    equ    0x0d
    9.  
    10. org    0x000
    11.  
    12. start    movlw    0x00        
    13.     tris    portB        
    14.     clrf    portB        
    15.     movlw    0x1F        
    16.     tris    portA        
    17.     clrf    portA
    18.     clrf    level
    19.  
    20. test0    btfsc    portA, '0'
    21.     call    test1
    22.     call     upLED
    23.  
    24. test1    btfsc    portA, '1'
    25.     goto    test0
    26.     call    downLED
    27.     goto   test0
    28.  
    29. upLED     movlw    0xD3
    30.     movwf    timer
    31.     movlw    0x31
    32.     movwf    timer1
    33.     incf    level
    34.     movf    level, w
    35.     call    tableU
    36.     movwf    duty
    37. repeat    bsf    portB, '0'
    38. onU    decfsz    duty
    39.     goto    onU
    40.     call    tableD
    41.     movwf    duty
    42.     bcf    portB, '0'
    43. offU    decfsz    duty
    44.     goto     offU
    45.     decfsz    timer
    46.     goto    $+2
    47.     decfsz    timer1
    48.     goto    repeat
    49.     return
    50.  
    51. downLED    movlw    0xD3
    52.     movwf    timer
    53.     movlw    0x31
    54.     movwf    timer1
    55.     decf    level
    56.     movf    level, w
    57.     call    tableU    
    58.     movwf    duty
    59. repeat    bsf    portB, '0'
    60. onD    decfsz    duty
    61.     goto    onD
    62.     call    tableD
    63.     movwf    duty
    64.     bcf    portB, '0'
    65. offD    decfsz    duty
    66.     goto    offD
    67.     decfsz    timer
    68.     goto    $+2
    69.     decfsz    timer1
    70.     goto    repeat
    71.     return
    72.  
    73. tableU    addwf    PCL
    74.     retlw    d'0'
    75.     retlw    d'51'
    76.     retlw    d'102'
    77.     retlw    d'153'
    78.     retlw    d'204'
    79.     retlw    d'255'
    80.  
    81. tableD    addwf    PCL
    82.     retlw    d'255'
    83.     retlw    d'204'
    84.     retlw    d'153'
    85.     retlw    d'102'
    86.     retlw    d'51'
    87.     retlw    d'0'
    88.  
    89. end
    Thanks for all the help!
     
    Last edited: May 31, 2010
  16. jordan22

    New Member

    May 30, 2010
    2
    0
    this code seems to work w/o errors in MPLAB. How should I test if it is working like its actually suppose to though?

    Code ( (Unknown Language)):
    1. processor 16F84A
    2. INCLUDE <p16f84A.inc>
    3. __CONFIG _CP_OFF & _WDT_OFF & _XT_OSC & _PWRTE_ON
    4. org H'00'
    5.  
    6. portB    equ    0x06        
    7. portA    equ    0x05
    8. duty    equ    0x0c
    9. timer    equ    0x0e
    10. timer1    equ    0x0f
    11. level    equ    0x0d
    12.  
    13. org    0x000
    14.  
    15. start    movlw    0x00        
    16.         tris    portB        
    17.         clrf    portB        
    18.         movlw    0x1F        
    19.         tris    portA        
    20.         clrf    portA
    21.         clrf    level
    22.  
    23. test0    btfsc    portA, H'0'
    24.         call    test1
    25.         call    upLED
    26.  
    27. test1    btfsc    portA, H'1'
    28.         goto    test0
    29.         call    downLED
    30.         goto    test0
    31.  
    32. upLED    movlw    0xD3
    33.         movwf    timer
    34.         movlw    0x31
    35.         movwf    timer1
    36.         incf    level
    37.         movf    level, w
    38.         call    tableU
    39.         movwf    duty
    40. repeat    bsf    portB, H'0'
    41.  
    42. onU        decfsz    duty
    43.         goto    onU
    44.         call    tableD
    45.         movwf    duty
    46.         bcf    portB, H'0'
    47.  
    48. offU    decfsz    duty
    49.         goto    offU
    50.         decfsz    timer
    51.         goto    $+2
    52.         decfsz    timer1
    53.         goto    repeat
    54.         return
    55.  
    56. downLED    movlw    0xD3
    57.         movwf    timer
    58.            movlw    0x31
    59.         movwf    timer1
    60.         decf    level
    61.         movf    level, w
    62.         call    tableU    
    63.         movwf    duty
    64. repeat2    bsf portB, H'0'
    65.  
    66. onD        decfsz duty
    67.         goto    onD
    68.         call    tableD
    69.         movwf    duty
    70.         bcf    portB, H'0'
    71.  
    72. offD    decfsz    duty
    73.         goto    repeat2
    74.         decfsz    timer
    75.         goto    $+2
    76.         decfsz    timer1
    77.         goto    onD
    78.         return
    79.  
    80. tableU    addwf    PCL
    81.         retlw    d'0'
    82.         retlw    d'51'
    83.         retlw    d'102'
    84.         retlw    d'153'
    85.         retlw    d'204'
    86.         retlw    d'255'
    87.  
    88. tableD    addwf    PCL
    89.         retlw    d'255'
    90.         retlw    d'204'
    91.         retlw    d'153'
    92.         retlw    d'102'
    93.         retlw    d'51'
    94.         retlw    d'0'
    95.  
    96. end
     
  17. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    There are is at least one big thing that is not right.

    The program isn't staying at whatever duty cycle is currently set while checking the inputs. It only goes through the duty cycle until the delay routine runs out then goes blank until it reads another input assertion.
     
  18. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    @Farscape - call test1 should be goto test1
    I don't think it will quite work properly yet, but keep going.

    @ Jordan
    Select Debugger - Select Tool - MPLAB SIM
    Select Debugger - Stimulus - New Workbook and set up something to change the input pins.
    Select View - Simulator Logic Analyzer and then you will be able to see the output.
     
    Last edited: Jun 1, 2010
  19. farscape

    Thread Starter New Member

    May 16, 2010
    26
    0
    So here's the updated code once again. I think its now set up so that the program will continue looping through at the existing duty level until reading another assertion.

    I am trying to find a way to constrain the "level" register values to be from 0-5 inclusive. Is there a way to ignore the increment command if "level" is already at 5?

    I hope I'm getting a little closer now...


    Code ( (Unknown Language)):
    1. processor 16F84A
    2. INCLUDE <p16f84A.inc>
    3. __CONFIG _CP_OFF & _WDT_OFF & _XT_OSC & _PWRTE_ON
    4. org H'00'
    5.  
    6. portB    equ    0x06        
    7. portA    equ    0x05
    8. duty     equ    0x0c
    9. timer    equ    0x0e
    10. level    equ    0x0d
    11.  
    12. org     0x000
    13.  
    14. start
    15.  
    16. movlw   0x00        
    17. tris    portB        
    18. clrf    portB        
    19. movlw   0x1F        
    20. tris    portA        
    21. clrf    portA
    22. clrf    level
    23.  
    24.  
    25. test0     btfss   portA, b'0'
    26.           call    test1
    27.           call    upLED
    28.  
    29. test1     btfss   portA, b'1'
    30.           goto    test0
    31.           call    downLED
    32.  
    33. upLED           incf     level
    34.                  goto     $+2
    35. downLED         decf     level
    36. same            movlw    0xD3
    37.                  movwf    timer
    38.                  movf     level, w
    39.                  call     tableON
    40.                  movwf    duty
    41. repeat         bsf      portB, b'0'
    42. on             decfsz   duty
    43.                 goto     on
    44.                 call     tableOFF
    45.                 movwf    duty
    46.                 bcf      portB, b'0'
    47. off            decfsz   duty
    48.                 goto     off
    49.                 decfsz   timer
    50.                 goto     repeat
    51.                 btfss     portA, b'0'
    52.                 goto     $+2
    53.                 goto     upLED
    54.                 btfss     portA, b'1'
    55.                 goto     same    
    56.                 goto     downLED
    57.    
    58. tableON    addwf    PCL
    59.                 retlw    d'0'
    60.                 retlw    d'51'
    61.                 retlw    d'102'
    62.                 retlw    d'153'
    63.                 retlw    d'204'
    64.                 retlw    d'255'
    65.  
    66. tableOFF   addwf    PCL
    67.                 retlw    d'255'
    68.                 retlw    d'204'
    69.                 retlw    d'153'
    70.                 retlw    d'102'
    71.                 retlw    d'51'
    72.                 retlw    d'0'
    73.  
    74. end
     
    Last edited: Jun 1, 2010
  20. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    This is one way of limiting level to 5 or less:

    movf level, W
    sublw 5
    btfsc STATUS, Z
    decf level, F ;if level was 5 make it 4, then the next line makes it 5 again
    incf level, F

    Have a look at the calls in your program, if you use a call there has to be a return or a retlw otherwise things go very wrong.
    Also if no button is pressed it gets stuck in the test0/test1 loop.
     
Loading...