Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Shawn Murphy
Published © GPL3+

Solar Powered EV Charging with Agrivoltaics

A 3.6kW, arduino controlled, single-axis tracking, ground mount solar system with 10kW battery backup, and smart garden for agrivoltaics

IntermediateWork in progressOver 4 days2,002
Solar Powered EV Charging with Agrivoltaics

Things used in this project

Hardware components

Longi 360watt used Solar panels
×1
24 inch Hall Sensor IP66 Linear actuator
×1
Arduino Giga R1 Wifi Microcontroller
×1
Arduino Giga Display
×1
Drok Motor controller XY-160D
×1
amazon MPPT solar charge controller
×1
job box - 220x170x110 mm
×1
DIY 18650 battery pack
×1
KKSB Case and adjustable stand for Arduino Giga R1 and Display
×1

Software apps and online services

Arduino IDE
Arduino IDE
Arduino Web Editor
Arduino Web Editor
Arduino IoT Cloud
Arduino IoT Cloud
Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino

Story

Read more

Schematics

Simple Single axis Actuator control

This is the simple, non-hall sensor actuator control system. When LDR #2's raw value is above the LDR#1, then the actuators move, moving the panel system more towards the sun, increasing output.

SolarTracker Giga v1.01

5 actuator Fritzing with Giga R1 wifi and full wiring (Work in Progress)

SolarTracker Giga V1.02

Fixed the LDR sensors which were on d1,d2, moved them to the correct locations A1, A2, and added Solar MPPT and panel.

Code

Single Axis Solar tracker for 5 actuators

Arduino
Code for triggering panel movement through moving actuators based on LDR sensor. There are some averaging functions that may be implemented eventually, but are commented out. This is Arduino Cloud code, so there is also a thingproperties.h file, and a secrets file which I will not include.
/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/72fdc536-58ed-467c-82a3-170dbd1a36ee 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  int sensorValue1;
  int sensorValue2;
  int threshDiff;
  int threshDiff_PM;
  int threshold;
  bool panel_Down;
  bool panel_Stop;
  bool panel_Up;
  bool useSensor;

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"
const int Extend0 = 2;
const int Retract0 = 3;
const int Extend = 4;
const int Retract = 5;
const int Extend1 = 6;
const int Retract1 = 7;
const int Extend2 = 8;
const int Retract2 = 9;
const int Extend3 = 10;
const int Retract3 =11;


int sensorPin1 = A1;
int sensorPin2 = A2;



int counter = 0;
//int valAverage1 = 0;
//int valAverage2 = 0;
int numAverage = 10;


void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500); 

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  // Your code here
  //valAverage1 =0;
  //valAverage2 =0;
  if (useSensor) {
    for (counter = 0; counter < numAverage; counter++) {
      sensorValue1 = analogRead(sensorPin1);
      sensorValue2 = analogRead(sensorPin2);
      threshDiff = (sensorValue2 + sensorValue1);
      threshDiff_PM = (sensorValue1 - sensorValue2);

      //valAverage1 = valAverage1 + sensorValue1;
      //valAverage2 = valAverage2 + sensorValue2;
    }

    //valAverage1 = valAverage1/numAverage;
    //valAverage2 = valAverage2/numAverage;

    //Serial.print(valAverage1, DEC);
    //Serial.print(", ");
    //Serial.println(valAverage2, DEC);

    if (sensorValue2 > sensorValue1 + threshold)
    {
      digitalWrite(Retract0, HIGH);
      digitalWrite(Extend0, LOW);
      digitalWrite(Retract1, HIGH);
      digitalWrite(Extend1, LOW);
      digitalWrite(Retract, HIGH);
      digitalWrite(Extend, LOW);
      digitalWrite(Retract2, HIGH);
      digitalWrite(Extend2, LOW);
      digitalWrite(Retract3, HIGH);
      digitalWrite(Extend3, LOW);
      delay(500);
      digitalWrite(Retract0, LOW);
      digitalWrite(Extend0, LOW);
      digitalWrite(Retract1, LOW);
      digitalWrite(Extend1, LOW);
      digitalWrite(Retract, LOW);
      digitalWrite(Extend, LOW);
      digitalWrite(Retract2, LOW);
      digitalWrite(Extend2, LOW);
      digitalWrite(Retract3, LOW);
      digitalWrite(Extend3, LOW);
      
      delay(500);
    }
    else if ((sensorValue2 + sensorValue1) < 100)
    {
      digitalWrite(Extend0, HIGH);
      digitalWrite(Retract0, LOW);
      digitalWrite(Extend1, HIGH);
      digitalWrite(Retract1, LOW);
      digitalWrite(Extend, HIGH);
      digitalWrite(Retract, LOW);
      digitalWrite(Extend2, HIGH);
      digitalWrite(Retract2, LOW);
      digitalWrite(Extend3, HIGH);
      digitalWrite(Retract3, LOW);
      delay(500);
    }
  }
  
  // If useSensor is false, read the button state and turn on/off the relay based on it
  else {
    if(panel_Up == HIGH) {
      digitalWrite(Extend0, HIGH);
      digitalWrite(Retract0, LOW);
      digitalWrite(Extend1, HIGH);
      digitalWrite(Retract1, LOW);
      digitalWrite(Extend, HIGH);
      digitalWrite(Retract, LOW);
      digitalWrite(Extend2, HIGH);
      digitalWrite(Retract2, LOW);
      digitalWrite(Extend3, HIGH);
      digitalWrite(Retract3, LOW);
    }  
    else if(panel_Down == HIGH) {
      digitalWrite(Extend0, LOW);
      digitalWrite(Retract0, HIGH);
      digitalWrite(Extend1, LOW);
      digitalWrite(Retract1, HIGH);
      digitalWrite(Extend, LOW);
      digitalWrite(Retract, HIGH);
      digitalWrite(Extend2, LOW);
      digitalWrite(Retract2, HIGH);
      digitalWrite(Extend3, LOW);
      digitalWrite(Retract3, HIGH);
    }
    else if(panel_Stop == HIGH) {
      digitalWrite(Retract0, LOW);
      digitalWrite(Extend0, LOW);
      digitalWrite(Retract1, LOW);
      digitalWrite(Extend1, LOW);
      digitalWrite(Retract, LOW);
      digitalWrite(Extend, LOW);
      digitalWrite(Retract2, LOW);
      digitalWrite(Extend2, LOW);
      digitalWrite(Retract3, LOW);
      digitalWrite(Extend3, LOW);
    }
      
  }




  Serial.print("sens_1 ");
  Serial.print(analogRead(sensorPin1));
  Serial.println();
  Serial.print("sens_2 ");
  Serial.print(analogRead(sensorPin2));
  Serial.println();
  Serial.print("Threshold Difference ");
  Serial.print(threshDiff);
  Serial.println();
  delay(500);


}


/*
  Since Threshold is READ_WRITE variable, onThresholdChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onThresholdChange()  {
  // Add your code here to act upon Threshold change
}

/*
  Since UseSensor is READ_WRITE variable, onUseSensorChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onUseSensorChange()  {
  // Add your code here to act upon UseSensor change
}

/*
  Since PanelUp is READ_WRITE variable, onPanelUpChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onPanelUpChange()  {
  // Add your code here to act upon PanelUp change
}

/*
  Since PanelDown is READ_WRITE variable, onPanelDownChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onPanelDownChange()  {
  // Add your code here to act upon PanelDown change
}

/*
  Since PanelStop is READ_WRITE variable, onPanelStopChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onPanelStopChange()  {
  // Add your code here to act upon PanelStop change
}

Thingproperties.h

Arduino
The automatically created Arduino cloud code
// Code generated by Arduino IoT Cloud, DO NOT EDIT.

#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

const char SSID[]     = SECRET_SSID;    // Network SSID (name)
const char PASS[]     = SECRET_OPTIONAL_PASS;    // Network password (use for WPA, or use as key for WEP)

void onThresholdChange();
void onPanelDownChange();
void onPanelStopChange();
void onPanelUpChange();
void onUseSensorChange();

int sensorValue1;
int sensorValue2;
int threshDiff;
int threshDiff_PM;
int threshold;
bool panel_Down;
bool panel_Stop;
bool panel_Up;
bool useSensor;

void initProperties(){

  ArduinoCloud.addProperty(sensorValue1, READ, 5 * SECONDS, NULL);
  ArduinoCloud.addProperty(sensorValue2, READ, 5 * SECONDS, NULL);
  ArduinoCloud.addProperty(threshDiff, READ, 5 * SECONDS, NULL);
  ArduinoCloud.addProperty(threshDiff_PM, READ, 10 * SECONDS, NULL);
  ArduinoCloud.addProperty(threshold, READWRITE, ON_CHANGE, onThresholdChange);
  ArduinoCloud.addProperty(panel_Down, READWRITE, ON_CHANGE, onPanelDownChange);
  ArduinoCloud.addProperty(panel_Stop, READWRITE, ON_CHANGE, onPanelStopChange);
  ArduinoCloud.addProperty(panel_Up, READWRITE, ON_CHANGE, onPanelUpChange);
  ArduinoCloud.addProperty(useSensor, READWRITE, ON_CHANGE, onUseSensorChange);

}

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

Display GUI for the Giga Display (Work in progress)

Arduino
Very beginning of the Dislay gui for the Single axis tracker control. Only the switches placed.
Meters come next!
/*
  LVGLDemo

  created 17 Apr 2023
  by Leonardo Cavagnis with additions by RacingToGreen
*/

#include "Arduino_H7_Video.h"
#include "Arduino_GigaDisplayTouch.h"

#include "lvgl.h"

Arduino_H7_Video          Display(800, 480, GigaDisplayShield); /* Arduino_H7_Video Display(1024, 768, USBCVideo); */
Arduino_GigaDisplayTouch  TouchDetector;

/* Button click event callback */
static void btn_event_cb(lv_event_t * e) {
  static uint32_t cnt = 1;
  lv_obj_t * btn = lv_event_get_target(e);
  lv_obj_t * label = lv_obj_get_child(btn, 0);
  lv_label_set_text_fmt(label, "%"LV_PRIu32, cnt);
  cnt++;
}

static void switch_use_sensor_event_handler(lv_event_t * e)
{   
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_target(e);
    if(code == LV_EVENT_VALUE_CHANGED) {
        LV_LOG_USER("State: %s\n", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "On" : "Off");
    }
}

/* Slider update value handler */
static void set_slider_val(void * bar, int32_t val) {
  lv_bar_set_value((lv_obj_t *)bar, val, LV_ANIM_ON);
}

void setup() {
  Serial.begin(115200);

  Display.begin();
  TouchDetector.begin();

  /* Create a container with grid 2x2 */
  static lv_coord_t col_dsc[] = {370, 370, LV_GRID_TEMPLATE_LAST};
  static lv_coord_t row_dsc[] = {215, 215, LV_GRID_TEMPLATE_LAST};
  lv_obj_t * cont = lv_obj_create(lv_scr_act());
  lv_obj_set_grid_dsc_array(cont, col_dsc, row_dsc);
  lv_obj_set_size(cont, Display.width(), Display.height());
  lv_obj_set_style_bg_color(cont, lv_color_hex(0x03989e), LV_PART_MAIN);
  lv_obj_center(cont);

  lv_obj_t * label;
  lv_obj_t * obj;

  /* [0;0] - Image */
  obj = lv_obj_create(cont);
  lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, 0, 1,
                        LV_GRID_ALIGN_STRETCH, 0, 1);

  LV_IMG_DECLARE(logo_165);
  lv_obj_t * img1 = lv_img_create(obj);
  lv_img_set_src(img1, &logo_165);
  lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0);
  lv_obj_set_size(img1, 165, 166);

  /* [1;0] - Switches */
  obj = lv_obj_create(cont);
  lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, 1, 1,
                        LV_GRID_ALIGN_STRETCH, 0, 1);
  //lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);

  lv_obj_t * sw0 = lv_switch_create(obj);
  lv_obj_add_event_cb(sw0, switch_use_sensor_event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(sw0, LV_ALIGN_CENTER, -100,-60);
  //lv_obj_align(sw0, LV_ALIGN_CENTER, 0, 0);
  //lv_obj_center(sw);
  label = lv_label_create(obj);
  lv_label_set_text(label, "Use Sensor");
  lv_obj_align_to(label, sw0, LV_ALIGN_OUT_RIGHT_MID, 15, 0);
  
  lv_obj_t * sw1 = lv_switch_create(obj);
  lv_obj_add_event_cb(sw1, switch_use_sensor_event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(sw1, LV_ALIGN_CENTER, -100,-20);
  //lv_obj_center(sw);
  label = lv_label_create(obj);
  lv_label_set_text(label, "Panel Up");
  lv_obj_align_to(label, sw1, LV_ALIGN_OUT_RIGHT_MID, 15, 0);

     lv_obj_t * sw2 = lv_switch_create(obj);
  lv_obj_add_event_cb(sw2, switch_use_sensor_event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(sw2, LV_ALIGN_CENTER, -100, 20);
  //lv_obj_center(sw);
  label = lv_label_create(obj);
  lv_label_set_text(label, "Panel Down");
  lv_obj_align_to(label, sw2, LV_ALIGN_OUT_RIGHT_MID, 15, 0);

     lv_obj_t * sw3 = lv_switch_create(obj);
  lv_obj_add_event_cb(sw3, switch_use_sensor_event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(sw3, LV_ALIGN_CENTER, -100, 60);
  //lv_obj_center(sw);
  label = lv_label_create(obj);
  lv_label_set_text(label, "Stop");
  lv_obj_align_to(label, sw3, LV_ALIGN_OUT_RIGHT_MID, 15, 0);

  /* [0;1] - Slider */
  obj = lv_obj_create(cont);
  lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, 0, 1,
                        LV_GRID_ALIGN_STRETCH, 1, 1);
  
  lv_obj_t * slider = lv_slider_create(obj);
  lv_slider_set_value(slider, 75, LV_ANIM_OFF);
  lv_obj_center(slider);
  label = lv_label_create(obj);
  lv_label_set_text(label, "Drag me!");
  lv_obj_align_to(label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
  
  /* [1;1] - Bar */
  obj = lv_obj_create(cont);
  lv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, 1, 1,
                        LV_GRID_ALIGN_STRETCH, 1, 1);

  lv_obj_t * bar = lv_bar_create(obj);
  lv_obj_set_size(bar, 200, 20);
  lv_obj_center(bar);
  lv_bar_set_value(bar, 70, LV_ANIM_OFF);

  lv_anim_t a;
  lv_anim_init(&a);
  lv_anim_set_exec_cb(&a, set_slider_val);
  lv_anim_set_time(&a, 3000);
  lv_anim_set_playback_time(&a, 3000);
  lv_anim_set_var(&a, bar);
  lv_anim_set_values(&a, 0, 100);
  lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
  lv_anim_start(&a);
}

void loop() { 
  /* Feed LVGL engine */
  lv_timer_handler();
}

Credits

Shawn Murphy
3 projects • 7 followers

Comments