#include <stdint.h>
#include <stdlib.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#define CONTROL_PORT PORTD
#define CONTROL_DDR  DDRD
#if defined(__AVR_ATtiny2313__)
#  define TRIGGER_DOWN PD2
#  define TRIGGER_UP   PD3
#  define FLASH        PD4
#  define CLOCKOUT     PD6
#else
#  define TRIGGER_DOWN PD2
#  define TRIGGER_UP   PD3
#  define TRIGGER_ADC  PD4
#  define CLOCKOUT     PD6
#  define FLASH        PD7
#endif
#if defined(__AVR_ATmega16__)
#  define PWMDDR     DDRD
#  define PWMOUT     PD5
#elif defined(__AVR_ATmega8__) || defined(__AVR_ATmega48__) ||\
      defined(__AVR_ATmega88__) || defined(__AVR_ATmega168__)
#  define PWMDDR     DDRB
#  define PWMOUT     PB1
#elif defined(__AVR_ATtiny2313__)
#  define PWMDDR     DDRB
#  define PWMOUT     PB3
#  define HAVE_ADC   0
#  define USART_RXC_vect USART_RX_vect
#  define MCUCSR     MCUSR
#else
#  error "Unsupported MCU type"
#endif
#if defined(__AVR_ATmega48__) || defined(__AVR_ATmega88__) ||\
    defined(__AVR_ATmega168__)
#  define USART_RXC_vect USART_RX_vect
#  define UDR     UDR0
#  define UCSRA   UCSR0A
#  define UCSRB   UCSR0B
#  define FE      FE0
#  define TXEN    TXEN0
#  define RXEN    RXEN0
#  define RXCIE   RXCIE0
#  define UDRE    UDRE0
#  define U2X     U2X0
#  define UBRRL   UBRR0L
#  define TIMSK   TIMSK1
#  define MCUCSR  MCUSR
#endif
#if !defined(HAVE_ADC)
#  define HAVE_ADC 1
#endif
#define F_CPU 1000000UL 
#define SOFTCLOCK_FREQ 100  
#define EE_UPDATE_TIME (3 * SOFTCLOCK_FREQ) 
#define TMR1_SCALE ((F_CPU * 10) / (2048UL * SOFTCLOCK_FREQ) + 9) / 10
volatile struct
{
  uint8_t tmr_int: 1;
  uint8_t adc_int: 1;
  uint8_t rx_int: 1;
}
intflags;
volatile char rxbuff;
volatile uint16_t adcval;
uint16_t ee_pwm __attribute__((section(".eeprom"))) = 42;
int16_t pwm;
int16_t pwm_backup_tmr;
uint8_t mcucsr __attribute__((section(".noinit")));
ISR(TIMER1_OVF_vect)
{
  static uint8_t scaler = TMR1_SCALE;
  if (--scaler == 0)
    {
      scaler = TMR1_SCALE;
      intflags.tmr_int = 1;
    }
}
#if HAVE_ADC
ISR(ADC_vect)
{
  adcval = ADCW;
  ADCSRA &= ~_BV(ADIE);     
  intflags.adc_int = 1;
}
#endif 
ISR(USART_RXC_vect)
{
  uint8_t c;
  c = UDR;
  if (bit_is_clear(UCSRA, FE))
    {
      rxbuff = c;
      intflags.rx_int = 1;
    }
}
void handle_mcucsr(void)
  __attribute__((section(".init3")))
  __attribute__((naked));
void handle_mcucsr(void)
{
  mcucsr = MCUCSR;
  MCUCSR = 0;
}
static void
ioinit(void)
{
  uint16_t pwm_from_eeprom;
  
  TCCR1A = _BV(WGM10) | _BV(WGM11) | _BV(COM1A1) | _BV(COM1A0);
  TCCR1B = _BV(CS10);
  OCR1A = 0;            
  
#if HAVE_ADC
  CONTROL_PORT = _BV(TRIGGER_DOWN) | _BV(TRIGGER_UP) | _BV(TRIGGER_ADC);
#else
  CONTROL_PORT = _BV(TRIGGER_DOWN) | _BV(TRIGGER_UP);
#endif
  
  CONTROL_DDR = _BV(CLOCKOUT) | _BV(FLASH);
  
  PWMDDR |= _BV(PWMOUT);
  UCSRA = _BV(U2X);     
  UCSRB = _BV(TXEN)|_BV(RXEN)|_BV(RXCIE); 
  UBRRL = (F_CPU / (8 * 9600UL)) - 1;  
#if HAVE_ADC
  
  ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS0);
#endif
  TIMSK = _BV(TOIE1);
  sei();            
  
  wdt_enable(WDTO_2S);
  
  if ((pwm_from_eeprom = eeprom_read_word(&ee_pwm)) != 0xffff)
    OCR1A = (pwm = pwm_from_eeprom);
}
static void
putchr(char c)
{
  loop_until_bit_is_set(UCSRA, UDRE);
  UDR = c;
}
static void
printstr(const char *s)
{
  while (*s)
    {
      if (*s == '\n')
    putchr('\r');
      putchr(*s++);
    }
}
static void
printstr_p(const char *s)
{
  char c;
  for (c = pgm_read_byte(s); c; ++s, c = pgm_read_byte(s))
    {
      if (c == '\n')
    putchr('\r');
      putchr(c);
    }
}
static void
set_pwm(int16_t new)
{
  char s[8];
  if (new < 0)
    new = 0;
  else if (new > 1000)
    new = 1000;
  if (new != pwm)
    {
      OCR1A = (pwm = new);
      
      new /= 10;
      itoa(new, s, 10);
      printstr(s);
      putchr(' ');
      pwm_backup_tmr = EE_UPDATE_TIME;
    }
}
int
main(void)
{
  
  enum
  {
    MODE_UPDOWN,
    MODE_ADC,
    MODE_SERIAL
  } __attribute__((packed)) mode = MODE_UPDOWN;
  uint8_t flash = 0;
  ioinit();
  if ((mcucsr & _BV(WDRF)) == _BV(WDRF))
    printstr_p(PSTR("\nOoops, the watchdog bit me!"));
  printstr_p(PSTR("\nHello, this is the avr-gcc/libc "
          "demo running on an "
#if defined(__AVR_ATmega16__)
          "ATmega16"
#elif defined(__AVR_ATmega8__)
          "ATmega8"
#elif defined(__AVR_ATmega48__)
          "ATmega48"
#elif defined(__AVR_ATmega88__)
          "ATmega88"
#elif defined(__AVR_ATmega168__)
          "ATmega168"
#elif defined(__AVR_ATtiny2313__)
          "ATtiny2313"
#else
          "unknown AVR"
#endif
          "\n"));
  for (;;)
    {
      wdt_reset();
      if (intflags.tmr_int)
    {
      
      intflags.tmr_int = 0;
      
      CONTROL_PORT ^= _BV(CLOCKOUT);
      
      flash++;
      if (flash == 5)
        CONTROL_PORT |= _BV(FLASH);
      else if (flash == 100)
        {
          flash = 0;
          CONTROL_PORT &= ~_BV(FLASH);
        }
      switch (mode)
        {
        case MODE_SERIAL:
          
          break;
        case MODE_UPDOWN:
          
          if (bit_is_clear(PIND, TRIGGER_DOWN))
        set_pwm(pwm - 10);
          else if (bit_is_clear(PIND, TRIGGER_UP))
        set_pwm(pwm + 10);
#if HAVE_ADC
          else if (bit_is_clear(PIND, TRIGGER_ADC))
        mode = MODE_ADC;
#endif
          break;
        case MODE_ADC:
#if HAVE_ADC
          if (bit_is_set(PIND, TRIGGER_ADC))
        mode = MODE_UPDOWN;
          else
        {
          
          ADCSRA |= _BV(ADIE);
          ADCSRA |= _BV(ADSC);
        }
#endif 
          break;
        }
      if (pwm_backup_tmr && --pwm_backup_tmr == 0)
        {
          
          eeprom_write_word(&ee_pwm, pwm);
          printstr_p(PSTR("[EEPROM updated] "));
        }
    }
#if HAVE_ADC
      if (intflags.adc_int)
    {
      intflags.adc_int = 0;
      set_pwm(adcval);
    }
#endif 
      if (intflags.rx_int)
    {
      intflags.rx_int = 0;
      if (rxbuff == 'q')
        {
          printstr_p(PSTR("\nThank you for using serial mode."
                  "  Good-bye!\n"));
          mode = MODE_UPDOWN;
        }
      else
        {
          if (mode != MODE_SERIAL)
        {
          printstr_p(PSTR("\nWelcome at serial control, "
                  "type +/- to adjust, or 0/1 to turn on/off\n"
                  "the LED, q to quit serial mode, "
                  "r to demonstrate a watchdog reset\n"));
          mode = MODE_SERIAL;
        }
          switch (rxbuff)
        {
        case '+':
          set_pwm(pwm + 10);
          break;
        case '-':
          set_pwm(pwm - 10);
          break;
        case '0':
          set_pwm(0);
          break;
        case '1':
          set_pwm(1000);
          break;
        case 'r':
          printstr_p(PSTR("\nzzzz... zzz..."));
          for (;;)
            ;
        }
        }
    }
      sleep_mode();
    }
}