/* Arduino_LC_meter_sketch.ino
   a sketch for Arduino based LC meter firmware based on an
   assembly language program written for the PIC-based
   digital LC meter design published in the May 2008 issue
   of SILICON CHIP.
   Written by Jim Rowe (Silicon Chip)
   Version 1.0, updated 28/03/2017 at 4:45 pm
   
   Software has been modified for use with the Altronics K9705
   Arduino LC Meter Shield by Tom Skevington.
   Also changed to use a 4-bit connected 16x2 LCD and implements
   automatic L/C detection and switching and Direct value calibration.
   
   To use direct calibration, connect a known capacitor to the test terminals (Known inductor will also work) 
   and enter and send the value via the serial terminal. Entered value should be in the format xxx.xnF where xxx.x 
   is an upto 5-character number (including decimal point, ie, .0001-12.34-99999), n is either m, u, n or p depending on 
   multiplier and F is either F or H depending on whether a reference capacitor or inductor is being used.
   EG, to calibrate with a 330nF capacitor (measured 330.4nF) enter "330.4nF" (without quotes or CR/LF)
   
   Serial baud at 115200
   
   Version 1.0
 */
 
#include <Wire.h>
#include <EEPROM.h>
#include <FreqCount.h>
#include <LiquidCrystal.h>

const int rs = 3, en = 4, d4 = 6, d5 = 7, d6 = 8, d7 = 9; //LCD connections 4-bit mode
const float Pi=3.14159;         //define Pi
const float FourPiSqrd=39.4784; //and Pi squared
const float uHmult=1.0e6;       //multiplier for converting H into uH
const float mHmult=1.0e3;       //multiplier for converting H into mH
const float pFmult=1.0e12;      //multiplier for converting F into pF
const float nFmult=1.0e9;       //multiplier for converting F into nF
const float uFmult=1.0e6;       //multiplier for converting F into uF
const float C2val=1e-9;     //nominal value of C2=1nF=1x10^-9F (change to actual value if known)
const int LCSwitchPin=2;        //digital IO pin 2 for C/L switching
const int RelayPin=14;           // digital IO pin 3 for driving RLY1
const int OscSigPin=5;          // digital IO pin 5 for input of oscillator signal

float C1val;    //C1's calculated value after calibration (F)
float CXval;    //calculated value for Cx (F)
float L1val;    //calculated value for L1 after calibration (H)
float LXval;    //calculated value for Lx (H)
float F1sqrd;   //calc value for Freq1 squared (as a float)
float F2sqrd;   //calc value for Freq2 squared (as a float)
float F3sqrd;   //calc value for Freq3 squared (as a float)
float CF=1.0;   //calibration factor

long Fcount;    //raw frequency count figure (from GetFrequency function)
long Freq1;     //measured Freq1 in Hz (L1 & C1 only)
long Freq2;     //measured Freq2 in Hz (L1 & (C1 + C2))
long Freq3;     //measured Freq3 in Hz (C1+Cx/L1 or C1/L1+Lx)

byte MTorNot=0; //flag: 0=EEPROM addresses empty (==255)
byte LCSwitch=0;//flag: 0=Measuring C, 1=Measuring L

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() {
  
  /*If CF is accidentally set to 0 (or a very low value) by any method, 
  the value won't be able to be corrected using the existing routine. 
  If this happens, uncomment the following line, and re-upload the program 
  to recover the CF value, then comment it out again and upload. 
  (otherwise the calibration value will be cleared every reset*/
  //CF=1;EEPROM.put(0,CF);
  Serial.begin(115200);
  digitalWrite(LCSwitchPin,LOW);
  digitalWrite(RelayPin,LOW);
  pinMode(LCSwitchPin,OUTPUT);
  pinMode(RelayPin,OUTPUT);
  pinMode(OscSigPin, INPUT);
  lcd.begin(16,2);
  lcd.clear();
  lcd.print("Silicon Chip");
  lcd.setCursor(0,1);
  lcd.print("Digital LC Meter");
  Serial.println("Silicon Chip");
  Serial.println("Digital LC Meter");
  delay(2000);
  for(int i=0;i<4;i++) {
    if(EEPROM.read(i)!=255) MTorNot=1;
  }
  if(MTorNot==0) {
    EEPROM.put(0,CF);
  } else {
    EEPROM.get(0,CF);
  }
  lcd.clear();
  lcd.print("Now calibrating");
  Serial.println("Now calibrating");
  FindC1andL1();
  DispCalData();
  delay(1000);
  lcd.clear();
  lcd.print("Calibration done");
  Serial.println("Calibration done");
  lcd.setCursor(0,1);
  lcd.print("Ready to use");
  Serial.println("Ready to use");
  delay(1000);
  lcd.clear();
}

void loop() {

  uint8_t CalType=0;//Have we received data and is it for a capacitor or inductor
  float CalVal=1;
  if(Serial.available()>6) {//Got serial data. Very little error checking is done on this data
    char TMP[6];
    uint8_t CountTMP=0;
    while((Serial.peek()<58)&&(Serial.peek()>45)&&(Serial.peek()!=47)&&(CountTMP<5)) {//0-9 or .   Limit number of characters to avoid seg-fault on incorrect entry
      TMP[CountTMP]=Serial.read();
      CountTMP++;
    }
    TMP[CountTMP]='\0';//Terminate string with null character
    CalVal=atof(TMP);
    switch(Serial.read()) {//Multiplier value
      //m, u, n, p
      case 'm':
        CalVal/=mHmult;
        break;
      case 'u':
        CalVal/=uFmult;
        break;
      case 'n':
        CalVal/=nFmult;
        break;
      case 'p':
        CalVal/=pFmult;
        break;
      default:
        Serial.print("ERROR: Invalid input value. Should be: xxx.xnF");
        Serial.println("(.0001-99999, 5 digits including decimal point) (m, u, n, p) (F, H)");
        break;
    }
    printFloat(CalVal,13);Serial.println();
    switch(Serial.read()) {//F-H switch
      case 'F'://Farad, so Capacitor
        CalType=1;
        break;
      case 'H'://Henry, so Inductor
        CalType=2;
        break;
      default:
        Serial.print("ERROR: Invalid input value. Should be: xxx.xnF");
        Serial.println("(.0001-99999, 5 digits including decimal point) (m, u, n, p) (F, H)");
        break;
    }
  }
  GetFrequency();
  Freq3=Fcount;
  float F3=float(Freq3);
  F3sqrd=pow(F3,2.0);
  String Cdisp;
  String Ldisp;
  CXval=C1val*CF*(float(F1sqrd/F3sqrd)-1.0);
  LXval=L1val*CF*(float(F1sqrd/F3sqrd)-1.0);
  if(CalType) {//This is where out direct calibration value is worked in.
    if(CalType==1) {
      CF=CalVal*CF/CXval;
      CXval=CalVal;
    } else if(CalType==2) {
      CF=CalVal*CF/LXval;
      LXval=CalVal;
    }
    CalType=0;
    EEPROM.put(0,CF);
    Serial.println("Updated calibration");
  }
  uint8_t ChangeTMP=0;
  if(!LCSwitch) {//Measuring capacitor
    if(CXval<0) {//Inductor attached
      digitalWrite(LCSwitchPin,HIGH);
      LCSwitch=1;
      ChangeTMP=1;
    } else if(CXval>10) {//L relay is still set
      digitalWrite(LCSwitchPin,LOW);
      LCSwitch=0;
      ChangeTMP=1;
    }
  } else {//Measuring inductor
    if((CXval>10)||(CXval<0)) {//Likely a capacitor
      digitalWrite(LCSwitchPin,LOW);
      LCSwitch=0;
      ChangeTMP=1;
    }
  }
  if(ChangeTMP) {//Switched relay, so reaquire
    delay(500);
    GetFrequency();
    Freq3=Fcount;
    float F3=float(Freq3);
    F3sqrd=pow(F3,2.0);
    CXval=C1val*CF*(float(F1sqrd/F3sqrd)-1.0);
  }
  if(LCSwitch) {
    LXval=L1val*CF*(float(F1sqrd/F3sqrd)-1.0);
    if(LXval<1.0e-3) {
      float LXuH=LXval*uHmult;
      Ldisp=" Lx = ";
      Ldisp+=String(LXuH,3);
      Ldisp+=" uH";
    } else if(LXval<1.5e-1) {
      float LXmH=LXval*mHmult;
      Ldisp=" Lx = ";
      Ldisp+=String(LXmH,3);
      Ldisp+=" mH";
    } else {
        Ldisp="  Over Range!";     
    }
    lcd.clear();
    lcd.print(Ldisp);
    Serial.println(Ldisp);
  } else {
    if(CXval<1.0e-9) {
      float CXpF;
      CXpF=CXval*pFmult;
      Cdisp="Cx = ";
      Cdisp+=String(CXpF,3);
      Cdisp+=" pF";
    } else if(CXval<1.0e-6) {
      float CXnF;
      CXnF=CXval*nFmult;
      Cdisp="Cx = ";
      Cdisp+=String(CXnF,3);
      Cdisp+=" nF";
    } else {
      float CXuF;
      CXuF=CXval*uFmult;
      Cdisp="Cx = ";
      Cdisp+=String(CXuF,3);
      Cdisp+=" uF";  
    }
    lcd.clear();
    lcd.print(Cdisp);
    Serial.println(Cdisp);
  }
  String F3disp="(F3 = ";
  F3disp+=String(Freq3);
  F3disp+=" Hz)";
  lcd.setCursor(0,1);
  lcd.print(F3disp);
  Serial.println(F3disp);
  delay(500);
}

void FindC1andL1() {

  digitalWrite(RelayPin,LOW);
  GetFrequency();
  Freq1=Fcount;
  digitalWrite(RelayPin,HIGH);
  delay(1000);
  GetFrequency();
  Freq2=Fcount;
  digitalWrite(RelayPin,LOW);
  float F1=float(Freq1);
  float F2=float(Freq2);
  F1sqrd=pow(F1,2.0);
  F2sqrd=pow(F2,2.0);
  float F1sqlessF2sq=(F1sqrd-F2sqrd);
  C1val=C2val*float(F2sqrd/F1sqlessF2sq);
  L1val=1.0/(FourPiSqrd*float(F1sqrd)*C1val);
  return; 
}

void GetFrequency() {

  FreqCount.begin(1000);
  while(!FreqCount.available()) {}
  Fcount=FreqCount.read();
  FreqCount.end();
  return;
}

void DispCalData() {

  float L1uH=L1val*uHmult;
  float C1pF=C1val*pFmult;
  
  String C1disp="C1= ";
  C1disp+=String(C1pF,2);
  C1disp+=" pF";
  lcd.clear();
  lcd.print(C1disp);
  lcd.setCursor(0,1);
  String L1disp="L1= ";
  L1disp+=String(L1uH,2);
  L1disp+="uH";
  lcd.print(L1disp);
  Serial.println(C1disp);
  Serial.println(L1disp);
  delay(3000);
  lcd.clear();
  return;
}

void printFloat(float value, int places) {

  int digit;
  float tens=0.1;
  int tenscount=0;
  int i;
  float tempfloat=value;
  float d=0.5;
  
  if(value<0) d*=-1.0;
  for(i=0;i<places;i++) d/=10.0;    
  tempfloat+= d;
  if(value<0) tempfloat*=-1.0;
  while((tens*10.0)<=tempfloat) {
    tens*=10.0;
    tenscount+=1;
  }
  if(value<0) Serial.print('-');
  if(tenscount==0) Serial.print(0,DEC);
  for(i=0;i<tenscount;i++) {
    digit=(int)(tempfloat/tens);
    Serial.print(digit,DEC);
    tempfloat=tempfloat-((float)digit*tens);
    tens/=10.0;
  }
  if(places<=0) return;
  Serial.print('.');  
  for(i=0;i<places;i++) {
    tempfloat*=10.0; 
    digit=(int)tempfloat;
    Serial.print(digit,DEC);  
    tempfloat=tempfloat-(float)digit; 
  }
}
