arduino-seguidor-solar-2-ejes

How to Build a Dual-Axis Solar Tracker with Arduino

  • 7 min

A dual-axis solar tracker is a system that orients a surface towards the area of greatest illumination using azimuth and elevation movements.

Orientation influences the energy a panel receives, although the real improvement depends on location, time of year, shadows, and the consumption of the mechanism itself. This prototype serves to understand differential control; it is not intended to optimize a real photovoltaic installation.

This project is a perfect example of a Closed-Loop Control System. The Arduino does not know where the sun is via GPS or time; it actively “searches” for it by comparing the light received from different directions and correcting its position until it finds maximum brightness.

This principle is not only useful for solar panels. It’s the same logic used by tracking radars or self-orienting satellite dishes.

The Differential Comparator

How does the Arduino know which way to move? The key is to use not just one sensor, but four, arranged in a cross and separated by opaque dividers (a cardboard or plastic cross).

The logic is purely comparative:

  • If the Left sensors receive more light than the Right ones, it means the sun is to the left. Action: Turn the horizontal motor to the left.
  • If the Top sensors receive more light than the Bottom ones, the sun is higher. Action: Raise the vertical motor.

The system’s goal (its equilibrium state) is for the difference to be zero, meaning all four sensors receive the same amount of light.

Components

  • [1x|blue] Arduino Uno | The brain of the project
  • [1x|green] Servo motor (SG90 or MG90S) | X-Axis (Azimuth/Horizontal)
  • [1x|green] Servo motor (SG90 or MG90S) | Y-Axis (Elevation/Vertical)
  • [4x|orange] Photoresistors (LDR) | Detect light in each quadrant
  • [4x|gray] 10kΩ Resistor | Voltage dividers for LDRs
  • [Various|gray] Support structure | 3D print, cardboard, or acrylic

We will need a moving structure. You can 3D print it (there are thousands of “Pan/Tilt brackets” on Thingiverse) or make one from cardboard and hot glue.

Connection Diagram

Sensors and Voltage Dividers

LDRs change their resistance with light. Arduino only measures voltage. Therefore, we need to create a voltage divider for each LDR.

[ { “from”: “LDR Top Left (TL)”, “to”: “Pin A0”, “color”: “orange”, “note”: “With 10kΩ pull-down to GND” }, { “from”: “LDR Top Right (TR)”, “to”: “Pin A1”, “color”: “orange”, “note”: “With 10kΩ pull-down to GND” }, { “from”: “LDR Bottom Left (BL)”, “to”: “Pin A2”, “color”: “orange”, “note”: “With 10kΩ pull-down to GND” }, { “from”: “LDR Bottom Right (BR)”, “to”: “Pin A3”, “color”: “orange”, “note”: “With 10kΩ pull-down to GND” }, { “from”: “LDR VCC”, “to”: “Arduino 5V”, “color”: “red” }, { “from”: “10kΩ Resistors”, “to”: “Arduino GND”, “color”: “black” } ]

  • One end of the LDR to 5V.
  • The other end of the LDR to the Analog Pin and also to a 10kΩ resistor.
  • The other end of the resistor to GND.

Servos

The signal for each servo comes from the Arduino, but it is advisable to power them separately.

[ { “from”: “Horizontal Servo Signal”, “to”: “Pin 9”, “color”: “green” }, { “from”: “Vertical Servo Signal”, “to”: “Pin 10”, “color”: “green” }, { “from”: “Servos VCC”, “to”: “External regulated power supply”, “color”: “red” }, { “from”: “Servos GND”, “to”: “Power supply GND + Arduino GND”, “color”: “black” } ]

Power the servos with an external regulated power supply at their nominal voltage and connect the grounds (GND). Two servos moving simultaneously can reset the Arduino if you try to power them only from USB.

The Code

The code implements continuous search logic. The critical part here is the Tolerance (or dead band). We don’t want the robot to jitter due to minuscule light differences or electrical noise. It will only move if the difference is significant.

#include <Servo.h> 

// --- Hardware Configuration ---
Servo servoHorizontal;
int servoh = 90;   // Initial horizontal position
int servohLimitHigh = 180;
int servohLimitLow = 0;

Servo servoVertical; 
int servov = 90;   // Initial vertical position
int servovLimitHigh = 180;
int servovLimitLow = 0;

// LDR pins
const int ldrTL = A0; // Top Left
const int ldrTR = A1; // Top Right
const int ldrBL = A2; // Bottom Left
const int ldrBR = A3; // Bottom Right

// --- Control Configuration ---
int tolerance = 50; // Sensitivity: Adjust according to ambient light
int responseTime = 10; // Delay to smooth movement

void setup() {
  Serial.begin(9600);
  
  servoHorizontal.attach(9); 
  servoVertical.attach(10);
  
  // Move to starting position
  servoHorizontal.write(servoh);
  servoVertical.write(servov);
  delay(2000); // Give time to position itself
}

void loop() {
  // 1. Sensor Reading
  int lt = analogRead(ldrTL); // Top Left
  int rt = analogRead(ldrTR); // Top Right
  int ld = analogRead(ldrBL); // Down Left
  int rd = analogRead(ldrBR); // Down Right
  
  // 2. Calculate Averages
  int avt = (lt + rt) / 2; // Average Top
  int avd = (ld + rd) / 2; // Average Down
  int avl = (lt + ld) / 2; // Average Left
  int avr = (rt + rd) / 2; // Average Right
  
  // 3. Vertical Logic (Elevation)
  int dvert = avt - avd; // Difference top - bottom
  
  // Only move if the difference exceeds the tolerance (Dead band)
  if (abs(dvert) > tolerance) {
    if (avt > avd) {
      servov++; // If there's more light at the top, move up
    } else {
      servov--; // If there's more light at the bottom, move down
    }
    
    // Limit the physical range of the servo to avoid breaking the mechanism
    servov = constrain(servov, servovLimitLow, servovLimitHigh);
    servoVertical.write(servov);
  }
  
  // 4. Horizontal Logic (Azimuth)
  int dhoriz = avl - avr; // Difference left - right
  
  if (abs(dhoriz) > tolerance) {
    if (avl > avr) {
      servoh--; // Depending on servo mounting, this might be ++ or --
    } else {
      servoh++;
    }
    
    servoh = constrain(servoh, servohLimitLow, servohLimitHigh);
    servoHorizontal.write(servoh);
  }
  
  delay(responseTime);
}
Copied!

Adjustment and Calibration

When you upload this code, one of these things is likely to happen:

  1. The robot runs away from the light instead of tracking it: This means the servo logic is inverted. Change servoh++ to servoh-- (or vice versa) in the code.
  2. The robot constantly trembles: The tolerance is too low. Increase it from 50 to 100.
  3. It moves very slowly: Reduce the responseTime.

When a Second Axis Helps

A single-axis solar tracker (East-West only) can capture a lot of extra energy, but it ignores the seasonal variation of the sun’s height (the sun is higher in summer than in winter).

With a dual-axis system, we guarantee that the rays strike at a perfect 90º angle both throughout the day and throughout the year. In real installations, this can mean an efficiency increase of up to 40% compared to fixed panels.

Next Steps

To turn this toy prototype into something serious:

  1. Limit power consumption: The sun moves slowly. We can apply a low-power mode compatible with the board and wake up periodically to correct the position.
  2. Limit switches: To prevent the robot from spinning 360º and breaking the cables.
  3. “Night” Mode: Detect when the total light is very low (nighttime) and automatically rewind the servos towards the East to wait for the next day’s dawn.