// ValveWizard Valve Analyzer MKII
// Last updated 30/06/26 - Added "Quick Test" mode.

// Pins & variables:
  #define DIODE_MODE 1
  #define TRIODE_MODE 2
  #define PENTODE_MODE 3
  #define PENTODE_G2_MODE 4
  #define QUICK_TEST_MODE 5
  #define DIAGNOSTIC_MODE 6
  #define MIN_HV_INCREMENT 5
  #define TRIODE_TUBE    1
  #define PENTODE_TUBE   2

  #include <math.h>
  #include <avr/pgmspace.h>
  #include <Wire.h>

  #define MASTER_ADDR 0x0A
  #define DAC1_ADDR 0x60
  #define DAC2_ADDR 0x61

  #define DO_NOT_USE_PIN 5
  #define CHARGE1_PIN 8
  #define DISCHARGE1_PIN 11
  #define FIRE1_PIN 7
  #define VA1_PIN A6
  #define VA2_PIN A7
  #define IA1_HI_PIN A1
  #define IA1_LO_PIN A0
  #define IA2_HI_PIN A3
  #define IA2_LO_PIN A2
  #define DISCHARGE2_PIN 4
  #define FIRE2_PIN 2
  #define CHARGE2_PIN 3

  #define HV1CalibFactor 1.66
  #define HV2CalibFactor 1.66
  #define IA_HI_1CalibFactor 16.5
  #define IA_HI_2CalibFactor 16.5
  #define IA_LO_1CalibFactor 1.65
  #define IA_LO_2CalibFactor 1.65
  #define ARRAY_LENGTH 10
  int targetValues[ARRAY_LENGTH];
  int measuredValues[ARRAY_LENGTH];
  float calculatedValues[ARRAY_LENGTH];

  uint8_t testMode = 0;
  uint8_t tubeType = 0;
  uint8_t dataPoints = 16;
  int maxAnodeVolts = 0;
  float maxGridVolts1 = 0;
  float voltsPerStep = 0;
  int voltsPerG2Step = 0;
  int screenVolts = 0;
  uint8_t ulPercent = 0;
  float maxGridVolts2 = 0;
  float maxCurrent = 0;
  bool maxCurrentLimit = false;

  const char selectTestMessage[] PROGMEM = "Select test mode:";
  const char selectTubeMessage[] PROGMEM = "Select tube type:";
  const char diodeMessage[] PROGMEM = "1 = Diode (dual);";
  const char triodeMessage[] PROGMEM = "2 = Triode (dual);";
  const char pentodeMessage[] PROGMEM = "3 = Pentode;";
  const char pentodeG2Message[] PROGMEM = "4 = Pentode g2 curves;";
  const char quickTestMessage[] PROGMEM = "5 = Quick test;";
  const char diagnosticMessage[] PROGMEM = "6 = Diagnostic;";
  const char triodeTubeMessage[] PROGMEM = "1 = Triode (dual);";
  const char pentodeTubeMessage[] PROGMEM = "2 = Pentode;";
  const char voltsSymbol[] PROGMEM = " V";
  const char mASymbol[] PROGMEM = " mA";
  const char anodeMessage[] PROGMEM = "Set anode voltage supply to: [up to 500V]";
  const char screenMessage[] PROGMEM = "Set screen voltage supply to: [up to 500V]";
  const char gridMessage[] PROGMEM = "Set grid voltage supply to: [up to -60V]";
  const char sweepHV1Message[] PROGMEM = "Sweep anode(s) from 0V up to: [up to 500V]";
  const char dataPointsMessage[] PROGMEM = "Number of datapoints per sweep: [not less than 2]";
  const char gridBias1Message[] PROGMEM = "Step first grid bias supply from 0V down to: [up to -60V]";
  const char stepSizeMessage[] PROGMEM = "Step size:";
  const char HV2Message[] PROGMEM = "Set second high voltage supply to: [up to 500V]";
  const char HV2Message2[] PROGMEM = "Step second high voltage supply up to: [up to 500V]";
  const char maxCurrentMessage[] PROGMEM = "Limit anode(s) current to: [Enter 0 for no limitation]";
  const char ulMessage[] PROGMEM = "UL percent: [0% = full pentode; 100% = full triode] ";
  const char percentSymbol[] PROGMEM = " %";
  const char gridBias2Message[] PROGMEM = "Set second grid bias supply to: [up to -60V]";
  const char runMessage[] PROGMEM = "Run test? [y/n]";
  const char errorMessage[] PROGMEM = "Error: Out of range!";
  const char overcurrentMessage[] PROGMEM = "Error: Overcurrent detected!";
  const char columnLabels[] PROGMEM = "Vb1(V), HV1(V), Ia1(mA), Vb2(V), HV2(V), Ia2(mA),";
  const char quickTestTriodeLabels[] PROGMEM = "Vg1(V), Va1(V), Ia1(mA), gm1(mA/V), ra1(kOhm), mu1 - Vg2(V), Va2(V), Ia2(mA), gm2(mA/V), ra2(kOhm), mu2,";
  const char quickTestPentodeLabels[] PROGMEM = "Vg(V), Va(V), Ia(mA), Vsc(V), Isc(mA), gm(mA/V), ra(kOhm), mu,";
  const char passMessage[] PROGMEM = "Pass";
  const char failMessage[] PROGMEM = "Fail";
  const char hv1Message[] PROGMEM = "HV1";
  const char hv2Message[] PROGMEM = "HV2";
  const char chargeMessage[] PROGMEM = "Charge";
  const char dischargeMessage[] PROGMEM = "Discharge";
  const char fireMessage[] PROGMEM = "Fire";
  const char timeOutMessage[] PROGMEM = "Timeout";

  // Array indexes:
  #define VH         0
  #define IH         1
  #define VG1        2
  #define HV1        3
  #define IA_HI_1    4
  #define IA_LO_1    5
  #define VG2        6
  #define HV2        7
  #define IA_HI_2    8
  #define IA_LO_2    9

// 

//================================================================

void setup() {
  Serial.begin(57600);
  pinMode(DO_NOT_USE_PIN, INPUT);
  analogReference(EXTERNAL);
  TCCR0B = (TCCR0B & 0b11111000) | 0x01;

  pinMode(LED_BUILTIN, OUTPUT);   
  pinMode(CHARGE1_PIN, OUTPUT);
  pinMode(DISCHARGE1_PIN, OUTPUT);
  pinMode(FIRE1_PIN, OUTPUT);
  pinMode(CHARGE2_PIN, OUTPUT);
  pinMode(DISCHARGE2_PIN, OUTPUT);
  pinMode(FIRE2_PIN, OUTPUT);

  digitalWrite(FIRE1_PIN, LOW);
  digitalWrite(FIRE2_PIN, LOW);
  digitalWrite(CHARGE1_PIN, LOW);
  digitalWrite(CHARGE2_PIN, LOW);
  digitalWrite(DISCHARGE1_PIN, HIGH);
  digitalWrite(DISCHARGE2_PIN, HIGH);

  Wire.begin(MASTER_ADDR);
  Wire.setClock(100000);
  Wire.setWireTimeout(25000, true);

  setGridSafeIdle();
}

//================================================================

void loop() {
  static int runTest = 0;
  while(!runTest){
    getUserParameters();
    runTest = checkForUserErrors();
  }

  float sweepTable1[dataPoints];
  float sweepTable2[dataPoints];

  int originalMaxAnodeVolts = maxAnodeVolts;
  runTest = getUserInstruction();

  if(runTest){
    if(testMode < PENTODE_G2_MODE){
      printMessage(columnLabels);
      float actual_max_bias = maxGridVolts1;
      if(actual_max_bias == 0){ actual_max_bias = 0.01;
      }

      for(float j = 0; j <= maxGridVolts1; j += voltsPerStep){
        setTargetValue(VG1, j);
        if(targetValues[VG1] < 1){
          targetValues[VG1] = 1; 
        }
        
        maxAnodeVolts = originalMaxAnodeVolts;
        calculateSweepValues(sweepTable1, sweepTable2);

        if(testMode == TRIODE_MODE){ targetValues[VG2] = targetValues[VG1];
        }
        else{ 
          setTargetValue(VG2, maxGridVolts2);
          if(targetValues[VG2] < 1){ targetValues[VG2] = 1;
          }
        }
        if(testMode == DIODE_MODE){
          j = actual_max_bias + 1;
        }
  
        if(maxGridVolts1 == 0){
          j = actual_max_bias + 1;
        }
  
        measuredValues[IA_LO_1] = 0;
        measuredValues[IA_LO_2] = 0;
        setGridVolts();
        maxCurrentLimit = false;
        for(int i = 0; i < dataPoints; i++){
          if((measuredValues[IA_LO_1] > 1022) || (measuredValues[IA_LO_2] > 1022)){
            i++;
            printMessage(overcurrentMessage);
          }
          else{
            float nextHV1 = sweepTable1[i];
            float nextHV2 = sweepTable2[i];
      
            setTargetValue(HV1, nextHV1);
            setTargetValue(HV2, nextHV2);
            chargeHighVoltages();
            doMeasurement();
            printMeasuredValues();
          }
          if(maxCurrentLimit){
            break;
          }
        }
        dischargeHighVoltages();
      }
      setGridSafeIdle();
    }
    else if(testMode == PENTODE_G2_MODE){
      printMessage(columnLabels);
      calculateSweepValues(sweepTable1, sweepTable2); 
      setTargetValue(VG1, 0);
      setTargetValue(VG2, 0);
      
      if(targetValues[VG1] < 1){ targetValues[VG1] = 1;
      }
      if(targetValues[VG2] < 1){ targetValues[VG2] = 1;
      }

      setGridVolts();
      for(int j = voltsPerG2Step; j <= screenVolts; j += voltsPerG2Step){
        setTargetValue(HV2, j);
        maxCurrentLimit = false;
        for(int i = 0; i < dataPoints; i++){
          float nextHV1 = sweepTable1[i];
          setTargetValue(HV1, nextHV1);
          chargeHighVoltages();
          doMeasurement(); 
          printMeasuredValues();
          if(maxCurrentLimit){
            break;
          }
          if((measuredValues[IA_LO_1] > 1022) || (measuredValues[IA_LO_2] > 1022)){
            i = dataPoints + 1;
            j = screenVolts + 1;
            printMessage(overcurrentMessage);
          }
        }
        dischargeHighVoltages();
      }
      setGridSafeIdle();
    }
    else if(testMode == QUICK_TEST_MODE){
      quickTest();
    }
    else if(testMode == DIAGNOSTIC_MODE){
      diagnostic();
    }
  }

  digitalWrite(LED_BUILTIN, LOW);
  setGridSafeIdle();
  runTest = 0; 
}

//================================================================

void setGridVolts(){
  if(measuredValues[VG1] != targetValues[VG1]){     
    Wire.beginTransmission(DAC1_ADDR);
    Wire.write(64);                               
    Wire.write(targetValues[VG1] >> 4);
    Wire.write((targetValues[VG1] & 15) << 4);
    Wire.endTransmission();            
    measuredValues[VG1] = targetValues[VG1];
  }
                      
  if(measuredValues[VG2] != targetValues[VG2]){   
    Wire.beginTransmission(DAC2_ADDR);
    Wire.write(64);                             
    Wire.write(targetValues[VG2] >> 4);
    Wire.write((targetValues[VG2] & 15) << 4);
    Wire.endTransmission();          
    measuredValues[VG2] = targetValues[VG2];
  }
}

//================================================================

void chargeHighVoltages(){ 
  digitalWrite(FIRE1_PIN, LOW);
  digitalWrite(FIRE2_PIN, LOW);
  digitalWrite(CHARGE1_PIN, LOW);
  digitalWrite(CHARGE2_PIN, LOW);
  digitalWrite(DISCHARGE1_PIN, LOW);
  digitalWrite(DISCHARGE2_PIN, LOW);
  delay(100);

  measuredValues[HV1] = analogRead(VA1_PIN);
  measuredValues[HV2] = analogRead(VA2_PIN);

  while((measuredValues[HV1] < targetValues[HV1]) || (measuredValues[HV2] < targetValues[HV2])){   
    while(measuredValues[HV1] < targetValues[HV1]) {
      digitalWrite(CHARGE1_PIN, HIGH);
      measuredValues[HV1] = analogRead(VA1_PIN);    
    }
    digitalWrite(CHARGE1_PIN, LOW);
    measuredValues[HV2] = analogRead(VA2_PIN);
    while(measuredValues[HV2] < targetValues[HV2]) {
      digitalWrite(CHARGE2_PIN, HIGH);
      measuredValues[HV2] = analogRead(VA2_PIN);    
    }
    digitalWrite(CHARGE2_PIN, LOW);
    measuredValues[HV1] = analogRead(VA1_PIN);
  }
}

//================================================================

void print_arrays(){  
  Serial.println("Target....Measured");
  for(int i = 0; i < ARRAY_LENGTH; i++){
    Serial.print(targetValues[i]);
    Serial.print("       ");
    Serial.println(measuredValues[i]);
  }
  Serial.println(" ");
}

//================================================================

void printMeasuredValues(void){
  float temp;
  
  temp = (measuredValues[VG1] / 60.4); 
  if (temp > 0.005) {
    Serial.print("-");
  }
  Serial.print(temp);
  Serial.print(", ");
  
  temp = (measuredValues[HV1] / HV1CalibFactor); 
  Serial.print(temp);
  Serial.print(", ");
  
  if(measuredValues[IA_HI_1] < 835){
    temp = (measuredValues[IA_HI_1] / IA_HI_1CalibFactor);
  }
  else{    
    temp = (measuredValues[IA_LO_1] / IA_LO_1CalibFactor);
  }
  if(maxCurrent > 0){
    if(temp >= maxCurrent){
      maxCurrentLimit = true;
    }
  }
  Serial.print(temp);
  Serial.print(", ");
  
  temp = (measuredValues[VG2] / 60.4); 
  if (temp > 0.005) {
    Serial.print("-");
  }
  Serial.print(temp);
  Serial.print(", ");
  
  temp = (measuredValues[HV2] / HV2CalibFactor); 
  Serial.print(temp);
  Serial.print(", ");
  
  if(measuredValues[IA_HI_2] < 835){
    temp = (measuredValues[IA_HI_2] / IA_HI_2CalibFactor);
  }
  else{    
    temp = (measuredValues[IA_LO_2] / IA_LO_2CalibFactor);
  }
  if(maxCurrent > 0){
    if(temp >= maxCurrent){
      maxCurrentLimit = true;
    }
  }
  Serial.print(temp);
  Serial.println(",");
}

//================================================================

void setTargetValue(int index, float volts){
  float temp = 0;
  switch(index){
    case VG1: temp = round(volts * 60.4); break;
    case VG2: temp = round(volts * 60.4); break;
    case HV1: temp = volts * HV1CalibFactor; break;
    case HV2: temp = volts * HV2CalibFactor; break; 
    default: break;
  }

  if(index == VG1 || index == VG2){
    if(temp < 4096){
      targetValues[index] = word(temp);
    }
  }
  else{
    if(temp < 1028){
      targetValues[index] = word(temp);
    }
  }  
}

//================================================================

void calculateSweepValues(float sweepArray1[], float sweepArray2[]){
  sweepArray1[0] = 0;
  sweepArray2[0] = 0;
  if(testMode < PENTODE_MODE){
    float temp = (float)maxAnodeVolts / (dataPoints - 1);
    float temp2 = 0;
    for(int i = 1; i < dataPoints; i++){
      temp2 += temp;
      sweepArray1[i] = temp2;
      sweepArray2[i] = sweepArray1[i];
    }
    sweepArray1[dataPoints - 1] = maxAnodeVolts; 
    sweepArray2[dataPoints - 1] = maxAnodeVolts;
  }
  else{
    for(int i = 1; i < dataPoints; i++){
      float temp = log10(i);
      float temp2 = log10(dataPoints);
      temp = temp / temp2;
      temp = (1 - temp);
      temp = temp * maxAnodeVolts;
      sweepArray1[dataPoints - i] = temp;
    }

    for(int i = 0; i < dataPoints; i++){
      if(sweepArray1[i] < i * MIN_HV_INCREMENT){ 
        sweepArray1[i] = i * MIN_HV_INCREMENT;
      }
      sweepArray2[i] = sweepArray1[i];
      if(testMode == PENTODE_MODE){
        float temp = screenVolts;
        temp += (sweepArray1[i] * ulPercent / 100); 
        temp -= (screenVolts * ulPercent / 100);
        sweepArray2[i] = temp;
      }
    }
  }
}

//================================================================

int checkForUserErrors(void){
  int okToProceed = 1;
  if(testMode == QUICK_TEST_MODE){
    dataPoints = 2;
  }
  if((maxAnodeVolts < (dataPoints * MIN_HV_INCREMENT)) || (maxAnodeVolts > 500)){
    okToProceed = 0;
  }
  if((dataPoints < 2) || (dataPoints > (maxAnodeVolts / MIN_HV_INCREMENT))){
    okToProceed = 0;
  }
  if((maxGridVolts1 < 0) || (maxGridVolts1 > 66)){
    okToProceed = 0;
  }
  if(voltsPerStep > maxGridVolts1){
    okToProceed = 0;
  }
  if((screenVolts < 0) || (screenVolts > 500)){
    okToProceed = 0;
  }
  if((maxGridVolts2 < 0) || (maxGridVolts2 > 66)){
    okToProceed = 0;
  }
  if((ulPercent < 0) || (ulPercent > 100)){
    okToProceed = 0;
  }
  if(voltsPerG2Step < MIN_HV_INCREMENT){
    okToProceed = 0;
  }

  if(!okToProceed){
    printMessage(errorMessage);
  }
  return okToProceed;
}

//================================================================

void doMeasurement(void){
  int volts1, amps1_hi, amps1_lo;
  int volts2, amps2_hi, amps2_lo;
  
  digitalWrite(FIRE1_PIN, HIGH);
  digitalWrite(FIRE2_PIN, HIGH);
  delayMicroseconds(200);
  
  volts1 = analogRead(VA1_PIN);  
  volts1 = analogRead(VA1_PIN);
  amps1_hi = analogRead(IA1_HI_PIN);
  amps1_hi = analogRead(IA1_HI_PIN);
  amps1_lo = analogRead(IA1_LO_PIN);
  amps1_lo = analogRead(IA1_LO_PIN);
  
  volts2 = analogRead(VA2_PIN);
  volts2 = analogRead(VA2_PIN);
  amps2_hi = analogRead(IA2_HI_PIN);
  amps2_hi = analogRead(IA2_HI_PIN);
  amps2_lo = analogRead(IA2_LO_PIN);
  amps2_lo = analogRead(IA2_LO_PIN);
  
  digitalWrite(FIRE1_PIN, LOW);      
  digitalWrite(FIRE2_PIN, LOW);
  
  measuredValues[HV1] = volts1;
  measuredValues[IA_HI_1] = amps1_hi;
  measuredValues[IA_LO_1] = amps1_lo;
  measuredValues[HV2] = volts2; 
  measuredValues[IA_HI_2] = amps2_hi;
  measuredValues[IA_LO_2] = amps2_lo;
}

//================================================================

void dischargeHighVoltages(void){
  digitalWrite(LED_BUILTIN, HIGH); 
  digitalWrite(FIRE1_PIN, LOW);
  digitalWrite(FIRE2_PIN, LOW); 
  digitalWrite(CHARGE1_PIN, LOW);
  digitalWrite(CHARGE2_PIN, LOW);
  digitalWrite(DISCHARGE1_PIN, HIGH);   
  digitalWrite(DISCHARGE2_PIN, HIGH);
  
  while((analogRead(VA1_PIN) > 10) || (analogRead(IA1_HI_PIN) > 5)){
    delay(10);
  }
  while((analogRead(VA2_PIN) > 10) || (analogRead(IA2_HI_PIN) > 5)){
    delay(10);
  }

  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
}

//================================================================

void getUserParameters(void){
  testMode = 0;
  tubeType = 0;
  dataPoints = 16;
  maxAnodeVolts = dataPoints * MIN_HV_INCREMENT;
  maxGridVolts1 = 0;
  voltsPerStep = 0;
  screenVolts = 0;
  ulPercent = 0;
  maxGridVolts2 = 0;
  maxCurrent = 0;
  voltsPerG2Step = 50;
  
  printMessage(selectTestMessage);
  printMessage(diodeMessage);
  printMessage(triodeMessage);
  printMessage(pentodeMessage);
  printMessage(pentodeG2Message);
  printMessage(quickTestMessage);
  printMessage(diagnosticMessage);

  while(Serial.available() > 0){ Serial.read(); } 
  while(Serial.available() < 1);
  testMode = Serial.parseInt();
  Serial.println(testMode);
  
  if(testMode < QUICK_TEST_MODE){
    printMessage(sweepHV1Message);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    maxAnodeVolts = Serial.parseInt();
    Serial.print(maxAnodeVolts); printMessage(voltsSymbol);
  }

  if(testMode == TRIODE_MODE){
    printMessage(gridBias1Message);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    maxGridVolts1 = Serial.parseFloat();
    Serial.print("-"); Serial.print(maxGridVolts1); printMessage(voltsSymbol);

    printMessage(stepSizeMessage);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    voltsPerStep = Serial.parseFloat();
    Serial.print(voltsPerStep);  printMessage(voltsSymbol);
  }
  else if(testMode == PENTODE_MODE){ 
    printMessage(gridBias1Message);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    maxGridVolts1 = Serial.parseFloat();
    Serial.print("-"); Serial.print(maxGridVolts1); printMessage(voltsSymbol);
  
    printMessage(stepSizeMessage);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    voltsPerStep = Serial.parseFloat();
    Serial.print(voltsPerStep);  printMessage(voltsSymbol);
  
    printMessage(HV2Message);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    screenVolts = Serial.parseInt();
    Serial.print(screenVolts); printMessage(voltsSymbol);
  
    printMessage(ulMessage);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    ulPercent = Serial.parseInt();
    Serial.print(ulPercent); printMessage(percentSymbol);
  
    printMessage(gridBias2Message);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    maxGridVolts2 = Serial.parseFloat();
    Serial.print("-"); Serial.print(maxGridVolts2); printMessage(voltsSymbol);
  }
  else if(testMode == PENTODE_G2_MODE){ 
    printMessage(HV2Message2);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    screenVolts = Serial.parseInt();
    Serial.print(screenVolts);  printMessage(voltsSymbol);
  
    printMessage(stepSizeMessage);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    voltsPerG2Step = Serial.parseInt(); 
    Serial.print(voltsPerG2Step);  printMessage(voltsSymbol);
  }
  else if(testMode == QUICK_TEST_MODE){ 
    printMessage(selectTubeMessage);
    printMessage(triodeTubeMessage);
    printMessage(pentodeTubeMessage);
  
    while(Serial.available() > 0){ Serial.read(); }
    while(Serial.available() < 1);
    tubeType = Serial.parseInt();
    Serial.println(tubeType);
  
    printMessage(gridMessage);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    maxGridVolts1 = Serial.parseFloat();
    Serial.print("-"); Serial.print(maxGridVolts1); printMessage(voltsSymbol);
  
    printMessage(anodeMessage);
    while(Serial.available() > 0){ Serial.read(); }
    while(Serial.available() < 1);
    maxAnodeVolts = Serial.parseInt();
    Serial.print(maxAnodeVolts);  printMessage(voltsSymbol);
    
    if(tubeType == PENTODE_TUBE){
      printMessage(screenMessage);
      while(Serial.available() > 0){ Serial.read(); } 
      while(Serial.available() < 1);
      screenVolts = Serial.parseInt();
      Serial.print(screenVolts);
      printMessage(voltsSymbol);
    }
  }
  
  if(testMode < QUICK_TEST_MODE){
    printMessage(maxCurrentMessage);
    while(Serial.available() > 0){ Serial.read(); } 
    while(Serial.available() < 1);
    maxCurrent = Serial.parseFloat();
    Serial.print(maxCurrent); printMessage(mASymbol);
  }
}

//================================================================

int getUserInstruction(void){
  int okToProceed = 0;
  printMessage(runMessage);
  while(Serial.available() > 0){ Serial.read(); }
  while(Serial.available() < 1);
  if(Serial.read() == 'y'){
    okToProceed = 1;
  }
  return okToProceed;
}

//================================================================

void printMessage(const char *messageptr) {
  char temp;
  int len = strlen_P(messageptr);
  for(int i = 0; i < len; i++){
    temp = pgm_read_byte_near(messageptr + i);
    Serial.print(temp);
  }
  Serial.println();
}

//================================================================

void setGridSafeIdle(void) {
  int safeValue = 2000; 
  
  Wire.beginTransmission(DAC1_ADDR);
  Wire.write(64);                               
  Wire.write(safeValue >> 4);
  Wire.write((safeValue & 15) << 4);
  Wire.endTransmission();            
  measuredValues[VG1] = safeValue;

  Wire.beginTransmission(DAC2_ADDR);
  Wire.write(64);                             
  Wire.write(safeValue >> 4);
  Wire.write((safeValue & 15) << 4);
  Wire.endTransmission();          
  measuredValues[VG2] = safeValue;
}

//================================================================

void diagnostic(void) {
  dischargeHighVoltages();
  int temp;                                                                 
  int time_out;
  
  digitalWrite(FIRE1_PIN, LOW);
  digitalWrite(FIRE2_PIN, LOW);
  digitalWrite(CHARGE1_PIN, LOW);    
  digitalWrite(CHARGE2_PIN, LOW);    
  digitalWrite(DISCHARGE1_PIN, LOW);
  digitalWrite(DISCHARGE2_PIN, LOW);  
   
  printMessage(hv1Message);
  printMessage(chargeMessage);   
  temp = analogRead(VA1_PIN);  
  temp = analogRead(VA1_PIN);
  time_out = 0;
  digitalWrite(CHARGE1_PIN, HIGH);
  while((temp < 100) && (time_out < 10000)) {
    temp = analogRead(VA1_PIN);
    delay(10);
    time_out++; 
  }
  digitalWrite(CHARGE1_PIN, LOW);
  if(temp >= 100){
    printMessage(passMessage);
  }
  else{
    printMessage(failMessage);
    printMessage(timeOutMessage);
  }

  printMessage(hv1Message);
  printMessage(dischargeMessage);
  time_out = 0;
  digitalWrite(DISCHARGE1_PIN, HIGH);
  while((temp > 10) && (time_out < 10000)) {
    temp = analogRead(VA1_PIN);
    delay(100);
    time_out++; 
  }
  if(temp <= 10){
    printMessage(passMessage);
  }
  else{
    printMessage(failMessage);
    printMessage(timeOutMessage);
  }

  printMessage(hv2Message);
  printMessage(chargeMessage);
  temp = analogRead(VA2_PIN);  
  temp = analogRead(VA2_PIN);
  
  time_out = 0;
  digitalWrite(CHARGE2_PIN, HIGH);
  while((temp < 100) && (time_out < 10000)) {
    temp = analogRead(VA2_PIN);
    delay(10);
    time_out++;
  }
  digitalWrite(CHARGE2_PIN, LOW);
  if(temp >= 100){
    printMessage(passMessage);
  }
  else{
    printMessage(failMessage);
    printMessage(timeOutMessage);
  }

  printMessage(hv2Message); 
  printMessage(dischargeMessage);
  time_out = 0;
  digitalWrite(DISCHARGE2_PIN, HIGH);
  while((temp > 10) && (time_out < 10000)) {
    temp = analogRead(VA2_PIN);
    delay(100);
    time_out++;
  }
  if(temp <= 10){
    printMessage(passMessage);
  }
  else{
    printMessage(failMessage);
    printMessage(timeOutMessage);
  }

  dischargeHighVoltages();
  delay(10000);

  // Bias voltage checking:

  Serial.println();
  Serial.println(F("--- Starting Grid Calibration Mode ---"));
  Serial.println(F("Connect multimeter to G1 or G2 relative to GND."));
  
  while(Serial.available() > 0) { Serial.read(); }

  Serial.println(F("Step 1: Setting VG1 and VG2 to 0V. Measure now..."));
  setTargetValue(VG1, 0.0);
  setTargetValue(VG2, 0.0);
  setGridVolts();
  Serial.println(F("Press ENTER or send any character in Serial Monitor to continue to Step 2..."));
  while(Serial.available() == 0) { 
    delay(10);
  }
  delay(500);
  while(Serial.available() > 0) { Serial.read(); } 

  delay(500);

  Serial.println(F("Step 2: Setting VG1 and VG2 to -10V. Measure now..."));
  setTargetValue(VG1, 10.0);
  setTargetValue(VG2, 10.0);
  setGridVolts();
  Serial.println(F("Press ENTER or send any character to continue to Step 3..."));
  while(Serial.available() == 0) { 
    delay(10);
  }
  delay(500);
  while(Serial.available() > 0) { Serial.read(); }

  delay(500);

  Serial.println(F("Step 3: Setting VG1 and VG2 to -40V. Measure now..."));
  setTargetValue(VG1, 40.0);
  setTargetValue(VG2, 40.0);
  setGridVolts();
  Serial.println(F("Press ENTER or send any character to finish test..."));
  while(Serial.available() == 0) { 
    delay(10);
  }
  delay(500);
  while(Serial.available() > 0) { Serial.read(); }

  delay(500);

  Serial.println(F("Grid calibration steps finished. Resetting to Safe Idle."));
  setGridSafeIdle();
  Serial.println(F("---------------------------------------"));
  Serial.println();

  dischargeHighVoltages();
}

//================================================================

void quickTest(void) {

  float VaStep1, VaStep2, VaStep3;
  float VscStep1, VscStep2, VscStep3;
  float Vg1Step1, Vg1Step2, Vg2Step1, Vg2Step2;
  float IaStep1, IaStep2, IaStep3;
  float IscStep1, IscStep2, IscStep3;

  if(tubeType == TRIODE_TUBE){
    // Step one:
    measuredValues[IA_LO_1] = 0;
    measuredValues[IA_LO_2] = 0;
    setTargetValue(VG1, maxGridVolts1);
    if(targetValues[VG1] < 1){
      targetValues[VG1] = 1;
    }
    targetValues[VG2] = targetValues[VG1];
    setGridVolts();
    delay(10000);
    setTargetValue(HV1, maxAnodeVolts);
    setTargetValue(HV2, maxAnodeVolts);
    chargeHighVoltages();
    doMeasurement();
    calculateRealValues();
  
    Vg1Step1 = calculatedValues[VG1];
    Vg2Step1 = calculatedValues[VG2];
    VaStep1 = calculatedValues[HV1];
    VscStep1 = calculatedValues[HV2];
    IaStep1 = calculatedValues[IA_HI_1];
    IscStep1 = calculatedValues[IA_HI_2];
  
    // Step two:
    if(IaStep1 < 2.0){
      maxGridVolts2 = maxGridVolts1 + 0.2;
    }
    else if(IaStep1 >= 2.0 && IaStep1 < 10.0){
      maxGridVolts2 = maxGridVolts1 + 0.4;
    }
    else if(IaStep1 >= 10.0 && IaStep1 < 50.0){
      maxGridVolts2 = maxGridVolts1 + 0.7;
    }
    else if(IaStep1 >= 50.0){
      maxGridVolts2 = maxGridVolts1 + 3.0;
    }
  
    measuredValues[IA_LO_1] = 0;
    measuredValues[IA_LO_2] = 0;
    setTargetValue(VG1, maxGridVolts2);
    if(targetValues[VG1] < 1){
      targetValues[VG1] = 1;
    }
    targetValues[VG2] = targetValues[VG1];
    setGridVolts();
    delay(10000);
    setTargetValue(HV1, maxAnodeVolts);
    setTargetValue(HV2, maxAnodeVolts);
    chargeHighVoltages();
    doMeasurement();
    calculateRealValues();
  
    Vg1Step2 = calculatedValues[VG1];
    Vg2Step2 = calculatedValues[VG2];
    VaStep2 = calculatedValues[HV1];
    VscStep2 = calculatedValues[HV2];
    IaStep2 = calculatedValues[IA_HI_1];
    IscStep2 = calculatedValues[IA_HI_2];
  
    // Step three::
    maxAnodeVolts = maxAnodeVolts + 20.0;
    measuredValues[IA_LO_1] = 0;
    measuredValues[IA_LO_2] = 0;
    setTargetValue(VG1, maxGridVolts1);
    if(targetValues[VG1] < 1){
      targetValues[VG1] = 1; 
    }
    targetValues[VG2] = targetValues[VG1];
    setGridVolts();
    delay(10000);
    setTargetValue(HV1, maxAnodeVolts);
    setTargetValue(HV2, maxAnodeVolts);
    chargeHighVoltages();
    doMeasurement();
    calculateRealValues();
  
    VaStep3 = calculatedValues[HV1];
    VscStep3 = calculatedValues[HV2];
    IaStep3 = calculatedValues[IA_HI_1];
    IscStep3 = calculatedValues[IA_HI_2];
  
    // Final calculations:
    float dVg1 = abs(Vg1Step2 - Vg1Step1);
    float dIa = abs(IaStep2 - IaStep1);
    float gm1 = dIa / dVg1;
  
    float dVg2 = abs(Vg2Step2 - Vg2Step1);
    float dIsc = abs(IscStep2 - IscStep1);
    float gm2 = dIsc / dVg2;
  
    float dVa = abs(VaStep3 - VaStep1);
    float dIa2 = abs(IaStep3 - IaStep1);
    float ra1 = dVa / dIa2;
  
    float dVsc = abs(VscStep3 - VscStep1);
    float dIsc2 = abs(IscStep3 - IscStep1);
    float ra2 = dVsc / dIsc2;
  
    float mu1 = gm1 * ra1;
    float mu2 = gm2 * ra2;
  
    // Print measured values:
    printMessage(quickTestTriodeLabels);
    Serial.print("-");
    Serial.print(Vg1Step1);
    Serial.print(" , ");
    Serial.print(VaStep1);
    Serial.print(" , ");
    Serial.print(IaStep1);
    Serial.print(" , ");
    Serial.print(gm1);
    Serial.print(" , ");
    Serial.print(ra1);
    Serial.print(" , ");
    Serial.print(mu1);
    Serial.print(" , ");
    Serial.print("-");
    Serial.print(Vg2Step1);
    Serial.print(" , ");
    Serial.print(VscStep1);
    Serial.print(" , ");
    Serial.print(IscStep1);
    Serial.print(" , ");
    Serial.print(gm2);
    Serial.print(" , ");
    Serial.print(ra2);
    Serial.print(" , ");
    Serial.print(mu2);
    Serial.println(",");
  }
  else if(tubeType == PENTODE_TUBE){
    // Step one:
    measuredValues[IA_LO_1] = 0;
    measuredValues[IA_LO_2] = 0;
    setTargetValue(VG1, maxGridVolts1);
    if(targetValues[VG1] < 1){
      targetValues[VG1] = 1; 
    }
    targetValues[VG2] = 0;
    setGridVolts();
    delay(10000);
    setTargetValue(HV1, maxAnodeVolts);
    setTargetValue(HV2, screenVolts);
    chargeHighVoltages();
    doMeasurement();
    calculateRealValues();
    
    Vg1Step1 = calculatedValues[VG1];
    VaStep1 = calculatedValues[HV1];
    VscStep1 = calculatedValues[HV2];
    IaStep1 = calculatedValues[IA_HI_1];
    IscStep1 = calculatedValues[IA_HI_2];
    
    // Step two:
    if(IaStep1 < 50.0){
      maxGridVolts2 = maxGridVolts1 + 0.4;
    }
    else{
      maxGridVolts2 = maxGridVolts1 + 1.0;
    }
    
    measuredValues[IA_LO_1] = 0;
    measuredValues[IA_LO_2] = 0;
    setTargetValue(VG1, maxGridVolts2);
    if(targetValues[VG1] < 1){
      targetValues[VG1] = 1; 
    }
    targetValues[VG2] = 0;
    setGridVolts();
    delay(10000);
    setTargetValue(HV1, maxAnodeVolts);
    setTargetValue(HV2, screenVolts);
    chargeHighVoltages();
    doMeasurement();
    calculateRealValues();
    
    Vg1Step2 = calculatedValues[VG1];
    VaStep2 = calculatedValues[HV1];
    VscStep2 = calculatedValues[HV2];
    IaStep2 = calculatedValues[IA_HI_1];
    IscStep2 = calculatedValues[IA_HI_2];

    // Step three::
    maxAnodeVolts = maxAnodeVolts + 40.0;  
    measuredValues[IA_LO_1] = 0;
    measuredValues[IA_LO_2] = 0;
    setTargetValue(VG1, maxGridVolts1);
    if(targetValues[VG1] < 1){
      targetValues[VG1] = 1; 
    }
    targetValues[VG2] = 0;
    setGridVolts();
    delay(10000);
    setTargetValue(HV1, maxAnodeVolts);
    setTargetValue(HV2, screenVolts);
    chargeHighVoltages();
    doMeasurement();
    calculateRealValues();
    
    VaStep3 = calculatedValues[HV1];
    VscStep3 = calculatedValues[HV2];
    IaStep3 = calculatedValues[IA_HI_1];
    IscStep3 = calculatedValues[IA_HI_2];
    
    // Final calculations:
    float dVg1 = abs(Vg1Step2 - Vg1Step1);
    float dIa = abs(IaStep2 - IaStep1);
    float gm = dIa / dVg1;
    
    float dVa = abs(VaStep3 - VaStep1);
    float dIa2 = abs(IaStep3 - IaStep1);
    if(dIa2 == 0){
      dIa2 = 0.01;
    }
    float ra = dVa / dIa2;
    float mu = gm * ra;
    
    // Print measured values:
    printMessage(quickTestPentodeLabels);
    Serial.print("-");
    Serial.print(Vg1Step1);
    Serial.print(" , ");
    Serial.print(VaStep1);
    Serial.print(" , ");
    Serial.print(IaStep1);
    Serial.print(" , ");
    Serial.print(VscStep1);
    Serial.print(" , ");
    Serial.print(IscStep1);
    Serial.print(" , ");
    Serial.print(gm);
    Serial.print(" , ");
    Serial.print(ra);
    Serial.print(" , ");
    Serial.print(mu);
    Serial.println(",");
  }
  dischargeHighVoltages();
  setGridSafeIdle();
}

//================================================================

void calculateRealValues(void){
  
  calculatedValues[VG1] = (measuredValues[VG1] / 60.4);
  calculatedValues[HV1] = (measuredValues[HV1] / HV1CalibFactor); 
  if(measuredValues[IA_HI_1] < 835){
    calculatedValues[IA_HI_1] = (measuredValues[IA_HI_1] / IA_HI_1CalibFactor);
  }
  else{
    calculatedValues[IA_HI_1] = (measuredValues[IA_LO_1] / IA_LO_1CalibFactor);
  }
  
  calculatedValues[VG2] = (measuredValues[VG2] / 60.4);
  calculatedValues[HV2] = (measuredValues[HV2] / HV2CalibFactor); 
  if(measuredValues[IA_HI_2] < 835){
    calculatedValues[IA_HI_2] = (measuredValues[IA_HI_2] / IA_HI_2CalibFactor);
  }
  else{    
    calculatedValues[IA_HI_2] = (measuredValues[IA_LO_2] / IA_LO_2CalibFactor);
  } 
  
  if((measuredValues[IA_LO_1] > 1022) || (measuredValues[IA_LO_2] > 1022)){
    printMessage(overcurrentMessage); 
    while(1){
    }
  }
}
