/*****************

This code is written & adjusted by Sharanya Das for the specific hardware build
The code is Open Source.No modification is necessary though if the same version of hardware is used
However,you may adjust/modify the code as per your own risk.No commercial use of this code is recommended
You can always buy me a beer if you like the working of the circuit

*******************/

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <U8x8lib.h>

U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(/* reset=*/U8X8_PIN_NONE, /* clock=*/SCL, /* data=*/SDA);  // pin remapping with ESP8266 HW I2C

#define R1 33370
#define R2 9970
#define divFactor (R1 + R2) / R2
#define RefVolt 1.098

#define ADC_CH A0
#define PWR 2
#define SW_PIN 3
#define COMP 8
#define TANK 7

#define VBAT_READ_INTERVAL 2000
#define DISPLAY_TIME 500            //Time for the Result to be displayed on OLED
#define AUTOSWITCHOFF_TIME 60000  //Time for auro switch-off if nothing measured

int adcBackup = 0;
long adcReadInterval;
long timeout_Loop;
long timeout_AutoOff;
double pulse, freq, capacitance, inductance, inductanceMH;

//Below variables will be used by the Input capture method
enum edge
{  
  RISING_, //If RISING is already defined somewhere in Arduino files,hence using the _
  FALLING_
};

volatile edge edgeState = RISING_;

// Input: Pin D8
volatile uint8_t dataReady = 0;
volatile uint16_t capt1,capt2,tempICR;
volatile uint16_t ovfCounter = 0;

// timer overflows (every 65536 counts)
ISR(TIMER1_OVF_vect) {
  ovfCounter++;
}

ISR(TIMER1_CAPT_vect) { 
  tempICR = ICR1; //Store the value ASAP regardless of logical checks
  if(edgeState == RISING_)
  {
    //Rising edge is detected by now,change the flag to detect notify a falling edge next time
    edgeState = FALLING_;
    TCCR1B &= ~(1<<ICES1); //Falling edge detection enabled
    if(ovfCounter > 0){capt1 = tempICR + (65536 * ovfCounter);}else{capt1 = tempICR;}   
  }

  else if(edgeState == FALLING_)
  {
    //If we are here,the the pulse is completed by now.No need to monitor further
    if(ovfCounter > 0){capt2 = tempICR + (65536 * ovfCounter);}else{capt2 = tempICR;}
    TIMSK1 = 0;
    dataReady = 1;
  }
}

void InitTC1(void) {
  //Reset all flags
  noInterrupts();
  ovfCounter = 0;
  dataReady = 0;
  edgeState = RISING_;
  capt1 = 0;
  capt2 = 0;
  tempICR = 0;
  //Clear Timer Registers & any pending interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  ICR1 = 0;
  //Reset ISR vectors
  TIFR1 = (1<<TOV1) | (1<<ICF1);
  //Load New Config & start
  TCCR1B |= (1 << ICES1); //First capture on rising edge
  TIMSK1 |= (1 << ICIE1) | (1 << TOIE1); //Enable input capture and overflow interrupts
  TCCR1B |= (1 << CS10); //Start timer without prescaller
  interrupts();
  delayMicroseconds(5); //This delay is necessary,otherwise,Serialprint doesn't work somehow
  //Serial.println("Leaving setup...");
}

void Wake() {
  //EIFR |= (1<<INTF1);
  sleep_disable();
  detachInterrupt(digitalPinToInterrupt(SW_PIN));
  //IntCounter=IntCounter+1;
}

double ReadVbat(uint8_t ch, uint16_t sample) {
  uint16_t adcRead;  //Drop the 1st Reading
  uint16_t sum = 0;
  adcRead = analogRead(ch);  //Leave the first reading
  adcRead = 0;
  for (uint8_t i = 0; i < sample; i++) {
    sum = sum + analogRead(ch);
  }
  adcRead = sum / sample;
  double Vcc = (RefVolt * adcRead * divFactor) / 1023;
  return Vcc;
}

void setup(void) {
  Serial.begin(115200);
  pinMode(COMP,INPUT);
  pinMode(PWR, OUTPUT);
  pinMode(SW_PIN, INPUT_PULLUP);
  pinMode(TANK, OUTPUT);
  digitalWrite(PWR, HIGH);
  analogReference(INTERNAL);
  delay(5);
  u8x8.begin();
  u8x8.setPowerSave(0);
  u8x8.clearDisplay();
  u8x8.setFont(u8x8_font_8x13_1x2_f);
  u8x8.setCursor(0, 0);
  u8x8.print("Vbat: ");
  double Vbat = ReadVbat(ADC_CH, 5);
  u8x8.print(Vbat);
  u8x8.setFont(u8x8_font_8x13B_1x2_f);
  u8x8.setCursor(0, 2);
  u8x8.print("Open          ");
  timeout_AutoOff = millis() + AUTOSWITCHOFF_TIME;
  adcReadInterval = millis() + VBAT_READ_INTERVAL;
  delay(2000);
  //Serial.println("Leaving Setup...");
}

void GoToSleep(void) {
  adcBackup = ADCSRA;
  ADCSRA = 0;
  u8x8.clearDisplay();
  //Must configure SDA & SCL as input & write 0s to them in order to stop OLED hang after sleep resumes
  pinMode(SDA, INPUT);
  pinMode(SCL, INPUT);
  digitalWrite(SDA, LOW);
  digitalWrite(SCL, LOW);
  digitalWrite(PWR, LOW);  //Cut off power to Comparator circuit,Voltage divider circuit & OLED

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  noInterrupts();                                             // make sure we don't get interrupted before we sleep
  sleep_enable();                                             // enables the sleep bit in the mcucr register
  attachInterrupt(digitalPinToInterrupt(SW_PIN), Wake, LOW);  // wake up on low level on D2
  interrupts();                                               // interrupts allowed now, next instruction WILL be executed
  sleep_cpu();
  //CPU resume after sleep
  //Wait till button pressed is released
  //Serial.begin(115200);
  while (digitalRead(SW_PIN) == LOW);
  pinMode(COMP,INPUT);
  pinMode(PWR, OUTPUT);
  digitalWrite(PWR, HIGH);
  analogReference(INTERNAL);
  delay(1);
  ADCSRA = adcBackup;  //Resume backup of ADCSRA
  delay(1);
  u8x8.begin();
  u8x8.setPowerSave(0);
  u8x8.clearDisplay();
  u8x8.setFont(u8x8_font_8x13_1x2_f);
  u8x8.setCursor(0, 0);
  u8x8.print("Vbat: ");
  double Vbat = ReadVbat(ADC_CH, 5);
  u8x8.print(Vbat);
  u8x8.setFont(u8x8_font_8x13B_1x2_f);
  u8x8.setCursor(0, 2);
  u8x8.print("Open          ");
  timeout_AutoOff = millis() + AUTOSWITCHOFF_TIME;
  adcReadInterval = millis() + VBAT_READ_INTERVAL;
}

#define PULSE_TIMEOUT 500
long pulseCaptureTimeout;

double CalculateInductance(double pulseLength) {
  capacitance = 1.0E-6;
  freq = 1.0E6 / (2 * pulseLength);
  inductance = 1.0 / (capacitance * freq * freq * 4. * 3.14159 * 3.14159);
  inductance = inductance * 1E6;
  return inductance;
}

void FireTank(long ReadDelayUs) {
  digitalWrite(TANK, HIGH);
  delay(5);
  digitalWrite(TANK, LOW);
  delayMicroseconds(ReadDelayUs);
}

void loop(void) {
  FireTank(100);
  pulse = pulseIn(COMP, HIGH, 5000);

  if (pulse >= 10)  //if a timeout did not occur and it took a reading:
  {
    double inductanceVal = CalculateInductance(pulse);
    u8x8.setFont(u8x8_font_8x13B_1x2_f);
    u8x8.setCursor(0, 2);
    if (inductance < 1000) {
      u8x8.print(String(inductanceVal) + " uH     ");
    } else {
      u8x8.print(String(inductanceVal / 1000) + " mH     ");
    }
    timeout_Loop = millis() + DISPLAY_TIME;
    timeout_AutoOff = millis() + AUTOSWITCHOFF_TIME;
  } 
  else  //Either Low value Inductor or open Lead,need precision measurement
  {
    FireTank(1);
    InitTC1();
    pulseCaptureTimeout = millis() + PULSE_TIMEOUT;
    while (millis() < pulseCaptureTimeout) {
      if (dataReady) 
      {
        double highPulseDuration = ((double)(capt2 - capt1) * 62.5) / 1000; //The calculation result is in nS.Divided by 1000 to get in uS 
        //Serial.println("Elapsed Time: "+ String(highPulseDuration));
        double inductanceVal = CalculateInductance(highPulseDuration);
        u8x8.setFont(u8x8_font_8x13B_1x2_f);
        u8x8.setCursor(0, 2);
        if (inductance < 1000) {
          u8x8.print(String(inductanceVal) + " uH     ");
        } else {
          u8x8.print(String(inductanceVal / 1000) + " mH     ");
        }

        timeout_Loop = millis() + DISPLAY_TIME;
        timeout_AutoOff = millis() + AUTOSWITCHOFF_TIME;
      } else {
        u8x8.setFont(u8x8_font_8x13B_1x2_f);
        u8x8.setCursor(0, 2);
        u8x8.print("Open          ");
        timeout_Loop = millis() + 10;
      }
    }
  }

  //Give user the time to read before refresh the display again,Switch off if SW pressed again between this loop
  while (millis() < timeout_Loop) {
    if (digitalRead(SW_PIN) == LOW) {
      delay(10);  //debounce delay
      if (digitalRead(SW_PIN) == LOW) {
        while (digitalRead(SW_PIN) == LOW);  //Stay here until button released
        GoToSleep();
      }
    }
  }

  if (millis() > adcReadInterval) {
    u8x8.setFont(u8x8_font_8x13_1x2_f);
    u8x8.clearLine(0);
    u8x8.setCursor(0, 0);
    u8x8.print("Vbat: ");
    double Vbat = ReadVbat(ADC_CH, 5);
    u8x8.print(Vbat);
    adcReadInterval = millis() + VBAT_READ_INTERVAL;
  }
  
  //If Auto off timeout crossed since last reading,go to sleep
  if (millis() >= timeout_AutoOff) {
    GoToSleep();
  }
}
