 
#include <io.h>
#include <interrupt.h>
#include <sig-avr.h>
#include "presepio.h"
   
byte dimmer_status;     /* stato del dimmer */
byte dimmer_ticks;      /* numero di ticks per conteggiare il tempo del ciclo */
byte steps;             /* nuovo step */
byte percent;           /* percentuale del ciclo giorno-notte */
byte scale;             /* velocita' del ciclo - solo per modo stand-alone */
 
byte random;            /* valore del generatore pseudo random per il fuoco */
byte fire_ch;           /* canale del fuoco */
byte fire_flag;         /* flag del fuoco attivo */

word oc_start;          /* valore di partenza del timer 1      */
word oc_min;            /* valore min di oc - partenza a crescere */
word oc_max;            /* valore max di oc - partenza a decrescere */
byte end_pulse;         /* and mask per terminare gli impulsi  */
byte pulse_a;           /* or mask canale da attivare con oc1a */ 
byte pulse_b;           /* or mask canale da attivare con oc1b */
word compare_a;         /* valore aggiornato da inserire in oc1a al trigger */
word compare_b;         /* valore aggiornato da inserire in oc1a al trigger */
byte remainder;         /* resto del comparatore a */
word step_inc;          /* unita' d'incremento per regolare la velocita' di dissolvenza */
word step_cent;         /* centesimi di incremento */
byte direction;         /* direzione - UP = oc1a */
volatile byte end_cycle;         /* indicazione fine impulso - inizio dimmer handler */

/****************************************
 INIT_PORTS
****************************************/

void init_ports(void)
{

  outp(0xFF,DDRA);      /* PORTA tutti outputs */
  outp(0x00,DDRB);      /* PORTB tutti inputs */

#ifdef EMUL
  outp(0x45,DDRC);      /* PORTC tutti inputs tranne PC.6 (led) - PC.2 mains sim - PC.0 testout */
#else
  outp(0x41,DDRC);      /* PORTC tutti inputs tranne PC.6 (led) - PC.0 testout */
#endif

  outp(0xff,PORTB);	/* AUTO PULLUP */
  outp(0xfe,PORTC);	/* AUTO PULLUP - PC.0 testout = 0 */
  cbi(PORTC, LED);      /* accendi il led */
  outp(0x40,PORTA);	/* luce giorno accesa - tutte le altre spente */

  return;

}

/****************************************
 INIT_VARS
****************************************/

void init_vars(void)
{

  dimmer_status = DIMMER_OFF;
  dimmer_ticks = 0;
  steps = 0;
  percent = 20;		/* stato = giorno */

  fire_flag = OFF;
  fire_ch = 0;

  end_pulse = 0xff;
  pulse_a = 0;
  pulse_b = 0;
  compare_a = 0;
  compare_b = 0;
  remainder = 0;
  step_inc = 50;
  step_cent = 0;
 
  direction = UP;
  end_cycle = OFF;

  random = 0x55;

/*  

Calcolo del controllo dimmer


                                       <- min ->
 inizio  oc_start  oc_min            oc_max  tout    fine
    |------|---------|-----------------|--------|-------|
         trigger     <- start pulse --->    end_pulse

- valori in us
- tout = 65536
- oc_min = lmax
- oc_max = lmin
- in salita = diminuzione della luminosita'
- in discesa = aumento della luminosita'
- fine-inizio = durata ciclo = 10000 us
- oc_start - inizio = ritardo per la rivelazzione inizio ciclo (da verificare)
- margine = 500 us (per tener conto del ritaardo  inizio e del margine tra tout e fine ciclo)
- oc_start - oc_min = margine per l'inizio dduty cicle = 100 us
- tout - oc_max = margine per la fine duty ccicle = larghezza minima dell'impulso = 100 us

*/

  oc_start = 65535 - 10000 + 500;
  oc_min   = oc_start + 100;
  oc_max   = 65535 - 100;

  if (bit_is_set(PINB, MASTER)) {

    fire_flag = ON;
    fire_ch = 0;
    get_scale();
  
  }
  
  return;

}

/****************************************
 INIT_COM
****************************************/

void init_com(void)
{

  outp(0x10, UCR);      /* rx enable - tx disabled - all irq disabled */
  outp(BR2400, UBRR);   /* baud rate = 2400 */

  return;

}

#ifdef EMUL

/****************************************
 INIT_TIMER0
****************************************/

void init_timer0(void)
{

  outp((256-79), TCNT0);        /* reset TCNT0 */
  outp(5, TCCR0);		/* count with cpu clock/1024 */

  return;

}

#endif

/****************************************
 INIT_TIMER1
****************************************/

void init_timer1(void)
{

  outp(0xa0, TCCR1A);   /* toggle OC1A and OC1B */
  outp(0x00, TCCR1B);   /* stop counter */

  return;

}

/****************************************
 INIT_TIMER
****************************************/

void init_timers(void)
{

#ifdef EMUL  
  init_timer0();
#endif
  init_timer1();

  return;

}           
/****************************************
 IS_CHAR_AV
****************************************/

int is_char_av(void)
{

  if (bit_is_set(USR, RXC)) return(1);

  else return(0);

}

/****************************************
 INIT
****************************************/

void init(void)
{

  init_ports();
  init_vars();
  init_timers();
  init_com();

  return;

}

/*****************************************
 SETUP_DIMMER
*****************************************/

/*

- DAYBREAK e SUNSET controllati da OC1A
- DAY e NIGHT controllati da OC1B
- DIRECTION e' riferita a OC1A
- UP diminuisce la luminosita'
- DOWN aumenta la luminosita'

*/

void setup_dimmer(byte event_type)
{

  remainder = 0;

  switch (event_type) {

    case DAYBREAK_STATE :

      direction = DOWN;
      dimmer_status = DAYBREAK_STATE;
      pulse_a = BV(DAYBREAK_CH);
      pulse_b = BV(NIGHT_CH);
      compare_a = oc_max;
      compare_b = oc_min;
      end_pulse = END_PULSE_DAYBREAK;

      break;

    case DAY_STATE :

      direction = UP;
      dimmer_status = DAY_STATE;
      pulse_a = BV(DAYBREAK_CH);
      pulse_b = BV(DAY_CH);  
      compare_a = oc_min;
      compare_b = oc_max;
      end_pulse = END_PULSE_DAY;

      break;

    case SUNSET_STATE :

      direction = DOWN;
      dimmer_status = SUNSET_STATE;
      pulse_a = BV(SUNSET_CH);
      pulse_b = BV(DAY_CH);
      compare_a = oc_max;
      compare_b = oc_min;
      end_pulse = END_PULSE_SUNSET;

      break;

    case NIGHT_STATE :    

      direction = UP;
      dimmer_status = NIGHT_STATE;
      pulse_a = BV(SUNSET_CH);
      pulse_b = BV(NIGHT_CH);
      compare_a = oc_min;
      compare_b = oc_max;
      end_pulse = END_PULSE_NIGHT;

      break;

    case DIMMER_OFF :
    default:

      STOP_TIMER;
      pulse_a = pulse_b = 0;
      dimmer_status = DIMMER_OFF;
      end_pulse = 0xff;

      break;

  }

  return;

}

/******************************************
 STOP_DIMMER
******************************************/

void stop_dimmer(void)
{

  switch (dimmer_status) {

    case DAYBREAK_STATE :

      sbi(PORTA, DAYBREAK_CH);
      cbi(PORTA, NIGHT_CH);

      break;

    case DAY_STATE :

      sbi(PORTA, DAY_CH);
      cbi(PORTA, DAYBREAK_CH);
     
      break;

    case SUNSET_STATE :

      sbi(PORTA, SUNSET_CH);
      cbi(PORTA, DAY_CH);

      break;

    case NIGHT_STATE :    

      sbi(PORTA, NIGHT_CH);
      cbi(PORTA, SUNSET_CH);
      break;

    case DIMMER_OFF :
    default:

      STOP_TIMER;	/* stop timer */
      pulse_a = pulse_b = 0;
      dimmer_status = DIMMER_OFF;

      break;

  }

  STOP_TIMER;	/* stop timer */

  end_pulse = 0xff;
  pulse_a = pulse_b = 0;
  dimmer_status = DIMMER_OFF;

  return;

}

/*******************************************
 IS_REMAINDER
*******************************************/

int is_remainder(void)
{

  remainder = remainder + step_cent;

  if (remainder < 100) return(0);  

  else remainder = remainder - 100;

  return(1);

}

/********************************************
 UPDATE_DIMMER
********************************************/

void update_dimmer(void)
{

  if (dimmer_status == DIMMER_OFF) return;

  if (direction == UP) {

   compare_a = compare_a + step_inc;
   compare_b = compare_b - step_inc;

    if (is_remainder()) {

      compare_a++;
      compare_b--;

    }

    if (compare_a > oc_max) stop_dimmer();
    
  }

  else {

    compare_b = compare_b + step_inc;
    compare_a = compare_a - step_inc;

    if (is_remainder()) {

      compare_b++;

    }

    if (compare_b > oc_max) stop_dimmer();

  }

  return;

}

/********************************************
 TIMER_1_OVERFLOW - END PULSES
********************************************/

SIGNAL (SIG_OVERFLOW1)
{

  byte temp;

  temp = inp(PINA);
  temp = temp & end_pulse;
  outp(temp, PORTA);

  end_cycle = ON; 

  cbi(PORTC, TEST_OUT);

}

/********************************************
 TIMER_0_OVERFLOW -
********************************************/

SIGNAL(SIG_OVERFLOW0)        /* signal handler for tcnt0 overflow interrupt */
{

  byte temp;

  temp = inp(PINC);

  if(temp & 0x04) outp((temp & 0xfb), PORTC);

  else outp((temp | 0x04), PORTC); 
 
  outp((256-79), TCNT0);

}

/********************************************
 TIMER_1_OUTPUT_COMPAREA_A - SET PULSE
********************************************/

SIGNAL (SIG_OUTPUT_COMPARE1A)
{

  byte temp;

  temp = inp(PINA);
  temp = temp | pulse_a;
  outp(temp, PORTA);

  sbi(PORTC, TEST_OUT);

}


/********************************************
 TIMER_1_OUTPUT_COMPARE_B - SET PULSE
********************************************/

SIGNAL (SIG_OUTPUT_COMPARE1B)
{

  byte temp;

  temp = inp(PINA);
  temp = temp | pulse_b;
  outp(temp, PORTA);

}

/********************************************
 COMMAND COMMAND COMMAND COMMAND COMMAND COMMAND COMMAND COMMAND COMMAND 
********************************************/

/********************************************
 CHECK_FOR_COMMAND
********************************************/

void check_for_command(void)
{

  byte command;
  byte value;

  if (is_char_av()) {

    command = inp(UDR);

    value = command % 16;
    command = command / 16;

    switch(command) {

      case CHA_OFF : do_choff(value);

        break;

      case CHA_ON : do_chon(value);

        break;

      case SET_SCALE : do_setscale(value);

        break;

      case DAYBREAK_ON : setup_dimmer(DAYBREAK_STATE);

        break;

      case DAY_ON : setup_dimmer(DAY_STATE);

        break;

      case SUNSET_ON : setup_dimmer(SUNSET_STATE);

        break;

      case NIGHT_ON : setup_dimmer(NIGHT_STATE);

        break;

      case FIRE_ON : do_rand(value); 

        break;

      default :

        break;

    }

  }

  return;

}

/********************************************
 GET_SCALE

 pos    scale   ciclo in sec

 0      2       40 sec
 1      4       80 sec
 2      6       120 sec
 3      8       160 sec
 4      10      200 sec
 5      12      240 sec
 6      14      280 sec

********************************************/

void get_scale(void)
{

  byte temp, i;

  temp = inp(PINB);

  for (i = 0; i < 7; i++, temp = temp >> 1) {

    if  ((temp & 0x01) == 0) break;

  }

  if (i > 7) i = 0;

  scale = (i + 1) * 2;	/* velocita' del ciclo giorno-notte */ 

  do_setscale(i);	/* velocita' dissolvenza */

  return;

}

/********************************************
 GET_KEYS

 Controllo del pulsante di test

********************************************/

void get_keys(void)
{

  byte temp;

  if (bit_is_clear(PINC, TEST)) {

    temp = inp(PINA);
    outp(0xff, PORTA);

    loop_until_bit_is_set(PINC, TEST);

    outp(temp, PORTA);

  }

  return;

}

/********************************************
 DO_CHOFF
********************************************/

void do_choff(byte ch_no)
{

  byte temp;

  if (ch_no == fire_ch) fire_flag = 0;

  temp = inp(PINA);
  temp = temp & ~(BV(ch_no));
  outp(temp, PORTA);

  return;

}


/********************************************
 DO_CHON
********************************************/

void do_chon(byte ch_no)
{

  byte temp;

  if (ch_no > 7) return;

  if (ch_no == fire_ch) fire_flag = 0;

  temp = inp(PINA);
  temp = temp | BV(ch_no);
  outp(temp, PORTA);

  return;

}

/********************************************
 DO_SETSCALE
********************************************/

void do_setscale(byte scale_value)
{

  switch (scale_value) {

    case 0 : 

      step_inc  = 50 ;
      remainder = 0;

      break;
   
    case 1 : 

      step_inc  = 25;
      remainder = 0;

      break;

    case 2 : 

      step_inc  = 16;
      remainder = 67;

    break;

    case 3 : 

      step_inc  = 12;
      remainder = 50;

    break;

    case 4 : 

      step_inc  = 10;
      remainder = 0;

    break;

    case 5 : 

      step_inc  = 8;
      remainder = 33;

    break;

    case 6 : 

      step_inc  = 7;
      remainder = 14;

    break;
 
    case 7 : 

      step_inc  = 6;
      remainder = 25;

    break;

    case 8 : 

      step_inc  = 5;
      remainder = 56;

    break;

    case 9 : 

      step_inc  = 5;
      remainder = 0;

    break;

   case 10 : 

      step_inc  = 4;
      remainder = 55;

    break;

    case 11 : 

      step_inc  = 4;
      remainder = 17;

    break;

    case 12 : 

      step_inc  = 3;
      remainder = 85;

    break;

    case 13 : 

      step_inc  = 3;
      remainder = 57;

    break;

    case 14 : 

      step_inc  = 3;
      remainder = 33;

    break;

    case 15 : 

      step_inc  = 3;
      remainder = 13;

    break;

  }

  return;
  
}

/*********************************************
 DO_RAND
*********************************************/

void do_rand(byte ch_no)
{

  if (ch_no > 7) return;

  fire_flag = 1;
  fire_ch = ch_no;

  return;

}

/***********************************************
 EVENTS - Solo in modo stand-alone!
***********************************************/

void events(void)
{

  switch (percent) {

    case START_DAYBREAK : setup_dimmer(DAYBREAK_STATE);

      break;

    case ZONE2_OFF : cbi(PORTA, ZONE2_CH);

      break;

    case ZONE3_OFF : cbi(PORTA, ZONE3_CH); 
    
      break;

    case START_DAY : setup_dimmer(DAY_STATE);

      break;

    case START_SUNSET : setup_dimmer(SUNSET_STATE);

      break;

    case ZONE1_ON : sbi(PORTA, ZONE1_CH);
   
      break;

    case START_NIGHT : setup_dimmer(NIGHT_STATE);

      break;

    case ZONE2_ON : sbi(PORTA, ZONE2_CH);
      
      break;

    case ZONE3_ON : sbi(PORTA, ZONE3_CH);

      break;

    case ZONE1_OFF : cbi(PORTA, ZONE1_CH);  
  
      break;

  }

  return;

}

/********************************************
 WAIT_NEG_EDGE
********************************************/

void wait_neg_edge(void)
{

  byte count;

  for (count = 0; count < PMAX; ) {

    if (bit_is_clear(PINC, MAINS)) count++;

    else count = 0;

  }

  /* loop_until_bit_is_clear(PINC, MAINS); */

  return;

}

/********************************************
 WAIT_POS_EDGE
********************************************/

void wait_pos_edge(void)
{

  byte count;

  for (count = 0; count < PMAX; ) {

    if (bit_is_set(PINC, MAINS)) count++;

    else count = 0;

  }

  /* loop_until_bit_is_set(PINC, MAINS); */

  return;

}

/********************************************
 DIMMER_HANDLER
********************************************/

void dimmer_handler(void)
{


  /* modo asservito */

  if (bit_is_clear(PINB, MASTER)) check_for_command();

  /* modo non asservito = master */

  else {

    dimmer_ticks++;

    if (dimmer_ticks >= scale) {

      dimmer_ticks = 0;

      steps++;

      if (steps >= 20) {

        steps = 0;
        percent++;
        if (percent == 100) percent = 0;
        events();

      }

    }

  }

  update_dimmer(); /* valido per ambedue i modi */

  return;

}

/********************************************
 TRIGGER
********************************************/

void trigger(void)
{

  if (dimmer_status != DIMMER_OFF) {

    end_cycle = OFF;

    __outw(compare_a, OCR1AL);
    __outw(compare_b, OCR1BL);

    __outw(oc_start, TCNT1L);

    START_TIMER;	/* start timer */

  }

  return;

}

/********************************************
 SCHEDULER
********************************************/

void scheduler(void)
{

  if (dimmer_status != DIMMER_OFF) {

    while (end_cycle == OFF) {
      if (end_cycle == ON) break;
    }

  }

  STOP_TIMER;
  end_cycle = OFF;

  dimmer_handler();

 /* MASTER MODE */

  if (bit_is_set(PINB, MASTER)) get_scale();

  return;

}

/********************************************
 IS_ODD_PARITY
********************************************/

byte is_odd_parity(byte value)
{

  byte temp = 0;
  byte i;

  for (i = 0; i < 8; i++) {

    if (value & 0x01) temp++;

    value = value >> 1;

  }

  return(temp % 2);

}

/********************************************
 RAND8
********************************************/

void rand8(void)
{

  byte parity, temp;

  if (fire_flag) {

    if (random == 0) random = ~random;

    temp = random & 0xb8;

    parity = is_odd_parity(temp);
   
    if (random & 0x01) {

      temp = inp(PINA);
      temp = temp | BV(fire_ch);
      outp(temp, PORTA);

    }

    else {

      temp = inp(PINA);
      temp = temp & ~(BV(fire_ch));
      outp(temp, PORTA);

    }

    random = random >> 1;

    if (parity) random = random | 0x80;

 
    return;

  }

  return;

}

/********************************************
 MAIN
********************************************/

int main(void)
{

  byte edge;

  init();

#ifdef EMUL
  ENABLE_TINT_EMU;	/* abililita tim1_ovfl, oc1a, oc1b */
#else
  ENABLE_TIM_INT;
#endif

  sei();


  wait_neg_edge();      /* Prima sincronizzazione */
  edge = NEG_EDGE;

  /* loop principale */

  while(1) {

    get_keys();

    if (edge == POS_EDGE) {

      wait_neg_edge();
      edge = NEG_EDGE;
      sbi(PORTC, LED);      /* spegni il led */

    }

    else {

      wait_pos_edge();
      edge = POS_EDGE;
      cbi(PORTC, LED);      /* accendi il led */

    }

    trigger();           /* start il timer1 se dimmer_status e' ON */
    scheduler();         /* */ 
    rand8();            /* gestione del generatore pseudorandom */

  }

}

