@DaveX Thanks for your help! Your changes are working, but now the problem is that it takes time to achieve the setpoint rpm. for example if i set it to 80 then at startup it will fluctuate at 67 and 90 without any change between the two numbers(and this 2 numbers could be any during each new start), and the motor jerks too much at this time, there's lot of vibration and after like 30s to 1min then it achieves 80 rpm and varies from 75 to 85 but this is only for little while and the vibration again increases as the error in rpm increases, it doesn't remain at 75 to 85 rpm where the vibration is negligible, the rpm error again kicks in after settling and becomes something like 70 and 90, switching between these two values(and again these two numbers could be any) and again the vibration starts and it's a lot and it never settles again remains like this(tested for uptill 5 mins). So what could be the possible solution? Do I need to tune the PID again? because I have already done PID tuning; if not, then what else could be the problem?
Yes, you'd need to tune the PID again with this computation scheme, since my code changes the scales of kI and kD. Or if it was well tuned with the ki = 0.1outputUnits/(RPMerror* millisecond)
and kd=0.082 outputUnits/(RPMerror/millisecond)
, try ki=60 outputUnits/(RPMerror*second)
and kd=0.000082outputUnits/(RPMerror/second)
Personally, I'd start with kp=1, ki=0, and kd=0 and manually tune from there.
Hey, yes it works fine after a little bit of tuning, but now the problem is that when I tried to control the speed of the motor with a joystick, there's some problem, and I don't know what's wrong. For example, I tried to edit the loop function with this piece of code:
setPoint = xValue; //xValue is the incoming x-axis value of the joystick
//Serial.println(currentRPM); // Print RPM1 value
if (setPoint < 116 && setPoint >= 1) {
mappedsetPoint = map(xValue, 116, 0, 0, maxMotorRPM); // Reverse mapping
Serial.println(mappedsetPoint);
digitalWrite(dirPin, LOW); // Set direction to reverse
output = computePID();
analogWrite(pwmPin, output); // Write new PID output to the pin
} else if (setPoint > 121 && setPoint <= 250) {
mappedsetPoint = map(xValue, 120, 255, 0, maxMotorRPM); // Forward mapping
Serial.println(mappedsetPoint);
output = computePID();
digitalWrite(dirPin, HIGH); // Set direction to forward
analogWrite(pwmPin, output);
} else if (setPoint > 115 && setPoint < 121) {
mappedsetPoint = 0; // Set motor speed to 0 at idle state
output = computePID();
analogWrite(pwmPin, output);
}
So the joystick output is from 0-255, and the middle value fluctuates around 117-119. So just to be on the safe side I'm taking values greater than 121and less than 116 to rotate the motor forward and backward respectively by mapping values from 116-0 to pwm signal of 0-255 for reverse direction and same for values>121 in other direction. if the joystick value is in the middle than the motor should be at 0rpm.
Problem- When i started testing the code one by one then at first stage with only 1 if statement the motor speed control worked fine, this one
setPoint = xValue;
//Serial.println(currentRPM); // Print RPM1 value
if (setPoint < 116 && setPoint >= 1) {
mappedsetPoint = map(xValue, 116, 0, 0, maxMotorRPM); // Reverse mapping
Serial.println(mappedsetPoint);
digitalWrite(dirPin, LOW); // Set direction to reverse
output = computePID();
analogWrite(pwmPin, output); // Write new PID output to the pin
}
But when i added second else if statement(leaving out the 3rd else if statement) then i could no longer control the motor speed...It started to behave abnormally. So any fix for this problem?
It's hard to guess without the full code, but I'd make sure you have full coverage on setPoint/xValue, like:
if (xValue <116) {...}
else if (xValue > 221 ) {...}
else {...}
It isn't clear what you intend to happen if setPoint ==0
or setPoint > 250
I often add a change-detection bit around code like that to limit output and make it easier to see changes and what happens:
if (xValue != lastXValue){
... // do stuff
...// print long debug report on input, output, and changes
lastXValue = xValue;
}
Filtering with change detection speeds up normal operations (when no change happens) and makes it easier to print details about the changes.
In that case i want to limit the outputs to the max which is 250 and minimum 1. So if it's 251 then it should become 250. But that would be the ending task i guess the limiting part but for now the motor isn't operating smoothly as it's supposed to. I'm correctly receiving xValues, I can see that using println. When I use single if statement speed control works properly but fails in multiple else if statement, so there's some programming error i guess.
I'd do the constraint separately, either as extra if clauses, or apply the constraint as postprocessing.
As is, if the xValue is at either of the limits of travel, it doesn't appear to do the computePID() or analogWrite() steps.
Since your motor runs both directions, I'd try to make the PID output continuous across zero, and transform the output sign and magnitude to the DIR and PWM appropriately. If the direction is controlled only by the setpoint and not by the output, then the PID can't handle the discontinuity across zero.
I might map the joystick to a +/- RPM, and then translate that to speed and direction. Completely untested snippet of code:
setPoint = xValue; //xValue is the incoming x-axis value of the joystick
//Serial.println(currentRPM); // Print RPM1 value
if (setPoint < 116 ) {
mappedsetPoint = map(xValue, 116, 1, 0, -maxMotorRPM); //
Reverse mapping
} else if (setPoint > 121 ) {
mappedsetPoint = map(xValue, 120, 250, 0, maxMotorRPM); // Forward mapping
} else {
mappedSetPoint = 0;
}
mappedSetPoint = constrain(mappedSetPoint,-maxMotorRPM,maxMotorRPM)
Serial.println(mappedsetPoint);
output = computePID();
digitalWrite(dirPin, output<0 ? LOW : HIGH); // Set direction to reverse
analogWrite(pwmPin, abs(output)); // Write new PID output to the pin
Tested it, but it doesn't rotate the motor now...the motor driver just makes a humming sound
It is an untested snippet. I'm not sure how your snippet of code of #23 fits into the whole program.
Here's the full code
#include <TimerOne.h>
const int encoderPinA = 2;
const int pwmPin = 9; // Motor PWM
const int dirPin = 51; // Motor DIR
const int maxMotorRPM = 520; // Maximum RPM of your motor
float counter = 0; // Counter variable for counting pulses
int RPM1, currentRPM;
unsigned long currentTime, previousTime;
double elapsedTime;
//receiving serial data(<x,y>format) from esp32 to mega - variables
const byte numChars = 32;
char receivedChars[numChars];
boolean newData = false;
int xValue = 0;
int yValue = 0;
double kp = 0.001; // Proportional gain
double ki = 0.6; // Integral gain
double kd = 0.000082; // Derivative gain
double error;
double lastError = 0; // Initialize lastError to 0
double input, output;
int setPoint;
int mappedsetPoint = 0;
double cumError = 0; // Initialize cumError to 0
double rateError;
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(pwmPin, OUTPUT);
pinMode(dirPin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(encoderPinA), encoder, RISING);
Timer1.initialize(250000); // Set timer interrupt interval to 0.25s
Timer1.attachInterrupt(computerpm); // Attach timer interrupt service routine
digitalWrite(dirPin, LOW); // Set direction
}
void loop() {
setPoint = xValue;
if (setPoint < 116) {
mappedsetPoint = map(xValue, 116, 0, 0, maxMotorRPM); //Reverse mapping
digitalWrite(dirPin,LOW);
output = computePID();
analogWrite(pwmPin, output); // Write new PID output to the pin
} else if (setPoint > 121) {
mappedsetPoint = map(xValue, 120, 250, 0, maxMotorRPM); // Forward mapping
digitalWrite(dirPin,HIGH);
output = computePID();
analogWrite(pwmPin, output); // Write new PID output to the pin
} else {
mappedsetPoint = 0;
output = computePID();
analogWrite(pwmPin, output); // Write new PID output to the pin
}
}
void encoder() {
counter++;
}
double computerpm() {
RPM1 = (counter / 96) * 240; // Calculate RPM1 from the counter value
counter = 0;
previousTime = millis(); // Update previousTime here
return RPM1;
}
double computePID() {
currentTime = millis(); // Get current time
elapsedTime = (double)(currentTime - previousTime); // Compute time elapsed from previous computation
if (elapsedTime < 100) return;
previousTime = currentTime; // Update previousTime
error = mappedsetPoint - RPM1; // Determine error
cumError += error * elapsedTime / 1000.0; // Compute integral
rateError = (error - lastError) / elapsedTime * 1000.0; // Compute derivative
double out = kp * error + ki * cumError + kd * rateError; // PID output
// Undo integral if out of limits
if (out > 255) {
cumError -= error * elapsedTime / 1000.0;
out = 255;
}
if (out < 0) {
cumError -= error * elapsedTime / 1000.0;
out = 0;
}
lastError = error; // Remember the current error
return out; // Return PID output (PWM)
}
The xValue is correctly received so no need for changes in that. The problem arises when I introduce more than 1 "if" statement for speed control, with one if statement it works fine but it becomes useless since I'm unable to control the speed in other directions
So xValue = 0;
Its hard for me to tell how it responds if the setpoint doesn't change and I don't have a similar motor and encoder hooked up to the device.
The things that stand out as problematic are the unchanging setpoint, the discontinuity on direction changes, and the bare return in computePID with the using of the return values in code like this:
Since the return value isn't specified most of the time, most of the calls will return some sort of unexpected garbage.
I'd think about changing the limits on the PID output from (0 to 255) to (-255 and 255) and then changing the direction pin based on the sign of the output and the PWM based on the absolute value of the output. Then the PID can control the motor speed smoothly and sanely across the setpoint deadband and direction changes.
In the Wokwi simulator I tried to look at what your code was doiing and made some changes:
// https://wokwi.com/projects/380882887246419969
// for https://forum.arduino.cc/t/implementing-pid-control-using-timerone-library/1179916/29
#include <TimerOne.h>
const int encoderPinA = 2;
const int pwmPin = 9; // Motor PWM
const int dirPin = 51; // Motor DIR
const int maxMotorRPM = 520; // Maximum RPM of your motor
float counter = 0; // Counter variable for counting pulses
int RPM1, currentRPM;
unsigned long currentTime, previousTime;
double elapsedTime;
//receiving serial data(<x,y>format) from esp32 to mega - variables
const byte numChars = 32;
char receivedChars[numChars];
boolean newData = false;
int xValue = 0;
int yValue = 0;
double kp = 0.001; // Proportional gain
double ki = 0.6; // Integral gain
double kd = 0.000082; // Derivative gain
double error;
double lastError = 0; // Initialize lastError to 0
double input, output;
int setPoint;
int mappedsetPoint = 0;
double cumError = 0; // Initialize cumError to 0
double rateError;
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(pwmPin, OUTPUT);
pinMode(dirPin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(encoderPinA), encoder, RISING);
Timer1.initialize(250000); // Set timer interrupt interval to 0.25s
Timer1.attachInterrupt(computerpm); // Attach timer interrupt service routine
digitalWrite(dirPin, LOW); // Set direction
}
void loop() {
xValue = analogRead(A0) / 4; // for sim in wokwi
setPoint = xValue;
static int lastSetPoint = 0;
if (setPoint != lastSetPoint) {
if (setPoint < 116) {
mappedsetPoint = map(xValue, 116, 0, 0, -maxMotorRPM); //Reverse mapping
} else if (setPoint > 121) {
mappedsetPoint = map(xValue, 120, 250, 0, maxMotorRPM); // Forward mapping
} else {
mappedsetPoint = 0;
}
}
output = computePID();
digitalWrite(dirPin, output < 0 ? LOW : HIGH);
analogWrite(pwmPin, abs(output)); // Write new PID output to the pin
report();
}
void encoder() {
counter++;
}
double computerpm() {
RPM1 = (counter / 96.0) * 240; // Calculate RPM1 from the counter value
counter = 0;
previousTime = millis(); // Update previousTime here
return RPM1;
}
double computePID() {
static double out = 0;
currentTime = millis(); // Get current time
elapsedTime = (double)(currentTime - previousTime); // Compute time elapsed from previous computation
if (elapsedTime < 100) return out;
previousTime = currentTime; // Update previousTime
error = mappedsetPoint - RPM1; // Determine error
cumError += error * elapsedTime / 1000.0; // Compute integral
rateError = (error - lastError) / elapsedTime * 1000.0; // Compute derivative
out = kp * error + ki * cumError + kd * rateError; // PID output
// Undo integral if out of limits
if (out > 255) {
cumError -= error * elapsedTime / 1000.0;
out = 255;
}
if (out < -255) {
cumError -= error * elapsedTime / 1000.0;
out = -255;
}
lastError = error; // Remember the current error
return out; // Return PID output (PWM)
}
void report(void) {
static unsigned long last = 0;
const unsigned long interval = 500;
unsigned long now = millis();
if (now - last < interval) return;
last = now;
// Serial.print(now);
// Serial.print("ms");
Serial.print("xValue:");
Serial.print(xValue);
Serial.print(", setPoint: ");
Serial.print(setPoint);
Serial.print(", mappedsetPoint:");
Serial.print(mappedsetPoint);
Serial.print(", output:");
Serial.print(output);
Serial.print(", dir:");
Serial.print(output>0);
Serial.print(", pwm:");
Serial.print(abs(output));
Serial.println();
}
The most helpful modification was adding the 'report()
' function to see how things were working. I then added a pot to set xValue to make changes in the setpoint. Then I noticed the effects of the bare return in the computePID() function & fixed that with a static variable. I then changed the PID limits to handle the change in direction and the setpoint mapping to match.
It is a bit different than your code, but it seems like it might work reasonably well across direction changes. Without the motor or encoder, the measured motor speed remains zero, and the PID drives the PWM towards the limits.
There are few problems with this. So I'm getting the xValue constantly updated from the joystick which ranges from 0-255 and around 118-119 being the joystick idle values. So when the joystick is at idle, the setpoint is 0 which is correct according to the report(), however, the pwm is -255(max speed in dir=0) but that's not what I expect. Here's the conditions that I would like to meet:
-I want the motors to be stopped at idle values 116-121
-motors should move in dir=1 when joystick is moved forward, i.e when the xValue becomes >121 and vice-versa for backward movement.
-And as the joystick value returns to idle after being moved, it should decelerate accordingly.(just like controlling a rc car).
Since there isn't a motor or encoder in the simulation, the feedback between the PWM output and the RPM input is broken.
Because of the missing encoder in the simulation, the RPM is always detected as zero, no matter what the PID output is.
Since the RPM doesn't change from zero, when the setpoint is above zero, the PID will try to increase the output until the RPM matches the setpoint, and will increase it until it hits the limit. It acts similar for negative setpoints, driving the PWM towards the lower limit. If you move the setpoint from a negative value (which drove the output towards -255) into a range where the setpoint=0, then, because of the broken feedback giving RPM=0 in the simulation, the error between setpoint and the RPM is setpoint-RPM = 0-0 = 0
. When the error == 0, this chunk:
...doesn't have a reason to change the output from whatever it was.
In particular for the integral term:
cumError += error * elapsedTime / 1000.0
cumError += 0.0 * elapsedTime / 1000.0
cumError += 0.0
and then after a timestep or two the summing calculateion becomes:
out = kp * error + ki * cumError + kd * rateError; // PID output
// is
out = 0 * error + ki * cumError + 0 * rateError; // PID output
out = 0 + ki * cumError; // PID output
The output sticking at whatever it was just before setpoint-RPM = 0
is an expected behavior of PIDs.
Since the sim doesn't have your motor or encoder, the lack of feedback will drive the PID outputs to the limits.
For your system with a motor and an encoder in the feedback loop, it would behave differently at pwm=255, producing a positive (520?) RPM and if the setpoint was 0, then the error would be error = mappesetPoint - RPM1=0-520 = -520
and try to reduce power to slow down the motor. Maybe think of the motor needing full power to keep the system at zero RPM as keeping a heavily loaded cart from rolling backwards down an incline: PWM = 255, setpoint = 0, RPM = 0.
If you were driving a system with a lot of inertia relative to the motor strength, like an old-school turntable, the PID could try to reverse the polarity to stop the motor more quickly than merely cutting the power.
...Or upon reading again, I might have misunderstood:
Is the "this" that you are having a few problems with not the simulation, but instead your actual system with working, physical feedback between the PWM and the Encoder RPM?
Yes sorry for not being too specific. I tried your actual code in my hardware system, motor with encoder,but as soon as I switch on the system the motor achieves full speed no matter in what direction I move the joystick, it doesn't influence the pwm and motor keeps rotating at max speed, according to the report, i can see the xValue update according to the joystick... Everything updates except for pwm value and direction,unable to control the motor
What does the error do? If there's some sign wrong somewhere max speed might be going the wrong way making the error larger.
So I tested again after modifying the PID values, Since you changed the pwm from -255-255, the current PID values were taking too much time reach set point, so after modification now I'm able to control the change in direction but I can't control the speed as desired. For example if i move the joystick half way forward and let it go, the joystick value becomes 119-121 the idle value but the pwm keeps on increasing until it reaches 255
And samething happens if I pull the joystick backwards, this time it drops the value from +255 to -255(but - sign cant be seen as abs() is used) again but in reverse direction.
So I'm able to control the direction, but the the motor just accelerates towards the direction of the joystick untill it reaches max speed(pwm=255) and remains there until the direction is changed either from 0 to 1 or vice-versa. Can't maintain a constant speed, So with the current program the speed will be either 255 or -255.
Here's a snapshot of the report when the joystick was moved and let go, the joystick value returned to normal but pwm kept rising till 255 and remained there. And the error also remains the same at 255 pwm.
From the report, it looks like error is constant around -69 and output is constant -255, which maps to dir=0 and pwm=255.
Based on your report numbers, I think there's a sign error somewhere. If the mappedSetPoint = 0, the output is -255 and the error = -69, then RPM1 must be 69, and this bit:
...should be incrementing by -69.0*elapsedTime /1000 = -17
each time, and reducing the output by -17*0.6=-10.2
Seems like a sign/dirPin/Polarity/direction error somewhere.
What is your code now?
What was the RPM?
Oh, your encoder is counting only pulses and not direction, so it doesn't give +/-RPM. with the sign changing when it spins forwards or reverse. That will cause a problem at the discontinuity when either the motor or the setpoint switches direction-- maybe assume the RPM direction matches the dirPin?
And also, because of
then
...does not return a float into RPM1, it just sets the global int RPM1 as an integer. Since counter is a 'double
', it looks like it will do the math in double, so it shouldn't fail with integer math truncation like (95/96)*240 = 0
I added a simulated motor and encoder to the Wokwi
// https://wokwi.com/projects/380882887246419969
// for https://forum.arduino.cc/t/implementing-pid-control-using-timerone-library/1179916/29
#include <TimerOne.h>
const int encoderPinA = 2;
const int pwmPin = 9; // Motor PWM
const int dirPin = 51; // Motor DIR
const int maxMotorRPM = 520; // Maximum RPM of your motor
float simSpeed =0;
float counter = 0; // Counter variable for counting pulses
int RPM1, currentRPM;
unsigned long currentTime, previousTime;
double elapsedTime;
//receiving serial data(<x,y>format) from esp32 to mega - variables
const byte numChars = 32;
char receivedChars[numChars];
boolean newData = false;
int xValue = 0;
int yValue = 0;
double kp = 0.001; // Proportional gain
double ki = 0.6; // Integral gain
double kd = 0.000082; // Derivative gain
double error;
double lastError = 0; // Initialize lastError to 0
double input, output;
int setPoint;
int mappedsetPoint = 0;
double cumError = 0; // Initialize cumError to 0
double rateError;
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(pwmPin, OUTPUT);
pinMode(dirPin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(encoderPinA), encoder, RISING);
Timer1.initialize(250000); // Set timer interrupt interval to 0.25s
Timer1.attachInterrupt(computerpm); // Attach timer interrupt service routine
digitalWrite(dirPin, LOW); // Set direction
}
void loop() {
xValue = analogRead(A0) / 4; // for sim in wokwi
setPoint = xValue;
static int lastSetPoint = 0;
if (setPoint != lastSetPoint) {
if (setPoint < 116) {
mappedsetPoint = map(xValue, 116, 0, 0, -maxMotorRPM); //Reverse mapping
} else if (setPoint > 121) {
mappedsetPoint = map(xValue, 120, 250, 0, maxMotorRPM); // Forward mapping
} else {
mappedsetPoint = 0;
}
}
output = computePID();
digitalWrite(dirPin, output < 0 ? LOW : HIGH);
analogWrite(pwmPin, abs(output)); // Write new PID output to the pin
simMotor();
report();
}
void encoder() {
counter++;
}
void computerpm() {
counter = simSpeed * 96 / 240 ;// override with simMotor speed
RPM1 = (counter / 96.0) * 240; // Calculate RPM1 from the counter value
counter = 0;
previousTime = millis(); // Update previousTime here
return RPM1;
}
double computePID() {
static double out = 0;
currentTime = millis(); // Get current time
elapsedTime = (double)(currentTime - previousTime); // Compute time elapsed from previous computation
if (elapsedTime < 100) return out;
previousTime = currentTime; // Update previousTime
error = mappedsetPoint - RPM1; // Determine error
cumError += error * elapsedTime / 1000.0; // Compute integral
rateError = (error - lastError) / elapsedTime * 1000.0; // Compute derivative
out = kp * error + ki * cumError + kd * rateError; // PID output
// Undo integral if out of limits
if (out > 255) {
cumError -= error * elapsedTime / 1000.0;
out = 255;
}
if (out < -255) {
cumError -= error * elapsedTime / 1000.0;
out = -255;
}
lastError = error; // Remember the current error
return out; // Return PID output (PWM)
}
void report(void) {
static unsigned long last = 0;
const unsigned long interval = 500;
unsigned long now = millis();
if (now - last < interval) return;
last = now;
// Serial.print(now);
// Serial.print("ms");
Serial.print("xValue:");
Serial.print(xValue);
Serial.print(", setPoint: ");
Serial.print(setPoint);
Serial.print(", mappedsetPoint:");
Serial.print(mappedsetPoint);
Serial.print(", output:");
Serial.print(output);
Serial.print(", dir:");
Serial.print(output>0);
Serial.print(", pwm:");
Serial.print(abs(output));
Serial.print(", simSpeed:");
Serial.print(simSpeed);
Serial.print(", RPM1:");
Serial.print(RPM1);
Serial.println();
}
void simMotor(void){
// simulate a motor based on PWM and DIR
// periodically smooth speed towards the full-speed RPM
static float speed = 0.0;
static unsigned long last = 0;
unsigned long now = millis();
if (now -last < 100) return;
last = now;
float volts = 1.0 * abs(output)/255 * (digitalRead(dirPin)==HIGH? 1 : -1) ;
const float VToRPM = maxMotorRPM/1.0;
speed += 0.1 * (volts * VToRPM - speed);
simSpeed = speed;
}
That code seems to work with my simulated motor and encoder hack.
Be sure to check dirPin's logic and the polarities and directions everywhere, and maybe use the dirPin value to set the sign of the RPM with something like this:
RPM1 = digitalRead(dirPin) ==1 ? RPM1 : -RPM1;
Yes this was a fix to that problem of pwm not returning to 0. But the problem of speed control still reamains the same, if I move the joystick forward...the pwm quickly accelerates to 255 and same for backward movement, pwm doesn't stay constant for the given xValue.
Another problem is that it takes a lot of time to come to a stop, from the moment I let go off the joystick, it takes roughly about 20-30s for the motor to come to a stop, so what can I do about that? It reaches the initial setpoint very quickly but the problem is stopping part, if you suggest tuning the pi parameters of pid then I'm afraid increasing the PI values increase the overshoot which I don't want in my system.
And 3rd problem is that once it comes to a stop with error 0 then the pwm value is around 7-8 which is a noise and my motor is producing humming sound due to that, so how can I I remove that noise so that whenever the motor is called to stop the pwm signal is clear 0.
Note: Initially when I upload the code then the pwm value is 0 and thus no sound from the motor, after I control it with joystick and let go then it introduces introduces that error of 7-8 in pwm, hence the constant humming sound when motor is stationary.
As you can see in the snapshot below, there is a pwm signal even though the setpoint and error is 0;
Using the output direction to identify the direction of spin is a hack, and could have poor behavior around zero pwm/low speed. If the PID calls for adjustments that change the sign of output, it changes pinDir and therefore the sign of the input RPM, even if inertia in still keeping the system moving the same direction. If you don't have any direction info from the motor, I'm not sure what would be a better fix.
Some of the oscillations changes could be due to timing--you are measuring RPM every 250ms but adjusting every 100ms, so you could be adjusting based on old data.
I would indeed suggest tuning--If a simple pot-controlled motor would reach a constant speed in much less than 30s, a properly tuned PID should be able to perform similarly.
For the humming at zero, you could add a deadband filter or mask to the output and only send pwm values above some threshold:
analogWrite(pwmPin, abs(output)>10? abs(output) : 0 );
So I relooked at the code, I was using incorrect encoder counts/rev, so I changed it and now I'm able to control the speed and direction correctly.
And also please suggest an efficient way to control all 4 motors, I tried to make separate functions for each movement but it didn't go well, here's what I tried to do:
void loop() {
setPoint = xValue;
static int lastSetPoint = 0;
if (setPoint != lastSetPoint) {
if (setPoint < 116) {
mappedsetPoint = map(xValue, 115, 0, 0, -maxMotorRPM); //Reverse mapping
forward();
} else if (setPoint > 122) {
mappedsetPoint = map(xValue, 123, 255, 0, maxMotorRPM); // Forward mapping
backward();
} else if (xValue > 115 && xValue < 122) {
mappedsetPoint = 0;
stop();
}
}
report();
}
And here's the individual functions:
void forward(){
output = computePID();
digitalWrite(dirPin, HIGH);
analogWrite(pwmPin, abs(output)>20? abs(output) : 0 ); // Write new PID output to the pin
}
void backward(){
output = computePID();
digitalWrite(dirPin, LOW);
analogWrite(pwmPin, abs(output)>20? abs(output) : 0 ); // Write new PID output to the pin
}
void stop(){
output = computePID();
digitalWrite(dirPin, output < 0 ? LOW : HIGH);
//digitalWrite(dirPin, HIGH);
analogWrite(pwmPin, abs(output)>20? abs(output) : 0 ); // Write new PID output to the pin
}
Everything else in the code remains same. When I ran this code the motor started to behave eratically maybe because I shifted the computePID() inside the if statements, so What other approach can I use?
The PID helps work with varying disturbances, for instance if your cart would roll down an incline if the power was off, the PID with a setpoint of 0 might need to add some power to balance against gravity. For example, if forward is up an incline, and you set the setpoint to 0RPM, the PID might determine that it needs a PWM=20 in the forward direction to hold the speed of rolling back down the incline to 0, and if you picked up the cart and turned it around, you might need PWM=20 in the reverse direction. If the PID is working right, it should handle any tilting or pushing automatically.
Cool, you have a AB encoder--you can just use the A interrupt and read the B signal when A triggers to get the direction. Sometimes A&B are labelled CLOCK and DATA, and you think of it as DATA being the direction at each CLOCK-rising edge:
void encoder() {
if(digitalRead(encoderPinB)==HIGH){
counter++;
} else {
counter--;
}
}
With something like that, counter goes negative in one direction and positive in the other, and RPM should follow proportionally.
Definitely use the encoderPinB signal. It can simplify much of the logic around zero.