/*
Das Programm schaltet per Taster einen ATtiny25 (45/85) ein und aus.
Der Taster wird dabei aus einer Kombination von Hard- und Software
entprellt, wodurch der Einschaltvorgang nur ca. 2ms benötigt.

Nach dem Anlegen der Versorgungsspannung gelangt der µC nach ein
paar Befehlen zur Initialisierung in den Power-Down-Modus mit einer Stromaufnahme
von <1µA und wartet auf einen Interrupt, der ihn aufweckt.

PB4 wird im Ruhezustand über den internen pullup-Widerstand (ca. 50k) 
auf '1' Pegel (VCC) gehalten. Beim Betätigen des Tasters wird der Pegel an PB4 über
ein RC-Glied (10k || 100nF) gefiltert gegen GND geschaltet.

Sobald die Schaltschwelle von PB4 zum Erkennen eines '0'-Pegels überschritten wird, 
wird der PinChange-Interrupt ausgelöst und der µC arbeitet mit voller Gechwindigkeit
(hier 8MHz interner RC-Oszillator). Die Entladung des Kondensators wird in der PCINT-Routine 
beschleunigt, bis er GND Potential erreicht hat. Dadurch wird die Eingangshysterese auf 
nahezu Vcc erhöht. Ferner wird ein Flag gesetzt: 'abschalten = 1;'

'abschalten' wird beim Aufwecken des µC gleich wieder gelöscht, sodass es unwirksam bleibt. Ist der µC
jedoch schon aktiv, wird in main() bei 'abschalten != 0' der Power-Down-Modus des µC eingeleitet.

Beim Öffnen des Tasters wird PB4 zunächst über den internen pullup-Widerstand nach VCC gezogen.
Ab Überschreiten des '1'-Pegels wird der Kondensator aktiv aufgeladen, bis VCC erreicht ist.


siehe Schaltbild:
In der einfachsten Variante, wird lediglich der Pegel von PB3 beim Einschalten auf '0' und 
beim Ausschalten auf '1' gesetzt. Damit kann eine einzelne LED oder auch ein Schaltregler IC
aktiviert werden.

Am Ausgang PB0 wird zusätzlich ein PWM-Signal im eingeschalteten Zustand ausgegeben, mit welchem
über eine Ladungspumpe ein N-Kanal MOSFET höhere Ströme von VCC schalten kann. Alternativ können auch 
höhere Versorgungsspannungen über Ladungspumpe+MOSFET geschaltet werden. Es empfiehlt sich dabei,
die Ausgangsspannung per ADC zu überwachen, um bei Überlast den µC abzuschalten.

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-03-18
*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <avr/sleep.h>


#define BIT(x)	(1<<x)

#define EIN_AUS_TASTER	4	        // PortB 4
#define EIN_AUS_SIGNAL	3	        // PortB 3 (ggf. mit LED)
#define PWM_OUT		    0	        // PortB 0
    
volatile int8_t timer_flag, abschalten;

void kurz_warten(void)		        // darf nicht wegoptimiert werden
{
volatile uint8_t n = 0xff;	        // daher als volatile anlegen
  while(n--);
}

ISR(TIMER0_COMPA_vect)              // ca. 3,9kHz
{
static int8_t delay;
  delay++;
  if(delay >= 39) {                 // Teiler / 39
    delay = 0;
    timer_flag = 1;	                // ca. alle 10ms
  }
}
void sync(void )
{
  timer_flag = 0;
  while(!timer_flag);
}

void warten_10ms(uint32_t zeit)     // in 10ms Stufen
{
  while(zeit) {
    zeit--;
    sync();
  }
}

int16_t lese_adc(int8_t kanal)
{
  ADMUX = BIT(REFS2) + BIT(REFS1) + kanal;// mit 2,56 als Vref, rechtsbuendig
  ADCSRA |= BIT(ADSC);			    // starten
  while(ADCSRA & BIT(ADSC));	    // warten
  return(ADC);    			        // fertig
}


// Interrupt vom Ein-Aus-Taster
ISR (PCINT0_vect)
{
  PCMSK &= ~BIT(EIN_AUS_TASTER);	// sperren
  if(PINB & BIT(EIN_AUS_TASTER)) {	// steigende flanke
    PORTB |= BIT(EIN_AUS_TASTER);	// pullup = ein
    DDRB  |= BIT(EIN_AUS_TASTER);	// und ausgang aktiv auf 1
    kurz_warten();			        // kondensator aufladen
  } else {
    abschalten = 1;			        // abschalten in main() erzwingen
    PORTB &= ~BIT(EIN_AUS_TASTER);	// pullup = aus
    DDRB  |= BIT(EIN_AUS_TASTER);	// und ausgang aktiv auf 0
    kurz_warten();			        // kondensator entladen
  }
  DDRB  &= ~BIT(EIN_AUS_TASTER);	// nur noch pullup
  PORTB |= BIT(EIN_AUS_TASTER);		// pullup = ein
  GIFR = 0x20;				        // flag löschen
  PCMSK |= BIT(EIN_AUS_TASTER);		// zulassen
}

void starte_adc(void)
{
  ADCSRA = BIT(ADEN) + 0x07;	    // einschalten mit Teiler /128
}

void init_interrupts(void)
{
  timer_flag = 0;
  TCCR0A = BIT(COM0A1) + BIT(WGM01) + BIT(WGM00);	// Fast PWM
  OCR0A = 25;			            // mit 10% PWM, Einschaltrampe ca. 10ms
  TCCR0B = 0x02;		            // 8 Vorteiler
  TIFR = BIT(OCIE0A);		        // flag löschen
  TIMSK |= BIT(OCIE0A);		        // int freigeben
  starte_adc();			            // bei Bedarf
}


void sperre_interrupts(void)
{
  TIMSK = 0;			            // T0 abschalten
  ADCSRA = 0;			            // ADC abschalten
  DDRB &= ~BIT(PWM_OUT);	        // Ausgang auf '0'
  PORTB &= ~BIT(PWM_OUT);	        // Ausgang auf '0'
  PORTB |= BIT(EIN_AUS_SIGNAL);     // Ausgang auf '1' = passiv !
}


int16_t main(void)
{
  CLKPR = 0x80; CLKPR = 0x0;	    // Vorteiler / 1 CPU-Takt = 8MHz

  PCMSK |= BIT(EIN_AUS_TASTER);	    // PCINT für PB4 freigeben
  GIFR = BIT(PCIF);		            // flag löschen
  GIMSK |= BIT(PCIF);		        // PCINT PORTB zulassen

  init_interrupts();                // hier nur für T0 wichtig: timer_flag
  sei();

  PORTB |= BIT(EIN_AUS_TASTER);	    // pullup fÜr ein/aus-Taster
  warten_10ms(10);             	    // 100ms Einschwingen abwarten
  abschalten = 1;		            // nach POWER-ON-RESET zunächst OFF

  while(1) {
    if(abschalten) {	            // wenn aus, dann Schlafen
      sperre_interrupts();
      while(!(PINB & BIT(EIN_AUS_TASTER)));	// warten, wenn Taste gedrückt
      MCUCR = BIT(SE) + BIT(SM1);	// Power-Down wählen
      sleep_cpu();				    // und stoppen
      abschalten = 0;	            // nach Aufwachen: Abschalt-Anforderung loeschen
      init_interrupts();
      PORTB &= ~BIT(EIN_AUS_SIGNAL);// Ausgang mit '0' aktiv
      DDRB |= BIT(PWM_OUT) + BIT(EIN_AUS_SIGNAL); // als Ausgänge
      starte_adc();				    // ggf. einschalten
    }
    // hier stehen die Befehle,
    // die im EIN-Zustand abgearbeitet werden sollen.
    // insbesondere, die Überwachung der Versorgungsspannungen
    // z. B. 'abschalten = 1;' schaltet den Controller wieder aus
  }
  return(0);
}

