/********************************************************************************************************************/
/*
Correction value: 
- 50.0% to 200.0% in 0.1% steps. 
- The value is stored and displayed as an integer number between 500..2000

Display correction value: 
- Press the button while the Cseb-o-healer is ON
- Four digits are displayed from left to right
- The start of the digit is marked with a short flash
- After that the number of long flashes equals the value of the digit. Zero = no long flashes.
- After displaying the four digits the operation returns to normal. 

Adjust correction value
- Press the button while turning on the inginition 
- Four digits are requested from left to right
- The start of the digit is marked with a short flash
- After that long flashes are made. 
- If the button is pressed at flash number X (during the LED is on or after that in the LED OFF time) 
  X is stored for that digit. For a zero the button must be pressed after the short blink.
- After requesting the four digits turn off ignition
- Check the stored value with the "Display correction value"
*/
/***********************************************************************************************************************/

/*
Version history: 

2021.06.10. - overflow limit changed to 32000. tested with input signal 0.5..2000Hz, correction = 500 and 2000 and 930

*/


#include <xc.h>
#include <C:\Program Files\Microchip\xc8\v2.31\avr\avr\include\avr\interrupt.h>


#define bit_set(port_address,bit_serial)   ((port_address) |= (1 << bit_serial))
#define bit_clear(port_address,bit_serial) ((port_address) &= ~(1 << bit_serial))
#define bit_get(port_address,bit_serial)   ( ((port_address) & (1 << bit_serial))?1:0)

#define LED_OFF                            bit_clear(PORTB,PORTB5)
#define LED_ON                             bit_set(PORTB,PORTB5)

#define SIGNAL_OUT_OFF                     bit_clear(PORTD,PORTB6)
#define SIGNAL_OUT_ON                      bit_set(PORTD,PORTB6)
#define READ_SIGNAL_OUT                    bit_get(PORTD,PORTB6)

#define BUTTON_PRESSED                     bit_get(PINC,PINC0) ? 0 : 1
#define UNDERFLOW_LIMIT                    1900
#define OVERFLOW_LIMIT                     32000
#define TIMER0_10MS                        156

#define CORRECTION_ADDR_H                  0
#define CORRECTION_ADDR_L                  1


volatile unsigned short actual_prescaler;
volatile unsigned short actual_output_prescaler;
volatile unsigned short timer_overflow;
volatile unsigned long  measured_period;
volatile unsigned long  measured_period_tuned;
//volatile unsigned short tcnt1_temp;
volatile unsigned short full_byte_counter;
volatile unsigned short modulo_counter;
volatile unsigned short output_disable;
volatile unsigned long correction;                        /* 500-2000 = 50.0%-200.0%                                     */
volatile unsigned long correction2;
volatile unsigned char correction_H;
volatile unsigned char correction_L;
volatile unsigned short prescaler_tmp;
volatile unsigned short divider;
volatile unsigned short it_counter;
volatile unsigned short i;
volatile unsigned short correction_tmp;
volatile unsigned short value_red;
volatile unsigned short blink_count;
//volatile unsigned short full_byte_missed;
//volatile unsigned short runtime_correction;
volatile unsigned short full_byte_counter_tmp;
volatile unsigned char  recalculate;
volatile unsigned char start_timer;

void set_prescaler(unsigned short prescaler)
{
  TCNT1 = 0;
  actual_prescaler = prescaler;

  switch(prescaler)
  {
    case 256:
    bit_set(TCCR1B,CS12);                                  /* set preascler for timer 1 16bit input                        */
    bit_clear(TCCR1B,CS11);
    bit_clear(TCCR1B,CS10);
    break;
    case 64:
    bit_clear(TCCR1B,CS12);
    bit_set(TCCR1B,CS11);
    bit_set(TCCR1B,CS10);
    break;
    case 8:
    bit_clear(TCCR1B,CS12);
    bit_set(TCCR1B,CS11);
    bit_clear(TCCR1B,CS10);
    break;
    case 1:
    bit_clear(TCCR1B,CS12);
    bit_clear(TCCR1B,CS11);
    bit_set(TCCR1B,CS10);
    break;
    TCNT1 = 0;

  }
}

void set_output_prescaler(unsigned short prescaler)
{
  //TCNT0 = 0;
  actual_output_prescaler = prescaler;

  switch(prescaler)
  {
    case 256:
    bit_set(TCCR0B,CS02);                                  /* set preascler for timer 1 16bit input                        */
    bit_clear(TCCR0B,CS01);
    bit_clear(TCCR0B,CS00);
    break;
    case 64:
    bit_clear(TCCR0B,CS02);
    bit_set(TCCR0B,CS01);
    bit_set(TCCR0B,CS00);
    break;
    case 8:
    bit_clear(TCCR0B,CS02);
    bit_set(TCCR0B,CS01);
    bit_clear(TCCR0B,CS00);
    break;
    case 1:
    bit_clear(TCCR0B,CS02);
    bit_clear(TCCR0B,CS01);
    bit_set(TCCR0B,CS00);
    break;
    //TCNT0 = 0;

  }
}

void stop_output_timer()
  {
  bit_clear(TCCR0B,CS02);
  bit_clear(TCCR0B,CS01);
  bit_clear(TCCR0B,CS00);
  }

void wait_button_released()
{
  while(BUTTON_PRESSED);
  TCNT2 = 0;
  it_counter = 0;
  while(it_counter < 2);
}

void long_read_blinks()
  {
  unsigned short flash_count;
  unsigned short button;
  
  
  button = 0;
  for(flash_count = 1; flash_count<10; flash_count++)
    {
    LED_ON;
    TCNT2 = 0;
    it_counter = 0;
    while(it_counter < 100)
      {
      if(BUTTON_PRESSED)
        {
        button=1;
        }      
      }
    LED_OFF;
    TCNT2 = 0;
    it_counter = 0;
    while(it_counter < 100)
      {
      if(BUTTON_PRESSED)
        {
        button=1;
        }
      }
    if(button)
      {
      blink_count = flash_count;
      return;
      }
    } 
  }



void short_read_blink()
  {
  unsigned short button;

  button = 0;
  LED_ON;
  TCNT2 = 0;
  it_counter = 0;
  while(it_counter < 10)
    {
    if(BUTTON_PRESSED)
      {
      button = 1;
      } 
    }
  LED_OFF;
  TCNT2 = 0;
  it_counter = 0;
  while(it_counter < 100)
    {
    if(BUTTON_PRESSED)
      {
      button = 1;
      }
    }
  if(button)
    {
    blink_count = 0;
    return;
    }
  }


void read_digit()
{
  blink_count = 100;
  short_read_blink();
  if(blink_count != 0)
    {
    long_read_blinks();
    }
  if(blink_count > 9)
    {
    while(1);  
    }
}



void init()
  {

  /* initialize LED pin */
  bit_set(DDRB,DDB5);                                      /* portB5 = out = LED drive                             */
  LED_OFF;

  /* interrupt */
  bit_set(SREG,7);                                        /* global interrupt enable                                      */
  
  output_disable = 1;

  if(BUTTON_PRESSED)                                      /* get correction than idle loop                                */
    {

    
    /* configure 8 bit timer/counter2 for timebase interrupt */
    bit_clear(TCCR2B,WGM22);                                 /* mode = CTC / OCRA                                           */
    bit_set(TCCR2A,WGM21);
    bit_clear(TCCR2A,WGM20);
    OCR2A = TIMER0_10MS;
    bit_set(TIMSK2,OCIE2A);                                  /* Timer/Counter0 CompareA Interrupt Enable                    */
    bit_set(TCCR2B,CS22);                                    /* start timer, prescaler = 1024                               */
    bit_set(TCCR2B,CS21);
    bit_set(TCCR2B,CS20);

    //wait_button_released();

    TCNT2 = 0;
    it_counter = 0;
    while(it_counter < 200);                                /* wait 2s for the user to relax                               */

    value_red = 0;
    divider = 1000;

    for(i=0;i<4;i++)
      {
      read_digit();
      value_red += divider*blink_count;
      divider /= 10;
      }
    correction_H = value_red>>8;
    correction_L = (unsigned char) value_red;
    LED_OFF;
    
    //store_correction
    asm ("cli");
    while(EECR & (1<<EEPE));
    /* Set up address and Data Registers */
    EEARH = 0;
    EEARL = (unsigned char)CORRECTION_ADDR_H;
    EEDR = correction_H;
    /* erase and write in one */
    bit_clear(EECR,EEPM1);
    bit_clear(EECR,EEPM0);
    asm ("sbi 0x1f,2");
    asm ("sbi 0x1f,1");
    asm ("sei");

    asm ("cli");
    while(EECR & (1<<EEPE));
    /* Set up address and Data Registers */
    EEARH = 0;
    EEARL = (unsigned char)CORRECTION_ADDR_L;
    EEDR = correction_L;
    /* erase and write in one */
    bit_clear(EECR,EEPM1);
    bit_clear(EECR,EEPM0);
    asm ("sbi 0x1f,2");
    asm ("sbi 0x1f,1");
    asm ("sei");


    while(1);
    }

  /* initilaize input timer - timer 1 16bit in SLOW state */
  bit_clear(TCCR1A,COM1A1);                               /* Output compare pins disconnected                             */
  bit_clear(TCCR1A,COM1A0);                               /* Output compare pins disconnected                             */
  bit_clear(TCCR1A,COM1B1);                               /* Output compare pins disconnected                             */
  bit_clear(TCCR1A,COM1B0);                               /* Output compare pins disconnected                             */

  bit_clear(TCCR1B,WGM13);                                /* Counter mode                                                 */
  bit_clear(TCCR1B,WGM12);                                /* Counter mode                                                 */
  bit_clear(TCCR1A,WGM11);                                /* Counter mode                                                 */
  bit_clear(TCCR1A,WGM10);                                /* Counter mode                                                 */

  bit_set(TCCR1B,ICNC1);                                  /* noise canceler on                                            */
  bit_set(TCCR1B,ICES1);                                  /* trigger on rising edge                                       */
  OCR1A = OVERFLOW_LIMIT;                                 /* interrupt at overflow                                        */

  bit_set(TIMSK1,ICIE1);                                  /* input capture interrupt enable                               */
  bit_set(TIMSK1,OCIE1A);                                 /* output compare A interrupt enable                            */
//  bit_set(TIMSK1,TOIE1);                                 /* overflow interrupt enable                            */

  set_prescaler(256);
  timer_overflow = 1;
//  input_timer_state = INPUT_TIMER_SLOW;                   /* input signal slower than 256*65000 clock                     */

  /* initialize output timer */
//  set_output_prescaler(256);
  bit_clear(TIMSK0,OCIE0A);                                 /* output signal compare interrupt                              */
  bit_clear(TIMSK0,TOV0);                                   /* output signal overflow interrupt                             */

  /* initialize speed signal output pin */
  bit_set(DDRD,DDD6);                                      
  SIGNAL_OUT_OFF;
  
  /* define correction */
  // new_period = period / (correction*1000) = period * [1000*1024/correction]/1024 where correction2 is [1000*1024/correction] so 
  // new period = period * correction2 / 1024 -> no division is necessary.
  
  asm ("cli");
  while(EECR & (1<<EEPE));
  /* Set up address register */
  EEARH = 0;
  EEARL = (unsigned char)CORRECTION_ADDR_H;
  /* Start eeprom read by writing EERE */
  EECR |= (1<<EERE);
  /* Return data from data register */
  correction_H = EEDR;
  asm ("sei");

  asm ("cli");
  while(EECR & (1<<EEPE));
  /* Set up address register */
  EEARH = 0;
  EEARL = (unsigned char)CORRECTION_ADDR_L;
  /* Start eeprom read by writing EERE */
  EECR |= (1<<EERE);
  /* Return data from data register */
  correction_L = EEDR;
  asm ("sei");

  correction = correction_H;
  correction <<=8;
  correction += correction_L;
  
  if(correction < 500 || correction > 2000)                /* no correction stored                                        */
    {
    correction = 1000;                                     /* no correction                                               */  
    }
  
  
  //correction = 2000;
  
  correction2 = 512000/correction;
  }


void short_blink()
  {
  TCNT2 = 0;
  LED_OFF;
  it_counter = 0;
  while(it_counter < 100);
  TCNT2 = 0;
  LED_ON;
  it_counter = 0;
  while(it_counter < 10);
  TCNT2 = 0;
  LED_OFF;
  it_counter = 0;
  while(it_counter < 50);
  }

void long_blinks(unsigned short blink_count)
  {
  for(;blink_count>0;blink_count--)
    {
    TCNT2 = 0;
    LED_ON;
    it_counter = 0;
    while(it_counter < 50);
    TCNT2 = 0;
    LED_OFF;
    it_counter = 0;
    while(it_counter < 50);
    }

  }

void display_digit(unsigned short digit)
  {
  short_blink();
  long_blinks(digit);
  }

SIGNAL(TIMER2_COMPA_vect)
{
  TCNT2 = 0;
  it_counter++;
}
/* output *****************************************************************************************************************/
SIGNAL(TIMER0_COMPA_vect)                                   /* output signal compare it */
{
  bit_clear(TIMSK0,OCIE0A);                                 /* output signal compare interrupt                       */
  TCNT0 = 0;
  bit_set(TIMSK0,TOV0);                                   /* output signal overflow interrupt                       */
  if(!READ_SIGNAL_OUT)
  {
    SIGNAL_OUT_ON;
  recalculate = 1;
  set_output_prescaler(actual_prescaler);
  }
  else
  {
    SIGNAL_OUT_OFF;
  }
  
  full_byte_counter = full_byte_counter_tmp;

}

SIGNAL(TIMER0_OVF_vect)                                           /* output signal overflow it */
{

LED_ON;
LED_OFF;

  full_byte_counter--;
  if(full_byte_counter == 0)
  {
//  TCNT0 = 0;
    bit_set(TIFR0,OCF0A);                                   /* clear output compare flag                                    */
    bit_clear(TIMSK0,TOV0);                                   /* output signal overflow interrupt                       */
    OCR0A = (unsigned char) modulo_counter;
    bit_set(TIMSK0,OCIE0A);                                 /* output signal compare interrupt                        */
  }
}

/* input ******************************************************************************************************************/
SIGNAL(TIMER1_CAPT_vect)                                   /* input signal capture it */
{
 
  TCNT1 = 0;                                              /* restart counter                                             */
//  bit_set(TIFR1,ICF1);




  if(timer_overflow)                                      /* beginning rising edge detected                               */
  {
    timer_overflow = 0;                                   /* starting period measurement                                  */
  }
  else                                                    /* period captured                                              */
  {
    if(ICR1 < UNDERFLOW_LIMIT)                      /* underflow                                                    */
    {
      
      
      switch(actual_prescaler)
      {
        case 256:
        set_prescaler(64);
        break;
        case 64:
        set_prescaler(8);
        break;
        case 8:
        set_prescaler(1);
        break;
        case 1:
        break;
      }
    }
    else                                                  /* measured period                                              */
    {
    measured_period = ICR1;
    measured_period = actual_prescaler*measured_period;     /* prescaler ignored, same in input and output signal. In clock cycle */

      if(output_disable)// || (!bit_get(TIMSK0,OCIE0A) && !bit_get(TIMSK0,TOV0)))
      {
        output_disable = 0;
        recalculate = 1;
        start_timer = 1;
      }
    }
  }

}

SIGNAL(TIMER1_COMPA_vect)                                           /* input signal overflow it */
{

  TCNT1 = 0;                                             /* restart counter                                             */
  timer_overflow = 1;                                    /* overflow happened, measurement must be restarted.           */
    output_disable = 1;

//  bit_set(TIFR1,OCF1A);
      
  if(actual_prescaler == 256)
  {
    SIGNAL_OUT_OFF;                                      /* clear output signal                                         */
    output_disable = 1;
  }
  else if(actual_prescaler == 64)
  {
    set_prescaler(256);
  }
  else if(actual_prescaler == 8)
  {
    set_prescaler(64);
  }
  else if(actual_prescaler == 1)
  {
    set_prescaler(8);
  }
  else
  {
  }

}



int main(void)
  {
  init();	
  
  while(1)
    {


    /* care about output signal */
    if(output_disable)
      {
      SIGNAL_OUT_OFF;
      /* disable output timer ITs */
      stop_output_timer();
      bit_clear(TIMSK0,OCIE0A);                                 /* output signal compare interrupt                        */
      bit_clear(TIMSK0,TOV0);                                   /* output signal overflow interrupt                       */
      }
    else if(recalculate)                                        /* half output period passed                                   */
      {
        recalculate = 0; 
        
        measured_period_tuned = (measured_period-0);          /* max. 65000*256*128=2e9                                         */
        measured_period_tuned <<= 6; 

        if(start_timer)
          {
          set_output_prescaler(actual_prescaler);  
          }    
  
        switch(actual_output_prescaler)
          {
          case 256:
            measured_period_tuned >>= 8;
            break;
          case 64:
            measured_period_tuned >>= 6;
            break;
          case 8:
            measured_period_tuned >>= 3;
            break;
          case 1:
            break;
          }
        
        measured_period_tuned *= correction2; 
        measured_period_tuned += 256; 
        measured_period_tuned >>= 9; 
        measured_period_tuned += 64;
        measured_period_tuned >>= 7; 
        measured_period_tuned -= 0;

//measured_period_tuned = measured_period >> 1;
        //switch(actual_output_prescaler)
        //{
        //case 256:
        //measured_period_tuned >>= 8;
        //break;
        //case 64:
        //measured_period_tuned >>= 6;
        //break;
        //case 8:
        //measured_period_tuned >>= 3;
        //break;
        //case 1:
        //break;
        //}


        full_byte_counter_tmp = measured_period_tuned >> 8; 
        if(start_timer)
          {
LED_ON;
LED_OFF;


          start_timer = 0;
          full_byte_counter = full_byte_counter_tmp;  
          set_output_prescaler(actual_prescaler);
          bit_set(TIMSK0,TOV0);                                   /* output signal overflow interrupt                       */
          TCNT0 = 0;
          }
        modulo_counter = measured_period_tuned & 0x000000ff; 
        if(actual_prescaler == 1)
          {
          if(modulo_counter<50)
            modulo_counter = 50;
          else if(modulo_counter>200)
            modulo_counter = 200;
          }
      }
      
    /* display correction **********************************************************************/
    if(BUTTON_PRESSED)   
      {
      SIGNAL_OUT_OFF;
      prescaler_tmp = actual_output_prescaler;
      /* configure 8 bit timer/counter2 for timebase interrupt */
      bit_clear(TCCR2B,WGM22);                                 /* mode = CTC / OCRA                                           */
      bit_set(TCCR2A,WGM21);
      bit_clear(TCCR2A,WGM20);
      OCR2A = TIMER0_10MS;
      bit_set(TIMSK2,OCIE2A);                                  /* Timer/Counter0 CompareA Interrupt Enable                    */
      bit_set(TCCR2B,CS22);                                    /* start timer, prescaler = 1024                               */
      bit_set(TCCR2B,CS21);
      bit_set(TCCR2B,CS20);

      
      divider = 1000;
      correction_tmp = correction;
      for(i=0;i<4;i++)
        {
        display_digit(correction_tmp/divider);
        correction_tmp = correction_tmp%divider;
        divider /= 10;
        }

      set_output_prescaler(prescaler_tmp);
      bit_clear(TIMSK2,OCIE2A);                                 /* Timer/Counter0 CompareA Interrupt disable                    */
      
      }
    }    
  }
