Introduction: Solar Clock / Solar Tracker

Picture of Solar Clock / Solar Tracker

This instructable was created in fulfillment of the project requirement of the Makecourse at the University of South Florida (www.makecourse.com)

This device is a solar powered clock. Its aim was to provide a nice desk piece that was self sufficient, and accurate. There are two motors that control the motion of the panel. The device is able to connect to the internet via an ESP8266, where it can update the time and location information. The arduino microcontroller then calculates the position of the sun, and guides the panel to its location for maximum efficiency.

Since there are many different features on this device, I will focus on time keeping and the solar tracking algorithm, and separate them into their respective functions. All of these functions can be implemented with an arduino and ESP8266, or solely an ESP8266.

Parts List:

  • Arduino UNO
  • Various jumper wires/breadboards/passive components
  • Adafruit LSM303
  • PCF8574 I2C Expanders
  • (2) Adafruit small Reduction Stepper Motor - 5VDC 32-Step 1/16 Gearing
    PRODUCT ID: 858
  • 7.4V rechargable batteries
  • (2) ULN2003 Motor Drivers\
  • Brain

Step 1: The Circuit

Picture of The Circuit

The first image shows the general setup for the arduino and ESP8266. You want to ensure that the ESP8266 has its own 3.3V power source, as the arduino can not supply enough current for stable operation.

Since the arduino has a limited number of pins, the PFC8574A I2C GPIO expansion ICs will be used as shown in the second image. These will interface with the second breadboard in the first image. A detailed schematic with their connection is shown in the third image. The Adafruit LSM is also used for solar cell position feedback

Step 2: 3D Printing

Picture of 3D Printing

All of the 3D printer models snap together, and thus, no screws are required. Most of the tolerances are quite low. This was by design, as shaving pieces until they fit perfectly ensured no loose ends. The bearing shown on the shaft has a half inch inner diameter and a 1.125 inch outer diameter. This bearing was one i had laying around. To implement, you will need to purchase one with these dimensions.

All parts printed with no supports, with exception to the shaft, which needs a large raft to ensure it does not tip during printing.

<= .1mm layer height with heavy infill for the smaller pieces.

.2-.3mm layer height for the larger pieces.

Step 3: ESP8266 Code

The code is split into pieces, allowing you to use partial features. First is the ESP8266 code.

#include "ESP8266WiFi.h"

const char* ssid = "YOUR SSID GOES HERE"; //2.4GHz wifi typical, unless your ESP is better const char* password = "YOUR PASSWORD GOES HERE"; // const char* host = "utcnist2.colorado.edu"; //Alternate const char* host = "time.nist.gov"; //Host we are accessing for time

int a = 0;

String hours = ""; String minutes = ""; String seconds = "";

void setup() {

Serial.begin(9600);

delay(10);

// We start by connecting to a WiFi network

// Set WiFi to station mode and disconnect from an AP if it was previously connected

WiFi.mode(WIFI_STA);

WiFi.disconnect();

WiFi.begin(ssid, password);

delay(100);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

}

}

void loop() {

//a insures it only runs once. typically, you

//can only request the time once per minute.

//If you are not getting a response after a few

//Successes, wait a couple of minutes.

if(a<1){

WiFiClient client;

const int httpPort = 13;

if (!client.connect(host, httpPort)) {

return;

}

// This will send the request to the server

client.print("HEAD / HTTP/1.1\r\nAccept: */*\r\nUser-Agent: Mozilla/4.0 (compatible; ESP8266 NodeMcu Lua;)\r\n\r\n");

delay(1000);

char buffer[12];

while(client.available()) {

//The date and time are stored in "dateTime"

String dateTime = client.readStringUntil('\r');

if (dateTime.indexOf("Date") != -1) {}

else{

//This sends the entire string with date and time.

//Its format is xxxxx xxxxxx xxxxxx HH:MM:SS.

//If you wish to send particular parts, I have

//Parsed them for you below. Just uncomment the Serial.prints

Serial.print(dateTime);

hours = dateTime.substring(16, 18);

//Serial.print(hours);

minutes = dateTime.substring(19, 21);

//Serial.print(minutes);

seconds = dateTime.substring(22, 24);

//Serial.print(seconds);

}

}

a = 1; //end

}

}

Step 4: Code 2: Arduino Time

These functions will recieve the time from the ESP

int Time;
int TimeDataRecieved = 0;

int PM = 0;

int Hours = 3;

int Minutes = 0;

int Seconds = 0;

int SecondsState;

int LastSecondsState;

int SyncHours = 0;

int SyncHours2 = 0;

int SyncMinutes = 0;

int SyncSeconds = 0;

int SyncCalibration = 0;

void SynchronizeTime(){

while (Serial.available() > 0) { //if the wifi chip is sending data

SyncTime = Serial.readString(); // synctime = that data

//Serial.print(SyncTime);

if ((SyncTime.length() > 45)) {

TimeDataRecieved++;

Sync = 0;

}else{

Serial.flush();

}

}

}

void UpdateTime(){

x = SyncTime.substring(16, 18); //the format is xxxxx xxxxxx xxxxxx HH:MM:SS,

y = SyncTime.substring(19, 21);

z = SyncTime.substring(22, 24);

SyncHours2 = x.toInt() - 4;

SyncMinutes = y.toInt();

SyncSeconds = z.toInt() + SyncCalibration;

if (SyncHours2 < 0) {

SyncHours2 = 12 + SyncHours2;

}

if (SyncHours > 12) {

SyncHours = SyncHours2 - 12;

PM = 1;

}

if (SyncHours2 < 12) {

PM = 0;

}

if ((SyncHours2 < 13) && (SyncHours2 > 0)) {

SyncHours = SyncHours2;

}

Hours = SyncHours;

if (Hours == 0) {

Hours = 1;

}

Minutes = SyncMinutes;

Seconds = SyncSeconds;

TimeDataRecieved = 0;

}

Step 5: Code 3: Arduino Calculate Sun Position

Calling this function will return the azimuth angle and the elevation angle. Just pass the syncronized time (already in UTC) to the time below. (Note, this code does not belong to me. Googling snippets may find the source.)

float Azimuth

float ElevationAngle

void sunPosition(){

//TIME HAS TO BE IN UT (UNIVERSAL TIME)! NO TIME ZONES OR SUMMER TIMES

int Year = 2017;

int Month = 11;

int Day = 12;

float HoursUTC = 21;

float MinutesUTC = 37;

float Longitude = -80.1059;

float Latitude = 25.4610;

float ZenithAngle;

float RightAscension;

float Declination;

float Parallax;

float ElapsedJulianDays;

float DecimalHours;

float EclipticLongitude;

float EclipticObliquity;

float JulianDate;

float dY;

float dX;

long int liAux1;

long int liAux2;

// Calculate difference in days between the current Julian Day

// and JD 2451545.0, which is noon 1 January 2000 Universal Time

DecimalHours = HoursUTC + (MinutesUTC / 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 + Parallax)/rad;

ElevationAngle = (90-ZenithAngle); //Retrieve useful elevation angle from Zenith angle

}

Step 6: Code 4: Arduino Motor Drives

calling these functions will drive the motors with the PFC expansion IC.Be sure to call "disableTilt" or "disablePan" when finish moving to make sure the motors are not stuck in an ON position.

#include "PCF8574.h"// library for an I2C expanison IC
PCF8574 GPIOMotorsP;

PCF8574 GPIOMotors;

uint8_t AddressGPIO = 0x20; // GPIO jumper 0 0 0

uint8_t AddressMotors = 0x21; // GPIO jumper 0 0 1

// Blue Pink Yellow Orange
int motorTilt[] = {1, 3, 2, 0}; // 0 0 0 0 p o y b

int motorPan[] = {3, 0, 2, 1};

//There are 2 coils per motor. motor[ Coil1+, Coil1-, Coil2+, Coil2-]

int backwardsTilt = 0; //1:up

float motorSpeed = 20; //Speed delay. lower is faster

float motorSpeedPan = 10;

float angleOffset = 0;

int jTilt = 4;

int stepsTilt = 0;

int backwardsPan = 1; // 1 left

int jPan = 4;

int stepsPan= 0;

void setup() {
GPIOMotors.begin(AddressMotors);

GPIOMotorsP.begin(AddressMotorsP);

for(int p = 0; p < 8; p++){

GPIOMotors.digitalWrite(p, LOW);

GPIOMotorsP.digitalWrite(p, LOW);

}

}

void loop() {

}

void driveTilt(){
if((millis()-motorSwitchTime) > motorSpeedDelay){

for(int i = 5; i < 13; i+=jTilt){

if(((i >> 3) & 1 == 1) == 1){

GPIOMotors.digitalWrite(motorTilt[1], HIGH);

}else{

GPIOMotors.digitalWrite(motorTilt[1], LOW);

}

if(((i >> 1) & 1 == 1) == 1){

GPIOMotors.digitalWrite(motorTilt[2*(!(2 == (2*backwardsTilt)))], HIGH);

//If backwards == 1, motorTilt[0]. else, motorTilt[2].

}else{

GPIOMotors.digitalWrite(motorTilt[2*(!(2 == (2*backwardsTilt)))], LOW);

}

if(((i >> 2) & 1 == 1) == 1){

GPIOMotors.digitalWrite(motorTilt[3], HIGH);

}else{

GPIOMotors.digitalWrite(motorTilt[3], LOW);

}

if((i & 1 == 1) == 1){

GPIOMotors.digitalWrite(motorTilt[(2*backwardsTilt)], HIGH);

//If backwards == 1, motorTilt[2]. else, motorTilt[0].

}else{

GPIOMotors.digitalWrite(motorTilt[(2*backwardsTilt)], LOW);

}

delay(motorSpeed);// Delay between sequences to set motor speed

if(i == 9){

jTilt = 1;

}

if(i == 10){

jTilt = -4;

}

if(i == 6){

i = 13;

jTilt = 4;

stepsTilt++;

}

}

motorSwitchTime = millis(); //Delay between switches

}

}

void drivePan(){

for(int i = 5; i < 13; i+=jTilt){

if(((i >> 3) & 1 == 1) == 1){

GPIOMotorsP.digitalWrite(motorPan[1], HIGH);

}else{

GPIOMotorsP.digitalWrite(motorPan[1], LOW);

}

if(((i >> 1) & 1 == 1) == 1){

GPIOMotorsP.digitalWrite(motorPan[2*(!(2 == (2*backwardsPan)))], HIGH);

//If backwards == 1, motorPan[0]. else, motorPan[2].

}else{

GPIOMotorsP.digitalWrite(motorPan[2*(!(2 == (2*backwardsPan)))], LOW);

}

if(((i >> 2) & 1 == 1) == 1){

GPIOMotorsP.digitalWrite(motorPan[3], HIGH);

}else{

GPIOMotorsP.digitalWrite(motorPan[3], LOW);

}

if((i & 1 == 1) == 1){

GPIOMotorsP.digitalWrite(motorPan[(2*backwardsPan)], HIGH);

//If backwards == 1, motorPan[2]. else, motorPan[0].

}else{

GPIOMotorsP.digitalWrite(motorPan[(2*backwardsPan)], LOW);

}

delay(motorSpeedPan);

// Delay between sequences to set motor speed

if(i == 9){

jTilt = 1;

}

if(i == 10){

jTilt = -4;

}

if(i == 6){

i = 13;

jTilt = 4;

}

}

}

void disableTilt(){

for(int i = 0; i < 8; i++){

GPIOMotors.digitalWrite(motorTilt[i], LOW);

}

}

void disablePan(){

for(int i = 0; i < 8; i++){

GPIOMotorsP.digitalWrite(motorPan[i], LOW);

}

}

Step 7: Code 5: Ardunio - Get Creative

Connecting the adafruit LSM303 accelerometer and magnometer to the I2C bus, and using adafruits built in library and functions will give you your angle with respect to Z, and the magnetic field strength.

You have your altitude angle, orientation, sun altitude, and sun azimuth. Now you can create your own algorithm to track the sun.

Since my algorithm implements solar feedback with complex circuitry, posting my code will not help. However, great accuracy comes from a simple:

while(adafruitAngle < sunElevationAngle){

backwards = 0;

driveTilt();

}

while(adafruitAngle > sunElevationAngle){
backwards = 0;

driveTilt();

}

Experiment with offsets and different algorithms to get your best results!

Step 8: Conclusion

If you are interested in replicating my EXACT project, feel free to email me at: evalonso@mail.usf.edu and I will be happy to send the gerber files for both boards, code, and a complete bill of materials available to order on digikey.

I would, however, prefer to keep most of the designs private unless specifically requested.

All of the parts and components will run around $65-$100 depending on certain options, solar cells, and extras outlined in the BOM.

Comments

BrownDogGadgets (author)2017-12-06

Why did you choose motors over servos?

Swansong (author)2017-12-06

That's a fun design, I hope you get an A on your project! :)

About This Instructable

295views

7favorites

License:

More by evalonso:Solar Clock / Solar Tracker
Add instructable to: