;******************************************************************
;*********** Freeware 12Volt Halogen Lamp Dimmer ****************
;******************************************************************
;
; Revision 4
;
; All text following semicolon ";" are comments.
;
; This code may not be used for commercial purposes. 
;
; Assembly code for PIC16F84. Use MPASM or MPLAB to assemble.
; All rights reserved. Jim Robertson, Oct 2000. 
; Details and schematic @ http://lightbrain.8m.com
; Default radix = hex decimal numbers are preceded by "." (i.e. ".200")
; Okay to use 4Mhz ceramic resonator with built in capacitors.
;
; Values used are for 12 volt system with 20 watt lamp.


    list p=16F84             ; list directive to define processor
    #include <p16F84.inc>    ; processor specific variable definitions

    __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC ; configuration word


;******************* VARIABLE DEFINITIONS ****************************

                             ; (16F84 ram locations start at 0x0c) 
hi_count      EQU 0x0c       ; delay register 
lo_count      EQU 0x0d       ; delay register
mainloop      EQU 0x0e       ; pwm loop register
ld_count      EQU 0x0f       ; long delay register
ontime        EQU 0x10       ; 
offtime       EQU 0x11       ;
button_timer  EQU 0x12       ; 


;******************* CONSTANT DEFINITIONS *****************************

lamp          EQU 1          ; define PORTB bit 1 for pwm output (HEXFET)
button        EQU 7          ; define PORTB bit 7 for button input 


;**********************************************************************

        ORG 0x000        ; processor reset vector

;**********************************************************************
;
; Program execution starts here after power up or wake up from sleep
;
;**********************************************************************

main
    clrf  PORTB             ; clear PORTB
    bsf   STATUS, RP0       ; select register bank 1 
    movlw b'10000000'       ; set PORTB bit 7 to input all others are outputs 
    movwf TRISB             ; move contents of W to tristate for PORTB
    bcf   STATUS, RP0       ; select register bank 0 (normal)
    movlw b'00001000' 
    movwf INTCON            ; enable wake up on PORTB change

    movlw .100              ; short delay to prevent accidental on 
    call  long_delay

    btfss PORTB, button     ; is button still being pressed?
    goto  power_down        ; if no - go to sleep 

pwr_on
    btfsc PORTB, button     ; has button been released?
    goto  pwr_on            ; (ensures mode is not entered while button pressed)


;********************** 7 watt mode **************************
seven_watt                  ; 35% duty cycle is approx 7 watts 

    movlw .19               ; sets how long the button must be held to 
    movwf button_timer      ; turn the lamp off (19 = approx 2 Seconds)

    movlw .35               ; sets pwm "on time" 3.5 ms 
    movwf ontime

    movlw .65               ; sets pwm "off time" to 6.5 ms 
    movwf offtime           ; \ (3.5 + 6.5 = 10 ms or 100Hz pwm rate)

    call  runbulb           ; call pwm subroutine 

;********************* 10 watt mode **********************
ten_watt                    ; 60% duty cycle is approx 10  watts 

    movlw .19
    movwf button_timer

    movlw .60
    movwf ontime
    movlw .40
    movwf offtime
    call  runbulb

;********************** 20 watt mode ************************
twenty_watt                 ; 100% duty cycle is full 20 watts

    movlw .19
    movwf button_timer

    movlw .100
    movwf ontime
    movlw .1                 ; short off time to allow normal button scan
    movwf offtime 
    call  runbulb

    goto  seven_watt


;************************ Subroutines **************************************

;***************************************************************************
; PWM and button scanning subroutine 
; Short button press will fall 
; thru to next power level. Long press (2sec)
; will go to power down (sleep).
;***************************************************************************

runbulb 
    movlw  .7             ; set button scan rate to approx 14 Hz
    movwf  mainloop       ; (7 x 10ms = 70ms)
run_d2
    bsf    PORTB, lamp    ; turn lamp on
    movf   ontime, 0      ; move ontime to W register 
    call   v_delay        ; call variable delay

    bcf    PORTB, lamp    ; turn lamp off
    movf   offtime, 0     ; move offtime to W register 
    call   v_delay        ; call variable delay

    decfsz mainloop       ; decrement mainloop, if zero then read button 
    goto   run_d2         ; if not keep looping

    btfsc  PORTB, button  ; read button
    goto   timer_1        ; if it is being pressed decrement timer_1

    movlw  .19
    subwf  button_timer, 0 ; compare value in button_timer register to 19
    btfsc  STATUS, Z       ; does button_timer register still = 19 ?

    goto runbulb           ; if yes - button has not been touched - continue pwm

    return                 ; if no - button has been pressed and released 
                           ; go to next power level


timer_1                    ; test for long button press 
    decfsz button_timer    ; decrement button_timer, is it = 0 ?
    goto runbulb           ; if no - continue pwm
    goto power_down        ; if yes - button was held for 2 seconds so power down

;***********************************************************

power_down                 ; turn lamp off and go to sleep (low power mode)
    bcf   PORTB, lamp
    movlw .250             ; delay 1 second to allow button to be released before 
    call  long_delay       ; putting processor to sleep 
    movlw .250
    call  long_delay
    movlw .250
    call  long_delay
    movlw .250
    call  long_delay
    clrf  PORTB
    sleep                  ; wake up (button press) will execute next instruction
    goto  main

;******************** delay subroutines *************************************

long_delay             ; enter here with desired ms in W (max 255ms)
    movwf  ld_count    ; (1ms resolution)
ld_loop
    call   ms_delay    ; call 1ms delay

    decfsz ld_count    ; decrement ld_count until it reaches zero
    goto   ld_loop

    return



ms_delay               ; enter here for fixed 1 ms (.001 second) delay
      movlw .10        ;(1) load 10 into W register
v_delay                ; enter here for delay with desired ms*10 in W (max 25.5ms)
      movwf  hi_count  ;(1) (i.e. for 20 ms delay W = .200)(.1ms resolution)

loop1 movlw  .31       ;(1) 
      movwf  lo_count  ;(1)

loop2 decfsz lo_count  ; (30x3)+2 = 92 cycles (4MHz = 1 uS per instruction cycle)
      goto   loop2     ;/ 

      nop              ;(1)
      decfsz hi_count  ;(1)
      goto   loop1     ;(2)

      return



      end