Help with using PID control and having an Auto and Manual control function

Hi,

Im tring to develop code that would allow a valve(servo motor) to operate on a scale from open to close depending on the light a sensor is recieving.
At the same time id like to switch this to a manual control where a potentiometer can be used to carry out the valve operation.
Currently it feels that the system is not reflecting any changes in light as expected.
Also my manual control has zero control.

// Setting up the Constraint Values for Pid Controller and Input Devices for Control Loop
#include <PID_v1.h>
const int photores = A0; // Photo Resistor Input
const int pot = A1; // Potentiometer Input
double lightLevel; // Variable that stores the incoming light level

// Setting up the Constraint Variables for Servo Motor
#include <Servo.h>
Servo myServo; // Creation of a servo object
int val; // Variable to read value from the analog pin
int voltValuue = 0; // variable to store the value from potentiometer
// Declaring Switch pin constants
const int sw1pin = A2; // Input pin for manual switch
const int manLEDpin = 7; // Output pin for manual indicator lamp

// Declarinf LED variable
int sw1_state = 0; // Manual switch state (HIGh/LOW)

// Tuning Parameters
float Kp=10; // Initial Proportional Gain
float Ki=10; // Initial Integral Gain
float Kd=10; // Initial Differential Gain

//Variable Creation for Storing Values
double Setpoint, Setpoint1, Input, Output;

// Setting up the PID loop
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // Input is the PV
// Output is the Output
// Setpoint is the SP
const int sampleRate = 1; // Variable that determines the speed of the PID Loop

// Communication setup
const long serialPing = 500; // This determins how often the loop is pinged
// Serial pingback interval in milliseconds
unsigned long now = 0; // Variable acting to keep track of time
// Placeholder for current timestamp
unsigned long lastMessage = 0; // This keeps track of when the loop last communicated to Serial

void setup() {
lightLevel = analogRead(photores); // Rean in the light level
Input = map(lightLevel, 0, 1024, 0, 1000); // Change the read scale to anolog out scale
Setpoint = map(analogRead(photores), 0, 1024, 0, 1000); //Get setpoint from Phot Resistor
Setpoint1 = map(analogRead(pot), 0, 1024, 0, 1000); //get our setpoint from our potentiometer
Serial.begin(9600); // Start a serial session
myPID.SetMode(AUTOMATIC); // Turns on the PID Loop
myPID.SetSampleTime(sampleRate); // Sets the sample rate
Serial.print("System EN2107 Starting"); // System stating message
lastMessage = millis(); // timestamp
myServo.attach(9); // Attaches the Servo connected to pin 9 to the servo object

// Initialize the I/O pins
pinMode( sw1pin, INPUT );
pinMode( manLEDpin, OUTPUT );
}

void loop() {
sw1_state = digitalRead( sw1pin ); // check manual switch state
if (sw1_state == HIGH) { // Switch activated for manual mode
Serial.println("System in Manual");
digitalWrite( manLEDpin, HIGH); // Activate manual mode
Setpoint1 = map(analogRead(pot), 0, 1024, 0, 1000); //Read our setpoint
Input = map( pot, 0, 1024, 0, 1000);
// Manual operation looks at the value from the potentiometer
analogWrite(9, Output);

     Serial.print("Setpoint = ");
Serial.print(Setpoint);
Serial.print(" Input = ");
Serial.print(Input);
Serial.print(" Output = ");
Serial.print(Output);
Serial.print("\n");

}
else {
digitalWrite( manLEDpin, LOW); // Manual mode off
Serial.println("System in Automatic");
Setpoint = map(analogRead(photores), 0, 1024, 0, 1000); // Read the setpoint value
lightLevel = analogRead(photores); // Get the light level
Input = map(lightLevel, 0, 900, 0, 1000); // Map it to the right scale
myPID.Compute(); // Run the PID loop
analogWrite(9, Output); // Write out the output from the PID loop to Servo Motor pin

now = millis(); // Keep track of time

{ // If its been long enough give us some info on serial
// send a message to serial monitor
Serial.print("Setpoint = ");
Serial.print(Setpoint);
Serial.print(" Input = ");
Serial.print(Input);
Serial.print(" Output = ");
Serial.print(Output);
Serial.print("\n");
if (Serial.available() > 0) { // If we sent the program a command deal with it
for (int x = 0; x < 4; x++) {
switch (x) {
case 0:
Kp = Serial.parseFloat();
break;
case 1:
Ki = Serial.parseFloat();
break;
case 2:
Kd = Serial.parseFloat();
break;
case 3:
for (int y = Serial.available(); y == 0; y--) {
Serial.read(); // Clear out any residual useless information
}
break;
}
}
Serial.print(" Kp,Ki,Kd = ");
Serial.print(Kp);
Serial.print(",");
Serial.print(Ki);
Serial.print(",");
Serial.println(Kd); // Display the value received
myPID.SetTunings(Kp, Ki, Kd); // Set the PID gain constants and start running
}

lastMessage = now; 
// update the time stamp. 

}
// Servo Motor control
val = &Output; // read the value of the output
// print out the value to the Serial Monitor
Serial.print("val: ");
Serial.print(val);

// wait for the servo to get there
delay(500);
}

}

That doesn't look right.

Please remember to use code tags when posting code.

Ah sorry its my first time posting and im still getting to grips using arduino and coding in general.

How is the selector switch wired up? Does the serial output show that it controls which mode you're in?

The selector switch is just breadboard jump wire i connect to the negative common. This works as the serial monitor does show that the mode has changed from manual to automatic. Its just in either mode it doesnt seem to be functioning with the desired input device. Not sure i have the outputs set up correctly.

Servos don't use analogWrite - take a look at some example code for the library. You'll need myServo.write I expect.

Thanks. I did change this for both cases but my output still seems very unstable.

Post what you have now.

I suggest that you hardwire the system to manual only for the moment and just test it by itself.

When you get back to the PID stuff, check your tuning parameters- the initial values look unusual.

Hopefully this is the correct format to do so

// Setting up the Constraint Values for Pid Controller and Input Devices for Control Loop
#include <PID_v1.h>
const int photores = A0; // Photo Resistor Input
const int pot = A1; // Potentiometer Input
double lightLevel; // Variable that stores the incoming light level

// Setting up the Constraint Variables for Servo Motor
#include <Servo.h>
Servo myServo; // Creation of a servo object
int val; // Variable to read value from the analog pin
int voltValuue = 0; // variable to store the value from potentiometer
// Declaring Switch pin constants
const int sw1pin = A2; // Input pin for manual switch
const int manLEDpin = 7; // Output pin for manual indicator lamp

// Declarinf LED variable
int sw1_state = 0; // Manual switch state (HIGh/LOW)

// Tuning Parameters
float Kp=10; // Initial Proportional Gain
float Ki=10; // Initial Integral Gain
float Kd=10; // Initial Differential Gain

//Variable Creation for Storing Values
double Setpoint, Setpoint1, Input, Output;

// Setting up the PID loop
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // Input is the PV
// Output is the Output
// Setpoint is the SP
const int sampleRate = 1; // Variable that determines the speed of the PID Loop

// Communication setup
const long serialPing = 500; // This determins how often the loop is pinged
// Serial pingback interval in milliseconds
unsigned long now = 0; // Variable acting to keep track of time
// Placeholder for current timestamp
unsigned long lastMessage = 0; // This keeps track of when the loop last communicated to Serial

void setup() {
lightLevel = analogRead(photores); // Rean in the light level
Input = map(lightLevel, 0, 1024, 0, 255); // Change the read scale to anolog out scale
Setpoint = map(analogRead(photores), 0, 1024, 0, 255); //Get setpoint from Phot Resistor
Setpoint1 = map(analogRead(pot), 0, 1024, 0, 255); //get our setpoint from our potentiometer
Serial.begin(9600); // Start a serial session
myPID.SetMode(AUTOMATIC); // Turns on the PID Loop
myPID.SetSampleTime(sampleRate); // Sets the sample rate
Serial.print("System EN2107 Starting"); // System stating message
lastMessage = millis(); // timestamp
myServo.attach(9); // Attaches the Servo connected to pin 9 to the servo object

// Initialize the I/O pins
pinMode( sw1pin, INPUT );
pinMode( manLEDpin, OUTPUT );
}

void loop() {
sw1_state = digitalRead( sw1pin ); // check manual switch state
if (sw1_state == HIGH) { // Switch activated for manual mode
Serial.println("System in Manual");
digitalWrite( manLEDpin, HIGH); // Activate manual mode
Setpoint1 = map(analogRead(pot), 0, 1024, 0, 255); //Read our setpoint
Input = map( pot, 0, 1024, 0, 255);
// Manual operation looks at the value from the potentiometer
myServo.write(Output);

     Serial.print("Setpoint = ");
Serial.print(Setpoint);
Serial.print(" Input = ");
Serial.print(Input);
Serial.print(" Output = ");
Serial.print(Output);
Serial.print("\n");

}
else {
digitalWrite( manLEDpin, LOW); // Manual mode off
Serial.println("System in Automatic");
Setpoint = map(analogRead(photores), 0, 1024, 0, 255); // Read the setpoint value
lightLevel = analogRead(photores); // Get the light level
Input = map(lightLevel, 0, 900, 0, 255); // Map it to the right scale
myPID.Compute(); // Run the PID loop
myServo.write(Output); // Write out the output from the PID loop to Servo Motor pin

now = millis(); // Keep track of time

{ // If its been long enough give us some info on serial
// send a message to serial monitor
Serial.print("Setpoint = ");
Serial.print(Setpoint);
Serial.print(" Input = ");
Serial.print(Input);
Serial.print(" Output = ");
Serial.print(Output);
Serial.print("\n");
if (Serial.available() > 0) { // If we sent the program a command deal with it
for (int x = 0; x < 4; x++) {
switch (x) {
case 0:
Kp = Serial.parseFloat();
break;
case 1:
Ki = Serial.parseFloat();
break;
case 2:
Kd = Serial.parseFloat();
break;
case 3:
for (int y = Serial.available(); y == 0; y--) {
Serial.read(); // Clear out any residual useless information
}
break;
}
}
Serial.print(" Kp,Ki,Kd = ");
Serial.print(Kp);
Serial.print(",");
Serial.print(Ki);
Serial.print(",");
Serial.println(Kd); // Display the value received
myPID.SetTunings(Kp, Ki, Kd); // Set the PID gain constants and start running
}

lastMessage = now; 
// update the time stamp. 

}
// Servo Motor control
val = &Output; // read the value of the output
// print out the value to the Serial Monitor
Serial.print("val: ");
Serial.print(val);

// wait for the servo to get there
delay(500);
}

}

Nope.

Code tags - the </> icon in the editor.

Servo.Write takes an angle in degrees. Hobby servos may be able to do 0 to 180 but many have a smaller range. You'll need to find out by experimenting.

You're using output to command the servo, but there is nothing that sets it in your manual code.

How would i set it to read from the potentiometer for the manual mode then?

You have a line that sets the input variable (to no real purpose). Add a similar one for output that maps from 45 to 135 to start with.

PID is not suitable for controlling a hobby servo because they have no position feedback you can read.

// Setting up the Constraint Values for Pid Controller and Input Devices for Control Loop

const int PhotosensorPin = A0; // Photo Resistor Input
const int PotentiometerPin = A1; // Potentiometer Input
const int AutoManualSwitchPin = A2; // Input pin for manual switch

// Setting up the Constraint Variables for Servo Motor
#include <Servo.h>
Servo ValveServo; // Creation of a servo object
const int ValveServoPin = 9;

// Declaring Switch pin constants

const int AutoManualLEDPin = 7; // Output pin for manual indicator lamp


void setup()
{
  Serial.begin(9600); // Start a serial session

  // Initialize the I/O pins
  pinMode( AutoManualSwitchPin, INPUT );
  pinMode( AutoManualLEDPin, OUTPUT );
}

void loop()
{
  boolean manualMode = digitalRead(AutoManualSwitchPin) == HIGH;
  if (manualMode)   // Switch activated for manual mode
  {
    // Manual mode
    Serial.println("System in Manual");
    digitalWrite( AutoManualLEDPin, HIGH);

    int valvePosition = map(analogRead(PotentiometerPin), 0, 1024, 0, 180);

    // Manual operation looks at the value from the PotentiometerPinentiometer
    ValveServo.write(valvePosition);

    Serial.print("valvePosition = ");
    Serial.println(valvePosition);
  }
  else
  {
    // Automatic mode
    Serial.println("System in Automatic");
    digitalWrite(AutoManualLEDPin, LOW); // Manual mode off

    int lightLevel = analogRead(PhotosensorPin); // Get the light level
    int valvePosition = map(lightLevel, 0, 900, 0, 180); // Map it to the right scale

    ValveServo.write(valvePosition); // Write out the output from the PID loop to Servo Motor pin

    Serial.print("valvePosition = ");
    Serial.println(valvePosition);
  }
}

Is there no way to use the output value generated as the feedback?

Servos provide no 'feedback'. They have a built-in controller. You put in the Setpoint and they move to there.

If you replaced the servo with a DC motor, an H-Bridge motor driver, and a potentiometer to provide feedback, then you could use a PID loop to turn that hardware into a servo.

I do have the use of a stepper motor in my kit and also attempting to use the potentiometer for manual operation. Would these items make it acheivable?

You would need a second potentiometer driven by the output shaft of the stepper motor. You could then read that feedback potentiometer with analogRead() and use that as 'Input' to your PID. The 'Output' of the PID would tell the stepper which way to step.

Post a link to the valve's datasheet or it's brand name and exact part number. If a ball valve, it's flow characteristics are non linear and not a good candidate for PID control.

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