/*
	Sump pump alarm
	
	Copyright (c) 2005 by Timothy J. Weber.
	
	When powered up, sounds an alarm.

	The alarm consists of some "notes" and "rests" played in sequence as output pulses on GP1.

	When an input on GP0 is pulled low, the chip goes into a coma - it sleeps
	and never wakes up (until it's power-cycled).
	
	Requires: HI-TECH PICC Lite.
*/

#include <pic.h>

__CONFIG(UNPROTECT & BORDIS & MCLRDIS & PWRTEN & WDTDIS & INTIO);

#define OUT_PIN  1
#define OUT_BIT  (1 << OUT_PIN)

#define IN_PIN  0
#define IN_BIT  (1 << IN_PIN)

#define NOTE_MASK  0x80

// Event data for the note/rest sequence.
// Values are durations in tenths of a second,
// with the high bit set for notes, cleared for rests.
// A 0 byte ends the sequence.
// So, each byte represents a delay (voiced or silent) of 0-12.7 seconds.
#define SEQUENCE_OFFSET  0
__EEPROM_DATA(50, 4 | NOTE_MASK, 3, 3 | NOTE_MASK, 4, 1 | NOTE_MASK, 1, 1 | NOTE_MASK);
__EEPROM_DATA(1, 1 | NOTE_MASK, 110, 110, 0, 0, 0, 0);

// Snooze pattern:
#define SNOOZE_OFFSET  (2 * 8)
__EEPROM_DATA(1, 1 | NOTE_MASK, 0, 0, 0, 0, 0, 0);
#define SNOOZE_END_OFFSET  (SNOOZE_OFFSET + 2)

// Global variables

// The offset of the next event in EEPROM data space.
unsigned char nextEvent;

// The remaining delay for the current event.
unsigned char delay;

// Functions

void main()
{

// Initialize.

	// Calibrate the internal oscillator.
	OSCCAL = _READ_OSCCAL_DATA();
	
	// Set up the output pin for digital output.
	TRISIO = ~OUT_BIT;
	
	// Input pin has a weak pull-up; all others don't.
	OPTION = 0x7F;  // /GPPU = 0
	WPU = IN_BIT;
	
	// Initialize output to 0.
	// This quiets the output pin and ties the unused pins to ground, saving power.
	GPIO = 0;
	
	// Interrupt when the input pin changes.
	IOCB = IN_BIT;
	GPIE = 0;

	CMCON = 0x07;  // Turn Off Comparator Peripheral
	
	// Set up Timer1 to interrupt ~10 Hz.
	// (Also relies on adding a constant to the timer value in the ISR.)
	TMR1IE = 1;
	PIE1 = 1;
	T1CON |= 0x30;  // Prescale 8:1
	
	TMR1ON = 1;
	PEIE = 1;
	
	// Initialize globals.
	nextEvent = SEQUENCE_OFFSET;
	delay = 1;  // So we pick up the next one immediately.

// Run.

	GIE = 1;  // Enable all interrupts.

	while (1) {
	}
}

// Go to sleep and never wake up.
// Well, until we're power-cycled.
void Coma()
{
	GPIO = 0;
	PEIE = 0;
	PIE1 = 0;
	GPIE = 0;
	asm("SLEEP");
}

void DoNextEvent()
{
	// Get the next event value.
	delay = EEPROM_READ(nextEvent);
	
	// If it's zero, start over.
	if (delay == 0) {
		if (nextEvent == SNOOZE_END_OFFSET)
			Coma();
		
		delay = EEPROM_READ(0);		
		nextEvent = SEQUENCE_OFFSET;
	}
	
	// Set the output according to the high bit of the delay value.
	if (delay & NOTE_MASK)	
		GPIO |= OUT_BIT;
	else
		GPIO &= ~(OUT_BIT);
		
	// Clear the high bit of the delay value.
	delay &= ~(NOTE_MASK);
	
	// Set up to read the next event.
	nextEvent++;
}

void interrupt InterruptServiceRoutine()
{
	if (GPIF) {
		// The input pin has changed.
		if (!(GPIO & IN_BIT) && nextEvent < SNOOZE_OFFSET) {
			// Set up to play the "snoozing" confirmation sequence, then go into a coma.
			delay = 1;
			nextEvent = SNOOZE_OFFSET;
		}
	}
		
	if (TMR1IF) {
		// Mark the delay, and do the next event if we're done.
		if (--delay == 0)
			DoNextEvent();
		
		// Add to the timer so we fire every ~10,000 cycles instead of 65 K.
		TMR1H += 0xCF;
		
		TMR1IF = 0;
	}
}
