;
; Fursuit clock
;
; Provides a simple way to keep track of time while in suit.
;
; (c) 2006, 2007 David Cooke.
;
; This source code is licensed under a Creative Commons Attribution
; Share Alike Licence.
;
; You are free:
;	* to copy, distribute, and perform the work
;	* to make derivative works
;
; Under the following conditions:
;	* Attribution.  You must give the original author credit.
;	* Share alike.  If you alter, transform, or build upon this
;	  work, you may distribute the resulting work only under a licence
;	  identical to this one.
;
; * For any reuse or distribution, you must make clear to others the
;   licence terms of this work.
; * Any of these conditions can be waived if you get permission from
;   the copyright holder.
; * Nothing in this license impairs or restricts the author's moral rights.
;
; For more information see: http://creativecommons.org/licenses/by-sa/2.0/uk/
;

	list	p=12f683

#include p12f683.inc

;
; Device configuration
;
	; Failsafe clock monitor on
	; Internal/External switch over on
	; XT external oscillator
	;
#define	clk_cfg		_FCMEN_ON & _IESO_ON & _XT_OSC

	; Code protect off
	; Data protect off
	;
#define	prot_cfg	_CPD_OFF & _CP_OFF

	; Brown out detect except when sleeping
	; Watchdog timer off
	; Master clear off
	; Power-up timer enabled
	;
#define	rst_cfg		_BOD_NSLEEP & _WDT_OFF & _MCLRE_OFF & _PWRTE_ON

	__CONFIG	clk_cfg & prot_cfg & rst_cfg

;
; Timer configuration
;
; Watchdog timer:
;	clk = 31khz
;	prescale = 1:65536
;	period = 2.11 seconds
;
; Timer 0
;	clk = 1MHz
;	prescale = 1:64
;	count = 125
;	tick = 125Hz



	;
	; I/O bits
	;
#define	sound	GPIO,0		; Pin connected to piezo sounder
#define	vibrate	GPIO,1		; Pin connected to pager motor
#define	debug	GPIO,2		; Debug output
#define	button	GPIO,3		; Button input (active low)

;
; -- RAM usage --
;
	cblock	020h

	temp			; For tight loops
	go			; Start chiming
	ctrl			; Current control action

	hours			; Hours device has been running
	minutes			; Minutes device has been running
	seconds			; Seconds device has been running
	fifths			; Fifths of a second
	ticks			; Ticks

	btnbounce		; Button de-bounce timer
	btnstate		; De bounced button state
	btncount		; Count of button pressed in fifths

	chimehours		; Number of hours to chime
	chimequarters		; Number of quarters to chime
	chimemins		; Number of minutes to chime

	chimeFreq		; Frequency of chime tone
	chimeSpeed		; Speed of motor
	chimeLength		; Duration of chime
	chimeFreqTmp		; Working register for frequency
	chimeSpeedTmp		; Working register for speed

	endc

	;
	; These general purpose registers are in the same location for each
	; register page.  This is useful for the interrupt handler storage
	; which can be invoked with the page select bits in any state.
	;
	cblock	0x70

	intW		; Working register 
	intS		; Status register
	intP		; PC Latch High order bits

	endc

;
; -- Program code --
;

	org	H'0000'
	banksel	GPIO
	clrf	GPIO		; Set the outputs low before enabling drivers.
	goto	Start



	; Interrupt entry point.
	;
	; NOTE: Swapf has to be used in place of movf as movf conditionally
	; sets the zero flag of the status register.
	;
	org	0x0004
Int	
	movwf	intW		; Save working register
	swapf	STATUS, w	; Status:0-3 -> w:7-4, s:7-4 -> w:0-3
	clrf	STATUS		; Clear the status register, access page 0
	movwf	intS		; Save the nibble swapped status register
	movf	PCLATH, w	; Move the PC High Latch register value
	movwf	intP		; Save the PC High Latch

	bsf	debug		; Set the interrupt debug bit

	; Check for timer0 interrupt
	;
	btfsc	INTCON,T0IF
	goto	Tick
TickRet
	;
	; Restore registers
	;
IntRet
	bcf	debug		; Clear the interrupt debug bit

	movf	intP, w		; Fetch the old PC High Latch value
	movwf	PCLATH		; Restore it
	swapf	intS, w		; Fetch the status register value and unswap
	movwf	STATUS		; Restore the flags and register pages
	swapf	intW, f		; Swap the nibbles in the saved version of W
	swapf	intW, w		; Restore the working register
	retfie			; Return from interrupt, and enable interrupts
	




	; Tick inturrupt is called every 125th second.
Tick	;
	; Increment the time.
	;
	incf	ticks,f
	;
	; Prepare for the next interrupt
	;
	movlw	-.125
	movwf	TMR0
	bcf	INTCON,T0IF

	;
	; If the bounce timer is running ignore the button state.
	;
	tstf	btnbounce
	bz	BtnTest
	decf	btnbounce,f
	goto	Fifths

BtnTest	;
	; Compare the state of the button with the recorded state.
	; If it has changed start the debounce timer.
	;
	movlw	0x08
	andwf	GPIO,w		; pressed w=0, if not w=8
	xorwf	btnstate,f	; z set if state is the same
	movwf	btnstate	; state now 0=pressed, 8=not
	bz	Fifths
	movlw	.25		; Ignore button state changes for 200ms
	movwf	btnbounce

Fifths	;
	; On the 25th tick increment the fifths.
	;
	movlw	.25
	subwf	ticks,w
	bnc	TickRet		; ticks < 25
	clrf	ticks
	incf	fifths,f

	; Check to see if the button is pressed.
	;
	btfsc	btnstate,3
	goto	BtnUp
BtnDown
	incf	btncount,f
	goto	BtnDone

BtnUp	; Don't overwrite an existing value.
	;
	tstf	ctrl
	bnz	BtnDone
	movfw	btncount
	movwf	ctrl
	clrf	btncount

BtnDone
	; On the 5th fifth increment the seconds.
	;
	movlw	.5
	subwf	fifths,w
	bnc	TickRet		; fifths < 5
	clrf	fifths
	incf	seconds,f

	; On the 60th second increment the minutes.
	;
	movlw	.60
	subwf	seconds,w
	bnc	TickRet		; seconds < 60
	clrf	seconds
	incf	minutes,f

	; Work out how many quarters and mins have passed.
	; If it is a whole number of quarters, chime them.
	;
	call	Quarters
	tstf	chimemins
	skpnz
	bsf	go,1

HrTst	; On the 60th minute increment the hours.
	;
	movlw	.60
	subwf	minutes,w
	bnc	TickRet		; minutes < 60
	clrf	minutes
	clrf	chimemins
	clrf	chimequarters
	incf	hours,f

	; On the 12th hour zero the hours.
	;
	movlw	.12
	subwf	hours,w		; hours < 12
	skpnc	
	clrf	hours

	; Chime the hours
	;
	movfw	hours
	movwf	chimehours
	bsf		go,1
	goto	TickRet



Delay	; Subroutine to produce a delay.
	;
	movwf	chimeLength
	clrf	chimeFreq
	clrf	chimeSpeed
	;
	; Run into chime with no sound or vibe.

Chime	;
	; Subroutine to produce a tone and a vibration
	;
	; Ballpark tuning:
	;	setting	freq
	;	0	quiet
	;	~5	10kHz
	;	~113	440Hz
	;
	;	-113	294Hz
	;
	; Speed 0x80 = 50%
	;
	; Duration
	; 	setting	length
	;	200	1s
	;	20	100ms
	;	1	5ms
	;	
	movfw	chimeFreq
	movwf	chimeFreqTmp
	movfw	chimeSpeed
	movwf	chimeSpeedTmp
	clrf	temp

ChimeSound
	; If the freq is zero, make no noise.
	;
	tstf	chimeFreq
	bz	ChimeVibe
	;
	; Increment the frequency counter, when it rolls over
	; complement the sounder io bit and reload.
	;
	incfsz	chimeFreqTmp,f
	goto	ChimeVibe
	movlw	0x01
	xorwf	GPIO,f
	movfw	chimeFreq
	movwf	chimeFreqTmp

ChimeVibe
	; If the speed is zero, make no noise.
	;
	tstf	chimeSpeed
	bz		ChimeLen
	;
	; Increment the speed counter, when it rolls over
	; complement the motor io bit and reload complement the
	; delay when the bit is set (motor running).
	;
	incfsz	chimeSpeedTmp,f
	goto	ChimeLen
	movlw	0x02
	xorwf	GPIO,f
	movfw	chimeSpeed
	movwf	chimeSpeedTmp
	btfsc	vibrate
	comf	chimeSpeedTmp,f

ChimeLen
	; Decrement the tight loop counter.
	;
	decfsz	temp,f
	goto	ChimeSound

	decfsz	chimeLength,f
	goto	ChimeSound

	; Set the outputs low, and return.
	;
	clrf	GPIO
	return


Quarters
	;
	; Subroutine to select the correct number of quarter hours
	; and minutes.
	;
	movlw	.15
	subwf	minutes,w
	bnc	Qtr0
	movwf	chimemins

	movlw	.15
	subwf	chimemins,w
	bnc	Qtr1
	movwf	chimemins

	movlw	.15
	subwf	chimemins,w
	bnc	Qtr2
	movwf	chimemins
	movlw	3
	movwf	chimequarters
	return

Qtr0	; Less than 15 minutes.
	;
	movfw	minutes
	movwf	chimemins
	return

Qtr1	; Indicate quarter past.
	;
	movlw	1
	movwf	chimequarters
	return

Qtr2	; Indicate half past.
	;
	movlw	2
	movwf	chimequarters
	return



Start	;
	; Initialise device
	;

	;
	; Set the oscillator configuration
	;	Internal oscillator is 31kHz
	;	Clock source is external (as defined by config)
	;
	banksel	OSCCON
	clrf	OSCCON

	;
	; Configure 0,1,2 as digital outputs, 3 as digital input.  Turn
	; off the comparator module.
	;
	banksel	ANSEL
	clrf	ANSEL

	banksel	CMCON0
	movlw	0x07
	movwf	CMCON0

	banksel	TRISIO
	movlw	0x28
	movwf	TRISIO

	; Program timer 0 for 125th second ticks
	; - Pull-ups disabled
	; - Interrupt on falling edge (INT not used)
	; - Timer0 source is internal clock (1MHz), rising edge
	; - Timer0 has prescaler (not WDT), divide by 64
	;
	banksel	OPTION_REG
	movlw	0x05
	movwf	OPTION_REG

	banksel	TMR0
	movlw	-.125
	movwf	TMR0

	;
	; Initialise the timers
	;
	banksel	ticks
	clrf	ticks
	clrf	fifths
	clrf	seconds
	clrf	minutes
	clrf	hours

	clrf	chimehours
	clrf	chimequarters
	clrf	chimemins

	clrf	btnbounce
	clrf	btncount
	clrf	btnstate
	bsf		btnstate,3
	clrf	ctrl
	clrf	go

	;
	; Power-up tone
	;
	movlw	-.63		; C5
	movwf	chimeFreq
	movlw	0x80
	movwf	chimeSpeed
	movwf	chimeLength
	call	Chime

	movlw	0x10
	call	Delay

	movlw	-.38		; A5
	movwf	chimeFreq
	movlw	0xC0
	movwf	chimeSpeed
	movlw	0x40
	movwf	chimeLength
	call	Chime

	; Enable interrupts
	;
	bsf	INTCON,T0IE
	bsf	INTCON,GIE


Loop	;
	; Main loop
	;

	;
	; Check for button activity
	;
	tstf	ctrl
	bz	ChkGo

	movlw	-.16		; C7
	movwf	chimeFreq
	movlw	0xff
	movwf	chimeSpeed
	movlw	.40
	movwf	chimeLength
	call	Chime

	; If the button is pressed for 4 seconds, go into low
	; power mode.
	;
	movlw	.20
	subwf	ctrl,w
	bc		Hibernate

	clrf	ctrl
	movfw	hours
	movwf	chimehours
	call	Quarters
	bsf		go,1
	goto	Loop

ChkGo	;
	; Check for start chiming
	;
	tstf	go
	bz	Loop

ChkHrs
	;
	; Check for hour times
	;
	tstf	chimehours
	bz	ChkQtrs
	decf	chimehours,f

	movlw	-.63		; C5
	movwf	chimeFreq
	movlw	0xff
	movwf	chimeSpeed
	movlw	.200
	movwf	chimeLength
	call	Chime

	movlw	.100
	call	Delay

	goto	Loop

ChkQtrs
	;
	; Check for quarter chimes
	;
	tstf	chimequarters
	bz	ChkMins
	decf	chimequarters,f

	movlw	-.32		; C6
	movwf	chimeFreq
	movlw	0xff
	movwf	chimeSpeed
	movlw	.100
	movwf	chimeLength
	call	Chime

	movlw	.50
	call	Delay

	movlw	-.38		; A5
	movwf	chimeFreq
	movlw	0xe0
	movwf	chimeSpeed
	movlw	.100
	movwf	chimeLength
	call	Chime

	movlw	.50
	call	Delay

	goto	Loop

ChkMins
	;
	; Check for minute chimes
	;
	tstf	chimemins
	bz	ChkDone
	decf	chimemins,f

	movlw	-.48		; F5
	movwf	chimeFreq
	movlw	0xf0
	movwf	chimeSpeed
	movlw	.50
	movwf	chimeLength
	call	Chime

	movlw	.50
	call	Delay
	goto	Loop

ChkDone
	clrf	go
	goto	Loop


Tone
	; Test tone
	;
	clrf	ctrl
	movlw	-.75		; A?
	movwf	chimeFreq
	movlw	0x80
	movwf	chimeSpeed
T2
	movlw	0xff
	movwf	chimeLength
	call	Chime
	tstf	ctrl
	bz	T2
	goto	Loop



Hibernate
	;
	; Enter low power mode - power down tone
	;
	movlw	-.38		; A5
	movwf	chimeFreq
	movlw	0xC0
	movwf	chimeSpeed
	movlw	0x40
	movwf	chimeLength
	call	Chime

	movlw	0x10
	call	Delay

	movlw	-.63		; C5
	movwf	chimeFreq
	movlw	0x80
	movwf	chimeSpeed
	movwf	chimeLength
	call	Chime

	; Disable interrupts
	;
	bcf	INTCON,GIE

	; Drop to 31kHz clock
	;
	banksel	OSCCON
	bsf	OSCCON, SCS

	;
	; Turn on the watchdog timer, 1 second snoozes
	;
	banksel	WDTCON
	movlw	0x15
	movwf	WDTCON

Snooze
	clrwdt
	sleep

	;
	; Check button
	;
	btfsc	button
	goto	Snooze

	; Turn off watchdog timer
	;
	movlw	0x10
	movwf	WDTCON
	clrwdt

	goto	Start

	end

; EoF
