#include <Adafruit_ADS1X15.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

float computeMilliVolts(int16_t counts);

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Adafruit_ADS1115 ads;  /* Use this for the 16-bit version */

// These constants won't change. They're used to give names to the pins used:
// constants won't change. They're used here to set pin numbers:
const int emitterPin = 2;           // arduino pin the emitter pin of the transistor is on
const int basePin = 3;              // arduino pin the base pin of the transistor is on
const int collectorPin = 5;         // arduino pin the collector pin of the transistor is on
const int collectorResistorPin = 6; // arduino pin the collector resistor is on
const int baseResistorPin = 7;      // arduino pin the base resistor is on

// variables will change:
int collectorPinState = 0;  // variable for reading the collector pin status
int transistorType = 0;     // Type 0 is NPN, Type 1 is PNP

void setup() {
  int16_t adc0, adc1;               // variables to hold the 16-bit ADC reading
  float collector_milliVolts = 0.0; // variable to hold the collector voltage in milli-volts
  float rail_milliVolts = 0.0;      // variable to hold the rail voltage in milli-volts
  float leak_milliVolts = 0.0;      // variable to hold leakage voltage in milli-volts
  float leak_uA = 0.0;              // variable to hold leakage current in micro-amps
  float baseCurrent_uA = 0.0;       // variable to hold the base current in micro-amps
  float gain = 0.0;                 // variable to hold the gain (which includes the leakage)
  float trueGain = 0.0;             // variable to hold the true gain (subtracting leakage)

  // below are the results of resistance of the resistors used on this board.
  int16_t collector_resistor = 997; // collector resistor 1K is really 0.997K
  long base_resistor = 1204000;     // base resistor 1.2M is really 1.204M
  
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();

  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1);
  }

  // In this step, we will set the emitter pin to GND and the base pin
  // to 5 volts. So, we need to set the emitter and base pins to output
  // mode, so we can connect them to those power rails. The collector
  // pin will need to be set to input, as we will use this to see if the
  // transistor is an NPN or PNP germanium transistor
  pinMode(emitterPin, OUTPUT);
  pinMode(basePin, OUTPUT);
  pinMode(collectorPin, INPUT);
  
  digitalWrite(emitterPin, LOW);  // set the emitter pin to GND
  digitalWrite(basePin, HIGH);    // set the base pin to 5V

  delay(50);  // wait a moment

  // see what the voltage is on the collector pin
  collectorPinState = digitalRead(collectorPin);

  // based off if voltage was detected on the collector pin...
  if (collectorPinState == HIGH) {
    // if we saw the voltage from the base pin jump over to the collector pin,
    // it's a PNP transistor (type 1)
    transistorType = 1;
  } else {
    // otherwise, the voltage from the base pin did not jump to the collector
    // pin, it's an NPN transistor (type 0)
    transistorType = 0;

    // if it is a silicon PNP transistor, this will have the same effect as an NPN
    // so it doesn't work.
  }

  delay(50);  // wait a moment

  // reset the emitter and base pins and set them back to floating
  digitalWrite(emitterPin, LOW);
  pinMode(emitterPin, INPUT);
  digitalWrite(basePin, LOW);
  pinMode(basePin, INPUT);

  delay(50);  // wait a moment

  // Now that we know if we have an NPN or PNP transistors, we can begin to read leak
  // of the transistor.
  // if it is a PNP transistor, emitter goes to 5V and collector goes through the
  // collector resistor to ground.
  // if it is an NPN transistor, emitter goes to ground and collector goes through the
  // collector resistor to 5V.
  if(transistorType == 1) {
    // Transistor is PNP
    pinMode(emitterPin, OUTPUT);          // Set emitter pin to output so that we can...
    digitalWrite(emitterPin, HIGH);       // Emitter goes straight to 5V
    pinMode(collectorResistorPin, OUTPUT);
    digitalWrite(collectorResistorPin, LOW); // Collector goes to ground via collector resistor
  } else {
    // Transistor is NPN
    pinMode(emitterPin, OUTPUT);         // Set emitter pin to output so that we can...
    digitalWrite(emitterPin, LOW);       // Emitter goes straight to ground
    pinMode(collectorResistorPin, OUTPUT);
    digitalWrite(collectorResistorPin, HIGH); // Collector goes to 5V via collector resistor
  }

  // read in voltages from the 16-bit ADC
  adc0 = ads.readADC_SingleEnded(0);
  adc1 = ads.readADC_SingleEnded(1);

  // rail voltage goes into ADC0 and collector voltage goes into ADC1
  rail_milliVolts = computeMilliVolts(adc0);
  collector_milliVolts = computeMilliVolts(adc1);

  // ohms law, V / R = I. Because we want current in uA and the volts are in milliVolts,
  // we need to multiply by 1000.0. This will give us the current from the base pin.
  // we have not applied base current at this point, but will do so later on.
  baseCurrent_uA = (rail_milliVolts * 1000.0) / base_resistor;

  if(transistorType == 1) {
    // Transistor is PNP

    // in case we get any weird jitter on the collector pin
    if(collector_milliVolts <= 0.1) {
      collector_milliVolts = 0.0;
    }

    // the leakage has caused an amount of collector voltage that we need to record and subtract
    // from the gain, when we get to that point.
    leak_milliVolts = collector_milliVolts;

    // ohms law, V / R = I. Because we want current in uA and the volts are in milliVolts,
    // we need to multiply by 1000.0. This will give us the current from the collector pin.
    leak_uA = (leak_milliVolts * 1000.0) / collector_resistor;

    // Output our findings thus far out the serial port
    Serial.println("PNP Transistor");
    Serial.print("PWR Rail:   ");
    Serial.print(rail_milliVolts);
    Serial.println("mV");
    Serial.print("Collector:  ");
    Serial.print(leak_milliVolts);
    Serial.println("mV");
    Serial.print("Leak: ");
    Serial.print(leak_uA);
    Serial.println("uA");
    Serial.print("Base: ");
    Serial.print(baseCurrent_uA);
    Serial.println("uA");

    delay(50);  // wait a moment

    // now we set the base pin to output and apply a small, but pre-calculated, current over it.
    pinMode(baseResistorPin, OUTPUT);
    digitalWrite(baseResistorPin, LOW); // Base goes to ground via base resistor because it is PNP

    // read in voltages from the 16-bit ADC
    adc0 = ads.readADC_SingleEnded(0);
    adc1 = ads.readADC_SingleEnded(1);

    // rail voltage goes into ADC0 and collector voltage goes into ADC1
    rail_milliVolts = computeMilliVolts(adc0);
    collector_milliVolts = computeMilliVolts(adc1);

    // gain is collector-voltage / collector-resistance / base-current
    // so if we have 750 millivolts on the collector after applying 4uA of current to the base pin,
    // and we are using a 1K resistor for the collector resistor, the our gain calculation would look like this:
    // gain = (750mV / 1000) / 1000 ohms / (4uA / 1,000,000)
    // gain = 0.75V / 1000 ohms / 0.000004A
    // gain = 187.5
    gain = (collector_milliVolts / 1000.0) / collector_resistor / (baseCurrent_uA / 1000000.0);

    // however, this isn't the true story. The leakage voltage also goes over the collector resistor, so we must subtract
    // the leakage voltage from the collector voltage. So, if we had a leakage voltage of 100mV (which with a 1K collector
    // resistor is 100uA of leakage current), we don't actually have 750 millivolts on the collector after applying the
    // 4uA of current to the base pin, but rather 750mV - 100mV = 650mV, so the true gain would calculate as:
    // gain = (650mV / 1000) / 1000 ohms / (4uA / 1,000,000)
    // gain = 0.65V / 1000 ohms / 0.000004A
    // gain = 162.5
    trueGain = ((collector_milliVolts - leak_milliVolts) / 1000.0) / collector_resistor / (baseCurrent_uA / 1000000.0);

    // output our findings out the serial port
    Serial.print("Gain: ");
    Serial.print(gain);
    Serial.println(" hfe");
    Serial.print("True Gain: ");
    Serial.print(trueGain);
    Serial.println(" hfe");

    // clear the OLED display
    display.clearDisplay();

    display.setTextSize(1);               // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);  // Draw white text
    display.setCursor(0,0);               // Start at top-left corner
    display.println(F("PNP Transistor")); // Start printing to screen
    // we will continue the rest later
    
  } else {
    // Transistor is NPN

    // the leakage has caused an amount of collector voltage that we need to record and subtract
    // from the gain, when we get to that point. Because the leakage is in reference to the rail
    // voltage, as this is an NPN transistor, we must subtract from the rail voltage to get leak
    // voltage
    leak_milliVolts = rail_milliVolts - collector_milliVolts;

    // in case we get any weird jitter on the collector pin
    if(leak_milliVolts < 0.1) {
      leak_milliVolts = 0.0;
    }

    // ohms law, V / R = I. Because we want current in uA and the volts are in milliVolts,
    // we need to multiply by 1000.0. This will give us the current from the collector pin.
    leak_uA = (leak_milliVolts / collector_resistor) * 1000.0;
    
    Serial.println("NPN Transistor");
    Serial.print("PWR Rail:   ");
    Serial.print(rail_milliVolts);
    Serial.println("mV");
    Serial.print("Collector:  ");
    Serial.print(leak_milliVolts);
    Serial.println("mV");
    Serial.print("Leak: ");
    Serial.print(leak_uA);
    Serial.println("uA");
    Serial.print("Base: ");
    Serial.print(baseCurrent_uA);
    Serial.println("uA");

    delay(50); // wait a moment

    // now we set the base pin to output and apply a small, but pre-calculated, current over it.
    pinMode(baseResistorPin, OUTPUT);
    digitalWrite(baseResistorPin, HIGH); // Base goes to 5V via base resistor because it is NPN

    // read in voltages from the 16-bit ADC
    adc0 = ads.readADC_SingleEnded(0);
    adc1 = ads.readADC_SingleEnded(1);

    // rail voltage goes into ADC0 and collector voltage goes into ADC1
    rail_milliVolts = computeMilliVolts(adc0);
    collector_milliVolts = computeMilliVolts(adc1);

    // because the collector voltage is in reference to the rail voltage, we have to subtract from the rail voltage
    // gain is collector-voltage / collector-resistance / base-current
    // so if we have 750 millivolts on the collector after applying 4uA of current to the base pin,
    // and we are using a 1K resistor for the collector resistor, the our gain calculation would look like this:
    // gain = (750mV / 1000) / 1000 ohms / (4uA / 1,000,000)
    // gain = 0.75V / 1000 ohms / 0.000004A
    // gain = 187.5
    gain = ((rail_milliVolts - collector_milliVolts) / 1000.0) / collector_resistor / (baseCurrent_uA / 1000000.0);

    // however, this isn't the true story. The leakage voltage also goes over the collector resistor, so we must subtract
    // the leakage voltage from the collector voltage. So, if we had a leakage voltage of 100mV (which with a 1K collector
    // resistor is 100uA of leakage current), we don't actually have 750 millivolts on the collector after applying the
    // 4uA of current to the base pin, but rather 750mV - 100mV = 650mV, so the true gain would calculate as:
    // gain = (650mV / 1000) / 1000 ohms / (4uA / 1,000,000)
    // gain = 0.65V / 1000 ohms / 0.000004A
    // gain = 162.5
    trueGain = (((rail_milliVolts - collector_milliVolts) - leak_milliVolts) / 1000.0) / collector_resistor / (baseCurrent_uA / 1000000.0);

    Serial.print("Gain: ");
    Serial.print(gain);
    Serial.println(" hfe");
    Serial.print("True Gain: ");
    Serial.print(trueGain);
    Serial.println(" hfe");

    // clear the OLED display
    display.clearDisplay();

    display.setTextSize(1);               // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);  // Draw white text
    display.setCursor(0,0);               // Start at top-left corner
    display.println(F("NPN Transistor")); // Start printing to screen
    // we will continue the rest later
  }

  // Now display our gain and leakage findings
  display.print(F("Gain: "));
  display.print(trueGain);
  display.println(F(" hfe"));
  display.print(F("Leak: "));
  display.print(leak_uA);
  display.println(F("uA"));
  display.display();

  // Then reset all pins back to input mode
  pinMode(emitterPin, INPUT);
  pinMode(basePin, INPUT);
  pinMode(collectorPin, INPUT);
  pinMode(collectorResistorPin, INPUT);
  pinMode(baseResistorPin, INPUT);

  // So that we can then set the pins back to floating
  digitalWrite(emitterPin, LOW);
  digitalWrite(basePin, LOW);
  digitalWrite(collectorPin, LOW);
  digitalWrite(collectorResistorPin, LOW);
  digitalWrite(baseResistorPin, LOW);
}

void loop() {
  
  // wait 500 milliseconds before the next loop for the analog-to-digital
  // converter to settle after the last reading:
  delay(500);
}

/**************************************************************************/
/*!
    @brief  Returns true if conversion is complete, false otherwise.

    @param counts the ADC reading in raw counts

    @return the ADC reading in milli-volts
*/
/**************************************************************************/
float computeMilliVolts(int16_t counts) {
  uint8_t bitShift = 0;            ///< bit shift amount
  return counts * 1000.0 * (6.144f / (32768 >> bitShift));
}