;*******************************************************************
;  DOLLHOUSE.ASM: Dollhouse Elevator and Lighting Controller
;
;  Controls eight lighting circuits (push ON, push OFF) and an
;  Elevator
;*******************************************************************
;  Written by Mark S. Csele, P.Eng.  2002/12
;*******************************************************************
;  HARDWARE:
;  MCU=PIC16C876  XTAL=3.6864 MHz  PICCTL21B PC Board Controller
;  PORT A: Elevator Control
;	RA0 - Elevator motor POWER
;	RA1 - Elevator motor REVERSE
;	RA2 - Bottom Limit Switch (active low)
;	RA3 - Top Limit Switch active low)
;	RA4 - Encoder Input (active low pulses, 0.5mS wide, 35Hz max)
;	RA5 - Floor 1 Pushbutton
;  PORT B: Lights ON/OFF pushbuttons
;	RB0 through RB6 - Pushbuttons (Active low = ON)
;	RB7 - Floor 2 Pushbutton
;  PORT C: Lights
;	RC0 through RC6 - Lamps (Active high = ON)
;	RC7 - Floor 3 Pushbutton
;*********************************************************************

        LIST    p=16F876       ;PIC16C876 is the target processor for this test
        INCLUDE "P16F876.INC"  ;Include file with register defines

;*********************************************************************
; Constant Defines
;*********************************************************************
;Port A bit definitions
MOTOR_PWR	equ	0	;Powers the elevator motor
MOTOR_REV	equ	1	;Reverses the elevator motor (downward)
SWT_BOTTOM	equ	2	;Low when elevator reaches bottom
SWT_TOP		equ	3	;Low when elevator reaches top
ENCODER		equ	4	;Pulses from encoder
PB_FLOOR_3    	equ  	5	;Floor 3 pushbutton (active low)

PB_FLOOR_1    	equ  	7	;Floor 1 pushbutton (active low)
PB_FLOOR_2    	equ  	7	;Floor 2 pushbutton (active low)

FOYER_TIME	equ	.120	;Two minutes for foyer lights
SHUTDOWN_TIME 	equ  	.30   	;Thirty minues to shutdown system
ELEVATOR_TIME 	equ  	.60   	;Sixty seconds to shutdown elevator

ENCODER_HI_DN	equ	.2	;# of pulses on the way down from floor 3 to 2
ENCODER_LO_DN	equ	.15
ENCODER_HI_UP	equ	.2	;# of pulses on the way up from floor 1 to 2
ENCODER_LO_UP	equ	.40

;*********************************************************************
; Register Defines
;*********************************************************************
;Assorted miscellaneous general-purpose registers
DlyRegA     	equ 	2E   	;Temp register for delay
DlyRegB     	equ  	2F   	;Temp register for delay

Lights		equ	30	;Pushbutton activated lights (toggled)
Foyer		equ	31	;Auto-on when elevator car arrives
OldPbState    	equ	32	;Last know PB state (used to flag changes)
OldLights     	equ  	33   	;Last known state of ON room lights

EncoderHi	equ	34	;Counter for # of pulses
EncoderLo	equ	35

Ticks    	equ 	38  	;one-fifteenth of a second counter
Seconds  	equ  	39   	;Seconds counter (internal)
FoyerTimer	equ  	3A	;A countdown timer in seconds (at zero, shutdown lights)
ElevatorTimer	equ	3B	;A countdown timer in seconds (at zero, kill motor)
AutoShutDownTimer equ	3C	;A countdown timer in minutes (at zero, shutdown lights)

ISR_w_temp  	equ  	3D  	;ISR temp save locations for w and status
ISR_s_temp  	equ  	3E


;********************************************************************
; Main Program Begins
;*********************************************************************

	nop
	nop
	goto	Main

;ISR: Timers for auto-shutdown, auto-foyer lights off, and elevator failure (timeout)
;     AutoShutDownTimer - a timer in units of MINUTES
;     FoyerTimer - a timer in units of SECONDS
;     ElevatorFailureTimer - a timer in units of SECONDS
	org	4
ISR
;This is the ISR which, interrupted at 15 Hz, keeps a running clock in minutes and seconds.
;Easy to modify to any purpose such as a countdown timer, etc.
    ;Timer is set to interrupt at 15 Hz
	movwf  	ISR_w_temp    ;Save W register
    	swapf  	STATUS,w      ;Save Status flags intact
    	movwf  	ISR_s_temp

	bcf    	STATUS,RP0    ;Select Page 0 ... Will be restored later anyway
    	bcf    	INTCON,T0IE   ;Disable Interrupt
	bcf    	INTCON,T0IF   ;Clear Interrupt flag
    	bsf    	INTCON,T0IE   ;Re-Enable RTC Interrupt

    	movlw  	.16           ;Re-Load RTCC with 16.  Will count to 256
    	movwf  	TMR0          ;then overflow to 0 (causing INT)
    	decfsz 	Ticks,f       ;Keep dec'ing until zero
    	goto 	intdone
    	movlw  	.15           ;Count is zero, one second has elapsed
    	movwf  	Ticks         ;Re-load temp counter

	movf	FoyerTimer,f	;If timer==0 (stopped), just ignore it
	btfsc	STATUS,Z
	goto	FoyerTimerDone
	decfsz	FoyerTimer,f	;Decrement SECONDS timers
	goto	FoyerTimerDone
	clrf	Foyer		;Timer expired ... turn off foyer lights automatically
FoyerTimerDone

	movf	ElevatorTimer,f
	btfsc	STATUS,Z
	goto    ElevatorTimerDone
	decfsz	ElevatorTimer,f
	goto	ElevatorTimerDone
	clrf	PORTA		;Turn OFF elevator motor automatically
ElevatorTimerDone

    	incf   	Seconds,f     	;Increment seconds
    	movfw  	Seconds
    	sublw  	.59           	;Check if seconds >=60
    	btfsc  	STATUS,C      	;W>59 then Carry=0, thus increment minute
    	goto 	intdone
	clrf	Seconds
	movf	AutoShutDownTimer,f	;If already expired, don't touch it
	btfsc	STATUS,Z
	goto	intdone
    	decfsz  AutoShutDownTimer,f	;Decrement MINUTES timers
	goto   	intdone
	clrf	PORTA		;Motors off
	clrf	PORTC		;Lights off
	clrf	Lights
	clrf	Foyer

intdone
    	swapf  	ISR_s_temp,W  	;Restore STATUS register intact
	movwf  	STATUS
    	swapf	ISR_w_temp,f  	;Restore w without changing status flags
    	swapf	ISR_w_temp,w
    	retfie                	;Return from Int and re-enable global interrupts


;#### MAIN PROGRAM ####
Main
	movlw   b'11111111'	;Port B all inputs
	bsf     STATUS,RP0    	;select Page 1
	movwf   TRISB
	bcf     STATUS,RP0    	;select Page 0

	movlw   b'10000000'	;Port C all outputs except RC7
	bsf     STATUS,RP0    	;select Page 1
	movwf   TRISC
	bcf     STATUS,RP0    	;select Page 0
	clrf	PORTC		;Turn Off All Lights

	movlw   6		;Configure Port A for all digital I/O (F876 and C73)
	bsf     STATUS,RP0    	;select Page 1
    	movwf   ADCON1
    	bcf     STATUS,RP0    	;select Page 1

	clrf    PORTA         	;Make All Elevator Control Outputs LOW
	movlw   b'00111100'   	;Port A all inputs except two relays
	bsf     STATUS,RP0    	;select Page 1
	movwf   TRISA
	bcf     STATUS,RP0    	;select Page 0
	clrf	PORTA

    ;Set up ISR variables needed
	movlw   .15
    	movwf  	Ticks           ;ISR temporary counter
	clrf	Seconds

;The following code sets the INTERRUPT CONTROLLER to int. the PIC at
;15Hz for a 3.6864MHz XTAL
    	bsf    	STATUS,RP0     	;Select Page 1
    	movlw  	b'00000111'    	;Timer Prescaler /256
    	movwf  	OPTION_REG
    	bcf    	STATUS,RP0     	;Select Page 0
    	movlw  	.16            	;Pre-Load TMR0 with 16
    	movwf  	TMR0
    	bcf    	INTCON,T0IF  	;Clear TMR0 Int Flag
    	bsf    	INTCON,T0IE  	;Set TMR0 Interrupt On
    	bsf    	INTCON,GIE     	;Global Interrupt Enable
;Interrupt driven clock is now running, just read Seconds and Minutes registers to
;check real time clock

	clrf	Lights
	clrf	Foyer
    	clrf   	Seconds  	;Seconds Counter (0-59)
	clrf	FoyerTimer
	clrf	ElevatorTimer
	clrf	AutoShutDownTimer

MainLoop

;Elevator control
;Check for a floor PB and begin moving car.  Do not check buttons again until
;elevator is at destination floor but do check for lighting PBs
CheckForFloorPB
	btfss	PORTA,PB_FLOOR_3
	goto	MoveToFloor3
	btfss	PORTB,PB_FLOOR_1
	goto	MoveToFloor1
	btfss	PORTC,PB_FLOOR_2
	goto	MoveToFloor2
	goto	NoFloorRequested

;### FLOOR 1 ###
MoveToFloor1
	;Reset shutdown timer (interrupt-driven) to 30 minutes
	movlw	SHUTDOWN_TIME
	movwf	AutoShutDownTimer

	btfss	PORTA,SWT_BOTTOM	;Check if we are already there
	goto	ElevatorDone		;Switch is low ... already there
	bsf	PORTA,MOTOR_REV		;Move car DOWN
	bsf	PORTA,MOTOR_PWR
	movlw	ELEVATOR_TIME		;Restart timer
	movwf	ElevatorTimer
WaitForFloor1
	call	DetectLightPB
	movf	ElevatorTimer,f
	btfsc	STATUS,Z
	goto	ElevatorDone		;Timeout.  Already shutdown in ISR
	btfsc	PORTA,SWT_BOTTOM
	goto	WaitForFloor1
	clrf	PORTA			;Motor off
	bsf	Foyer,5			;Turn on lights when car arrives
	movlw   FOYER_TIME
	movwf	FoyerTimer
	goto	ElevatorDone

;### FLOOR 2 ###
MoveToFloor2
	;Reset shutdown timer (interrupt-driven) to 30 minutes
	movlw	SHUTDOWN_TIME
	movwf	AutoShutDownTimer

	btfss	PORTA,SWT_TOP		;Check if we are at top floor
	goto	AtTop			;Switch is low ... we're there
	btfss	PORTA,SWT_BOTTOM	;Check if we are at bottom floor
	goto	AtBottom		;Switch is low ... we're there
	goto	ElevatorDone		;Must be there already ... just exit
AtTop
	bsf	PORTA,MOTOR_REV		;Move car DOWN
	bsf	PORTA,MOTOR_PWR
	movlw	ELEVATOR_TIME		;Restart timer
	movwf	ElevatorTimer
	movlw	ENCODER_HI_DN		;Load Encoder counts for trip down
	movwf	EncoderHi
	movlw	ENCODER_LO_DN
	movwf	EncoderLo
WaitForCarDown
;	call	DetectLightPB
	movf	ElevatorTimer,f
	btfsc	STATUS,Z
	goto	ElevatorDone		;Timeout.  Already shutdown in ISR
	btfsc	PORTA,ENCODER		;Wait for encoder to pulse low
	goto	WaitForCarDown
WaitForEncoderHigh
	btfss   PORTA,ENCODER
	goto	WaitForEncoderHigh
	decfsz	EncoderLo,f		;Decrement low count and check if zero
	goto	WaitForCarDown
	movf	EncoderHi,f		;Check if Hi count ==0 also
	btfsc	STATUS,Z
	goto	DownDone		;Both counts zero, stop elevator
	decf	EncoderHi,f		;Not zero, decrement it
	goto	WaitForCarDown
DownDone
	clrf	PORTA
	bsf	Foyer,3			;Turn on lights when car arrives
	movlw   FOYER_TIME
	movwf	FoyerTimer
	goto	ElevatorDone

AtBottom
	bcf	PORTA,MOTOR_REV	;Move car UP
	bsf	PORTA,MOTOR_PWR
	movlw	ELEVATOR_TIME		;Restart timer
	movwf	ElevatorTimer
	movlw	ENCODER_HI_UP		;Load Encoder counts for trip up
	movwf	EncoderHi
	movlw	ENCODER_LO_UP
	movwf	EncoderLo
WaitForCarUp
	call	DetectLightPB
	movf	ElevatorTimer,f
	btfsc	STATUS,Z
	goto	ElevatorDone		;Timeout.  Already shutdown in ISR
	btfsc	PORTA,ENCODER		;Wait for encoder to pulse low
	goto	WaitForCarUp
WaitForEncoderHighUp
	btfss   PORTA,ENCODER
	goto	WaitForEncoderHighUp
	decfsz	EncoderLo,f		;Decrement low count and check if zero
	goto	WaitForCarUp
	movf	EncoderHi,f		;Check if Hi count ==0 also
	btfsc	STATUS,Z
	goto	UpDone			;Both counts zero, stop elevator
	decf	EncoderHi,f		;Not zero, decrement it
	goto	WaitForCarUp
UpDone
	clrf	PORTA
	bsf	Foyer,3			;Turn on lights when car arrives
	movlw   FOYER_TIME
	movwf	FoyerTimer
	goto	ElevatorDone

;### FLOOR 3 ###
MoveToFloor3
	;Reset shutdown timer (interrupt-driven) to 30 minutes
	movlw	SHUTDOWN_TIME
	movwf	AutoShutDownTimer

	btfss	PORTA,SWT_TOP		;Check if we are already there
	goto	ElevatorDone		;Switch is low ... already there
	bcf	PORTA,MOTOR_REV		;Move car UP
	bsf	PORTA,MOTOR_PWR
	movlw	ELEVATOR_TIME		;Restart timer
	movwf	ElevatorTimer
WaitForFloor3
	call	DetectLightPB
	movf	ElevatorTimer,f
	btfsc	STATUS,Z
	goto	ElevatorDone	;Timeout.  Already shutdown in ISR
	btfsc	PORTA,SWT_TOP
	goto	WaitForFloor3
	clrf	PORTA		;Motor off
	bsf	Foyer,0			;Turn on lights when car arrives
	movlw   FOYER_TIME
	movwf	FoyerTimer
	goto	ElevatorDone

ElevatorDone
	;Reset shutdown timer (interrupt-driven) to 30 minutes
	movlw	SHUTDOWN_TIME
	movwf	AutoShutDownTimer

	movlw	FOYER_TIME	;Reload timer to shutdown foyer lights after elevator movement
	movwf	FoyerTimer

NoFloorRequested
	call	DetectLightPB
	call	Delay		;Delay for debounce
	goto	MainLoop

;Light can be switched-on from either (a) pushbuttons or (b) auto when elevator arrives
;When a Pushbutton is activated, it is toggled in the 'Lights' register and the 'Foyer'
;register bit is cleared (Off) when toggled off manually
;When the elevator arrives a bit if turned ON in the 'Foyer' register and a 2 minute timer
;activated.  When two minutes elapses, it is cleared (but light may stay on if pushbutton
;toggled in the 'Lights' register)
DetectLightPB
	movf	PORTB,w		;Read pushbuttons
	iorlw    b'10000000'	;Mask Off upper bit
	subwf	OldPbState,w	;Check if anything changed
	btfsc	STATUS,Z
	goto	NoPbChanges

	movf	Lights,w	;Save old value of 'Lights'
	movwf	OldLights

	movf	PORTB,w		;Read pushbuttons
	iorlw   b'10000000'	;Mask Off upper bit
	xorlw	0xFF		;Invert all bits (high bits = PB held down)
	btfsc	STATUS,Z	;Check if PB released
	goto	NoPbChanges	;If so, just ignore it
	xorwf	Lights,w	;Toggle only the changed bits
	movwf	Lights		;Update the value of active lights

	;If a Light was turned OFF, clear the appropriate 'Foyer' bit
	movf	PORTB,w		;Read pushbuttons
	iorlw   b'10000000'	;Mask Off upper bit
	xorlw	0xFF		;Invert all bits (high bits = PB held down)
	andwf	OldLights,w
	btfsc	STATUS,Z
	goto	LightsDone

ToggleFoyerOff
	movf	OldLights,w	;Check which bit was turned off
	xorlw   0xFF
	andwf	Foyer,f		;Clear any bits set OFF in Foyer

LightsDone
	;Reset shutdown timer (interrupt-driven) to 30 minutes
	movlw	SHUTDOWN_TIME
	movwf	AutoShutDownTimer

NoPbChanges
	movf	PORTB,w		;Read pushbuttons
	iorlw   b'10000000'	;Mask Off upper bit
	movwf	OldPbState	;Update last known state

;NoPbChanges
	movf	Lights,w	;OR value of Lights and Foyer to determine which should be ON
	iorwf	Foyer,w
	movwf	PORTC		;Turn ON appropriate lights
	return

Delay
	movlw	0xFF
	movwf	DlyRegA
DelayA
	movlw	0xFF
	movwf	DlyRegB
DelayB
	decfsz	DlyRegB,f
	goto	DelayB
	decfsz	DlyRegA,f
	goto	DelayA

	return

	END
