[work in progress]

**Arduino project: have a solar panel find and follow the sun**

Many solar tracking projects use four Light Dependent Resistors for finding the brightest spot in the sky, but for the ever changing Dutch skies I chose a different approach:

- Calculate the sun’s azimuth and inclination, based on geographical position and the current date and time.
- Attach an absolute orientation sensor to the solar panel to tell Arduino its orientation.
- Have two stepper motors (pan and tilt) rotate the panel towards the sun’s position and then follow its path over the celestial sphere.
- Send Arduino to sleep at sunset and wake it up again at sunrise.

For a start, I will hard code my geographical position in the sketch and use a DS3231 Real Time Clock module to tell Arduino the current date and time. Although a GPS module can give both position and time, it will certainly use more power.

The absolute orientation sensor I will use is the Adafruit BNO005. This module already takes care of the Kalman filtering, so I don’t have to include code for that in my Arduino sketch.

My reason for choosing stepper motors instead of servos is that the motors of a solar tracker will be idle for most of the time. My guess is that idle steppers use less power than idle servos. Moreover, I always find servos to be a bit nervous and noisy.

I will only use the *steps per rotation* info of the stepper motors during initial positioning of the panel. Once the panel is facing the sun, intermediate adjustments will never need more than one step for one or both motors. The direction of those single steps comes from the orientation sensor. So the more interesting part of the sketch will be the initial positioning, at least if you want it to happen as efficiently as possible. Here’s the plan:

**I. Horizontal rotation (pan)**

At startup, the orientation sensor will tell the Arduino the horizontal (compass) angle that the panel is pointing at (* Ap*). The Arduino reads the current date and time from the RTC and calculates the sun’s azimuth

*(its compass direction). Now it can calculate the number of degrees it has to rotate the panel to give it the same compass direction as the sun.*

**As**We obviously don’t want rotations over 180 degrees ore more because in that case rotation in the opposite direction would be more efficient.

If * Adif = As – Ap* then we can calculate the (efficient) number of degrees to move:

* ToGo(Adif) = Adif – 360 * int(Adif/180) *where 360<Adif<360

This graph may help:

**ToGo(x) = x – 360 * int(x/180) ** (-360<x<360)

In Arduino code this would be something like:

1 2 3 |
int Adif = int(As-Ap); float fraction = Adif/180; int ToGo = Adif-360*int(fraction); |

Now that we can efficiently adjust the panel in horizontal direction (pan), we could let our sketch do this first, and then adjust its vertical orientation (tilt). The following example shows that this wouldn’t be the best strategy.

Imagine the situation where the sensor’s compass direction is 0 degrees (North) and the sun’s azimuth is 180 degrees (South). In our formula, that would mean that Adif = 180, so according to our formula we would have to rotate the panel horizontally over -180 degrees.Let’s further imagine that our sensor’s tilt angle is 120 degrees (90 degrees meaning facing upwards) and the current sun’s elevation angle is 60 degrees. So, after adjusting the panel horizontally over the maximum 180 degrees, we would also have to tilt it over another 60 degrees. But, the panel in our situation was already facing the sun right from the start! Although its mounting (and the sensor) was facing North, it’s panel was tilted ‘backwards’, looking right at the sun.

What we learn from this extreme example is that finding the most efficient adjustment depends on the current status in both orientations together. We have to calculate the steps needed in two scenarios (1. *horizontal before vertical* and 2. *vertical before horizontal*) and then pick the cheapest scenario.

**II. Vertical rotation (tilt)**

Let * Ep* and

*be the elevation angles of the panel and of the sun, respectively. We are only interested in*

**Es***values between 0 and 90 degrees because that’s when the sun is visible.*

**Es**As we saw earlier, we have two scenarios for getting the right tilt angle. Depending on how we will rotate horizontally, the number of tilt steps needed will either be * abs(Es-Ep)* or

*.*

**abs(Ez+Ep-180)**

**III. Finding the best scenario**

After some simple math, I think the algorithm for the most efficient initial panel adjustment would become something like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
float As,Ap,Es,Ep,s1,s2; int pan_steps, tilt_steps; void setup() { // General setup stuff here // Initial panel adjustment at Arduino wakeup /* Read Ap and Ep from Absolute Orientation Sensor. Calculate As and Es based on RTC time. */ int Adif = int(As-Ap); int Edif = int(Es-Ep); int fracE = Edif/180; int fracA = Adif/180; s1 = abs(Adif-360*int(fracA))+abs(Edif); if(Adif==0) { pan_steps = 0; tilt_steps = Edif - 360*int(fracE); } else if (Adif>0) { s2 = abs(Adif-180) + abs(Es+Ep-180); if (s1>=s2){ pan_steps = Adif-180; tilt_steps = Es+Ep+180; } else { pan_steps = Adif-360*int(fracA); tilt_steps = Edif; } } else { s2 = abs(Adif+180) + abs(Es+Ep-180); if (s1>=s2){ pan_steps = Adif+180; tilt_steps = Es+Ep-180; } else { pan_steps = Adif-360*int(fracA); tilt_steps = Edif; } } // make the steppers do the calculated pan and tilt steps } void loop() { /* periodically calculate sun position. if adjustments needed are > 1 degree: adjust if Es<=0 put Arduino to sleep with wakeup timer set to next day's sunrise */ } |

**IV. Mounting**

This is what I have in mind for the mechanical part of the project.

I will need a rectangular box, about the size of my solar panel. I will also need a U-shape mounting to hold the box so it can rotate around its horizontal axis. Finally I’ll need a ground plane that allows the U-shape mounting to rotate around the pan stepper’s (vertical) axis.

- The solar panel will be on top of the cover of the box.
- The orientation sensor will be at the back of the cover, exactly in line with the panel.
- Apart from the Arduino, the box will also contain a LiPo battery, a LiPo charger, a Power Booster to deliver 5 volt, the Real Time Clock, two ULN2003 stepper motor drivers, the tilt stepper motor and a 6-wire slip ring.
- The box can rotate around its horizontal axis in the U-shape mounting.
- One end of the axis will be the tilt stepper’s shaft (the stepper is fixed inside the box, the shaft is fixated in the mounting).
- The slip ring will form the other end of the axis. Its 6 wires will be used to control the second stepper (pan) and to power a 5 volt USB output connector.
- The U-shape mounting will hold the pan stepper. Its shaft will go into the ground plane. It will be connected to the 6 wires from the slip ring.
- The U-shape mounting will also hold a 5 volt USB output connector (the sun should not only power the tracker!).

By using a slip ring and continuous steppers, both pan and tilt rotations are unrestricted. If an external device is powered by the 5 volt USB output, we could attach it to the mounting or rely on the fact that solar tracking will have limited rotations anyway.

**V. Intermezzo: calculating the position of the sun**

Here’s an algorithm that I found on Internet (author unknown). Its accuracy is good enough for a solar tracker.

The following variables should be declared and be given values before calling sunPos():

1 2 |
int Year, Month, Day; float Hours, Minutes, Longitude, Latitude; |

This section must also be included in the main program of the sketch:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <math.h> #define pi 3.14159265358979323846 #define twopi (2*pi) #define rad (pi/180) #define EarthMeanRadius 6371.01 // In km #define AstronomicalUnit 149597890 // In km float ZenithAngle; float Azimuth; float RightAscension; float Declination; float Parallax; float ElevationAngle; float ElapsedJulianDays; float DecimalHours; float EclipticLongitude; float EclipticObliquity; |

This is the sunPos function:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
void sunPos(){ // Auxiliary variables float dY; float dX; // Calculate difference in days between the current Julian Day // and JD 2451545.0, which is noon 1 January 2000 Universal Time float JulianDate; long int liAux1; long int liAux2; // Calculate time of the day in UT decimal hours DecimalHours = Hours + (Minutes / 60.0); // Calculate current Julian Day liAux1 =(Month-14)/12; liAux2=(1461*(Year + 4800 + liAux1))/4 + (367*(Month - 2-12*liAux1))/12- (3*((Year + 4900 + liAux1)/100))/4+Day-32075; JulianDate=(float)(liAux2)-0.5+DecimalHours/24.0; // Calculate difference between current Julian Day and JD 2451545.0 ElapsedJulianDays = JulianDate-2451545.0; // Calculate ecliptic coordinates (ecliptic longitude and obliquity of the // ecliptic in radians but without limiting the angle to be less than 2*Pi // (i.e., the result may be greater than 2*Pi) float MeanLongitude; float MeanAnomaly; float Omega; Omega=2.1429-0.0010394594*ElapsedJulianDays; MeanLongitude = 4.8950630+ 0.017202791698*ElapsedJulianDays; // Radians MeanAnomaly = 6.2400600+ 0.0172019699*ElapsedJulianDays; EclipticLongitude = MeanLongitude + 0.03341607*sin( MeanAnomaly ) + 0.00034894*sin( 2*MeanAnomaly )-0.0001134 -0.0000203*sin(Omega); EclipticObliquity = 0.4090928 - 6.2140e-9*ElapsedJulianDays +0.0000396*cos(Omega); // Calculate celestial coordinates ( right ascension and declination ) in radians // but without limiting the angle to be less than 2*Pi (i.e., the result may be // greater than 2*Pi) float Sin_EclipticLongitude; Sin_EclipticLongitude= sin( EclipticLongitude ); dY = cos( EclipticObliquity ) * Sin_EclipticLongitude; dX = cos( EclipticLongitude ); RightAscension = atan2( dY,dX ); if( RightAscension < 0.0 ) RightAscension = RightAscension + twopi; Declination = asin( sin( EclipticObliquity )*Sin_EclipticLongitude ); // Calculate local coordinates ( azimuth and zenith angle ) in degrees float GreenwichMeanSiderealTime; float LocalMeanSiderealTime; float LatitudeInRadians; float HourAngle; float Cos_Latitude; float Sin_Latitude; float Cos_HourAngle; GreenwichMeanSiderealTime = 6.6974243242 + 0.0657098283*ElapsedJulianDays + DecimalHours; LocalMeanSiderealTime = (GreenwichMeanSiderealTime*15 + Longitude)*rad; HourAngle = LocalMeanSiderealTime - RightAscension; LatitudeInRadians = Latitude*rad; Cos_Latitude = cos( LatitudeInRadians ); Sin_Latitude = sin( LatitudeInRadians ); Cos_HourAngle= cos( HourAngle ); ZenithAngle = (acos( Cos_Latitude*Cos_HourAngle *cos(Declination) + sin( Declination )*Sin_Latitude)); dY = -sin( HourAngle ); dX = tan( Declination )*Cos_Latitude - Sin_Latitude*Cos_HourAngle; Azimuth = atan2( dY, dX ); if ( Azimuth < 0.0 ) Azimuth = Azimuth + twopi; Azimuth = Azimuth/rad; // Parallax Correction Parallax=(EarthMeanRadius/AstronomicalUnit) *sin(ZenithAngle); ZenithAngle=(ZenithAngle //Zenith angle is from the top of the visible sky (thanks breaksbassbleeps) + Parallax)/rad; ElevationAngle = (90-ZenithAngle); //Retrieve useful elevation angle from Zenith angle } |

To be continued…