My PID tuned with only 'I' component works very well, in terms of performance to maintain the set-point, the only problem being it overshoots precisely "double" the set-point I give it. other than there's no jitter and the loop maintains the speed as needed.
I have tried to work with some single or double-digit values under 100 for the 'P' component, it works but doesn't seem to be needed and the reason I have only 'I' component is because my motor doesn't even move at all or very slowly with lower values.
My problem is why the precisely double set-point maintained by the PID loop?!
attached is my code, I'm using the latest version of PID_RT lib(1.4.0
ATsam51d_cytronDualDRV_PID_RT.ino (2.7 KB)
)
Forum prefers code vs attachment:
#include "PID_RT.h"
//#include <PID_v1.h>
#define MTR_ONE_REV (1024 * 71) + 0.002 * (1024 * 71) // 72704 + 0.002% error
/*
* PID experimentation
*/
PID_RT PID;
//Define Variables we'll be connecting to
float Setpoint = 0.03, Input, Output;
/*
* Motor ctrl pinout defines
* Max speed 0.89 mtrs/sec ~32vDC
*/
#define PWM_PIN_MTR1 9
#define DIR_PIN_MTR1 8
#define PWM_PIN_MTR2 11
#define DIR_PIN_MTR2 7
#define circum_x(x) 2 * 3.1415 * x
//const int PWM_PIN = 3; // UNO PWM pin
int op = 0;
long input = 0;
const float whl_rad_mtrs = 0.1016;
const float circum = 2 * 3.1415 * whl_rad_mtrs;
long currentTicks, targetTicks;
volatile uint8_t rotCount = 0;
volatile uint64_t ticksCount = 0;
volatile boolean serial_ = false;
unsigned long previousMillis = 0;
const long interval = 1000;
float ticks_to_angSpeed(volatile uint64_t *ticks) {
// cout << *ticks << endl;
float ticks_1rev = (float)MTR_ONE_REV;
float mtrs_per_sec = (circum * (*ticks / ticks_1rev));
return mtrs_per_sec;
}
long angSpeed_to_ticks(float *mtrs_per_sec) {
long ticks = (*mtrs_per_sec / circum) * (long)MTR_ONE_REV;
return ticks;
}
void rotationISR() {
rotCount++;
serial_ = true;
}
void ticksISR() {
ticksCount++;
}
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
pinMode(DIR_PIN_MTR1, OUTPUT);
pinMode(DIR_PIN_MTR2, OUTPUT);
digitalWrite(DIR_PIN_MTR2, 1);
PID.setPoint(Setpoint);
PID.setOutputRange(0, 255); // PWM range
PID.setInterval(1);
PID.setK(0,600, 0); // working values-> (2, 200, 2), (10, 600, 0)(intermittend push)
PID.start();
//attachInterrupt(A2, rotationISR, FALLING);
attachInterrupt(A2, ticksISR, RISING);
//analogWrite(PWM_PIN_MTR1, 155);
}
void loop()
{
Input = ticks_to_angSpeed(&ticksCount);
if (PID.compute(Input))
{
op = PID.getOutput();
analogWrite(PWM_PIN_MTR2, op);
}
//Serial.println(PID.getSetPoint());
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
float ticks = (float)MTR_ONE_REV;
float revs = (float)ticksCount / ticks;
float mtrsPsec = ticks_to_angSpeed(&ticksCount);
Serial.print(revs);
Serial.print(" revs/sec");
Serial.print("\t");
Serial.print(ticksCount);
Serial.print("\t");
Serial.print(ticks_to_angSpeed(&ticksCount));
Serial.print(" mtrs/sec");
Serial.print('\t');
Serial.print(angSpeed_to_ticks(&mtrsPsec));
Serial.println(" ticks");
ticksCount = 0;
//rotCount = 0;
}
//analogWrite(PWM_PIN_MTR1, 55);
if(serial_) {
serial_ = false;
//Serial.println(ticksCount);
//ticksCount = 0;
}
}
// -- END OF FILE --
64 bit integer math is not completely supported (*) on Arduino, at least for the AVR series. Best to declare this uint32_t until you understand the other problems.
"P" is absolutely fundamental to PID, and MUST work. Put some Serial.print statements throughout the code to see if variables are what you expect. It could be due to the 64 bit math.
(*) printing 64 bit variables can be problematic.
Thanks for the support and your post!
I had an unsigned 32 bit before, had the same result. increasing the bit width to maybe accommodate absurb large numbers as my encoder is very high resolution and motors have 71:1 gear ratio.
Yes, absolutely! "p" is the ultimate contender but my motors just don't move! with it. It has to be an "i" .
Also, my system is an Adafruit metro m4 (with a 32-bit uC ATsam51D)
Noted!
Been almost half a decade since visiting the forum. so much has changed, hopefully for good 
Just went back and spent another hour with mods as using unsigned 32 bitter and tuning further I can say that 'p' goes from 0 to up to 140ish for smooth movements that are all in control but the issue of double overshooting of setpoint persists for all other values like just 'p' the thing will never ever move until 'i' is given and if 'i' is give in 3 digits i.e. to make any considerable movement in practical time then with 'p' above 140 is causing jitter.
So, the constants are more like 'p': 0 <= 140, 'i': 200.0 <= 600.0, 'd': 0.0
for 'i': the bigger the number the faster catchup and hold 2xsetpoint.
Something else is very wrong, but without seeing the printouts of intermediate variables and access to the hardware, it is impossible to make a reasonable guess as to the real problem.
PID takes about four lines of code, total. Write your own. Nothing magic about this, so good luck with the project.
You have probably got integral windup q.v. going on while the motor isn't moving. The PID library has some constrain function for the output - perhaps try constraining to a range where the lowest value is stopped but adding anything gets the motor running.
Alternatively, give the motor a high signal when you want it to run and after a small delay, start the PID.
Hi bill,
Yes, the library's constrain function for output lower and higher threshold values is being used i.e. 0-255 (8 bit std. pwm)
Intuitive, will try this and share the results.
Indeed, but it sounds like the low values do you no good if they can't move the motor. Of course, starting it and keeping it running are likely very different things.
You won't get to the bottom of this until you completely understand what the code is doing.
You should already know what PWM values are required to start the motors turning. What are those values?
If the motors are not turning, the PID loop must send comparable values. What is it sending instead?
@wildbill @jremington
The min value that gets the wheel moving is 17to 20 un-signed int. now the loop holds well(i mean no jolted bouts of push to the wheel) for just the 'P' but the output is pretty under the setpoint ,so now I'm working with some 'I'.
I have no idea what that post means, but good luck with the project anyway.
How long does loop take?
Your Serial.print() may take quite some time.
The PID might need more updates than it gets now.
Also your programming style is error prone.
Why #define a function instead of make a proper function?
You can inline it if you are afraid that the function overhead takes too long (but then first remove Serial.Print from loop).
Does this do what you want ?
Yes, it does but anyhow I assign this macro output to a float variable that is const or could be const.
#include "PID_RT.h"
//#include <PID_v1.h>
#define MTR_ONE_REV (1024 * 71) + 0.002 * (1024 * 71) // 72704 + 0.002% error
/*
* PID experimentation
*/
PID_RT PID;
//Define Variables we'll be connecting to
float Setpoint = 0.03, Input, Output;
/*
* Motor ctrl pinout defines
* Max speed 0.89 mtrs/sec ~32vDC
*/
#define PWM_PIN_MTR1 9
#define DIR_PIN_MTR1 8
#define PWM_PIN_MTR2 11
#define DIR_PIN_MTR2 7
#define circum_x(x) 2 * 3.1415 * x
//const int PWM_PIN = 3; // UNO PWM pin
int op = 0;
long input = 0;
const float whl_rad_mtrs = 0.1016;
const float circum = 2 * 3.1415 * whl_rad_mtrs;
long currentTicks, targetTicks;
volatile uint8_t rotCount = 0;
volatile uint64_t ticksCount = 0;
volatile boolean serial_ = false;
unsigned long previousMillis = 0;
const long interval = 1000;
float ticks_to_linSpeed(volatile uint64_t *ticks) {
// cout << *ticks << endl;
float ticks_1rev = (float)MTR_ONE_REV;
float mtrs_per_sec = (circum * (*ticks / ticks_1rev));
return mtrs_per_sec;
}
float ticks_to_angSpeed(volatile uint64_t *ticks) {
// cout << *ticks << endl;
float ticks_1rev = (float)MTR_ONE_REV;
float angV_per_sec = (2 * 3.1415 * (*ticks / ticks_1rev));
return angV_per_sec;
}
long angSpeed_to_ticks(float *mtrs_per_sec) {
long ticks = (*mtrs_per_sec / circum) * (long)MTR_ONE_REV;
return ticks;
}
void rotationISR() {
rotCount++;
serial_ = true;
}
void ticksISR() {
ticksCount++;
}
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
pinMode(DIR_PIN_MTR1, OUTPUT);
pinMode(DIR_PIN_MTR2, OUTPUT);
digitalWrite(DIR_PIN_MTR2, 1);
PID.setPropOnError();
PID.setPoint(Setpoint);
PID.setOutputRange(0, 255); // PWM range
PID.setInterval(1);
PID.setK(0,600, 0); // working values-> (2, 200, 2), (10, 600, 0)(intermittend push)
PID.start();
//attachInterrupt(A2, rotationISR, FALLING);
attachInterrupt(A2, ticksISR, RISING);
//analogWrite(PWM_PIN_MTR1, 155);
}
void loop()
{
Input = ticks_to_angSpeed(&ticksCount);
if (PID.compute(Input))
{
op = PID.getOutput();
analogWrite(PWM_PIN_MTR2, op);
}
//Serial.println(PID.getSetPoint());
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
float ticks = (float)MTR_ONE_REV;
float revs = (float)ticksCount / ticks;
float mtrsPsec = ticks_to_angSpeed(&ticksCount);
Serial.print(revs);
Serial.print(" revs/sec");
Serial.print("\t");
Serial.print(ticksCount);
Serial.print("\t");
Serial.print(ticks_to_angSpeed(&ticksCount));
Serial.print(" mtrs/sec");
Serial.print('\t');
Serial.print(angSpeed_to_ticks(&mtrsPsec));
Serial.println(" ticks");
ticksCount = 0;
//rotCount = 0;
}
//analogWrite(PWM_PIN_MTR1, 55);
if(serial_) {
serial_ = false;
//Serial.println(ticksCount);
//ticksCount = 0;
}
}
// -- END OF FILE --
so adding:
PID.setPropOnError();
Makes it now behalf properly and the system moves with 'p'. However, I always have a higher than-setpoint value being turned out, so loop behavior is consistent (no wobble or haphazard movements) However, it never attains the setpoint.
Also, I'm inputting Angular Velocity as an input/feedback, does going plain ticks/pulses from the encoder value to be set as a target/setpoint a better solution?!
Did you print the value of MTR_ONE_REV?
It is pretty usual that a P controller never reaches the target.
There is 2 ways to improve that:
If you double P, the error wel be half... but you might get stability problems.
Otherwise: add some I.
And indeed: if you set the encoder ticks as a target, and directly use your encoder ticks measured as an input, you will save all the calculations in loop. You cannot make errors in calculations that are not done... and loop will run faster (but that might not really be the problem...).
The reason im thinking to use some other quantity as. A basis for feedback is because when i use linear speed feedback it works, but for everything same ,if i use angular speed as feedback, evdn though the setpoint is comparable to linear speed setpoint the system doesnt work.