#include <avr\interrupt.h>
#include <avr\sleep.h>
#include <util\atomic.h>


//#define USE_SLEEP_MODE                // remove comment sign to see the error


struct bits {                           // Macro to access bits like variables
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define SBIT_(port,pin) ((*(volatile struct bits*)&port).b##pin)
#define	SBIT(x,y)	SBIT_(x,y)


#define LED0			SBIT( PORTB, 0 )
#define LED0_DDR		SBIT( DDRB,  0 )
#define KEY0_PULLUP		SBIT( PORTB, KEY0 )

#define	KEY_PIN		PINB			// key input port, low active:
						// bit = 0: key pressed
#define	KEY0		PB4

#define LED_OFF		1
#define	LED_ON		0


volatile uint8_t key_state;			// debounced and inverted key state:
						// bit = 1: key pressed
volatile uint8_t key_press;			// key press detect

uint8_t pwm_led0, pwm_cycle, pwm_dir;


ISR( PCINT0_vect )
{
  GIMSK = 0;					// disable itself after awake
}


ISR( TIM0_OVF_vect )				// 9.6MHz / 256 = 37.5kHz
{
  static uint8_t ct0, ct1;
  uint8_t i;

  if( pwm_led0 > pwm_cycle ){		        // soft-PWM
    LED0 = LED_ON;
  }else{
    LED0 = LED_OFF;
  }
  if( --pwm_cycle == 0 ){			// 9.6MHz / 256 / 256 = 146Hz (7ms)

    if( pwm_dir ){				// fader
 	  if( --pwm_led0 == 0 )
	    pwm_dir = 0;
    }else{
     if( ++pwm_led0 == 255 )
	    pwm_dir = 255;
    }
				                // key debounce and press detection:
    i = key_state ^ ~KEY_PIN;		        // key changed ?
    ct0 = ~( ct0 & i );				// reset or count ct0
    ct1 = ct0 ^ (ct1 & i);			// reset or count ct1
    i &= ct0 & ct1;				// count until roll over ?
    key_state ^= i;				// then toggle debounced state
    key_press |= key_state & i;		        // 0->1: key press detect
  }
}


uint8_t get_key_press( uint8_t key_mask )       // get press event of a key
{
  ATOMIC_BLOCK(ATOMIC_FORCEON){
    key_mask &= key_press;                      // read key(s)
    key_press ^= key_mask;                      // clear key(s)
  }
  return key_mask;
}


void init( void )
{
  LED0_DDR = 1;
  KEY0_PULLUP = 1;

  TCCR0A = 0;
  TCCR0B = 1<<CS00;                             // XTAL / 1
  TIMSK0 = 1<<TOIE0;

  ACSR = 1<<ACD;                                // disable analog comparator
  PCMSK = 1<<KEY0;                              // select pin change input
  sleep_enable();                               // enable sleep
}


void go_idle( void )
{
  ATOMIC_BLOCK(ATOMIC_FORCEON){
    set_sleep_mode( SLEEP_MODE_IDLE);	        // prepare idle
  }
  sleep_cpu();					// do SLEEP instruction only !
}


void go_power_down( void )
{
  ATOMIC_BLOCK(ATOMIC_FORCEON){                 // = CLI
    LED0 = LED_OFF;				// switch off external loads
    TIMSK0 = 0;					// other interrupts off !
    GIMSK = 1<<PCIE;				// awake interrupt on
    set_sleep_mode( SLEEP_MODE_PWR_DOWN);       // prepare power down
  }                                             // = SEI
#ifdef USE_SLEEP_MODE
                                                // use never sleep_mode()
  sleep_mode();                                 // under no circumstances
                                                // its always dangerous !!!
#else
  sleep_cpu();					// do SLEEP instruction only !
#endif
  TIMSK0 = 1<<TOIE0;                            // enable other interrupts again
}


int main( void )
{
  uint8_t sleep_on = 0;

  init();                                       // do init stuff
  sei();

  for(;;){
    go_idle();                                  // nothing to do until interrupt

	if( (key_state ^ KEY_PIN) & 1<<KEY0){	// if key_state == input:
						// debounce was finished
      if( get_key_press( 1<<KEY0 ))
        sleep_on ^= 0xFF;			// toggle sleep/awake

      if( sleep_on )
        go_power_down();                        // deep power saving
    }
  }
}
