Inconsistent voltage readings using a photodiode and amplifier

I am working on a project with a collimator using and ESP32 Max V1.0 and stepper motors to move the collimator to scan for the highest voltage reading from a photodiode in the collimator in the X axis, returning to that location and then scanning the Z axis to perform the same function from that point.

I have a momentary scan button that initiates the scan and a home button send the motors back for another test run.

The United Detector Photodiode in the collimator is connected to a Thorlabs PDA 200 photodiode amplifier. The analog output goes to pin 32 and ground on the ESP32. The output also goes to a multimeter to get another data point for the test runs. As the motors step through both axes in 5 degree increments the voltage readings on the multimeter and PDA 200 seem to increase and decrease appropriately with signal strength. The maximum reading seen on the PDA 200 is approximately 3.40 nA and 180 mV on the multimeter.
However, the serial monitor steps are erratic with widely varying values including zeros.

Below is an example:

Z: 55 Voltage: 2.40
Z: 60 Voltage: 1.10
Z: 65 Voltage: 0.00
Z: 70 Voltage: 0.00
Z: 75 Voltage: 0.00
Z: 80 Voltage: 0.00
Z: 85 Voltage: 14.10
Z: 90 Voltage: 0.00
Z: 95 Voltage: 4.00
Z: 100 Voltage: 3.35
Z: 105 Voltage: 0.00
Z: 110 Voltage: 0.00
Z: 115 Voltage: 0.00
Z: 120 Voltage: 0.00
Z: 125 Voltage: 6.85
Z: 130 Voltage: 1.35
Z: 135 Voltage: 3.30

I would expect the values to remain close to each other. I am looking for a reason why the voltage appears so erratic which makes it difficult to consistently find and return to the highest reading.

Here is the code I am using:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>

// Define stepper motor connections and interface type  
#define motorInterfaceType 1                      // 1 means using a driver with STEP/DIR pins  
AccelStepper motorX(motorInterfaceType, 19, 23);  // Step pin on GPIO 19, Dir pin on GPIO 23  
AccelStepper motorZ(motorInterfaceType, 12, 13);  // Step pin on GPIO 12, Dir pin on GPIO 13

// Define constants and variables  
const int buttonPin = 36;    // Define the scan button pin  
const int homeButtonPin = 39;  // Define the home button pin  
const int sensorPin = 32;     // Analog pin for voltage sensor on ESP32  
const int stepsPerRevolution = 1600;  // Defines the number of steps per rotation based on each motor (1.8° motor is 200/360°)

float maxVoltage = 0.0;
int maxX = 0;

float maxZVoltage = 0.0;
int maxZ = 0;

bool scanTriggered = false;  // Variable to track if a scan is triggered

// Set the LCD address (I2C address) and dimensions (columns, rows)
LiquidCrystal_I2C lcd(0x27, 20, 4);  

void setup() {
  Serial.begin(115200);
  
  pinMode(buttonPin, INPUT_PULLUP);  // Initialize the scan button pin with internal pull-up resistor  
  pinMode(homeButtonPin, INPUT_PULLUP); // Initialize the home button pin with internal pull-up resistor  

  motorX.setMaxSpeed(2000);  // Set Max Speed  
  motorZ.setMaxSpeed(2000);
  motorX.setAcceleration(2000);  // Set Max Acceleration  
  motorZ.setAcceleration(2000);

  // Initialize the LCD  
  lcd.init();
  lcd.backlight();  // Turn on the backlight (if your display has one)
  lcd.print("Voltage = ");
}

void loop() {
  // Check if the scan button is pressed  
  if (digitalRead(buttonPin) == LOW) { // Button pressed (active LOW)
    // Debounce the button press  
    delay(50); // Simple debounce delay  
    if (digitalRead(buttonPin) == LOW) { // Check if still pressed  
      if (!scanTriggered) { // Only trigger if not already scanning  
        scanTriggered = true; // Set flag to indicate a scan is in progress  
        maxVoltage = 0.0;  // Reset maxVoltage for new scan  
        maxX = 0;          // Reset maxX for new scan  
        maxZVoltage = 0.0; // Reset maxZVoltage for new scan  
        maxZ = 0;          // Reset maxZ for new scan

        // Start scanning process  
        runScanningProcess(); // Call your scanning function  
      }
    }
  } else {
    // Reset the scanTriggered flag when button is released  
    scanTriggered = false;
  }

  // Check if the home button is pressed  
  if (digitalRead(homeButtonPin) == LOW) { // Home button pressed (active LOW)
    // Debounce the home button press  
    delay(50); // Simple debounce delay  
    if (digitalRead(homeButtonPin) == LOW) { // Check if still pressed  
      homeMotors(); // Call the homing function  
    }
  }
}

// Function for scanning process  
void runScanningProcess() {
    // Step 1: Scan the X-axis  
    for (int x = 0; x <= 360; x += 10) {
        motorX.moveTo(x * stepsPerRevolution / 360);
        while (motorX.distanceToGo() != 0) {
            motorX.run();
        }

        float voltage = analogRead(sensorPin) * 5.0 / 100; // Read voltage  previously 5.0/4096
        Serial.print("X: ");
        Serial.print(x);
        Serial.print(" Voltage: ");
        Serial.println(voltage);

        // Display on LCD  
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("X: ");
        lcd.print(x);
        lcd.setCursor(0, 1);
        lcd.print("Voltage: ");
        lcd.print(voltage);

        if (voltage > maxVoltage) {
            maxVoltage = voltage;
            maxX = x;
        }
    }

    // Step 2: Return to the highest X-axis position  
    motorX.moveTo(maxX * stepsPerRevolution / 360);
    while (motorX.distanceToGo() != 0) {
        motorX.run();
    }

    // Step 3: Scan the Z-axis at the highest X position  
    for (int z = 0; z <= 360; z += 5) {
        motorZ.moveTo(z * stepsPerRevolution / 360);
        while (motorZ.distanceToGo() != 0) {
            motorZ.run();
        }

        float voltage = analogRead(sensorPin) * 5.0 / 100; // Read voltage  Previously 5.0/4096
        Serial.print("Z: ");
        Serial.print(z);
        Serial.print(" Voltage: ");
        Serial.println(voltage);

        if (voltage > maxZVoltage) {
            maxZVoltage = voltage;
            maxZ = z;
        }
    }

    // Step 4: Return to the highest Z-axis position  
    motorZ.moveTo(maxZ * stepsPerRevolution / 360);
    while (motorZ.distanceToGo() != 0) {
        motorZ.run();
    }

    // Display highest voltage readings in the Serial Monitor  
    Serial.print("Highest Voltage: ");
    Serial.println(maxVoltage);
    Serial.print("At X: ");
    Serial.println(maxX);
    Serial.print("Highest Z Voltage: ");
    Serial.println(maxZVoltage);
    Serial.print("At Z: ");
    Serial.println(maxZ);

    // Display results on LCD  
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Max Voltage:  ");
    lcd.print(maxVoltage);
    lcd.setCursor(0, 1);
    lcd.print("At X: ");
    lcd.print(maxX);
    lcd.setCursor(0, 2);
    lcd.print("Max Z Voltage:");
    lcd.print(maxZVoltage);
    lcd.setCursor(0, 3);
    lcd.print("At Z: ");
    lcd.print(maxZ);
}

// Function to home the motors  
void homeMotors() {
    // Move both motors to the home position (0 degrees)  
    motorX.moveTo(0);  // Move motorX to home position  
    motorZ.moveTo(0);  // Move motorZ to home position  

    // Run motors to home position  
    while (motorX.distanceToGo() != 0 || motorZ.distanceToGo() != 0) {
        motorX.run();
        motorZ.run();
    }

    // Optionally, reset the maximum values after homing  
    maxVoltage = 0.0;
    maxX = 0;
    maxZVoltage = 0.0;
    maxZ = 0;
    Serial.println("Motors homed to position 0.");
}

[Photodiode collimator diagram.pdf|attachment](upload://j5cG1vJuWSJ0g5h9tvA0okr3Xw5.pdf) (488.8 KB)
[Photodiode collimator layout.pdf|attachment](upload://hmVutq9HowP78tYuJmrIEvQKlE5.pdf) (292.7 KB)

Usually a wiring problem, such as lack of common ground. Please post links to all the devices and a complete wiring diagram of the setup, showing all connections, with pins, connections and parts clearly labeled. Hand drawn is fine.

Such currents can be injected into every wire from any external source. Get a scope if you want to learn more about such ghost effects.

I assume you mean maximum current? 3.40nA seems pretty small. What is the light source you're measuring? How much optical power would you estimate is impinging on the photodiode's active area?

Say the diode's responsivity is 0.65 A/W. With only 10uW of optical power (-20 dBm) hitting the diode, you'd see 6.5uA of current. That's more than 1000 times the current you claim. 3.40nA would correspond to about -52.8 dBm optical power.

You might want to check your voltage conversion.
Also be aware that Esp32 ADC in not able to measure voltages below ~150mV.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.