Hein (fub) wrote,

  • Mood:

PWM code

By popular demand (well, by greatbiggary's demand), here's the (documented! Sometimes I'm so proud of myself) assembly code for the RGB LED PWM. Connect the three inputs of the LED to pins b0, b1 and b2, add a 4MHz crystal (as usual), and watch it go!

; PWM test
; 3 different outputs with 7 values

        LIST P=16F628, R=DEC    ; Use the PIC16F628 and decimal system 

        #include "P16F628.INC"  ; Include header file 

        __config  _XT_OSC & _LVP_OFF & _WDT_OFF & _PWRTE_ON & _BODEN_ON 

        cblock 0x20

        GOTO init
        org 0x4                ; Choose starting adress

; Interrupt routine. This is called every 256 instructions.
isr        MOVWF        temp_w          ; save w register
        MOVF        STATUS, W       ; W = STATUS
        MOVWF        temp_s          ; save STATUS register
        TSTF bres_mid                ; first test for mid==0
        SKPNZ                        ; nz = no underflow needed
        DECF bres_hi,f                ; z, so is underflow, so dec the msb

        DECFSZ bres_mid,f        ; dec the mid byte (subtract 256)
        GOTO end_isr                ; nz, so definitely not one second yet.

        TSTF bres_hi                ; test hi for zero too
        SKPZ                        ; z = both hi and mid are zero, is one second!
        GOTO end_isr                ; nz, so not one second yet.

        MOVLW 0x0F                ; get msb value 
        MOVWF bres_hi                ; load in msb
        MOVLW 0x42                ; get mid value
        MOVWF bres_mid                ; load in mid
        MOVLW 0x40                ; lsb value to add
        ADDWF bres_lo,f                ; add it to the remainder already in lsb
        SKPNC                        ; nc = no overflow, so mid is still ok

        INCF bres_mid,f                ; c, so lsb overflowed, so inc mid

        CALL continue                ; A second has passed, cycle to the next color                  
end_isr                                ; End of the interrupt routine
        BCF        INTCON, T0IF        ; clear the TMR0 flag bit
        MOVF        temp_s, W        ; Put STATUS and W back where they belong
        MOVWF        STATUS
        SWAPF        temp_w, F
        SWAPF        temp_w, W
        RETFIE                        ; Return from interrupt

continue                        ; Second has passed!
        movf mode_counter, W
        xorlw 0x01
        btfsc STATUS, Z
        goto mode_one
        movf mode_counter, W
        xorlw 0x02
        btfsc STATUS, Z
        goto mode_two
        goto mode_three
mode_one                        ; Mode 1: first output high
        decfsz b1, f                ; Decrease b1
        movlw 0x07                ; b1 == 0, reset to 7
        movwf b1
        decfsz b2, f                ; Decrease b2
        movlw 0x02                ; b2 == 0, cycle to next mode
        movwf mode_counter
        goto reset_values
mode_two                        ; Mode 2: Second output high
        decfsz b0, f                ; Decrease b0
        movlw 0x07                ; b0 == 0, reset to 7
        movwf b0
        decfsz b2, f                ; Decrease b2
        movlw 0x03                ; b2 == 0, cycle to next mode
        movwf mode_counter
        goto reset_values
mode_three                        ; Mode 3: Third output high
        decfsz b0, f                ; Decrease b0
        movlw 0x07                ; b0 == 0, reset to 7
        movwf b0
        decfsz b1, f                ; Decrease b1
        movlw 0x01                ; b1 == 0, cycle to first mode
        movwf mode_counter
        movlw 0x07                ; Everything on zero, reset all to 7
        movwf b0
        movwf b1
        movwf b2

        movlw 0x07                ; Set initial values to 7
        movwf b0
        movwf b1
        movwf b2
        movlw 0x01                ; Set initial mode to 1
        movwf mode_counter
        bsf STATUS,RP0          ; RAM PAGE 1
        clrf TRISB                ; portB all pins output
        MOVLW        b'10001000'        ; Set the options (prescaler for watchdog timer)
        MOVWF        OPTION_REG
        bcf STATUS,RP0          ; RAM PAGE 0
        clrf PORTB
        MOVLW 0x0F                ; get msb value 
        MOVWF bres_hi                ; put in hi
        MOVLW 0x42 +1                ; get mid value (note we added 1 to it)
        MOVWF bres_mid                ; put in mid
        MOVLW 0x40                ; get lsb value
        MOVWF bres_lo                ; put in mid

        CLRF        INTCON
        BSF        INTCON, T0IE
        BSF        INTCON, GIE        ; Set Timer interrupt

        call pwm_start                ; Initialise PWM variables
        movlw 0x30
        movwf counter                ; initialise counter on 48
        call pwm_pulse                ; Pulse!
        decfsz counter, f
        goto inner_loop                ; Duty cycle isn't finished yet
        goto main_loop                ; Duty cycle finished, re-initialise

pwm_start                        ; Initialise the variables
        clrf PORTB                ; Also, if the value > 0, then
        movf b0, W                ;   pull the pin high
        call get_pwm_cycle
        movwf cur_b0
        btfss STATUS, Z
        bsf PORTB, 0
        movf b1, W
        call get_pwm_cycle
        movwf cur_b1
        btfss STATUS, Z
        bsf PORTB, 1
        movf b2, W
        call get_pwm_cycle
        movwf cur_b2
        btfss STATUS, Z
        bsf PORTB, 2

get_pwm_cycle                        ; Get the duty-cycle from this lookup-table
        addwf PCL, f
        retlw 0x00                ; This will never get called!
        retlw 0x00
        retlw 0x01
        retlw 0x04
        retlw 0x08
        retlw 0x10
        retlw 0x20
        retlw 0x30

pwm_pulse                        ; Pulse!
        btfss PORTB, 0                ; Only check if the pin is high
        goto b0_end                ; If it's low, then it will stay low for the
        decfsz cur_b0, f        ;   rest of the duty cycle
        goto b0_end
        bcf PORTB, 0
b0_end        btfss PORTB, 1                ; Check b1
        goto b1_end
        decfsz cur_b1, f
        goto b1_end
        bcf PORTB, 1
b1_end        btfss PORTB, 2                ; Check b2
        goto b2_end
        decfsz cur_b2, f
        goto b2_end
        bcf PORTB, 2
b2_end        return

Hmmm, I see that semagic uses only 5 spaces as TABs. My excuses for the poor formatting.

  • Update

    Wow, what with one thing and another, I haven’t posted on here in a month! Time to give a short update on what’s been happening.…

  • Final RPG-a-Day: Thank

    The last prompt for RPG-a-Day this year is ‘Thank’. If you have read every entry of this year’s RPG-a-Day, then I certainly…

  • Next-to-last RPG-a-Day: Mention

    Today’s prompt is ‘Mention’. I guess this is where I mention people I look up to, or websites I frequent? Ok, here’s…

  • Post a new comment


    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded