Solar tracker using linear actuators

Hi all,
Not sure if this is the best category to post this project as I'm not really looking for guidance, more so just sharing with the community. If there is somewhere more appropriate please let me know so I can delete this one and repost under the correct category.

Anyway... I've been plucking away at this for some time and have it all working pretty well.

It uses LDR's to track the sun, it has manual control mode, it uses POT's to monitor and limit travel on the axii (linear actuators), it has an RGB LED as an indicator to the current status the tracker is in and at the moment it has a POT to simulate wind strength. This wind potentiometer would need to be replaced with a proper wind speed monitor (anemometer) if one was to build a full size tracker. For now the wind pot is merely there for testing of safety features. If the wind is to strong the tracker will move into what I've called "lie flat mode" to minimize surface area against strong wind.
During low light or night time the tracker will also move into lie flat mode.

Although my test rig is crudely made, it has been tested and seems to preform as desired.

I currently have the code in different tabs on the arduino IDE so it shouldn't matter what order it's pieced together in as long as you have the main code first.

Main Sketch

/* Dual axis solar tracker using 12v DC motor linier actuators,
4 LDR's, 4 limit switches, a wind sensor and manual control 
joystick */


//LDR 
const int LeftTopLDR = A0;
const int RightTopLDR = A1;
const int LeftBottomLDR = A2;
const int RightBottomLDR = A3;
int lt = 0;
int rt = 0;
int lb = 0;
int rb = 0;
int avt = 0;
int avb = 0;
int avl = 0;
int avr = 0;
int dvert = 0;
int dhoriz = 0;
int tolerance = 1;
const int minLight = 500;
boolean isLightOK = true;

//MOTOR DRIVER 
const int AIN1 = 4;
const int AIN2 = 5;
const int APWM = 6;
const int BIN1 = 7;
const int BIN2 = 8;
const int BPWM = 9;
const int StandBy = 10;
boolean motorAstate = true;
boolean motorBstate = true;

//LED
const int redLEDpin = 3;
const int greenLEDpin = 12;
const int blueLEDpin = 11;
int slowFlash = 3000;
int displace = 500;
int value;
long time = 0;

//WIND SENSOR
boolean isWindOK = true;
const int windSensorPin = A6;
const int maxWind = 600;
const int numReadings = 20;
int windReadings[numReadings];
int windIndex = 0;
int windTotal = 0;
int windAverage = 0;
unsigned long startWindMillis = 0;
unsigned long prevWindMillis = 0;
unsigned long windInterval = 1000;

//POTENTIOMETERS
const int vertPotPin = A4;
const int horizPotPin = A5;
const int vertPotMax = 510;
const int vertPotMin = 300;
const int horizPotMax = 750;
const int horizPotMin = 300;
const int horizPotMid = 510;
const int deadBand = 10;
int vertPotVal = 0;
int horizPotVal = 0;

//ALIGNMENT VARIABLES
unsigned long startAlignMillis = 0;
unsigned long prevAlignMillis = 0;
unsigned long alignedInterval = 5000;
boolean isAlignFirstLoop = true;

//LIE FLAT VARIABLES
unsigned long startLieFlatMillis = 0;
unsigned long prevLieFlatMillis = 0;
unsigned long lieFlatInterval = 5000;
boolean lieFlatTrigger = false;
boolean isLieFlatFirstLoop = true;
boolean comingFromAligned = true;

//MANUAL CONTROL VARIABLES
boolean manualControlSwitch;
const int buttonPin = 22;    // the number of the pushbutton pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin
unsigned long startDebounceTime = 0;
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 30;    // the debounce time; increase if the output flickers
const int ledPin = 13; 
int ledState = LOW;
const int joyVertPin = A8;
const int joyHorizPin = A9;
int joyVertVal = 0;
int joyHorizVal = 0;
int joyVertMidPoint = 127;
int joyHorizMidPoint = 113;
int joyDeadZone = 10;
int buttonReading = 0;


void setup()
{
Serial.begin(9600);
   
// INITIALIZE WIND SENSOR TO 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
    windReadings[thisReading] = 0;
    
// SET PIN MODES FOR MANUAL CONTROL.
    pinMode(buttonPin, INPUT_PULLUP);
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, ledState);
    
// SET PIN MODES FOR LEDS    
    pinMode(redLEDpin, OUTPUT);
    pinMode(greenLEDpin, OUTPUT);
    pinMode(blueLEDpin, OUTPUT);
    digitalWrite(blueLEDpin, HIGH);
    digitalWrite(greenLEDpin, HIGH);
    digitalWrite(redLEDpin, LOW);
    delay(4000);
    digitalWrite(redLEDpin, HIGH);

// SET PIN MODES FOR MOTOR DRIVER    
    pinMode(AIN1, OUTPUT);
    pinMode(AIN2, OUTPUT);
    pinMode(BIN1, OUTPUT);
    pinMode(BIN2, OUTPUT);
    
    pinMode(APWM, OUTPUT);
    pinMode(BPWM, OUTPUT);
    pinMode(StandBy, OUTPUT);
    
    digitalWrite(AIN1, LOW);
    digitalWrite(AIN2, LOW);
    digitalWrite(BIN1, LOW);
    digitalWrite(BIN2, LOW);
    
    digitalWrite(APWM, LOW);
    digitalWrite(BPWM, LOW);
    digitalWrite(StandBy, LOW);
    
}
void loop(){

  CheckPots();
  
// CHECK BOTTON STATE FOR MANUAL CONTROL AND SWITCH ACCORDINGLY  
  startDebounceTime = millis();
  buttonReading = digitalRead(buttonPin);

  if(buttonReading != lastButtonState) {
     lastDebounceTime = startDebounceTime;
  } 
  
  if((unsigned long)startDebounceTime - lastDebounceTime >= debounceDelay) {
     if(buttonReading != buttonState) {
        buttonState = buttonReading;

        if(buttonState == LOW) {
           manualControlSwitch = !manualControlSwitch;
        }
     }
  }
  digitalWrite(ledPin, ledState);
  lastButtonState = buttonReading;      
  
  if(manualControlSwitch == true){
     digitalWrite(greenLEDpin, HIGH);             //  FADE BLUE LED OFF AND ON.
     digitalWrite(redLEDpin, HIGH); 
     time = millis();
     value = 128+127*cos(2*PI/slowFlash*time);
     analogWrite(blueLEDpin, value);
     ManualControl();
    // Serial.println("  ManualControl ");
  }                                               
  //else{                                         
     CheckLight();                                //IF MANUAL CONTROL NOT ACTIVE CHECK WIND AND LIGHT CONDITIONS:
     CheckWind();
  //}
  
  if((lieFlatTrigger == true) && (manualControlSwitch == false)){
     LieFlat();                                   //MOVE PANELS SO THEY ARE FLAT AND LEVEL FOR 
            //  Serial.println("  Lie Flat ");    //ETHER HIGH WINDS OR LOW LIGHT (NIGHT TIME).
  }
  else if((isLightOK == true) && (isWindOK == true) && (lieFlatTrigger == false) && (manualControlSwitch == false)){
     UpdatePosition();                                  //ONLY WHEN ALL CONDITIONS ARE 
            //  Serial.println("   UpdatePosition  ");  //MET IS IT OK TO UPDATE POSITION. 
  }
}

Checklight

void CheckLight(){
  
  lt = analogRead(LeftTopLDR);          // READ AND CALCULATE LIGHT SENSORS
  rt = analogRead(RightTopLDR); 
  lb = analogRead(LeftBottomLDR); 
  rb = analogRead(RightBottomLDR);
  
  avt = (lt + rt) / 2; // average value top
  avb = (lb + rb) / 2; // average value bottom
  avl = (lt + lb) / 2; // average value left
  avr = (rt + rb) / 2; // average value right
  dvert = avt - avb; // calculate the diffirence between the top and bottom
  dhoriz = avl - avr;// calculate the diffirence between the left and rigt
  
  int aveLight = (lt + rt + lb + rb) / 4;//GET THE AVERAGE LIGHT READING FROM THE LDR's
Serial.print("  Average Light ");
Serial.println(aveLight);
  
  if(aveLight >= minLight){        // IF THERE IS ENOUGH LIGHT
     isLightOK = true;
  }
  else{
     isLightOK = false;
     lieFlatTrigger = true;           // SETS THE CONDITION FOR THE LIE FLAT FUNCTION 
  }
}

CheckPots

// POTENTIOMETERS ATTACHED TO EACH AXIS ARE ABLE TO KEEP TACK OF EXACT POSITION OF AXII

void CheckPots(){
    
  vertPotVal = analogRead(vertPotPin);
  horizPotVal = analogRead(horizPotPin);
  Serial.print("  vertPotVal   ");
  Serial.print(vertPotVal);
  Serial.print("  horizPotVal   ");
  Serial.print(horizPotVal);
}

CheckWind

void CheckWind(){
   
     startWindMillis = millis();
     
   if((unsigned long)startWindMillis - prevWindMillis >= windInterval){
//      Serial.println(" Ping!!!  ");
      windReadings[windIndex] = analogRead(windSensorPin);
      windTotal = windTotal + windReadings[windIndex];
      windIndex = windIndex + 1;
     
      if(windIndex >= numReadings){
         windAverage = windTotal / numReadings;
       //  Serial.print(" AverageWind  ");
         //Serial.println(windAverage);
        
         if(windAverage <= maxWind){                     // IF THE WIND IS NOT TO STRONG
            isWindOK = true;
         }
         else{
            isWindOK = false;
            lieFlatTrigger = true;       // SETS THE CONDITION FOR THE LIE FLAT FUNCTION 
         }
         windTotal = 0;
         windIndex = 0;
      }
      prevWindMillis = startWindMillis;
   } 
}

LieFlat

void LieFlat(){

     startLieFlatMillis = millis();
     
  if(comingFromAligned == true){
     motorAstate = true;
     motorBstate = true;
     comingFromAligned = false;
  }
     
  if((motorAstate == true) || (motorBstate == true)){
     CheckPots(); 
     digitalWrite(redLEDpin, LOW);                        // POSITION TURN RED LED ON
     digitalWrite(greenLEDpin, HIGH);
     digitalWrite(blueLEDpin, HIGH); 
     digitalWrite(StandBy, HIGH);
     analogWrite(APWM, 255);
     analogWrite(BPWM, 255);
     
        if(vertPotVal <= vertPotMax){
          digitalWrite(AIN1, HIGH);       //MOVE VERTICAL MOTOR ACTUATOR TO THE TOP
          digitalWrite(AIN2, LOW);
          
       }
       else{
          digitalWrite(AIN1, LOW);         
          digitalWrite(AIN2, LOW);
          digitalWrite(APWM, LOW);
          motorAstate = false;              // AND TURN OFF MOTOR ONCE AT VERTPOTMAX 
       }

                                         //MOVE HORIZONTAL MOTOR ACTUATOR TO THE CENTER      
       if(horizPotVal <= horizPotMid - deadBand){
          digitalWrite(BIN1, HIGH);
          digitalWrite(BIN2, LOW);
          
       }
       else if(horizPotVal >= horizPotMid + deadBand){ 
          digitalWrite(BIN1, LOW);
          digitalWrite(BIN2, HIGH);
          
       }
       else{
          digitalWrite(BIN1, LOW);
          digitalWrite(BIN2, LOW);
          digitalWrite(BPWM, LOW);
          motorBstate = false;   
       }
  }
    
  if((motorAstate == false) && (motorBstate == false)){  // ONLY IF BOTH CONDITIONS ARE TRUE THEN
     digitalWrite(StandBy, LOW);                    //TURN OFF MOTOR DRIVER
     digitalWrite(greenLEDpin, HIGH);             //  FADE RED LED OFF AND ON.
     digitalWrite(blueLEDpin, HIGH); 
     time = millis();
     value = 128+127*cos(2*PI/slowFlash*time);
     analogWrite(redLEDpin, value);
    // Serial.print("   Lying Flat Now ");
          
     if(isLieFlatFirstLoop == true){          //RESET PREVIOUS LIE FLAT MILLIS 
        prevLieFlatMillis = startLieFlatMillis;
        isLieFlatFirstLoop = false;
     }
                                  //TIMER TO WAIT FOR LIE FLAT INTERVAL THEN RESET FLAGS 
     if((unsigned long)startLieFlatMillis - prevLieFlatMillis >= lieFlatInterval){
        lieFlatTrigger = false;
        motorAstate = true; 
        motorBstate = true;
    //    Serial.print("   PING !!!!!!!!!!!!!          ");
        isLieFlatFirstLoop = true;
     }
  }
}

ManualControl

void ManualControl(){

   joyVertVal = analogRead(joyVertPin);
   joyHorizVal = analogRead(joyHorizPin);
   
   joyVertVal = map(joyVertVal, 0, 1023, 0, 255);
   joyHorizVal = map(joyHorizVal, 0, 1023, 0, 225);

  /* Serial.print(" joyVertVal  ");
   Serial.print(joyVertVal);
   Serial.print("      joyHorizVal  ");
   Serial.println(joyHorizVal);*/

   digitalWrite(StandBy, HIGH);

   if(joyVertVal >= joyVertMidPoint + joyDeadZone){
      if(!PositionStopDown()){ 
         digitalWrite(AIN1, HIGH);       //MOVE VERTICAL MOTOR ACTUATOR TO THE BOTTOM
         digitalWrite(AIN2, LOW);
         joyVertVal = constrain(joyVertVal, 0, 255);
         analogWrite(APWM, joyVertVal);
      }
   }
   else if(joyVertVal <= joyVertMidPoint - joyDeadZone){
      if(!PositionStopUp()){
         digitalWrite(AIN1, LOW);       //MOVE VERTICAL MOTOR ACTUATOR TO THE TOP
         digitalWrite(AIN2, HIGH);
         joyVertVal = constrain(joyVertVal, 1, 225);
         analogWrite(APWM, -joyVertVal);
      }
   }
   else{
      StopVert();
      digitalWrite(APWM, LOW);
   }
//=============================================//

   if(joyHorizVal >= joyHorizMidPoint + joyDeadZone){
      if(!PositionStopRight()){
         digitalWrite(BIN1, HIGH);                    //MOVE HORIZONTAL ACTUATOR RIGHT
         digitalWrite(BIN2, LOW);
         joyHorizVal = constrain(joyHorizVal, 0, 255);
         analogWrite(BPWM, joyHorizVal); 
      }   
   }
   else if(joyHorizVal <= joyHorizMidPoint - joyDeadZone){ 
      if(!PositionStopLeft()){
         digitalWrite(BIN1, LOW);                    //MOVE HORIZONTAL ACTUATOR LEFT
         digitalWrite(BIN2, HIGH);
         joyHorizVal = constrain(joyHorizVal, 1, 255);
         analogWrite(BPWM, -joyHorizVal);  
      }  
   }
   else{
      StopHoriz();
      digitalWrite(BPWM, LOW);
   }
}

PositionStops

/* IF HORIZPOT REACHES MIN 235 OR MAX 700 STOP MOTOR"B" FROM DRIVING PAST MIN OR MAX BUT ALLOW MOTOR TO DRIVE BACK THE OPPOSITE WAY
 * IF VERTPOT REACHES MIN 285 OR MAX 555 DO SAME AS ABOVE FOR MOTOR"A" 
 */
bool PositionStopUp(){
   if(vertPotVal <= 285){
      digitalWrite(AIN1, LOW);
      digitalWrite(AIN2, LOW);
      return true;
   }
   return false;
}

bool PositionStopDown(){
   if(vertPotVal >= 555){
      digitalWrite(AIN1, LOW);
      digitalWrite(AIN2, LOW);
      return true;
   }
   return false;
}

bool PositionStopLeft(){
   if(horizPotVal <= 235){
      digitalWrite(BIN1, LOW);
      digitalWrite(BIN2, LOW);
      return true;
   }
   return false;
}

bool PositionStopRight(){
   if(horizPotVal >= 700){
      digitalWrite(BIN1, LOW);
      digitalWrite(BIN2, LOW);
      return true;
   }
   return false;
}

void StopVert(){
   digitalWrite(AIN1, LOW);
   digitalWrite(AIN2, LOW);
}

void StopHoriz(){
   digitalWrite(BIN1, LOW);
   digitalWrite(BIN2, LOW);
}

UpdatePosition

void UpdatePosition(){
   
   startAlignMillis = millis();

  if((motorAstate == true) || (motorBstate == true)){   
     digitalWrite(redLEDpin, HIGH);                    //WHEN MOVING INTO ALIGNMENT TURN
     digitalWrite(blueLEDpin, LOW);                   // BLUE LED ON
     digitalWrite(greenLEDpin, HIGH);
  }
  
  if(motorAstate == true){ 
// CHECK IF THE DIFFERENCE IS WITHIN THE TOLERANCE ELSE CHANGE THE VERTICAL ANGLE  
     if(-1*tolerance > dvert || dvert > tolerance){
        digitalWrite(StandBy, HIGH);
        analogWrite(APWM, 200);
      
        if(avt > avb){                 //DRIVE MOTOR DOWN
           if(!PositionStopDown()){
              digitalWrite(AIN1, HIGH);
              digitalWrite(AIN2, LOW);
           }
        }
        else if(avt < avb){            //DRIVE MOTOR UP
           if(!PositionStopUp()){
              digitalWrite(AIN1, LOW);
              digitalWrite(AIN2, HIGH);
           }
        }
     }
     else{                   // IF THE DIFFERENCE IS WITHIN THE TOLERANCE TURN OFF MOTOR
        StopVert();
        digitalWrite(APWM, LOW);
        motorAstate = false;
     }
  }

  if(motorBstate == true){  
// CHECK IF THE DIFFERENCE IS WITHIN THE TOLERANCE ELSE CHANGE THE HORIZONTAL ANGLE 
     if(-1*tolerance > dhoriz || dhoriz > tolerance){
        digitalWrite(StandBy, HIGH);
        analogWrite(BPWM, 200);
    
        if(avl > avr){                      //DRIVE MOTOR RIGTH
           if(!PositionStopRight()){
              digitalWrite(BIN1, HIGH);
              digitalWrite(BIN2, LOW);
           }
        }
        else if(avl < avr){                  //DRIVE MOTOR LEFT
           if(!PositionStopLeft()){
              digitalWrite(BIN1, LOW);
              digitalWrite(BIN2, HIGH);
           }
        }
     }
     else{                    // IF THE DIFFERENCE IS WITHIN THE TOLERANCE TURN OFF MOTOR
        StopHoriz();
        digitalWrite(BPWM, LOW);
        motorBstate = false;
     }
  }

  if((motorAstate == false) && (motorBstate == false)){ 
 // WHEN ALINDED FADE GREEN LED ON AND OFF. TURN OFF MOTOR DRIVER FOR THE alingnedInterval
     digitalWrite(redLEDpin, HIGH);
     digitalWrite(blueLEDpin, HIGH);
     time = millis();                            
     value = 128+127*cos(2*PI/slowFlash*time);
     analogWrite(greenLEDpin, value); 
     //Serial.print("   Aligned  ");
     digitalWrite(StandBy, LOW);    
     comingFromAligned = true;
  
     if(isAlignFirstLoop == true){          //RESET THE TIMER
        prevAlignMillis = startAlignMillis;
        isAlignFirstLoop = false;
     }
     
     if((unsigned long)startAlignMillis - prevAlignMillis >= alignedInterval){    
        motorAstate = true; //ONCE THE ALIGNED INTERVAL HAS FINISHED SET MOTOR STATES TO HIGH
        motorBstate = true;
        isAlignFirstLoop = true;    //SET PREVMILLIS TO CURRENTMILLIS
     //   Serial.print("   UPDATE PING !!!!!!!!!!!!!          ");
     }
   }
}

Under Community there is a section for Exhibitions/ Galleries which is where your post should go.

I have not studied you post post in detail but the "lie flat mode" for strong wind caught my eye.
Does the system really try to lie flat? I would have thought that would be risky. Think of holding a sheet of cardboard flat in a high wind, when the wind gets under it it will give it a sudden violent twist. Better to face the board into the wind at a bit of an angle so that the thrust from the wind forces the board down.

ardly:
Under Community there is a section for Exhibitions/ Galleries which is where your post should go.

I have not studied you post post in detail but the "lie flat mode" for strong wind caught my eye.
Does the system really try to lie flat? I would have thought that would be risky. Think of holding a sheet of cardboard flat in a high wind, when the wind gets under it it will give it a sudden violent twist. Better to face the board into the wind at a bit of an angle so that the thrust from the wind forces the board down.

To add to that concern, most strong winds in our area, not in the winter, are part of a thunder storm with large hail. Vertical and an angle to the wind would be best. The solar energy sites around Central Oregon are on fixed frames and just factor in replacement pieces when damaged by storms.

Paul

Hey there,
your project seems awesome and i would like to implement it in my current project.
actually i am designing and fabricating low cost solar concentrators for the rural areas of India at university of rajasthan.
the project pic you attached from fritzing was not full explainatory to me as i am not from electronic background. can you please share the fritzing project file with me at khansbox4mail@gmail.com

it will be really helpful to me.
thanks in advance.
regards
Saquib

The Mega is not powered correctly.

The sensors are powered from an unspecified voltage regulator (assuming LM7805, 5volt).
And the Mega is powered on V-in (wrong).
Undervolting the Mega (~4volt), and potentially dangerous for the I/O lines.

No decoupling caps on the LM7805 is another beginners mistake.

I also don't see any pull down resistors on the LDRs.
LDR wiring could have been 'safer' with LDRs between pin and ground, and pull up resistors.
Leo..

VERY NICE post. thanks for sharing.
would you be able to post the schematic view from fritzring ?

Wawa:
The Mega is not powered correctly.

The sensors are powered from an unspecified voltage regulator (assuming LM7805, 5volt).
And the Mega is powered on V-in (wrong).
Undervolting the Mega (~4volt), and potentially dangerous for the I/O lines.

No decoupling caps on the LM7805 is another beginners mistake.

I also don't see any pull down resistors on the LDRs.
LDR wiring could have been 'safer' with LDRs between pin and ground, and pull up resistors.
Leo..

Will you please you help me with the appropriate corrections in the above circuit

I think the author should correct his own work. Try to PM him.

All sensors must be powered from the Mega's 5volt pin(s) for starters.
The motor must have it's own supply, and that depends on the motors used.
The LM7805?? might not be needed, but that depends on (unspecified) supply/battery voltage.
The Mega can be powered with 7.5-12volt on the DC socket.

Don't start this project unprepared.
Make sure you understand, and have worked with pots, LDRs, joysticks, and motor drivers separately.
Leo..