I'm currently trying to run a Parallax 360 Servo motor for use in a project, but I've run into some issues with the PID portion of the code. I can get the servo to turn perfectly fine, but when I try to implement a PID controller to control the angular position, I run into an issue where the output of the PID Compute() function is always 1.
and when I try to modify it to use the PID library, I run into problems. My code (ignore the LCD stuff, I haven't gotten that far yet):
// Libraries to include
#include <Servo.h> // Servo Library
#include <PID_v1.h> // PID Library
#include <Adafruit_RGBLCDShield.h> // LCD Display Library
// Define the address numbers for LCD panel backlight colors
#define OFF 0x0
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7
// PID variables
double TARGET_ANGLE = 350.0; // Target angle to achieve
double ANGLE = 0.0; // Current angle read from the feedback pin
double SERVO_VAL = 93; // Initial setpoint is the servo value that maintains zero motion
int OUTPUT_VALUE = 0;
/***************************************
* PID Gains
***************************************/
double Kp = 0.7;
double Ki = 0.2;
double Kd = 0.4;
/**************************************/
/**************************************/
// Parallax 360 Servo variables
int PIN_FEEDBACK = 5; // Connect the feedback pin from the Parallax 360 servo to the #5 PWM pin
float T_HIGH = 0;
float T_LOW = 0;
int T_CYCLE = 0;
float DC = 0;
float DC_MIN = 2.9; //From Parallax spec sheet
float DC_MAX = 97.1; //From Parallax spec sheet
// Instantiate the PID and Servo instances
Servo CAM_CONTROL;
PID PID_LOOP(&ANGLE, &SERVO_VAL, &TARGET_ANGLE,Kp,Ki,Kd,DIRECT);
void setup()
{
Serial.begin (9600);
// Servo initialization
CAM_CONTROL.attach(9); // Attach the signal pin of servo to pin 9 of arduino
pinMode(5, INPUT); // Sets PWM pin 5 as the Feedback input pin
// PID initialization
PID_LOOP.SetMode(AUTOMATIC); // Turns the PID loop on
PID_LOOP.SetOutputLimits(0,90); // Sets the PID output to a range usable by the Parallax 360 Servo
PID_LOOP.SetSampleTime(10); // Set the PID to actually compute every 10 ms.
}
void loop()
{
// From the Parallax spec sheet
while(1)
{
T_HIGH = pulseIn(PIN_FEEDBACK, HIGH);
T_LOW = pulseIn(PIN_FEEDBACK, LOW);
T_CYCLE = T_HIGH + T_LOW;
if ( T_CYCLE > 1000 && T_CYCLE < 1200)
{
break; //valid T_CYCLE;
}
}
/*********************************************************************************
* From the Parallax 360 spec sheet
*********************************************************************************/
DC = (100 * T_HIGH) / T_CYCLE;
ANGLE = ((DC - DC_MIN) * 360) / (DC_MAX - DC_MIN + 1);
/********************************************************************************/
/********************************************************************************/
OUTPUT_VALUE = PID_LOOP.Compute();
SERVO_VAL = 93 - OUTPUT_VALUE;
if (SERVO_VAL > 180) SERVO_VAL = 180;
else if (SERVO_VAL < 93) SERVO_VAL = SERVO_VAL - 3;
CAM_CONTROL.write(SERVO_VAL); //Move the servo
Serial.println(SERVO_VAL);
Serial.println(ANGLE);
Serial.println(OUTPUT_VALUE);
}
Am I doing something wrong that causes the PID Compute() function to give me a value of 1, no matter how large the angular error is? Any help would be greatly appreciated!
Needs a circuit diagram, and no idea what your servo is - I’m not convinced you have a loop here ?? . PID only works as a control loop - for example , temperature control - a thermocouple measured a temperate if a block, the block is heated by a heater . The PID controller has a desired temperature to achieve and controls the heater until the thermocouple reads that desired temperature .
The output has to be in the right “ direction” - in the example if the PID loop output shows the temperature is below the required value , then it must turn the heater on . Also bear in mind PID loops need to be tuned to work correctly .
You obviously don't know how to use the PID library. Study the documentation and simple examples that come with it to learn how it is used.
Well, yeah, that's why I'm asking questions. If I knew what I was doing, I wouldn't need to ask. Granted, I should have noticed what I was doing wrong after reading the documentation, but everyone makes idiot mistakes. I altered the code to run Compute() and to use the output in the servo value:
PID_LOOP.Compute();
SERVO_VAL = 93 - OUTPUT_VAL;
The reason I'm doing the subtraction is because 93 is the dead zone of this particular servo. Lesser values cause CW rotation (lower is faster), higher are CCW (higher is faster). So my reasoning is that the dead zone number minus max positive error will move the servo to a higher angular position, while 93 minus the max negative number will result in lower angular position at the fastest speed. Because of this I changed the values in SetOutputLimits() to -90,90 in order to make sure that the error is in the range that makes sense.
The problem now is that it just moves in the direction of the first PID error calculation and never updates. Should I increase the time between computations? I added a check to ensure Compute() returns true before writing to the servo, but that didn't correct the issue. Am I missing something else that is obvious?
How about the fact that Compute() returns a bool?
This was a helpful comment. I read that in the documentation and then immediately completely forgot about it and used it the wrong way.
The code is a Train Wreck.
This isn't helpful. Considering I wasn't born knowing how to program, I feel like some random douche trash talking my first arduino code is just an insecure, neck-bearded reminder of why every CS friend I have told me not to post here.[/quote]
I should also add, the TARGET_ANGLE variable is where the value changes. I'm using a simple fixed value for now until I can get something working well. The code I linked to in my first post works fine using a fixed value, but I wanted to implement the PID library in an effort to learn more coding. Eventually, the code will read from a GPS to update TARGET_ANGLE each iteration.
No one is. Good programmers start at the beginning, study hard and read the documentation.
I started from a good, working code by JavelinPoint
I looked at that, and disagree with your assessment. It has very obvious errors, but PID is robust and can often continue to function in spite of such ridiculous mistakes as the following line:
I didn't notice that in the original code, but I only used it long enough to see that it actually worked. It has a PID method kind of coded in, but I wanted to use the library instead, so that part isn't in my code that I posted in the OP.
I also fixed the T_HIGH, T_LOW, and T_CYCLE variables to be of unsigned long type. I think the float assignments there come from the data sheet of the servo, where they use C to code the servo instead of Arduino (which I realize is similar, but copy/pasting the code from the data sheet definitely doesn't work, as they have additional libraries).
The documentation can be found here:
Here is my updated code:
// Libraries to include
#include <Servo.h> // Servo Library
#include <PID_v1.h> // PID Library
#include <Adafruit_RGBLCDShield.h> // LCD Display Library
// Define the address numbers for LCD panel backlight colors
#define OFF 0x0
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7
// PID variables
double TARGET_ANGLE = 30.0; // Target angle to achieve
double ANGLE = 0.0; // Current angle read from the feedback pin
double SERVO_VAL = 93; // Initial setpoint is the servo value that maintains zero motion
double OUTPUT_VAL = 0; // Output value of the PID loop when it executes the Compute() function
int RESULT = 0;
/***************************************
* PID Gains
***************************************/
double Kp = 0.7;
double Ki = 0.2;
double Kd = 0.0;
/**************************************/
/**************************************/
// Parallax 360 Servo variables
int PIN_FEEDBACK = 5; // Connect the feedback pin from the Parallax 360 servo to the #5 PWM pin
unsigned long T_HIGH = 0;
unsigned long T_LOW = 0;
unsigned long T_CYCLE = 0;
float DC = 0;
float DC_MIN = 2.9; // From Parallax spec sheet
float DC_MAX = 97.1; // From Parallax spec sheet
// Instantiate the PID and Servo instances
Servo CAM_CONTROL;
PID PID_LOOP(&ANGLE,&OUTPUT_VAL,&TARGET_ANGLE,Kp,Ki,Kd,DIRECT);
void setup()
{
Serial.begin (9600);
// Servo initialization
CAM_CONTROL.attach(9); // Attach the signal pin of servo to pin 9 of arduino
pinMode(5, INPUT); // Sets PWM pin 5 as the Feedback input pin
// PID initialization
PID_LOOP.SetMode(AUTOMATIC); // Turns the PID loop on
PID_LOOP.SetOutputLimits(-90,90); // Sets the PID output to a range usable by the Parallax 360 Servo
PID_LOOP.SetSampleTime(100); // Set the PID to actually compute every 10 ms.
}
void loop()
{
/*********************************************************************************
* From the Parallax 360 spec sheet
*********************************************************************************/
while(1)
{
T_HIGH = pulseIn(PIN_FEEDBACK, HIGH);
T_LOW = pulseIn(PIN_FEEDBACK, LOW);
T_CYCLE = T_HIGH + T_LOW;
if ( T_CYCLE > 1000 && T_CYCLE < 1200)
{
break; //valid T_CYCLE;
}
}
DC = (100 * T_HIGH) / T_CYCLE;
ANGLE = ((DC - DC_MIN) * 360) / (DC_MAX - DC_MIN + 1);
/********************************************************************************/
/********************************************************************************/
RESULT = PID_LOOP.Compute();
Serial.print("RESULT: ");
Serial.println(RESULT);
if (RESULT == 1)
{
SERVO_VAL = 93 - OUTPUT_VAL;
if (SERVO_VAL > 180) SERVO_VAL = 180;
else if (SERVO_VAL < 93) SERVO_VAL = SERVO_VAL - 3;
CAM_CONTROL.write(SERVO_VAL); //Move the servo
}
Serial.print("PID Output Val: ");
Serial.println(OUTPUT_VAL);
Serial.print("Servo Val: ");
Serial.println(SERVO_VAL);
Serial.print("Angle: ");
Serial.println(ANGLE);
}
UPDATE: It works. As it turns out, even the small gains I had on the PIDs were WAY too big. I've now settled on:
Kp = 0.1;
Ki = 0.002;
Kd = 0.0001;
Thanks to everyone who offered constructive criticism. I know I have a lot to learn about coding but it's helpful to have places like this to be able to ask for help, even if the questions are sometimes pretty dumb.
One final question:
Now that it is working, I'm noticing it still has ~2-4 degrees of error in the angle, whereas the other code I linked from the other thread had a lot less angular error. Any ideas on how to correct this?