Open joaovictor78 opened 4 years ago
Trying to see in your code what is wrong. Just an idea about what you might need to look at.
You declared the output limits between -255 and 255, later you use it in an AnalogWrite. The AnalogWrite can only use values between 0 and 255. However in the Loop() you check for Output>0, so in reality you never put out any negative value. But if your motor are running backwards, you will wind up the output value to -255 anyway and probably you need some time before the output value reaches 0 and above.
It is common and sometimes necessary to declare global variables used in interrupt routines as volatile. That is to prevent the compiler to "optimize" the code for them. ISR functions need them untouched by the compiler. I am not sure how Arduino IDE handles that. So for an example: volatile int pulseNumber;
You set the sample time at 1ms but have an delay in code at 100ms. Compute() will be executed every 100ms but the kp, ki and kd is set for a 1ms cycle. (i.e. about 100x out of sync).
Check the direction of the PIDs working. i.e. if you want a constant setpoint (as rpm), and the measured value gets below that setpoint. You need to increase the output to raise the measured value (pulses). Are your PID working in the right direction?
If you not familiar with control loops like PIDs, try to eliminate the D part by setting it to 0. D is usually only needed to match sudden changes on the measured value. In this case. like if you have a variable load on the DC motor that you need to compensate for. Most simple control loops only need the P and I part. At least try without it until you are pretty happy with the control and maybe fine adjust the PID behaviour with the D later.
Also consider the use of sampling time and the values you collect (pulses) during that period. If too short sampling time, the pulses values will be few and one small difference might mean a big difference in the actual value (as a rpm value). Few pulses during sample times will mean low resolution in the sensor signal and hence give you harder to control object. Many pulses will give you the opportunity to react smoothly on changes and give you a better resolution. However making the sample time long enough for getting better resolution might impact poorly on the number of times you really need to adjust the output.
But to help you better there is a some information lacking that would be useful. Parameters of the mechanical part / sensors etc. For example, at what speed are you trying to run the DC motor? at what speed will the DC motor run on its nominal voltage? the encoder, how many pulses per rotation? Can sample time and cycle times be adjusted to match the pulses to get a higher resolution? Can the encoder be set to quadruple count to get a higher resolution?
You can also try to write values to the serial monitor or serial plotter of the arduino. Cut and paste the serial monitor values to a spreadsheet and use it to make graphs. Or just check the values in the graph of the plotter. To get a hint of how the PID works. You probably want to see the Input, Setpoint and Output. But the error and errsum might be good to keep an eye onto too. Maybe even the pulseNumber.
I hope this will help you finding your problem.
I work as an control system engineer, so I know some about control loops. But I work with professional industrial PLC's and their builtin PID controllers. Not used to see PIDs in embedded environments. In industrial (normal) environments 100ms is a good value for what we call fast loops, like pressure and flow controls. And we usually use it for temperatures too, but in most cases the temperature is slow and would be able to do on 1s cycles too without any problems. Notice I said normal, there is always exceptions now and then.
Trying to see in your code what is wrong. Just an idea about what you might need to look at.
You declared the output limits between -255 and 255, later you use it in an AnalogWrite. The AnalogWrite can only use values between 0 and 255. However in the Loop() you check for Output>0, so in reality you never put out any negative value. But if your motor are running backwards, you will wind up the output value to -255 anyway and probably you need some time before the output value reaches 0 and above.
It is common and sometimes necessary to declare global variables used in interrupt routines as volatile. That is to prevent the compiler to "optimize" the code for them. ISR functions need them untouched by the compiler. I am not sure how Arduino IDE handles that. So for an example: volatile int pulseNumber;
You set the sample time at 1ms but have an delay in code at 100ms. Compute() will be executed every 100ms but the kp, ki and kd is set for a 1ms cycle. (i.e. about 100x out of sync).
Check the direction of the PIDs working. i.e. if you want a constant setpoint (as rpm), and the measured value gets below that setpoint. You need to increase the output to raise the measured value (pulses). Are your PID working in the right direction?
If you not familiar with control loops like PIDs, try to eliminate the D part by setting it to 0. D is usually only needed to match sudden changes on the measured value. In this case. like if you have a variable load on the DC motor that you need to compensate for. Most simple control loops only need the P and I part. At least try without it until you are pretty happy with the control and maybe fine adjust the PID behaviour with the D later.
Also consider the use of sampling time and the values you collect (pulses) during that period. If too short sampling time, the pulses values will be few and one small difference might mean a big difference in the actual value (as a rpm value). Few pulses during sample times will mean low resolution in the sensor signal and hence give you harder to control object. Many pulses will give you the opportunity to react smoothly on changes and give you a better resolution. However making the sample time long enough for getting better resolution might impact poorly on the number of times you really need to adjust the output.
But to help you better there is a some information lacking that would be useful. Parameters of the mechanical part / sensors etc. For example, at what speed are you trying to run the DC motor? at what speed will the DC motor run on its nominal voltage? the encoder, how many pulses per rotation? Can sample time and cycle times be adjusted to match the pulses to get a higher resolution? Can the encoder be set to quadruple count to get a higher resolution?
You can also try to write values to the serial monitor or serial plotter of the arduino. Cut and paste the serial monitor values to a spreadsheet and use it to make graphs. Or just check the values in the graph of the plotter. To get a hint of how the PID works. You probably want to see the Input, Setpoint and Output. But the error and errsum might be good to keep an eye onto too. Maybe even the pulseNumber.
I hope this will help you finding your problem.
I work as an control system engineer, so I know some about control loops. But I work with professional industrial PLC's and their builtin PID controllers. Not used to see PIDs in embedded environments. In industrial (normal) environments 100ms is a good value for what we call fast loops, like pressure and flow controls. And we usually use it for temperatures too, but in most cases the temperature is slow and would be able to do on 1s cycles too without any problems. Notice I said normal, there is always exceptions now and then.
@peberg65 Thank you very much for the tips and for answering me. I'm 17 and over in Brazil, last year I spent a year working on a robot and I couldn't compete because I couldn't get the PID to work. I'm using a dc motor with an encoder and my goal is to run very slowly always with a constant speed, for example, a rotation measured by the encoder equal to 40.
const byte Encoder_C1 = 2; const byte Encoder_C2 = 8; byte Encoder_C1Last; int pulseNumber; boolean direction_m; //variaveis for PID unsigned long lastTime; double Input, Output, Setpoint; double errSum, lastErr; double kp, ki, kd; int SampleTime = 1000; //1 sec double outMin, outMax; //*** //Pinos de ligacao ponte H L298N
define pino_motor1 10
define pino_motor2 9
void setup() { Serial.begin(57600); SetOutputLimits(-255, 255); SetSampleTime(1); SetTunings(5, 0.5, 0.01); //Definicao pinos ponte H pinMode(pino_motor1, OUTPUT); pinMode(pino_motor2, OUTPUT); //Definicao do encoder pinMode(Encoder_C1, INPUT); pinMode(Encoder_C2, INPUT); attachInterrupt(0, encoder, CHANGE); }
void loop() { Serial.println(pulseNumber); Serial.println(Output);
if(Output > 0){ analogWrite(pino_motor1, Output); analogWrite(pino_motor2, 0); } Compute(); pulseNumber = 0; delay(100); }
void encoder() { int Lstate = digitalRead(Encoder_C1); if(!Encoder_C1Last && Lstate){ int val = digitalRead(Encoder_C2); if(!val && direction_m){ direction_m = false; } else if(val && !direction_m){ direction_m = true; }
}
Encoder_C1Last = Lstate; if(direction_m) pulseNumber++; else pulseNumber--; } void Compute() { unsigned long now = millis(); int timeChange = (now - lastTime); if(timeChange>=SampleTime) { Setpoint = 25; Input = pulseNumber; double error = Setpoint - Input; errSum += error; double dErr = (error - lastErr);
} }
void SetTunings(double Kp, double Ki, double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000; kp = Kp; ki = Ki * SampleTimeInSec; kd = Kd / SampleTimeInSec; }
void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime; ki *= ratio; kd /= ratio; SampleTime = (unsigned long)NewSampleTime; } } void SetOutputLimits(double Min, double Max) { if(Min > Max) return; outMin = Min; outMax = Max; if(Output > outMax) Output = outMax; else if(Output < outMin) Output = outMin; if(errSum> outMax) errSum= outMax; else if(errSum < outMin) errSum= outMin; }