
#pragma PL (60)
#pragma PW (120)
#pragma OT (3)
#pragma ROM (SMALL)

#include <reg751.h>

typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;

#define TRUE 		1
#define FALSE 		0
#define ON		0
#define OFF		1
#define FLASH		2

/* parameters */
#define ONE_OVER_DELTA		128
#define	ONE_SECOND		14

/* timers */
/* 1 cpu cycle = 1.085uS */
/* 1 tic = 0xffff cpu cycles */
/* 1 "second" = 14 tics = 995.5uS */
#define MAX_BATT_PERIOD		5530	/* 60ms period, 0.9 volts */
#define MIN_BATT_PERIOD 	430	/* 4.67ms period, 10 volts */
#define DEAD_MAN_TIMEOUT 	2712	/* 45 minutes */
#define MAINTAINANCE_PERIOD 	500	/* 1 pulse in 500 Seconds */
#define INITIALISATION_TIME	20

/* errors */
#define BATTERY_VOLTAGE_OUT_OF_RANGE	0x01
#define BATTERY_CHARGED			0x02
#define DM_TIMEOUT			0x03

/* states */
#define	FAULT		0x01
#define INITIALIZING	0x02
#define CHARGING	0x03
#define DONE		0x04

/* global variables */
byte		tic;
byte		state;
word		seconds;
word		this_period;
word		last1;
word		last2;
word		last3;
word		last4;
word		last5;
word		last6;
word		last7;
word		valley;

sbit 	comp_out 	= 0x90;		/* P1.0 */
sbit 	clear_cap	= 0x93;		/* P1.3 */
sbit 	charge		= 0x97;		/* P1.7 */
sbit 	fault_led	= 0x80;		/* P0.0 */
sbit	charge_led	= 0x82;		/* P0.2 */


word
measure_batt(void) {
	byte	tic_now;
	word	interval;

	tic_now = tic;

	interval = 0;
	clear_cap = FALSE;
	while(!comp_out && tic==tic_now)
		interval++;
	clear_cap = TRUE;
	return (interval);
}

byte
check_limits(
	word	batt_period
	 ) {

	if ((batt_period > MIN_BATT_PERIOD)&&(batt_period < MAX_BATT_PERIOD)) {
		return(FALSE);
	}
	else {
		return(BATTERY_VOLTAGE_OUT_OF_RANGE);
	}
}

word
filter (
	word	last0
	) {

	word temp1, temp2, temp3, temp4;
	word result1, result2;

	temp1 = ((last0 / 2) + (last1 / 2));
	temp2 = ((last2 / 2) + (last3 / 2));
	temp3 = ((last4 / 2) + (last5 / 2));
	temp4 = ((last6 / 2) + (last7 / 2));

	result1 = ((temp1 / 2) + (temp2 / 2));
	result2 = ((temp3 / 2) + (temp4 / 2));

	last7 = last6;
	last6 = last5;
	last5 = last4;
	last4 = last3;
	last3 = last2;
	last2 = last1;
	last1 = last0;

	return((result1 / 2) + (result2 / 2));
}

byte
delta_peak (
	word	period
	) {

	if (period < valley) 
		valley = period;
	if (period > (valley + (valley/ONE_OVER_DELTA)))
		return (BATTERY_CHARGED);
	else
		return (FALSE);

}
byte
watchdog (
	word	now
	)
{
	if (now < DEAD_MAN_TIMEOUT)
		return (FALSE);
	else
		return (DM_TIMEOUT);
}

void
charger ( void ) {
	
	this_period = measure_batt();
	this_period = filter(this_period);
	
	switch (state) {
		case FAULT: {
			if (!check_limits(this_period)) {
				seconds = 0;
				state = INITIALIZING;
			}
			else
				state = FAULT;
			break;
		}
		case INITIALIZING: {
			if (check_limits(this_period))
				state = FAULT;
			else {
				charge = TRUE;
				if (seconds < INITIALISATION_TIME) 
					state = INITIALIZING;
				else {
					valley = 0xffff;
					state = CHARGING;
				}
			}
			break;
		}
		case CHARGING: {
			if (check_limits(this_period))
				state = FAULT;
			else {
				if (!watchdog(seconds)) {
					if (delta_peak(this_period)) {
						state = DONE;
						seconds = 0;
					}
					else {
						state = CHARGING;
						charge = TRUE;
					}
				}
				else {
					state = DONE;
					seconds = 0;
				}
			}
			break;
		}
		case DONE: {
			if (check_limits(this_period))
				state = FAULT;
			else {
				if(seconds < MAINTAINANCE_PERIOD) {
					charge = TRUE;
					seconds = 0;
				}
				state = DONE;
			}
			break;
		}
	}
}

void
leds ( void ) {

	switch (state) {
		case FAULT: {
			charge_led = OFF;
			fault_led = ON;
			break;
		}
		case INITIALIZING: {
			charge_led = ON;
			fault_led = OFF;
			break;
		}
		case CHARGING: {
			charge_led = ON;
			fault_led = OFF;
			break;
		}
		case DONE: {
			charge_led = !charge_led;
			fault_led = OFF;
			break;
		}
	}
}


/* Timer 0 interrupt */
void 
timer0(void) interrupt 1 {

	tic++;
}

void
main()
{

	/* initialize pins */
	charge=FALSE;
	charge_led = OFF;
	fault_led = OFF;

	/* initialize timer */
	TR=1;
	CT=0;
	GATE=0;
	RTH=0;
	RTL=0;
	IT0 = TRUE;
	ET0=1;
	EA=TRUE;

	/* initialize globals */
	tic=0;
	seconds = 0;
	valley = 0xffff;
	state = FAULT;

	/* main program scheduler */
	while(1) {
		switch (tic) {
			case 0: {
				charger();
				while (tic < 1);
				break;
			} 
			case 7: {
				leds();
				while (tic < 8);
				break;
			}
			case ONE_SECOND: {
				tic = 0;
				seconds++;
				break;
			}
		}
	}

}

