// Circuit for the switching of a second bike headlight depending on the speed
// Copyright 2004 Michael Hofmann <mh21@gmx.net>.

// This code is free software; you can redistribute it and/or modify it under 
// the terms of the GNU General Public License, version 2, as published by the 
// Free Software Foundation.

// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY, to the extent permitted by law; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.

// Last Modified: mh21 Sat 07 May 2005 16:12:45 CEST (VIM)

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <avr/signal.h> 
#include <avr/eeprom.h> 
#include <avr/sleep.h> 

#define EEPROM __attribute__((section(".eeprom")))
#define NOINIT __attribute__((section(".noinit")))

// Determination of the period length ****************************************

// Last 8 Samples
volatile uint8_t samples;
// Are we in the positive of negative part of the sine wave at the input
volatile uint8_t low;
// Ticks since last period start
volatile uint8_t samplecount;

// Speed measurement *********************************************************

// Samples marked 1 when below or above speed limit values
volatile uint8_t fastsamples;
volatile uint8_t slowsamples;

// General state of the headlight ********************************************

// State of the headlight state machine
volatile uint8_t state;
// State when the button was pressed first time
volatile uint8_t key1state;

// Initial state after boot
#define statINIT    0x00
// Temporary state for selecting one of the following auto states: 
// has to go above upper frequency limit to switch on
#define statAUTOSL  0x01
// Temporary state for selecting one of the following auto states: 
// has to go below lower frequency limit to switch off
#define statAUTOFT  0x02
// Automatic mode: switched on
#define statAUTOON  0x03
// Automatic mode: switched off
#define statAUTOOFF 0x04
// Manual mode: switched on
#define statON      0x05
// Manual mode: switched off
#define statOFF     0x06

// State of the button *******************************************************
//
// Samples for the button input signal
volatile uint8_t keysamples;
// Counter for button time measurements
volatile uint16_t keycount;
// Button events for the main program
volatile uint8_t keyint;
// state of the button state machine
volatile uint8_t keystate;

// Pseudo interrupt: short button press one time
#define intKEY1     0x01
// Pseudo interrupt: long button press one time
#define intKEY1LNG  0x02
// Pseudo interrupt: short button press two times
#define intKEY2     0x04
// Pseudo interrupt: short button press, then long button press
#define intKEY2LNG  0x08

// Not pressed down
#define statNOKEY   0x00
// Button pressed down first time
#define statKEY1DN  0x01
// Button released first time
#define statKEY1UP  0x02
// Button pressed down a long time, still pressed down
#define statKEY1LNG 0x03
// Button pressed down the second time
#define statKEY2DN  0x04
// Button pressed down a long time (second press), still pressed down
#define statKEY2LNG 0x05

// EEPROM storage ************************************************************

// Counter for the averaging passes
volatile uint8_t saveleft;
// Big counter for the time measurement while averaging
volatile uint16_t savecount;
// State of the eeprom storage state machine
volatile uint8_t savestate;

// Don't save anything
#define saveNOTHING 0x00
// Save lower speed limit, while approximating speed average
#define saveLO	    0x01
// Save upper speed limit, while approximating speed average
#define saveHI      0x02
// Save lower speed limit, wait for period begin to start averaging
#define savePREPLO  0x03
// Save upper speed limit, wait for period begin to start averaging
#define savePREPHI  0x04

// Miscellaneous *************************************************************

// Limits for the automatic switching of the headlight read from eeprom
volatile uint8_t hightrigger NOINIT, lowtrigger NOINIT;

// Constants *****************************************************************

// Number of period while averaging the limit for eeprom storage
#define savebits    4
#define savenum     (1<<savebits)
// Input/output pins
#define INPUT  _BV(PB0)
#define OUTPUT _BV(PB1)
#define KEY    _BV(PB2)
// Speed limits that will be saved to the eeprom while programming
#define HIGHSPEED 27
#define LOWSPEED 23
// Frequency of the RC or external oscillator
#define QUARZFREQ 4000000
// Prescaler (4MHz/8=500kHz)
#define TIMERINT 8
// uncorrected (see below) initial value for the timer (4MHz/8*100=5kHz)
#define TIMERFREQ 100
// Electrical pole pairs of the used dynamo
#define DYNAMOPOLES 13
// Wheel circumference (m, 26 Zoll=2.100)
#define WHEELLEN 2.100
// Time for the distinction between normal/long button presses (in timer passes, here 1s)
#define KEYLONG 1*QUARZFREQ/(TIMERFREQ*TIMERINT)
// Time for the initial start state to compensate gold cap loading (if any) in the headlight
// (in timer passes, here 60s)
#define INITTIME 60*QUARZFREQ/(TIMERFREQ*TIMERINT)

// Trigger frequency
#define TRIGGERFREQ(speed) ((speed*DYNAMOPOLES)/(3.6*WHEELLEN))
// Trigger timerticks: speed = (3.6*WHEELLEN*QUARZFREQ)/(DYNAMEOPOLES*TRIGGER*TIMERINT*TIMERFREQ)
uint8_t hightrigger_e EEPROM = QUARZFREQ/(TIMERINT*TIMERFREQ*TRIGGERFREQ(HIGHSPEED));
uint8_t lowtrigger_e EEPROM  = QUARZFREQ/(TIMERINT*TIMERFREQ*TRIGGERFREQ(LOWSPEED));
// Counter for the initial state duration
volatile uint32_t initcount = INITTIME;

// Time in interrupt until timer register is set (look at the lst output of the compiler)
#define TIMERFUZZ (40/TIMERINT)
/*
 *  4 clock cycles for interrupt acknowledge, 
 *  2 cycles for the jump from the interrupt vector
 *  2 134:	1f 92       	push	r1
 *  2 136:	0f 92       	push	r0
 *  1 138:	0f b6       	in	r0, 0x3f	; 63
 *  2 13a:	0f 92       	push	r0
 *  1 13c:	11 24       	eor	r1, r1
 *  2 13e:	2f 93       	push	r18
 *  2 140:	3f 93       	push	r19
 *  2 142:	4f 93       	push	r20
 *  2 144:	5f 93       	push	r21
 *  2 146:	6f 93       	push	r22
 *  2 148:	7f 93       	push	r23
 *  2 14a:	8f 93       	push	r24
 *  2 14c:	9f 93       	push	r25
 *  2 14e:	af 93       	push	r26
 *  2 150:	bf 93       	push	r27
 *  2 152:	ef 93       	push	r30
 *  2 154:	ff 93       	push	r31
 *  1 156:	80 ed       	ldi	r24, 0xA1	; 161
 *  1 158:	82 bf       	out	0x32, r24	; 50
 *  ======
 *  40 cycles --> 40/8 = 5 timer increments
 */

void keys() {
    // State machine for the key processing
    /* /------>NOKEY<---------------------\
     * |         |             key=0xff   |
     * |         |key=0x00                |
     * |         |     Time               |
     * |       KEY1DN---------->KEY1LNG---+
     * |         |     int1LNG            |
     * |     int1|key=0xff                |
     * |  Time   V                        |
     * \-------KEY1UP                     |
     *           |                        |
     *           |key=0x00                |
     *           V     Time               |
     *         KEY2DN---------->KEY2LNG---/
     *           |     int2LNG
     *       int2|key=0xff
     *           V
     *         NOKEY
     */
    switch (keystate) {
	case statNOKEY:
	    if (keysamples == 0x00) {
		keystate = statKEY1DN;
		keycount = KEYLONG;
	    }
	    break;
	case statKEY1DN:
	    if (!keycount) {
		keystate = statKEY1LNG;
		keyint |= intKEY1LNG;
	    } else if (keysamples == 0xff) {
		keystate = statKEY1UP;
		keycount = KEYLONG;
		keyint |= intKEY1;
	    }
	    break;
	case statKEY1LNG:
	case statKEY2LNG:
	    if (keysamples == 0xff) {
		keystate = statNOKEY;
	    }
	    break;
	case statKEY1UP:
	    if (!keycount) {
		keystate = statNOKEY;
	    } else if (keysamples == 0x00) {
		keystate = statKEY2DN;
		keycount = KEYLONG;
	    }
	    break;
	case statKEY2DN:
	    if (!keycount) {
		keystate = statKEY2LNG;
		keyint |= intKEY2LNG;
	    } else if (keysamples == 0xff) {
		keystate = statNOKEY;
		keyint |= intKEY2;
	    }
	    break;
    }
}

// 5 kHz with 4MHz clock frequency: 800 cycles maximum
SIGNAL (SIG_OVERFLOW0) {
    // Reinitialization of the timer
    TCNT0 = 256 - TIMERFREQ + TIMERFUZZ;

    // Sine wave input sampling
    samples = (uint8_t)(samples << 1) | ((PINB & INPUT) > 0);
    // Button input sampling
    keysamples = (uint8_t)(keysamples << 1) | ((PINB & KEY) > 0);

    // Are we in the positive of negative part of the sine wave at the input
    if (low) {
	if (samples == 0xff) {
	    low = 0;
	}
    } else {
	// Start of a new period
	if (samples == 0x00) {
	    low = 1;
	    // Sample the speed state in regard to the limits
	    slowsamples = (uint8_t)(slowsamples << 1) | (samplecount > lowtrigger);
	    fastsamples = (uint8_t)(fastsamples << 1) | (samplecount < hightrigger);
	    samplecount = 0;
	    if ((savestate == savePREPLO) || (savestate == savePREPHI)) {
		saveleft = savenum;
		savestate -= 2;
	    } else if (savestate) saveleft--;
	}
    }

    // State machine for the button
    keys();

    if (keyint & intKEY1) {
	// State changes for the first button press
	key1state = state;
	switch (state) {
	    case statINIT:
		state = statAUTOSL;
		break;
	    case statAUTOON:
		state = statOFF;
		break;
	    case statAUTOOFF:
		state = statON;
		break;
	    case statON:
		state = statOFF;
		break;
	    case statOFF:
		state = statON;
		break;
	}
    } else if (keyint & intKEY2) {
	// State changes for a second button press
	switch (key1state) {
	    case statAUTOON:
		state = statON;
		break;
	    case statAUTOOFF:
		state = statOFF;
		break;
	    case statON:
		state = statAUTOSL;
		break;
	    case statOFF:
		state = statAUTOFT;
		break;
	}
    } else if (keyint & intKEY2LNG) {
	// State changes for a second long button press
	switch (state) {
	    case statAUTOON:
	    case statON:
		savestate = savePREPHI;
		break;
	    case statAUTOOFF:
	    case statOFF:
		savestate = savePREPLO;
		break;
	}
    } else {
	// State changes for initial state timeout and speed changes
	switch (state) {
	    case statINIT:
		if (!initcount) {
		    state = statAUTOSL;
		}
		break;
	    case statAUTOSL:
		if (fastsamples == 0xff) {
		    state = statAUTOON;
		} else {
		    state = statAUTOOFF;
		}
		break;
	    case statAUTOFT:
		if (slowsamples == 0xff) {
		    state = statAUTOOFF;
		} else {
		    state = statAUTOON;
		}
		break;
		
	    case statAUTOON:
		if (slowsamples == 0xff) {
		    state = statAUTOOFF;
		}

		break;
	    case statAUTOOFF:
		if (fastsamples == 0xff) {
		    state = statAUTOON;
		}
		break;
	}
    }
    keyint = 0;

    // Switch the headlight
    switch (state) {
	case statON:
	case statAUTOON:
	    PORTB |= OUTPUT;
	    break;
	case statINIT:
	case statOFF:
	case statAUTOOFF:
	    PORTB &= ~OUTPUT;
	    break;
    }

    // Save new limit values to eeprom memory
    if (((savestate == saveLO) || (savestate == saveHI)) && !saveleft) {
	// Round it
	savecount += savenum/2;
	savecount >>= savebits;
	// Reasonable value?
	if (savecount < 256) {
	    // Save the right limit
	    eeprom_write_byte (savestate == saveHI ? &hightrigger_e : &lowtrigger_e, savecount);
	}
	//TODO: Actually use the new values now, not only after restart
	savestate = saveNOTHING;
    }

    // Count them all
    if (samplecount < 0xff) samplecount++; // 0xff means much too slow
    if (keycount > 0) keycount--;
    if (initcount > 0) initcount--;
    if (saveleft) savecount++;
}

int main (void) {
    // Read the limit values from eeprom memory
//  lowtrigger  = QUARZFREQ/(TIMERINT*TIMERFREQ*TRIGGERFREQ(LOWSPEED));
//  hightrigger = QUARZFREQ/(TIMERINT*TIMERFREQ*TRIGGERFREQ(HIGHSPEED));
    hightrigger = eeprom_read_byte (&hightrigger_e);
    lowtrigger  = eeprom_read_byte (&lowtrigger_e);
    
    DDRB = OUTPUT;			// Activate output pins
    PORTB = KEY;			// Pullup resistors for button

    TIMSK = _BV(TOIE0);			// Activate overflow interupt 0
    TCCR0 = _BV(CS01);			// Timer with CK/8=500kHz

    sei();				// Activate interrupts

    set_sleep_mode (SLEEP_MODE_IDLE);

    for (;;) {
	sleep_mode ();
    }
}
