Build a Solar Panel Sun Tracker with Arduino
A fixed-mount solar panel only hits peak efficiency at one moment of the day — early morning and late afternoon, when light hits at a steep angle, output drops sharply. A solar tracker rotates the panel toward the strongest light all day long, which can boost harvested energy by 20-30% over a fixed panel of the same rating. This project uses an Arduino, two light-dependent resistors (LDRs), and a servo to build a single-axis demo — enough to understand the principle before scaling up to a full two-axis system.
How it works
- Two LDR sensors sit on either side of a small divider, so that when light comes in at an angle, one side falls in shadow while the other gets direct light.
- The Arduino reads both LDR light levels on its analog pins.
- If the difference between the two sides crosses a threshold, the Arduino commands the servo to rotate a bit toward the LDR receiving more light.
- When the difference drops back near zero (the panel is now pointed straight at the strongest light source), the servo stops — the cycle repeats continuously as the sun moves through the day.
Parts list
- Arduino Uno or Nano
- 2 light-dependent resistors (LDRs) + 2× 10kΩ resistors (voltage divider)
- SG90 servo, or a higher-torque servo if the panel is heavier
- A small solar panel (5V, for demoing or trickle-charging a backup battery)
- A small divider between the two LDRs (cardboard or 3D-printed) to cast an uneven shadow when light hits at an angle
- A mechanical rotating mount linking the servo to the panel (3D-printed or hand-built)
Sample code
#include <Servo.h>
Servo panelServo;
const int LDR_LEFT = A0;
const int LDR_RIGHT = A1;
const int SERVO_PIN = 9;
int servoAngle = 90; // starting midpoint, 0-180 degrees
const int STEP = 1; // step size per correction
const int DEAD_ZONE = 40; // difference threshold, avoids constant jitter
const int READ_DELAY = 3000; // re-read every 3s, gives the servo time to settle
void setup() {
Serial.begin(9600);
panelServo.attach(SERVO_PIN);
panelServo.write(servoAngle);
}
void loop() {
int left = analogRead(LDR_LEFT);
int right = analogRead(LDR_RIGHT);
int diff = left - right;
Serial.print("Left: "); Serial.print(left);
Serial.print(" | Right: "); Serial.print(right);
Serial.print(" | Diff: "); Serial.println(diff);
if (abs(diff) > DEAD_ZONE) {
if (diff > 0 && servoAngle < 180) {
servoAngle += STEP; // left side brighter, rotate toward it
} else if (diff < 0 && servoAngle > 0) {
servoAngle -= STEP; // right side brighter, rotate toward it
}
panelServo.write(servoAngle);
}
delay(READ_DELAY);
}
Common pitfalls
- No divider between the two LDRs — there’s no uneven shadow, so the panel never “decides” which way to turn because both sensors read nearly the same value.
- Missing dead zone (
DEAD_ZONE) causes the servo to jitter constantly around the balance point, draining the servo and wearing out the mechanics fast — always leave a tolerance band before issuing a rotation command. - Reading and rotating too fast makes the servo react to a passing cloud instead of the actual light trend over the day — space out readings by a few seconds, or average several samples.
- A small SG90 servo lacks torque if the panel and mount are heavy — use a higher-torque servo or a geared DC motor with a driver instead.
Extensions
Add a second axis (tilt for solar elevation, in addition to azimuth) for a full two-axis tracker, attach a current/voltage sensor (INA219) to directly measure harvested power and compare it against a fixed mount, or use an ESP32 to log data in real time to a dashboard like the one in self-hosted sensor dashboard with Grafana.
Comments