Reset calibration for 4 photoresistors for varying sunlight throughout the day

I am trying to reset calibration for 4 photoresistors for varying sunlight throughout the day
I am calibrating 4 photoresistors for a solar tracking application.
I want the photoresistors to continuously be calibrated from sunrise to sunset.
Once the photoresistors are properly calibrated then they can properly track the sun.
I’m using an esp32 which has a 12 bit ADC, although people say it’s noisy.

I am using the Arduino Calibration example:

If I understood it correctly the void setup() function calibrates only once.
The values it will calibrate for in the morning when the sun rises would not be correct for noon, afternoon, etc correct?

The way my code differs is that I put the void setup() portion of the code in the calibration example into my void loop() function

From Arduino calibration example:

// calibrate during the first five seconds
  while (millis() < 5000) {
    sensorValue = analogRead(sensorPin);

    // record the maximum sensor value
    if (sensorValue > sensorMax) {
      sensorMax = sensorValue;
    }

    // record the minimum sensor value
    if (sensorValue < sensorMin) {
      sensorMin = sensorValue;
    }

Can anyone give feedback if my thought process is correct or if my code is correct?
I have a sample serial output below of my code.
I’m wondering if there will be a conflict between the while millis() < 5000 portion of the code and delay(2000) portion of the code.

My calibration code:

//top left photoresistor sensor pin = PinTopLeft
const uint16_t PinTopLeft = 33;
const uint16_t PinBottomLeft = 35;
const uint16_t PinTopRight = 32;
const uint16_t PinBottomRight = 34;

// The sensor value
uint16_t TopLeft = 0;
uint16_t BottomLeft = 0;
uint16_t TopRight = 0;
uint16_t BottomRight = 0;

// As photoresistor approaches minimum sensor value more light is seen by it
uint16_t MinTopLeft = 4096;
uint16_t MinBottomLeft = 4096;
uint16_t MinTopRight = 4096;
uint16_t MinBottomRight = 4096;

uint16_t MaxTopLeft = 0;
uint16_t MaxBottomLeft = 0;
uint16_t MaxTopRight = 0;
uint16_t MaxBottomRight = 0;


void setup() {
  Serial.begin(115200);
}
void loop() {
  
  // Using example from: https://www.arduino.cc/en/tutorial/calibration
  // Calibrate during the first five seconds
  while (millis() < 5000) {
    TopLeft = analogRead(PinTopLeft);
    BottomLeft = analogRead(PinBottomLeft);
    TopRight = analogRead(PinTopRight);
    BottomRight = analogRead(PinBottomRight);

    // Record the maximum sensor value
    if (TopLeft > MaxTopLeft) {
      MaxTopLeft = TopLeft;
    }
    if (BottomLeft > MaxBottomLeft) {
      MaxBottomLeft = BottomLeft;
    }
    if (TopRight > MaxTopRight) {
      MaxTopRight = TopRight;
    }
    if (BottomRight > MaxBottomRight) {
      MaxBottomRight = BottomRight;
    }

    // Record the minimum sensor value
    if (TopLeft < MinTopLeft) {
      MinTopLeft = TopLeft;
    }
    if (BottomLeft < MinBottomLeft) {
      MinBottomLeft = BottomLeft;
    }
    if (TopRight < MinTopRight) {
      MinTopRight = TopRight;
    }
    if (BottomRight < MinBottomRight) {
      MinBottomRight = BottomRight;
    }
  }

  // Read the sensor
  TopLeft = analogRead(PinTopLeft); // Top left sensor
  BottomLeft = analogRead(PinBottomLeft);
  TopRight = analogRead(PinTopRight);
  BottomRight = analogRead(PinBottomRight);

  // Apply the calibration to the sensor reading
  TopLeft = map(TopLeft, MinTopLeft, MaxTopLeft, 0, 4000);
  BottomLeft = map(BottomLeft, MinBottomLeft, MaxBottomLeft, 0, 4000);
  TopRight = map(TopRight, MinTopRight, MaxTopRight, 0, 4000);
  BottomRight = map(BottomRight, MinBottomRight, MaxBottomRight, 0, 4000);

  // In case the sensor value is outside the range seen during calibration
  TopLeft = constrain(TopLeft, 0, 4000);
  BottomLeft = constrain(BottomLeft, 0, 4000);
  TopRight = constrain(TopRight, 0, 4000);
  BottomRight = constrain(BottomRight, 0, 4000);

  //Sends analog values in this format: i.e. {380,148,224,260}
 uint16_t data[4] = {TopLeft, BottomLeft, TopRight, BottomRight};
 uint16_t i;

  for (i = 0; i < 4; i++) {
      Serial.print(data[i]);
      Serial.println(" ");
      }
      Serial.println(" ");

  delay(2000);
}

The following results are from a 2700k, 850 lumen CFL bulb covered with a lamp shade.
The photoresistors are lying flat on a breadboard that is about a meter away from the lamp and it is night time.

Sample serial output:

731 
659 
830 
691 
 
733 
667 
831 
696 
 
739 
667 
837 
698 
 
733 
665 
835 
696

I tried experimenting and increasing the delay times to see if things become worse.
I changed it back to 2000 ms.
Still getting bad results after covering up certain photoresistors with my fingers.
All the values remain same.

It's weird, now all I'm getting is this for my serial output, while covering up 2 random photoresistors periodically:

4000 
628 
228 
1094 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
723 
4000 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
4000 
4000 
 
3384 
4000 
3619 
4000 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
4000 
4000 
 
4000 
4000 
4000 
4000

So you are trying to make a heliotropic tracking system. Wouldn’t it be easier to just compare pairs of sensors rather than concern yourself with their actual values? Also maybe rethink your sensor layout, ie.
top
left right
bottom

instead of your obvious setup of,

topleft topright
bottomleft bottomright

I’m using this person’s example and making improvements to it for the heliostat. I think it’s great.
https://www.instructables.com/id/Arduino-Solar-Tracker/

It’s doing what you are saying, but a little bit differently.
It’s comparing the difference between pairs.
Every part has tolerances.
You can tell just by physically looking at the photoresistors,
the etched patterns on some are larger than others.

My other posts have code doing this where I’m comparing the averages of the photo resistor values.
The problem is setting the tolerance for the resistors and ensuring the motors stop moving.
If the tolerance is too low, the motors will keep moving and consume power when they don’t need to.
If the tolerance is too high, the alignment to the sun isn’t complete.

This is were the calibration comes into play. It frees the user from adjusting for the tolerances of the photoresistors. I’m looking to switch from photoresistors to using other types of light sensors.
(even though they are cheaper, they contain Cadmium and they aren’t exactly environmentally friendly)

i.e.
TSL2561
Digital and more precise but,
“this board/chip uses I2C 7-bit addresses 0x39, 0x29, 0x49, selectable with jumpers”
I want to use it on one microprocessor and I need to use 4 sensors for the application.
Not sure how I can do this do differentiate results from 4 sensors, but only 3 unique I2C addresses.

ADPS 9900,9930,9960
It’s an ambient light sensor but also a proximity sensor.
“The device supports a single slave address of 0x39 hex using
7 bit addressing protocol”
Ever harder situation then above.

TEMT6000
Analog phototransistor, more accurate versus photoresistor??? I’m not sure.

I’m getting these to try out and see if I can avoid calibrating them.

For now, I’m wondering why my photoresistor values are reporting the same results for my code in the previous post.

Here is a screenshot of my three iterations of this project:

Solar Tracker Different Revisions.jpg

The tolerance of the LDRs is inconsequential. At most the error would only amount to a fraction of a degree. The tolerance they are referring to is the dead zone that once reached will stop the motion of the associated servo. By rotating the tracker 45 degrees (as I suggested in my previous post) you only need to measure the difference between the top and bottom LDRs to control the vertical (elevation) motion and likewise, only the left and right LDRs to control the azimuth of the tracker. When there's a difference, move the servo until it reaches the appropriate dead zone, then stop. If you take it further, you can use the difference to control the speed so the tracker will slow as it approaches equilibrium between the 2 associated LDRs which should help reduce or eliminate the jitter as it reaches 0 difference.

The LDRs would be the simplest way to go. By wiring the top & bottom in series (same for the left & right) and measuring the junction voltage for 1/2 source using an analog input you only need to use 2 analog inputs to control the tracker, because when the tracker is aiming straight at the sun, their resistance will be equal resulting in a voltage of 1/2 the source at the junction, but be careful that their total resistance doesn't go so low as to short out the supply. If so, you can add 2 equal value resistors to eliminate the problem. ie res-LDR-LDR-res.

Are you using Arduino analogRead() to read the ESP32 A:D’s? I found that by using the ESP32 API for the analog reads the jitter goes way down.

#include <driver/adc.h> for the ESP32 API

For the set up:

// https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
// set up A:D channels
adc1_config_width(ADC_WIDTH_12Bit);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);

Which includes a link to the ESP32 API reference for the A:D.

And some code to read the A:D

void TaskAnalogVoltRead_LIDAR( void *pvParameters )
{
  int iBit = 1;
  float ADbits = 4095;
  float offSET = 0.0f;
  float r1 = 100800.0f;
  float r2 = 38780.0f; 
  float uPvolts = 3.3f;
  // ADC1 channel 0 is GPIO36
  // ADC1 channel 1 is GPIO37
  // ADC1 channel 6 is GPIO34
  // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // to get resistor R2 go to:
  // http://www.ohmslawcalculator.com/voltage-divider-calculator
  //   used 12 volts for the input voltage to calculate R2, used 100K for R1
  for (;;)
  {
    xEventGroupWaitBits( eg, evtAnalogVoltReadTask_LIDAR, pdTRUE, pdTRUE, portMAX_DELAY );
    iBit = iBit << 1;
    if ( iBit == 1073741824 )
    {
      if ( xSemaphoreTake( sema_AnalogVoltRead_LIDAR, xSemaphoreTicksToWait ) == pdTRUE )
      {
        Vbatt_LIDAR += ( ((( uPvolts * adc1_get_raw(ADC1_CHANNEL_6)) / ADbits) / r2 * ( r1 + r2)) + offSET );
        Vbatt_LIDAR = Vbatt_LIDAR /2; // average readings
      } // if ( xSemaphoreTake( sema_AnalogVoltRead_LIDAR, xSemaphoreTicksToWait ) == pdTRUE )
      iBit = 1;
    } // if ( iBit == 1073741824 )
  }
  vTaskDelete( NULL );
}

A solar tracker: DIY Solar Tracker || How much solar energy can it save? - YouTube

A breadboard produces a lot of noise, especially with A:D circuits. I found soldering my projects to a proto board increased stability. I use sockets for items like the ESP32.

I see what you are saying now.
So I should separate the two sets of photoresistors by rotating my light divider orientation 45 degrees and also the photoresistors?
That would also simplify the code.

I just wonder if both those motions of azimuth and elevation rotation can happen simultaneously with that orientation as the light balances it self out and the shadows disappear.

@Ken_F
Regarding the photoresistors in series and 2 analog inputs looks like a really nice and simpler concept.
Although I don’t understand how I would short things out if I put them in series?
V=IR → Resistance drops and I give the analog input pin too much current?

@Idahowalker
Thanks I’ll take a look at this.
I am using Arduino analogRead().
I’ve read online there are some issues with that wish Espressif would fix that.
Greatscott is a great youtube channel. :slight_smile:

The sample code for reading analog inputs is a bit confusing for me. I’ll study it some more.
If I use driver/adc.h can’t I still use the same syntax analogRead()… in my code?

I’ll try to use:
#include <driver/adc.h>

is this code what I should put in void setup()?

 // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // set up A:D channels
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);

Meanwhile here is some code that I think might help with the calibration using the current method.
I found some code to use millis() to delay time values in my void loop () function.
https://www.norwegiancreations.com/2017/09/arduino-tutorial-using-millis-instead-of-delay/

I also found code to reset the Arduino so that the void setup() function can calibrate again after a few minutes.

unsigned long delay_time = 240000;  //Basing it off of Earth rotating .25 degrees/minute (360 degrees/1440 minutes in a day). Program will reset after 4 minutes (Earth rotates 1 degree).
unsigned long time_now = 0;

//top left photoresistor sensor pin = PinTopLeft
const uint16_t PinTopLeft = 33;
const uint16_t PinBottomLeft = 35;
const uint16_t PinTopRight = 32;
const uint16_t PinBottomRight = 34;

// The sensor value
uint16_t TopLeft = 0;
uint16_t BottomLeft = 0;
uint16_t TopRight = 0;
uint16_t BottomRight = 0;

// As photoresistor approaches minimum sensor value more light is seen by it
uint16_t MinTopLeft = 4096;
uint16_t MinBottomLeft = 4096;
uint16_t MinTopRight = 4096;
uint16_t MinBottomRight = 4096;

uint16_t MaxTopLeft = 0;
uint16_t MaxBottomLeft = 0;
uint16_t MaxTopRight = 0;
uint16_t MaxBottomRight = 0;

void(* resetFunc)
(void) = 0; //declare reset function @ address 0

void setup() {
  Serial.begin(115200);
  calibrate();
}
void loop() {

  time_now = millis();
  

  // Read the sensor
  TopLeft = analogRead(PinTopLeft); // Top left sensor
  BottomLeft = analogRead(PinBottomLeft);
  TopRight = analogRead(PinTopRight);
  BottomRight = analogRead(PinBottomRight);

  // Apply the calibration to the sensor reading
  TopLeft = map(TopLeft, MinTopLeft, MaxTopLeft, 0, 4000);
  BottomLeft = map(BottomLeft, MinBottomLeft, MaxBottomLeft, 0, 4000);
  TopRight = map(TopRight, MinTopRight, MaxTopRight, 0, 4000);
  BottomRight = map(BottomRight, MinBottomRight, MaxBottomRight, 0, 4000);

  // In case the sensor value is outside the range seen during calibration
  TopLeft = constrain(TopLeft, 0, 4000);
  BottomLeft = constrain(BottomLeft, 0, 4000);
  TopRight = constrain(TopRight, 0, 4000);
  BottomRight = constrain(BottomRight, 0, 4000);

  //Sends analog values in this format: i.e. {380,148,224,260}
  uint16_t data[4] = {TopLeft, BottomLeft, TopRight, BottomRight};
  uint16_t i;

  for (i = 0; i < 4; i++) {
    Serial.print(data[i]);
    Serial.println(" ");
  }
  Serial.println(" ");

  while (millis() < time_now + delay_time) { //wait approx. [period] ms}
  }

  Serial.println("Resetting and starting program again");
  resetFunc();  //call reset
}

void calibrate()
{
  // Using example from: https://www.arduino.cc/en/tutorial/calibration
  // Calibrate during the first five seconds
  while (millis() < 5000) {
    TopLeft = analogRead(PinTopLeft);
    BottomLeft = analogRead(PinBottomLeft);
    TopRight = analogRead(PinTopRight);
    BottomRight = analogRead(PinBottomRight);

    // Record the maximum sensor value
    if (TopLeft > MaxTopLeft) {
      MaxTopLeft = TopLeft;
    }
    if (BottomLeft > MaxBottomLeft) {
      MaxBottomLeft = BottomLeft;
    }
    if (TopRight > MaxTopRight) {
      MaxTopRight = TopRight;
    }
    if (BottomRight > MaxBottomRight) {
      MaxBottomRight = BottomRight;
    }

    // Record the minimum sensor value
    if (TopLeft < MinTopLeft) {
      MinTopLeft = TopLeft;
    }
    if (BottomLeft < MinBottomLeft) {
      MinBottomLeft = BottomLeft;
    }
    if (TopRight < MinTopRight) {
      MinTopRight = TopRight;
    }
    if (BottomRight < MinBottomRight) {
      MinBottomRight = BottomRight;
    }
  }  
}

Regarding the photoresistors in series and 2 analog inputs looks like a really nice and simpler concept.
Although I don't understand how I would short things out if I put them in series?
V=IR --> Resistance drops and I give the analog input pin too much current?

This is clearly the way to go. Two LDRs in series between Vcc and GND make a voltage divider, and if their responses are similar enough, the voltage at the junction will be about Vcc/2 regardless of total illumination.

If one LDR is shaded, then the junction voltage will go up or down (depending on which LDR is shaded). Your program just needs to turn the motors in such a direction as to bring the two LDRs back into balance.

The Arduino analog input draws essentially no current, unless the input voltage is negative or exceeds Vcc.

// https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // set up A:D channels
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);

Yes, that goes into void setup.

By using freeRTOS with vTaskDelay or vTaskDelayUntil you can still have other tasks running whiles there is a task in delay.

freeRTOS is built into the ESP32: FreeRTOS API categories. Tasks can be assigned to a core.

The sample code for reading analog inputs is a bit confusing for me. I'll study it some more.
If I use driver/adc.h can't I still use the same syntax analogRead().. in my code?

. With the ESP32, analog read is converted to an Arduino API, then down to the ESP32 API. By using the ESP32 API your code skips a few steps. By not setting up the Arduino analog chain their will not be a connection to the upstream calls.

As a note, for analog on the ESP32, I find it best to exhaust the GPIO pins from 33+ before going to the pins that are below GPIO 32. GPIO pins 33 and above are input only pins.

There is a ESP32 servo library that wraps the ESP32 API quite nicely. I've switched over to using the ESP32 PWM API; faster and more control than uSeconds.

The API call to reset the ESP32 is: ESP.restart(); Using freeRTOS the void loop() looks like: void loop() {}

As jremington said the analog inputs draw virtually no current. What I was referring to is since I don't know the exact LDR you have in mind to use, I don't know its resistance range. It maybe possible, under certain conditions, for the total r value to drop low enough to overload the voltage source (ie: vcc) used to power the divider made up of the 2 LDRs. When the light is in balance, the 2 LDRs will have about the same r, but when 1 is shaded and the other is illuminated, their r curve may not be linear and the total r may drop low enough to be a problem. Just a warning of a possible problem.

Since I’m such a tree hugger and want to avoid the cadmium based photoresistors.

Wouldn’t I be able to use the TEMT6000 sensors in series too?
They are phototransistors.
Although they are on a pcb so, the analog out is a voltage output???
TEMT6000
According to the sparkfun website they come with 10k pull down resistors.
temt6000-ambient-light-sensor-hookup-guide

TEMT6000 SCHEMATIC.PNG

temt6000 example

I measured each photoresistor and these are the ranges I got

full light and full darkness

pr 1 ~212 ohms - 206 kohms
pr 2 ~140 ohms - 170 kohms
pr 3 ~160 ohms - 190 kohms
pr 4 ~155 ohms - 160 kohms

Voltage divider explanation

V out = Vsource * R2 / (R1+R2) → we know V out will be less than V source

i.e. pr 1 (full light) and pr 2 (full darkness)

V out = 3.3V * 170kohms/(212 ohms + 170kohms)
V out = 3.296 volts

i.e. pr 1 (full light) and pr 2 (full light)
V out = 3.3V * 170kohms/(206 khms + 170kohms)
V out = 1.808 volts

Using these equations I don’t understand

for the total r value to drop low enough to overload the voltage source (ie: vcc) used to power the divider made up of the 2 LDRs

How is the voltage source overloaded?

but be careful that their total resistance doesn’t go so low as to short out the supply.

How is the power supply shorted out, if the output voltage turns outs to be nearly the same based on the example above?

@Idahowalker

ESP.restart();

works better for resetting ESP32, thanks.

How is the voltage source overloaded?

If both LDRs in your example were in "full light" (sunlight?), the total resistance of the series combination would be roughly 300 Ohms.

The current draw from a 5V power supply would in that case be about 5 V/300 Ohms = 0.016 A or 16 mA.

That is very unlikely to overload any power supply that you might be using, but it would be a significant drain on a battery operated system.

How is the power supply shorted out

It is not. See above.

Wouldn't I be able to use the TEMT6000 sensors in series too?

Sure, but what is the point?

Both the LDRs were placed under a 20 watt halogen lamp.

Since I’m using ESP32 it runs at 3.3v
So 3.3V/300ohms = 11 mA.

I’m trying to get away with using a small solar panel to power the circuit.
I used this first with a 3.3v arduino pro mini and HC-12 module:
https://grabcad.com/library/monocrystalline-solar-cell-5-volts-200-ma-1

But on cloudy days I had to end up using this since the smaller solar panel did not provide enough power to run the 3.3v arduino pro mini and hc-12 module:
https://grabcad.com/library/4-5-w-solar-panel-6-v-720-ma-monocrystalline-epoxy-coated-165-mm-x-165-mm-1

I think the current consumption is lower on the temt6000 phototransistor plus no cadmium :slight_smile:
http://www.vishay.com/docs/84154/appnotesensors.pdf

The ESP32 has the ESP now protocol which is useful for low power consumption.
I’m hoping to switch back to using the smaller solar panel with the hope that it cast’s less of a shadow over the solar cooker I’m working on.

solar powered tracker.PNG

I made the voltage divider circuit.
I’m using 10kohm resistors before and after the photoresistors that are in series.

ElevationAzimuth Voltage Divider.jpg

Can’t seem to figure out why the output voltage from ESP32 Vin pin and GND reads out 4.63 volts after I plugged the ESP32 microusb port into the PC.
The chip on the top right side of the breadboard is a voltage step down converter that’s supposed to drop down the input voltage to the ESP32 to 3.3V.
I am going to power it with a 6 volt solar panel.
https://grabcad.com/library/dd0503ma-ultra-mini-dc-3-7v-4-5v-5v-to-3-3v-dc-dc-step-down-converter-buck-module-1

unsigned long delay_time = 5000;  //Earth rotates .25 degrees/minute. In 4 minutes Earth rotates 1 degree.
unsigned long time_now = 0;

//Voltage divider analog in pins
const int PinAzimuth = 35;
const int PinElevation = 34;

int Azimuth = 0;
int Elevation = 0;

float AzimuthVoltage = 0;
float ElevationVoltage = 0;

void setup() {
  Serial.begin(115200);
}
void loop() {

  time_now = millis();

  // Read the sensor
  Azimuth = analogRead(PinAzimuth);
  Elevation = analogRead(PinElevation);

  // Apply the calibration to the sensor reading
  AzimuthVoltage = map(Azimuth, 0, 4096, 0, 3.3);
  ElevationVoltage = map(Elevation, 0, 4096, 0, 3.3);

  //Sends analog values in this format: i.e. {1.8,2.2}
  float data[2] = {AzimuthVoltage,ElevationVoltage};
  uint8_t i;

  for (i = 0; i < 2; i++) {
    Serial.print(data[i]);
    Serial.println(" ");
  }
  Serial.println(" ");

  //wait approx. [period] ms}
  while (millis() < time_now + delay_time) {}
}

Can’t seem to figure out why the serial print reads out only a voltage of
1 volts.

The readout I’m getting from my multimeter across the photoresistors where the voltage dividing occurrs:

Pin 35 Azimuth
2.23 Volts

Pin 34 Elevation
1.95 Volts

Serial printout I’m getting is:

1.00 
1.00 
 
1.00 
1.00 
 
1.00 
1.00

I changed the code up and mapped the values in terms of milliVolts instead of a float Volt value.
These results are closer to my multimeter values.
I'll try to improve them with multisampling and .1uF capacitors.
https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/adc.html

The readout I'm getting from my multimeter across the photoresistors where the voltage dividing occurrs:

Pin 35 Azimuth
2.23 Volts

Pin 34 Elevation
1.95 Volts

1757.00 
2154.00 
 
1755.00 
2101.00 
 
1702.00 
2085.00 
 
1689.00 
2057.00 
 
1737.00 
2061.00

I was wondering if my setup is correct for reducing noise based on other setups I’ve seen online?

I know breadboards versus pcb boards have more noise but I wanted avoid soldering so it’s easier for others to make this circuit themselves.
I have capacitors going from the analog outputs to gnd of the circuit.
I measured my capacitors to be near 70 nF or .07 uF with the multimeter.
The results seem to be closer than before when I wasn’t using capacitors.
I will try to multisample next.

In the screenshot below am I measuring analog output voltages at the correct locations circled in green and red?

azimuth and elevation analog in with capacitors to reduce noise.jpg

This is the output I’m measuring for the elevation voltage:
2.19 volts

2051.00 
2023.00 
2037.00 
2061.00

This is the output I’m measuring for the azimuth voltage:
1.5 volts

1625.00 
1602.00 
1599.00 
1521.00