Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
Dual-Axis Solar Tracker: the finished build
UnoRobotics

Dual-axis solar tracker with four LDRs and two servos (Arduino Uno)

A small panel that follows the sun across the sky on two servos.

Difficulty
Medium
Build time
3 h
Parts cost
₹1,300 · $15
Verified — core avr 1.8.8, Servo 1.3.0, 3 Nov 2026Compile-only

Community draft — report a mistake

Parts list

PartQtyApprox.Buy
Arduino Uno R3 (CH340 clone)1₹450Buy Arduino Uno R3 (CH340 clone)
LDR light sensor module (4-pin, with AO)4₹160Buy LDR light sensor module (4-pin, with AO)
SG90 micro servo (or MG90S metal gear)2₹240Buy SG90 micro servo (or MG90S metal gear)
Pan-tilt bracket for SG90 servos1₹90Buy Pan-tilt bracket for SG90 servos
Small solar panel, 6 V 1 W (or a dummy board)1₹150Buy Small solar panel, 6 V 1 W (or a dummy board)
470 µF 10 V capacitor2₹10Buy 470 µF 10 V capacitor
5 V 2 A adapter (servo supply)1₹180Buy 5 V 2 A adapter (servo supply)
Black card or 3D-printed “+” shade1₹20Buy Black card or 3D-printed “+” shade
Total₹1,300

Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.

Wiring

Arduino Uno R3Only the pins used are shown · 5 V logic5 V 2 A adapter (servos)LDR light sensor moduleLDR light sensor module #2LDR light sensor module #3LDR light sensor module #4SG90 micro servo (9 g)SG90 micro servo (9 g) #2+5VGND5VGNDA0A1A2A3D9D10VCCGNDDO (not connected)AOVCCGNDDO (not connected)AOVCCGNDDO (not connected)AOVCCGNDDO (not connected)AOSIGVCCGNDSIGVCCGND
Every connection
Arduino Uno R3Part · pinWireNotes
A0LDR light sensor module · AOgreenTop-left LDR
5VLDR light sensor module · VCCred
GNDLDR light sensor module · GNDblack
A1LDR light sensor module #2 · AOyellowTop-right LDR
5VLDR light sensor module #2 · VCCred
GNDLDR light sensor module #2 · GNDblack
A2LDR light sensor module #3 · AOblueBottom-left LDR
5VLDR light sensor module #3 · VCCred
GNDLDR light sensor module #3 · GNDblack
A3LDR light sensor module #4 · AOpurpleBottom-right LDR
5VLDR light sensor module #4 · VCCred
GNDLDR light sensor module #4 · GNDblack
D9SG90 micro servo (9 g) · SIGorangePan (east–west) servo
GNDSG90 micro servo (9 g) · GNDblackCommon ground with the servo adapter
5 V 2 A adapter (servos) · +5VSG90 micro servo (9 g) · VCCredFit a 470 µF capacitor across each servo’s VCC and GND
5 V 2 A adapter (servos) · GNDSG90 micro servo (9 g) · GNDblack
D10SG90 micro servo (9 g) #2 · SIGwhiteTilt (elevation) servo
GNDSG90 micro servo (9 g) #2 · GNDblack
5 V 2 A adapter (servos) · +5VSG90 micro servo (9 g) #2 · VCCred
5 V 2 A adapter (servos) · GNDSG90 micro servo (9 g) #2 · GNDblack

Board quirks: This is a demonstrator: SG90s can turn a 1–5 W panel, not a rooftop module. Real trackers use worm-gear motors or linear actuators and a sun-position calculation as well as sensors. Most LDR modules read lower in brighter light; the sketch assumes that (<code>BRIGHT_IS_LOW</code>). Shine a torch on one LDR and watch Serial — if its number rises instead, set it to false. The servos take their own 5 V adapter: two SG90s moving together can pull close to 1 A, enough to reset the Uno if they share its 5 V pin. Join the grounds. The Servo library uses Timer1, so analogWrite stops working on D9 and D10 — which is why the servos sit on exactly those pins.

Code

dual-axis-solar-tracker.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Dual-axis solar tracker — Arduino Uno + 4 LDR modules + 2 SG90 servos (pan and tilt)
// LDR modules (AO): top-left → A0, top-right → A1, bottom-left → A2, bottom-right → A3, all at 5V.
// Pan servo → D9, tilt servo → D10, both powered from a separate 5 V supply (common GND).
// The four LDRs sit in the corners of a "+" shaped shade. When the sun is off-centre, one side
// gets more light; the servos step towards the brighter side until all four read the same.
#include <Servo.h>

constexpr uint8_t LDR_TL = A0, LDR_TR = A1, LDR_BL = A2, LDR_BR = A3;
constexpr uint8_t PAN_PIN = 9, TILT_PIN = 10;
// Most LDR modules read LOWER in brighter light. Shine a torch on one and watch Serial; if the
// number goes up instead, set this to false.
constexpr bool BRIGHT_IS_LOW = true;
constexpr int TOLERANCE = 25;          // ignore differences smaller than this (0–1023 scale)
constexpr int DARK_LEVEL = 60;         // all four below this (as brightness) = night or heavy cloud
constexpr uint8_t PAN_MIN = 10, PAN_MAX = 170, TILT_MIN = 20, TILT_MAX = 160;
constexpr uint8_t PARK_PAN = 20, PARK_TILT = 60;  // where to wait overnight: facing the sunrise

Servo pan, tilt;
int panPos = 90, tiltPos = 90;
unsigned long lastPrint = 0;

int brightness(uint8_t pin) {
  int raw = analogRead(pin);
  return BRIGHT_IS_LOW ? 1023 - raw : raw;
}

void sweepTest() {  // power-on self test: shows both axes move freely before tracking starts
  for (int a = 90; a >= 45; a--) pan.write(a), delay(10);
  for (int a = 45; a <= 135; a++) pan.write(a), delay(10);
  for (int a = 135; a >= 90; a--) pan.write(a), delay(10);
  for (int a = 90; a <= 120; a++) tilt.write(a), delay(10);
  for (int a = 120; a >= 90; a--) tilt.write(a), delay(10);
}

void setup() {
  Serial.begin(115200);
  pan.attach(PAN_PIN);
  tilt.attach(TILT_PIN);
  pan.write(panPos);
  tilt.write(tiltPos);
  delay(500);
  sweepTest();
  Serial.println(F("tl,tr,bl,br,pan,tilt"));
}

void loop() {
  int tl = brightness(LDR_TL), tr = brightness(LDR_TR), bl = brightness(LDR_BL), br = brightness(LDR_BR);
  int top = (tl + tr) / 2, bottom = (bl + br) / 2, left = (tl + bl) / 2, right = (tr + br) / 2;
  bool dark = max(max(tl, tr), max(bl, br)) < DARK_LEVEL;

  if (dark) {
    // Night or a thick monsoon cloud: park facing east and wait.
    if (panPos != PARK_PAN || tiltPos != PARK_TILT) {
      panPos += panPos < PARK_PAN ? 1 : panPos > PARK_PAN ? -1 : 0;
      tiltPos += tiltPos < PARK_TILT ? 1 : tiltPos > PARK_TILT ? -1 : 0;
    }
  } else {
    // One degree per step towards the brighter side. Flip a sign if an axis runs away from the light.
    if (top - bottom > TOLERANCE) tiltPos++;
    else if (bottom - top > TOLERANCE) tiltPos--;
    if (left - right > TOLERANCE) panPos++;
    else if (right - left > TOLERANCE) panPos--;
  }
  panPos = constrain(panPos, PAN_MIN, PAN_MAX);
  tiltPos = constrain(tiltPos, TILT_MIN, TILT_MAX);
  pan.write(panPos);
  tilt.write(tiltPos);

  if (millis() - lastPrint > 1000) {
    lastPrint = millis();
    Serial.print(tl);
    Serial.print(',');
    Serial.print(tr);
    Serial.print(',');
    Serial.print(bl);
    Serial.print(',');
    Serial.print(br);
    Serial.print(',');
    Serial.print(panPos);
    Serial.print(',');
    Serial.print(tiltPos);
    Serial.println(dark ? F(",parked") : F(""));
  }
  delay(40);  // ~25 steps a second: smooth, and the servos don't overheat
}

Compiled for uno on avr 1.8.8 with Servo 1.3.0 · 3 Nov 2026

How it works

A fixed solar panel faces the sun squarely for only an hour or two a day. A tracker that follows the sun collects noticeably more — often quoted as a quarter to a third more for dual-axis tracking on clear days. This build shows the idea at desk scale: four light sensors, two servos, and a small panel that turns to face the sun.

How it finds the sun

The four LDRs sit in the four quarters of a “+”-shaped shade, a cross of black card a few centimetres tall. When the panel points straight at the sun, the shade casts no shadow on any LDR and all four read the same. When the sun moves west, the cross shades the eastern LDRs; the western pair reads brighter.

Every 40 ms the Uno reads all four on A0–A3 and compares averages:

  • top vs bottom → tilt servo one degree towards the brighter side;
  • left vs right → pan servo one degree towards the brighter side.

Differences smaller than TOLERANCE are ignored, so the servos rest once it is aligned instead of buzzing. When all four are dark (night, or a dark monsoon cloud) it parks facing the eastern morning sky, ready for sunrise. Serial prints all four readings and both angles once a second — handy for checking which LDR is which.

At power-on it does a short self-test sweep of both axes, so you can see the mechanics move freely before it starts tracking.

Building the sensor head

Glue the four LDR modules (or just their LDRs on short wires) to a small square of card, one per quarter, and stand a black “+” in the middle, 3–5 cm tall. Fix the head to the panel so it faces exactly where the panel faces. Taller arms make it more sensitive and slower to settle.

India notes

At Indian latitudes (roughly 8–35° N) the noon sun is high — nearly overhead in May–June in the south — and well to the south in December. Set the tilt range (TILT_MIN/TILT_MAX) so the bracket can reach both. Dust on the LDRs matters as much as dust on panels: wipe both.

Troubleshooting

One axis runs away from the light to its end stop

That pair of LDRs is swapped (or the servo is mounted the other way round). Swap the two AO wires of that axis, or flip the ++/−− in the sketch for it.

The panel hunts back and forth around the sun

Raise TOLERANCE (try 40), make the shade’s arms taller, and check all four LDRs read about the same under the same light.

The servos buzz and jitter while still

Supply noise: fit the 470 µF capacitors at the servos and use a proper 2 A adapter.

Upgrades

  • Measure the panel’s output with an INA219 and compare fixed vs tracking on the same day.
  • Add an RTC and calculate the sun’s position, using the LDRs only for fine correction.
  • Replace the SG90s with a worm-gear motor for a panel that holds position in wind with no power.

↑↓ to move · Enter to open