PID problem - Dual axis sun tracker with servo motors

Hello guys, i have a project for my university and my job is to follow the sun (or any light). I have created the full parts including 4 photoresistors and two servos (one for the horizontal and one for the vertical control). With the help of some online codes that i found, and my details in the codes, i have completed the model and its working pretty good. In the project we must include a PID controller and i have tried several libraries.
My main problem is that i am not sure about what my setpoint should be in this case. The way i constructed it , it has a dead zone of +- 6 degrees to recognize a change in the lights' angle and turn in that direction. Every other problem has a standard setpoint (lets say 10cm from the sonar sensor), but mine its always changing as the part is moving all the time. The servo motors also take the input of what degrees to turn, so they just turn and it is just fine if i just use a step change smaller than 6 degrees. Still the professor wants to apply a PID controller.
Any suggestions please about how to set my setpoint at this problem?
Thank you very much.

My code is here (main code from The IoT Projects on youtube and edited to my preferences).

#include <Servo.h> 


Servo horizontal; // horizontal servo
int servoh = 90; 
int servohLimitHigh = 175;
int servohLimitLow = 5;

Servo vertical; // vertical servo
int servov = 45; 
int servovLimitHigh = 175;
int servovLimitLow = 5;

// LDR pin connections
// name = analogpin;
int ldrlt = A1; //LDR top left - BOTTOM LEFT <--- BDG 
int ldrrt = A2; //LDR top rigt - BOTTOM RIGHT
int ldrld = A0; //LDR down left - TOP LEFT 
int ldrrd = A3; //ldr down rigt - TOP RIGHT
int lt,rt,ld,rd;
void setup(){
Serial.begin(9600);
horizontal.attach(9);
vertical.attach(10);
horizontal.write(servoh);
vertical.write(servov);
delay(250);
}
void loop() {
  if (servov<=90) {
      lt = analogRead(ldrlt); // top left
      Serial.print(lt);
      Serial.print(" ");
      rt = analogRead(ldrrt); // top right
      Serial.print(rt);
      Serial.print(" ");
      ld = analogRead(ldrld); // down left
      Serial.print(ld);
      Serial.print(" ");
      rd = analogRead(ldrrd); // down right
      Serial.print(rd);
      Serial.print("\n");
  }
  //anapodogirismos deksion me aristeron plevron
  else if(servov>90){
      lt = analogRead(ldrrt); // top right
      Serial.print(lt);
      Serial.print(" ");
      rt = analogRead(ldrlt); // top left
      Serial.print(rt);
      Serial.print(" ");
      ld = analogRead(ldrrd); // down right
      Serial.print(ld);
      Serial.print(" ");
      rd = analogRead(ldrld); // down left
      Serial.print(rd);
      Serial.print("\n");
  }

//delay(500);

int dtime = 10; int tol =50; // dtime=diffirence time, tol=toleransi
int avt = (lt + rt) / 2; // average value top
int avd = (ld + rd) / 2; // average value down
int avl = (lt + ld) / 2; // average value left
int avr = (rt + rd) / 2; // average value right
int dvert = avt - avd; // check the diffirence of up and down
int dhoriz = avl - avr;// check the diffirence og left and rigt

//vertical difference check 
if (dvert <-1*tol || dvert > tol) 
 {
   if (avt > avd)
     {
     //servov = ++servov;
     servov=servov+3
     ;
     if (servov > servovLimitHigh)
          servov = servovLimitHigh;
     }
 else if (avt < avd)
     {
      //servov = --servov;
     servov=servov-3;
     if (servov < servovLimitLow)
          servov = servovLimitLow;
     }
 else if (avt = avd)
      delay(5);
 vertical.write(servov);
 }

//horizontal difference check
if (dhoriz< -1*tol || dhoriz > tol) // check if the diffirence is in the tolerance else change horizontal angle
 {
 if (avl > avr)
     {
     //servoh = ++servoh;
     servoh=servoh+3;
     if (servoh < servohLimitLow)
          servoh = servohLimitLow;
     }
 else if (avl < avr)
     {
     //servoh = --servoh;
     servoh=servoh-3;
     if (servoh > servohLimitHigh)
          servoh = servohLimitHigh;
      }
 else if (avl = avr)
      delay(5);
  horizontal.write(servoh);
 }
 
 delay(dtime);
 
}

A photo of my project to understand how the photoresistors are positioned.

Can't you use the values read from the analog inputs when pointing directly at the sun as the setpoint(s)?

Hello john, i thought of it but the values of the four inputs vary at steady light , from 850-900 . When stabled, the steady value of each one is different(still having the noise in).
I tried to use as a setpoint the dvert and dhoriz , which in my code is the difference between the top and down values, and right and left. So the difference that i want is zero. So my setpoint should be zero if i use this value as an input in the PID correct?

Correct. You'll probably have to work on that noise, though. Use some kind of software filtering.
You've got plenty of time, so you could probably just use the average of say, the last 50 readings, and make sure you read the inputs a lot more often than you run the PID routines.
You could also calibrate the inputs. Find out what numbers you need to multiply by to get equal readings from opposed sensors when pointing at the sun. Also check that there's not a zero offset. i.e. find M and C in
y' = M.y + C, where y is the input value, y' is the corrected value, M is the slope and C is the intercept.

I tried that but i am not sure for the following steps, i found a library which uses this code:

PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

and this in void setup

//Hardcode the brigdness value
  Setpoint = 0;
  //Turn the PID on
  myPID.SetMode(AUTOMATIC);
  //Adjust PID values
  myPID.SetTunings(Kp, Ki, Kd);

and this in void loop

Input = map(dvert, -1024, 1024, -90, 90);  // photo senor is set on analog pin 5
  //PID calculation
  myPID.Compute();

so what is the Output going to be? I mean in what type of value?
Also in map() function, am i correct? what are the values that i must put in, in order to apply that output in my code here:

vertical.write(Output+servov);

Thank you for answering!

Sorry, I know the theory but I haven’t used a PID on the Arduino (only in motor drives and temperature controllers and such-like). Also it’s well past my bed-time (1.23am here) and I’m old.
Maybe someone else could pick up the pieces? Any takers?

Thank you anyway for the replies. Well here is 3.30am but it must be finished in some days. If anyone could help me with these last steps it would be very appreciated!

The PID library output properties are detailed here (one of many places):

https://playground.arduino.cc/Code/PIDLibrarySetOutputLimits/

And, yes, zero is valid setpoint.

So the output of the pid is 0-255? Then i should also map the output like Output=map(Output, 0,255,0,180) to receive the result in degrees?
And what about the input?

johndg:
find M and C in
y' = M.y + C, where y is the input value, y' is the corrected value, M is the slope and C is the intercept.

Forget that - use the map() function - it does it for you. Realised that about 2 minutes after I got to bed. I said I was old and it was past my bed-time! Gone are the days of all-nighters :frowning: .

It sound like sun-tracking isn't relevant here, the "follow any light" requirement is why PID is needed
(sun-tracking is much simpler).

As with any PID scheme latency will increase instability so its important to run the loop rapidly enough,
any attempt to add hysteresis or suchlike (deadzone) into the loop will destabilize it.

Since you are using light as the feedback signal you are forced into losing any filtering or hysteresis.

An alternative, more controllable, approach is to use encoders to sense the orientation, and a PID
loop based on a synthesized setpoint. A second bit of code reads the light sensors and sets the
set-point, with hysteresis, latency, low-pass filtering or whatever.

Basically whatever is inside the control loop is intimately responsible for loop stability, and latency or
slop will cause big problems - only reducing the gain considerably will cure these, leading to
slow response.

500 loops per second for the PID is a good place to start. You'll need servos that handle high update
rates and drive then with a suitable library (typically used for quadcopter ESCs for instance).

johndg:
Forget that - use the map() function - it does it for you. Realised that about 2 minutes after I got to bed. I said I was old and it was past my bed-time! Gone are the days of all-nighters :frowning: .

I like how you were still thinking about it! Could you please guide me into how to use the map function for this situation? I don't know where exactly to use it (input or output) and with what values and i really want to learn about it. I think this is what i am missing and it might be complete.

Hello Mark, the servos that i am using are "Micro SERVO motor 180 degree MG90S ".
This is what i was thinking about the difference of sun light and other light sources and i can see it with serial plotting the input of the LDRs.
My problem here is which is the setpoint of my system. I figured out how to position it to the correct angle only using if() functions.
How could i know how many loops per second i am running, and which loop do you mean? The whole void loop?
Thank you very much for replying!

By the way i have only several days to present it!

petrosch98:
I like how you were still thinking about it! Could you please guide me into how to use the map function for this situation? I don't know where exactly to use it (input or output) and with what values

I do about 50% of my work in my head, either in bed or in the pub (damn' lockdown is playing hell with my output!).

Assuming the PID is outputting where it "thinks" the tracker should be pointing, scaled to 0-255, and the range of servo movement is 0-180, then you were nearly correct in #9 with " Output=map(Output, 0,255,0,180)", except it should be "Demanded_servo_position = map((PID_output, 0,255,0,180)".

Tomorrow i will try the test because i run multiple projects now. So you suggest to use the map() in output and ALSO use it in the input as i said before in #5?

petrosch98:
So you suggest to use the map() in output and ALSO use it in the input as i said before in #5?

Yes, but if your PID is working with inputs in the range -90 to +90, the PID output will also be in that range, so the mapping for the output would be "Demanded_servo_position = map(PID_output, 0,255,-90,90)".

You can also use it to equalize the raw values from your light sensors, e.g. for the left/right pair, "corrected_left_value=map(actual_left_value, left_value_dark, left_value_full_light,, right_value_dark, right_value_full_light)", and use the value from the right sensor unchanged.

johndg:
Yes, but if your PID is working with inputs in the range -90 to +90, the PID output will also be in that range.

I'm talking rubbish there. The input comes from your light detectors somehow (eg the difference between left and right for one, and up and down for the other - after correcting for the differences in output of eg left/right as I described).
The output does however, map to +/- 90.

PID input and output ranges are not necessarily related. They can be arbitrary as the integral term will
absorb any offset, and the overall gain will address any ratio.

johndg:
Yes, but if your PID is working with inputs in the range -90 to +90, the PID output will also be in that range, so the mapping for the output would be "Demanded_servo_position = map(PID_output, 0,255,-90,90)".

Hello. Why would i map it around (-90 , 90 ) if my servo input takes only (0,180) in degrees ?

johndg:
You can also use it to equalize the raw values from your light sensors, e.g. for the left/right pair, "corrected_left_value=map(actual_left_value, left_value_dark, left_value_full_light,, right_value_dark, right_value_full_light)", and use the value from the right sensor unchanged.

I did something else, i tried to measure the full dark and the light to a steady distance opposite of each LDR and i mapped all 4 inputs from their values to 0-100, so they are all now in the same range and almost have the same value. Also that decreased very much the turbulence in the input signal.

MarkT:
PID input and output ranges are not necessarily related. They can be arbitrary as the integral term will
absorb any offset, and the overall gain will address any ratio.

I am trying to use a PD controller without the integration part, because its a simple mechanism. A PD is enough for my situation. So i imagine that the input and output ranges will be the same?