Can someone help me in creating the program for my dual axis solar tracking system




Project: Dual axis solar tracking system

The components I have:

Two 12v geared motor
One Arduino mega
One L298n motor driver
Four 1k ohm resistor
Four LDR sensor
One 10v power supply

Here is my ESP32 code I used for solar tracking. Perhaps the code can serve as a model for your project or not.

/*
   Project, use solar cells to generate power
   2/2/2020

*/
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html
///esp_timer_get_time(void) //gets time in uSeconds like Arduino Micros. see above link
//////// http://www.iotsharing.com/2017/09/how-to-use-arduino-esp32-can-interface.html
#include "sdkconfig.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
// #include "esp32/ulp.h"
// #include "driver/rtc_io.h"
#include <SimpleKalmanFilter.h>
#include <driver/adc.h>
#include "esp_sleep.h"
#include "driver/mcpwm.h"
const int TaskCore1 = 1;
const int TaskCore0 = 0;
const int TaskStack20K = 20000;
const int Priority3 = 3;
const int Priority4 = 4;
const int SerialDataBits = 115200;
volatile bool EnableTracking = true;
////
//
////
void setup()
{
  Serial.begin( 115200 );
  // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // set up A:D channels
  log_i( "Setup A:D" );
  adc_power_on( );
  vTaskDelay( 1 );
  adc1_config_width(ADC_WIDTH_12Bit);
  // ADC1 channel 0 is GPIO36 (ESP32), GPIO1 (ESP32-S2)
  adc1_config_channel_atten(ADC1_CHANNEL_0 , ADC_ATTEN_DB_11); // azimuth 0
  //  // ADC1_CHANNEL_3  ADC1 channel 3 is GPIO39 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11); // azimuth 1
  //  // ADC1 channel 5 is GPIO33
  adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11); // altitude 1
  //  // ADC1 channel 6 is GPIO34
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); // altitude 0
  //  // adc for light dark detection, ADC1 channel 7 is GPIO35 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11);
  // adc for solar cell voltage detection, ADC1 channel 4 is GPIO33 (ESP32)
  adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11);
  //
  log_i( "A:D setup complete" );
  // control for relay to supply power to the servos
  pinMode( 5, OUTPUT );
  vTaskDelay(1);
  log_i( "setup MCPWM" );
  //
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 ); // Azimuth
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 ); // Altitude servo
  //
  mcpwm_config_t pwm_config = {};
  pwm_config.frequency = 50;    //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
  pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0
  pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0
  pwm_config.counter_mode = MCPWM_UP_COUNTER;
  pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
  log_i( "MCPWM complete" );
  //
  //
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A timer 0 with above settings
  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);    //Configure PWM0A timer 1 with above settings
  //  ////
  fMoveAltitude( 1525 );
  fMoveAzimuth( 1500 );
  ////
xTaskCreatePinnedToCore( TrackSun, "TrackSun", TaskStack20K, NULL, Priority3, NULL, TaskCore1 ); // assigned to core
 xTaskCreatePinnedToCore( fDaylight, "fDaylight", TaskStack20K, NULL, Priority3, NULL, TaskCore0 ); // assigned to core
  ////  // esp_sleep_enable_timer_wakeup( (60000000 * 2) ); // set timer to wake up once a minute 60000000uS * 2

}
//
void fDaylight( void * pvParameters )
{
  int lightLevel = 0;
  log_i( "Start up fDaylight" );
  while (1)
  {
    // sufficent voltage at the solar cells to do the thing?
    log_i( "Solar Cell: %d", adc1_get_raw(ADC1_CHANNEL_4) );
    if (adc1_get_raw(ADC1_CHANNEL_4) > 1600 )
    {
      lightLevel = adc1_get_raw(ADC1_CHANNEL_7);
      if ( adc1_get_raw(ADC1_CHANNEL_7) >= 300 )
      {
        log_i( "Light Level: %d", lightLevel );
        EnableTracking = true;
        digitalWrite( 5, LOW ); // power to relay module/servos
        vTaskDelay( 1 );
      } else {
        // if light level to low park boom
        fMoveAltitude( 1475 );
        fMoveAzimuth( 1900 );
        //     put esp32 into deep sleep
        digitalWrite( 5, HIGH ); // deenergize servo motors
        EnableTracking = false;
        esp_sleep_enable_timer_wakeup( (60000000 * 5) ); // set timer to wake up once a minute 60000000uS
        esp_deep_sleep_start();
      }
    } else {
              digitalWrite( 5, HIGH ); // deenergize servo motors
        EnableTracking = false;
        esp_sleep_enable_timer_wakeup( (60000000 * 5) ); // set timer to wake up once a minute 60000000uS
        esp_deep_sleep_start();
    }
    vTaskDelay( 1000 );
  } // while(1)
} // void fDaylight( void * pvParameters )
////
/**
   @brief Use this function to calcute pulse width for per degree rotation
   @param  degree_of_rotation the angle in degree to which servo has to rotate
   @return
       - calculated pulse width
*/
//static uint32_t servo_per_degree_init(uint32_t degree_of_rotation)
//{
//  const int SERVO_MIN_PULSEWIDTH = 500; //Minimum pulse width in microsecond
//const int SERVO_MAX_PULSEWIDTH 2500 //Maximum pulse width in microsecond
//const int SERVO_MAX_DEGREE 180 //Maximum angle in degree upto which servo can rotate
//  uint32_t cal_pulsewidth = 0;
//  cal_pulsewidth = (SERVO_MIN_PULSEWIDTH + (((SERVO_MAX_PULSEWIDTH - SERVO_MIN_PULSEWIDTH) * (degree_of_rotation)) / (SERVO_MAX_DEGREE)));
//  return cal_pulsewidth;
//}
////
void mcpwm_gpio_initialize(void)
{
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_NUM_4 );
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_NUM_12 );
}
////
// void TrackSun( int Altitude, int Azimuth )
void TrackSun( void * pvParameters )
{
  log_i( "Startup TrackSun" );
  int64_t EndTime = esp_timer_get_time();
  int64_t StartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros
  int Altitude = 1500;
  int Azimuth = 900;
  int maxAltitudeRange = 2144;
  int minAltitudeRange = 900;
  int maxAzimuthRange = 2144;
  int minAzimuthRange = 900;
  float AltitudeThreashold = 10.0f;
  float AzimuthThreashold = 10.0f;
  float kalmanThreshold = 80.0f;
  SimpleKalmanFilter kfAltitude0( AltitudeThreshold, kalmanThreshold, .01 ); // kalman filter Altitude 0
  SimpleKalmanFilter kfAltitude1( AltitudeThreshold, kalmanThreshold, .01 ); // kalman filter Altitude 1
  SimpleKalmanFilter kfAzimuth0( AzimuthThreshold, kalmanThreshold, .01 ); // kalman filter Azimuth 0
  SimpleKalmanFilter kfAzimuth1( AzimuthThreshold, kalmanThreshold, .01 ); // kalman filter Azimuth 1
  float filteredAltitude_0 = 0.0f;
  float filteredAltitude_1 = 0.0f;
  float filteredAzimuth_0 = 0.0f;
  float filteredAzimuth_1 = 0.0f;
  uint64_t AzimuthEndTime = esp_timer_get_time();
  uint64_t AzimuthStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,but rolls over after 207 years
  uint64_t AltitudeEndTime = esp_timer_get_time();
  uint64_t AltitudeStartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros,
  int StartupCount = 0;
  int TorqueAmmount = 5;
  while (1)
  {

    if ( EnableTracking )
    {
      //Altitude
      AltitudeEndTime = esp_timer_get_time() - AltitudeStartTime; // produce elapsed time for the simpleKalmanFilter
      kfAltitude0.setProcessNoise( float(AltitudeEndTime) / 1000000.0f );
      kfAltitude1.setProcessNoise( float(AltitudeEndTime) / 1000000.0f );
      // Serial.println( AltitudeEndTime,6 );
      filteredAltitude_0 = kfAltitude0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_6) );
      filteredAltitude_1 = kfAltitude1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_5) );
      //      Serial.print( filteredAltitude_0 );
      //      Serial.print( ", " );
      //      Serial.print( filteredAltitude_1 );
      //      Serial.print( ", " );
      //      Serial.println();
      if ( (filteredAltitude_0 > filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreshold))
      {
        Altitude -= TorqueAmmount;
        // log_i( "> Alt %d", Altitude );
        if ( Altitude < minAltitudeRange )
        {
          Altitude = 900;
        }
        fMoveAltitude( Altitude );
        AltitudeStartTime = esp_timer_get_time();
      }
      if ( (filteredAltitude_0 < filteredAltitude_1) && (abs(filteredAltitude_0 - filteredAltitude_1) > AltitudeThreshold) )
      {
        Altitude += TorqueAmmount;
        if ( Altitude >= maxAltitudeRange )
        {
          Altitude = 900;
        }
        fMoveAltitude( Altitude );
        // log_i( "< Alt %d", Altitude );
        AltitudeStartTime = esp_timer_get_time();
      }
      //// AZIMUTH
      AzimuthEndTime = esp_timer_get_time() - AzimuthStartTime; // produce elasped time for the simpleKalmanFilter
      //      Serial.print( (float)adc1_get_raw(ADC1_CHANNEL_3) );
      //      Serial.print( "," );
      //      Serial.print( (float)adc1_get_raw(ADC1_CHANNEL_0) );
      //      Serial.print( "," );
      kfAzimuth0.setProcessNoise( float(AzimuthEndTime) / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      kfAzimuth1.setProcessNoise( float(AzimuthEndTime) / 1000000.0f ); //convert time of process to uS, update SimpleKalmanFilter q
      filteredAzimuth_0 = kfAzimuth0.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_3) );
      filteredAzimuth_1 = kfAzimuth1.updateEstimate( (float)adc1_get_raw(ADC1_CHANNEL_0) );
      //      Serial.print( filteredAzimuth_0 );
      //      Serial.print( ", " );
      //      Serial.print( filteredAzimuth_1 );
      //      Serial.println();
      if ( (filteredAzimuth_0 > filteredAzimuth_1) && (abs(filteredAzimuth_0 - filteredAzimuth_1)) > AzimuthThreashold )
      {
        Azimuth += TorqueAmmount;
        if ( Azimuth >= maxAzimuthRange )
        {
          Azimuth = 900;
        }
        // log_i( "> Az %d", Azimuth );
        fMoveAzimuth( Azimuth );
        AzimuthStartTime = esp_timer_get_time();
      }
      //    //
      if ( (filteredAzimuth_0 < filteredAzimuth_1) && (abs(filteredAzimuth_1 - filteredAzimuth_0)) > AzimuthThreashold )
      {
        // log_i( "< Az %d", Azimuth );
        Azimuth -= TorqueAmmount; // make new position to torque to
        if ( (Azimuth >= maxAzimuthRange) || (Azimuth <= minAzimuthRange) )
        {
          Azimuth = 900;
        }
        fMoveAzimuth( Azimuth );
        AzimuthStartTime = esp_timer_get_time();
      } //azmiuth end
      if ( StartupCount < 500 )
      {
        ++StartupCount;
      } else {
        TorqueAmmount = 1;
        // esp_sleep_enable_timer_wakeup( (60000000 / 30) ); // set timer to wake up
        // esp_sleep_enable_timer_wakeup( (60000000 / 20) ); // set timer to wake up
        // esp_light_sleep_start();
      }
    }
     vTaskDelay( 65 );
  } // while(1)
} //void TrackSun()
////
void fMoveAltitude( int MoveTo )
{
  mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MoveTo);
  vTaskDelay( 12 );
}
//////
void fMoveAzimuth( int MoveTo )
{
  mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A, MoveTo);
  vTaskDelay( 12 );
}
////
void loop() {}
////
1 Like

Can you send me the schematics of your project please

hazernestmillet@gmail.com

@haz_ernest

Your post was MOVED to its current location as it is more suitable.

Could you also take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum in the future.

would you mind describing your sensors?

Osram BPW34 Photodiode PIN Chip, 850 nm, 0.62A/W, Sensitivity, 2-Pin

do you simply have these mounted on a flat surface or are the oriented is some specific way?

Put opaque white baffles between the up/down and the left/right sensors. Read the LDRs. Move the motors toward the direction with more light.

const byte UpLDRPin = A0;
const byte DownLDRPin = A1;
const byte LeftLDRPin = A2;
const byte RightLDRPin = A3;

// Pick the lowest value that actually makes the motor move
const unsigned LowerSpeedLimit = 30;

void setup()
{
  // pinMode() not used for analogRead()
}

// 1 to 255 = UP, -1 to -255 = DOWN, 0 = STOP
void UpDownMotor(int speed)
{
  // Depends on the motor driver being used.
}

// 1 to 255 = LEFT, -1 to -255 = RIGHT, 0 = STOP
void LeftRightMotor(int speed)
{
  // Depends on the motor driver being used.
}

void loop()
{
  // Can add multipliers here if the speeds never hit 255
  int upDownSpeed = analogRead(UpLDRPin) - analogRead(DownLDRPin);
  int leftRightSpeed = analogRead(LeftLDRPin) - analogRead(RightLDRPin);

  if (abs(upDownSpeed) > LowerSpeedLimit)
  {
    if (upDownSpeed > 0) // Move up
      UpDownMotor(constrain(upDownSpeed, 0, 255));
    else
      UpDownMotor(constrain(upDownSpeed, 0, -255));
  }
  else
    UpDownMotor(0); // Stop

  if (abs(leftRightSpeed) > LowerSpeedLimit)
  {
    if (leftRightSpeed > 0) // Move up
      LeftRightMotor(constrain(leftRightSpeed, 0, 255));
    else
      LeftRightMotor(constrain(leftRightSpeed, 0, -255));
  }
  else
    LeftRightMotor(0); // Stop
}

Each photodiode is mounted at the ends of the solar cells.

but how do you use the sensors to align the array?

are you simply moving the array around to maximize the output from a sensor? are the sensors angled pairs so that one has more output when the array is not aligned with the sun?

See that task.

is see a comparison between filterAltitide0 and 1 to make some direction decision. but i don't understand why either should be greater if they are simply mounted flat in the same plane

do you have a baffle between them as john wasser suggested? a diagram of how the are mounted would help

Each photo diode is mounted at the end of and level with the solar cell. The separation is the distance between the photodiodes. The blinder is the photocells mounted so that the surface of the photocell is level with the surface of the solar cell. The edge of the solar cell provides the light differential.

what is "blinder"?

blinder is the blocker of light..

how is it mounted? a tube or just a baffle?

I soldered the photocell to a small piece of perf board which is cemented to the bottom of the solar cell. The perf board allows the sun to hit the photodiode and the photodiode remains below the rim of the solar cell.

so by having the sensor "set back" the solar cell "shades" the sensor?

correct.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.