Solar Tracker based on Sun Position Calculation

Hello Everyone!

I am prototyping a dual-axis solar tracker based on RTC, GPS, and LDR readings (For feedback mechanism, I am thinking if I should include it or not). I have created a code based on other related studies and tutorials on how to do so. However, I am having trouble debugging the sketch and the mechanism isn't, somehow, working as intended. I am thinking maybe it is from the wirings/on how the modules are connected to its pins or the code is the problem itself.

Please help me correct the code, to what should be implemented, removed, added, adjusted, etc. I am very open to suggestions and feedback that would help me improve my work. The details are given below. Thank you in Advance!

A Dual-axis solar tracker composed of 6 solar panels positioned like a Sunflower. The system is based on RTC and GPS reading for the calculation of the sun position, and the code implements a feedback mechanism using 5 sets of LDRs which help position the servo motors accordingly. Moreover, if the LDR readings are low the gear moves clockwise which serves as a closing mechanism. Conversely, if the LDR detects light the gear moves counter-clockwise which is the opening of the panels. Additionally, the code utilizes a smart monitoring system using a Bluetooth module which would direct the data outputs (Average Voltage, time, date, etc) to an android app created in MIT App inventor. Specifically, this uses a DS3231 RTC, NEO 6m GPS with antenna, HC-05 Bluetooth module, 2 MG90s Servo motors, DC Gear motor, L293D Dual H-Bridge Motor Driver IC, 6 pieces of 5V 60mA mini solar panels, and a microcontroller Arduino Uno R3.

NOTE: I am not a professional nor an expert on this matter, the sketch is might be incomplete, very unorganized and some parts might not even make sense. This is very stressful, please bear with me :grinning:

#include <RTClib.h>
#include <SoftwareSerial.h>
#include <TinyGPS++.h>
#include <Servo.h>

Servo horizontal;  // horizontal servo
int servoh = 180;
int servohLimitHigh = 175;
int servohLimitLow = 5;
// 65 degrees MAX

Servo vertical;  // vertical servo
int servov = 45;
int servovLimitHigh = 100;
int servovLimitLow = 1;

// LDR pin connections
// name = analogpin;
int ldrlt = A1;  //LDR top left - BOTTOM LEFT <--- BDG
int ldrrt = A2;  //LDR top right - BOTTOM RIGHT
int ldrld = A3;  //LDR down left - TOP LEFT
int ldrrd = A4;  //ldr down right - TOP RIGHT
int ldrmt = A5;

const int button1 = 9;
const int button2 = 10;
const int motorA = 7;
const int motorB = 8;
const int pvPin = 1;  // +V from PV is connected to analog pin 1
int buttonStateA = 0;
int buttonStateB = 0;
int angle = 0;
int angle_ref = 110;  // angle of horizontal array; facing the sky
int pos = 0;
int pos2 = 0;
int oldvalue;
int oldvalue2;

// Define BT variables
long previousMillis = 0;  // will store last time data was sent
long interval = 8000;     // interval at which to send data (ms)

// Define running average variables
const int numReadings = 6;         // number of sample for the running average (*****)
int readings[numReadings][5];      // the readings from the 6 analog inputs
int index = 0;                     // the index of the current reading
int total[5] = { 0, 0, 0, 0, 0 };  // the running total
int average_diff = 0;              // the average of the difference

//GPS variables
static const int RXPin = 4, TXPin = 3;
static const uint32_t GPSBaud = 9600;
TinyGPSPlus gps;
SoftwareSerial ss(RXPin, TXPin);  // RX, TX

RTC_DS3231 rtc;
SoftwareSerial BTSerial(2, 3);  // RX, TX

void setup() {

  Serial.begin(9600);
  ss.begin(GPSBaud);
  angle = angle_ref;
  horizontal.attach(5);
  vertical.attach(6);
  horizontal.write(180);
  vertical.write(0);  //45? //0?

  pinMode(motorA, OUTPUT);
  pinMode(motorB, OUTPUT);
  pinMode(button1, INPUT);
  pinMode(button1, INPUT);
  pinMode(pvPin, INPUT);
  delay(2500);
  Wire.begin();
  rtc.begin();
   //Set the RTC time
  //rtc.adjust(DateTime(F(12, 3, 2023), F(10, 00, 00)));  // day, month, year // hour, minute, second
  rtc.adjust(DateTime(2023, 3, 13, 15, 30, 0));  // set time to March 13, 2023, 3:30 PM
  //BTSerial.begin(9600);
}
// Calculate the equation of time for a given date
double equationOfTime(int year, int month, int day) {
  double B = 360 / 365.24 * (day - 81);
  double EOT = B + 9.87 * sin(radians(2 * B)) - 7.53 * cos(radians(B)) - 1.5 * sin(radians(B));
  return EOT;
}

void loop() {
  while (ss.available() > 0) {
    gps.encode(ss.read());
    if (gps.location.isUpdated()) {
      double latitude = gps.location.lat();
      double longitude = gps.location.lng();
      double currentHour = gps.time.hour();
      double currentMinute = gps.time.minute();
      double currentSecond = gps.time.second();
      Serial.print("Latitude= ");
      Serial.println(gps.location.lat(), 6);
      Serial.print("Longitude= ");
      Serial.println(gps.location.lng(), 6);
      calculateSunPosition();      
    }
  }
  DateTime now = rtc.now();  // Get the current time from the RTC
  // calculate the position of the sun based on current time and location
  float latitude, longitude;
  float altitude, azimuth;
  calculateSunPosition(now, latitude, longitude, altitude, azimuth);
  Serial.print("Altitude: ");
  Serial.println(altitude);
  Serial.print(", Azimuth: ");
  Serial.println(azimuth);
  delay(1000);

  int year = now.year();
  int month = now.month();
  int day = now.day();
  int hour = now.hour();
  int minute = now.minute();
  int second = now.second();

  // Calculate the current day of the year
  int N = now.day();

  // Calculate the decimal hour
  float h = now.hour() + now.minute() / 60.0 + now.second() / 3600.0;

  // Calculate the solar declination angle
  float delta = 23.45 * sin(2 * PI * (N - 81) / 365);

  // Calculate the solar hour angle
  float H = 15 * (h - 12) + longitude;

  // Calculate the solar altitude angle
  float sinAlt = sin(radians(latitude)) * sin(radians(delta)) + cos(radians(latitude)) * cos(radians(delta)) * cos(radians(H));
  float alt = degrees(asin(sinAlt));

  // Calculate the solar azimuth angle
  float cosAz = (sin(radians(delta)) - sin(radians(latitude)) * sin(radians(alt))) / (cos(radians(altitude)) * cos(radians(alt)));
  float az = degrees(acos(cosAz));
  if (H > 0)
    az = 360 - az;

  // Calculate the sun's position for the given date and time

  // Convert latitude and longitude to radians
  float latRad = radians(latitude);
  float longRad = radians(longitude);

  // Calculate the declination angle
  float sinDeclination = sin(radians(23.45)) * sin(radians(360 / 365.24 * (day - 81)));  //sin(radians(altitude));
  float cosDeclination = sqrt(1 - pow(sinDeclination, 2));
  float declination = degrees(atan2(sinDeclination, cosDeclination));

  // Calculate the hour angle
  float solarTime = hour + minute / 60.0 + second / 3600.0;
  float solarNoon = 12.0 - (longitude / 15.0) - (4 * (longitude - 15 * equationOfTime(year, month, day)) / 60.0);
  float hourAngle = 15.0 * (solarTime - solarNoon);

  // Calculate the altitude angle
  float sinAltitude = sin(latRad) * sin(radians(declination)) + cos(latRad) * cos(radians(declination)) * cos(radians(hourAngle));
  altitude = degrees(asin(sinAltitude));

  // Calculate the azimuth angle
  float cosAzimuth = (sin(radians(declination)) - sin(latRad) * sin(radians(altitude))) / (cos(latRad) * cos(radians(altitude)));
  azimuth = degrees(acos(cosAzimuth));
  if (hourAngle > 0) {
    azimuth = 360 - azimuth;
  }

  // convert altitude and azimuth angles to servo motor positions
  // control the servo motors to move to the desired positions

  int servohNew = map(azimuth, 0, 360, servohLimitLow, servohLimitHigh);
  int servovNew = map(altitude, 0, 90, servovLimitLow, servovLimitHigh);

  // move the servo motors to the desired positions
  if (abs(servohNew - servoh) > 2) {
    if (servohNew > servoh) {
      for (pos = servoh; pos <= servohNew; pos += 1) {
        horizontal.write(pos);
        delay(100);  //10(.1 second)? maybe adjusted (mm)
      }
    } else {
      for (pos = servoh; pos >= servohNew; pos -= 1) {
        horizontal.write(pos);
        delay(100);  //10(.1 second)?maybe adjusted (mm)
      }
    }
    servoh = servohNew;
  }

  if (abs(servovNew - servov) > 2) {
    if (servovNew > servov) {
      for (pos2 = servov; pos2 <= servovNew; pos2 += 1) {
        vertical.write(pos2);
        delay(100);  //maybe adjusted (mm)
      }
    } else {
      for (pos2 = servov; pos2 >= servovNew; pos2 -= 1) {
        vertical.write(pos2);
        delay(100);  //maybe adjusted (mm)
      }
    }
    servov = servovNew;
  }

  {
    int ldrStatus = analogRead(ldrmt);
    if (ldrStatus > 30) {
      buttonStateA = digitalRead(button1);
      if (buttonStateA == LOW) {

        digitalWrite(motorA, HIGH);  //COUNTER clockwise
        digitalWrite(motorB, LOW);
      } else {
        digitalWrite(motorA, LOW);
        digitalWrite(motorB, LOW);
      }
      buttonStateB = digitalRead(button2);
      if (buttonStateB == LOW) {

        digitalWrite(motorA, LOW);  //clockwise
        digitalWrite(motorB, HIGH);
      } else {
        digitalWrite(motorA, LOW);
        digitalWrite(motorB, LOW);
      }


      // Feedback Mechanism using LDR readings
      //void feedbackMechanism() {
      int lt = analogRead(ldrlt);  // top left
      int rt = analogRead(ldrrt);  // top right
      int ld = analogRead(ldrld);  // down left
      int rd = analogRead(ldrrd);  // down right
      int dtime = 10;
      int tol = 90;             // dtime=diffirence time, tol=toleransi
      int avt = (lt + rt) / 2;  // average value top
      int avd = (ld + rd) / 2;  // average value down
      int avl = (lt + ld) / 2;  // average value left
      int avr = (rt + rd) / 2;  // average value right
      int dvert = avt - avd;    // check the diffirence of up and down
      int dhoriz = avl - avr;   // check the diffirence og left and rigt

      //if(lt>90){
      //if(Switch_a==LOW){
      // digitalWrite(9==HIGH);
      // digitalWrite(10==LOW);
      // delay(1000);
      //}}

      if (-1 * tol > dvert || dvert > tol) {
        if (avt > avd) {
          servov = ++servov;
          if (servov > servovLimitHigh) {
            servov = servovLimitHigh;
          }
        } else if (avt < avd) {
          servov = --servov;
          if (servov < servovLimitLow) {
            servov = servovLimitLow;
          }
        }
        vertical.write(servov);
      }
      if (-1 * tol > dhoriz || dhoriz > tol)  // check if the diffirence is in the tolerance else change horizontal angle
      {
        if (avl > avr) {
          servoh = --servoh;
          if (servoh < servohLimitLow) {
            servoh = servohLimitLow;
          }
        } else if (avl < avr) {
          servoh = ++servoh;
          if (servoh > servohLimitHigh) {
            servoh = servohLimitHigh;
          }
        } else if (avl = avr) {
          delay(10);
        }
        horizontal.write(servoh);
      }

      delay(dtime);

    } else {
      oldvalue = horizontal.read();
      oldvalue2 = vertical.read();

      for (pos = oldvalue; pos <= 180; pos += 1) {  // goes from 0 degrees to 180 degrees
        // in steps of 1 degree
        horizontal.write(pos);
        delay(15);
      }
      for (pos2 = oldvalue2; pos2 <= 0; pos2 += 1) {  // goes from 0 degrees to 180 degrees
        // in steps of 1 degree

        vertical.write(pos2);  // tell servo to go to position in variable 'pos'
        delay(15);
      }
      buttonStateB = digitalRead(button2);
      if (buttonStateB == LOW) {

        digitalWrite(motorA, LOW);  //clockwise
        digitalWrite(motorB, HIGH);
      } else {
        digitalWrite(motorA, LOW);
        digitalWrite(motorB, LOW);
      }
    }
  }

  if (BTSerial.available()) {
    // handle Bluetooth commands here
    //Calculate the runnning average
    total[3] -= readings[index][3];
    readings[index][3] = analogRead(pvPin);
    total[3] += readings[index][3];

    float ref_voltage = (total[1] / numReadings) / 1000.0;  // return the runnning average of Vcc
    float average_pv = total[3] / numReadings;              // return the runnning average of batt

    //Update PV voltage
    float voltage_pv = average_pv * ref_voltage * 2.0 / 1024.0;  //    /(R2/(R1 + R2))
    float power_pv = voltage_pv * 0.150 * 5;

    // Correct angle for display
    float angle_corr = angle * (148 - 110) / 45 - 100 * (148 - 110) / 45;

    // Datalogging over bluetooth every X seconds
    unsigned long currentMillis = millis();
    unsigned long currentTime = millis();  // 3600000.0;
    if (currentMillis - previousMillis > interval) {
      previousMillis = currentMillis;
      Serial.print("Time = ");
      Serial.print(millis() / 3600000.0);
      Serial.print(" s       |  ");
      Serial.print("Array angle = ");
      Serial.print(angle_corr);
      Serial.println(" deg");
      //Serial.print("Temperature = "); Serial.print(temperatureC); Serial.println(" deg C");
      //Serial.print("Battery SOC = "); Serial.print( (int) battery_capa); Serial.print("%  |  ");
      //Serial.print("V batt = "); Serial.print(voltage_batt); Serial.println(" V");
      Serial.print("V pv = ");
      Serial.print(voltage_pv);
      Serial.print(" V        |  ");
      Serial.print("P pv (estimate) = ");
      Serial.print(power_pv);
      Serial.println(" W");
      Serial.print("Vcc = ");
      Serial.print(ref_voltage);
      Serial.println(" V");
      Serial.println();
    }
  }
}

big project, big code..
quickly, what's up with them buttons..
do you have external pull down resistors??
or use built in pull_up and reverse your logic..
some de-bouncing using a millis timer as buttons tend to bounce..
is something not working??
stuck on something??
~q

1 Like

If you know the sun position, then there is no need for feedback. However, the mechanism must be able to point accurately to a given (altitude, azimuth).

Here is an example I put together a while back, from readily available, off the shelf parts: GitHub - jremington/Arduino_heliostat: Model alt/az heliostat using Arduino

It is currently configured as a heliostat (to reflect sunlight on a fixed position), but sun tracking is part of the algorithm, and can be used instead.

2 Likes

In that case, you should post complete hardware details in addition to the sketch you already posted.

1 Like

Just so we don't have to make a 1,000 guesses, what parts are working as you intend?

1 Like

Here are the wirings, however, this is incomplete since tinkercad has limited components but:

The servos are connected to pins 5 and 6

The RTC is connected to A4 (SDA), and A5 (SCL) pins. Or should it be connected to the SDA and SCL pins on the Arduino board?

The GPS is connected to the RX, TX, 3.3V, and GND pins on the Arduino board.

The bluetooth module is connected to pins 2 and 3.

Here are my concerns right now:

  1. When connected to the power supply, the servos are moving as intended, however, overtime it doesn't change in position. I am thinking whether it is from the wirings or the RTC and GPS modules isn't working/getting the data for the calculation or maybe the calculation it self is the problem.

  2. I tried testing with the LDRs only, it is working as intended however, with lower readings it is suppose to come back from its Default. Also some LDR seems to be not responding. I'll be testing the LDRs or maybe try on new sets

  3. I tried testing the bluetooth to an android app, I was able to pair the bluetooth from my device however, the app (from MIT app inventor) wasn't recognizing the bluetooth. It says "Error: device not connected to a Bluetooth device"

  4. My references is techboystoys' project, (Link is given below) however, his project was using a custom PCB board so i am also confused on how I will be wiring the gear, trigger buttons, and the driver.
    I made the most complicated science fair project for students 🔥🔥 | Pro project for Pro DIY lovers | TecH BoyS ToyS

  5. The servos are bugging back and forth.

Hello crazz

Use a local clock including calendar to control direction and elevation angle.

Have a nice day and enjoy coding in C++.

1 Like

The buttons (mini switchs that we got from DVDs) are for triggering the clockwise and counterclockwise movement of the gear motor since the prototype is designed like a sunflower. Our basis is techboystoys' project. https://www.techboystoys.com/i-made-the-most-complicated-science-fair-project-for-students-🔥🔥-pro-project-for-pro-diy-lovers/

I am also having a hardtime on how I will be wiring the button, gear, and driver since techboystoys' project is using a customized PCB.

Hi,

Can you please tell us your electronics, programming, arduino, hardware experience?

Can we please have a circuit diagram?
An image of a hand drawn schematic will be fine, include ALL power supplies, component names and pin labels.

Thanks.. Tom... :smiley: :+1: :coffee: :australia:

Consider using an RTC set to UTC time, and a library to track the local sun position:
https://github.com/KenWillmott/SolarPosition

You can monitor the illumination with photo sensors as well, combine the two methods to obtain the most data, in order to decide where to point.

1 Like

I use this library, SolarCalculator library for Arduino and the position of my project to drive the solar panels to point at the sun.

I found that the expense of the components to drive a solar array to point at the sun is greater than buying a slightly larger properly positioned solar cell to power the project.

1 Like

Maybe why some systems only rotate on one axis?

1 Like

I have some solar panels rotating on only one axis. 2 axis rotation is a waste of money when the 2nd axis only needs adjusting once every few months to compensate for seasonal sun position change. And again a bigger solar cell will compensate for seasonal variations and would cost less than a motor and mechanical positioning system.

Also, many solar mount move motors wear out after a year or so. Especially if the array encounters winds above 5MPH. What ends up happening is that most owners of automated moving solar arrays lock their arrays at due south and add a few more solar panels.

1 Like

Your code will not compile, among other problems, the function calculateSunPosition is not defined.

Using two instances of software serial is very problematic, only one instance can be receiving at any given time.

Why the need for both an RTC and a GPS? Most solar arrays are in a fixed position, so the latitude/longitude would be fixed, and the RTC should have enough long-term accuracy on its own. Regardless, there is no need to constantly be checking the GPS in loop(), getting the current time and lat/long in setup() should be sufficient, and allow for switching the software serial receive to bluetooth full-time. If you are concerned about long-term drift of the RTC, then check the GPS monthly/weekly and power it down the remainder of the time.

DO NOT power the servos from the 5v pin of the arduino, that cannot reliably supply sufficient power.

Presumably this is more of an artistic piece than a real solar power system, folding the solar panels when not in use is a complete waste of power and an additional hardware complication.

2 Likes

Probably because you are using an inadequate servo power supply. The supply should easily be able to provide at least 1 Ampere per small (SG90) servo, 2.5 Amperes per large (MG996R) servo.

1 Like

Hi,

I have a personal soapbox stand about using sensor based rather than software timer based solar trackers.

Can you tell us why you are going down this complicated method of solar tracking when just detecting where in the sky the maximum solar radiation is coming from can be done with external sensors.

Tom... :smiley: :+1: :coffee: :australia:

When I experimented with using photo diodes to track the sun the system worked very well to track the sun in both axis but the power drain was not economical. When I switched over to using an actuator to tilt the cell on a single axis once every 6 minutes in relation to a calculated sun angle my setup became economical. I turn off the power to the actuator for most of the time and do not need to manage power for the photo diodes.

Maximum power generation was why I originally chose such a method. In the end it would have been cheaper to increase the size of the solar cell. The actuator has taken a beating this winter and may not last the summer. Meanwhile the solar projects with the stationary mounted larger cells do not need any spring parts replacements and have supplied power to their projects with minimal maintenance this winter.

Based on what I have read sensors are prone to several factors affecting its efficacy in solar energy harvesting. Two major problem is the blockage of clouds and the weather condition so I decided to do this but realized it is way more complicated :skull: Although, however, is there a way to enhance or a better use of sensor based trackers? If this project won't be successful, I'll be using just sensors instead.