2
\$\begingroup\$

I have a PID controlling a DC motor. I am attempting to control the speed of the motor very precisely. My controller allows me to change the direction of the motor and give it a pwm for speed. Therefore, I have a PID that has a plus and minus maximum and minimum. In order to speed up the device and slow down the device quick enough. The output of the PID is for the pwm and therefore is a absolute value of the PID, just changing a direction pin when PID < 0. I am using the opposite direction of the motor only as a braking system. Thus the motor should always be going in one direction but should slow it self down faster by applying reverse torque.

I am writing C firmware in MCUXpresso. The graphs come from sending data over UART to an Arduino to graph data easily.

My problem is that sometimes when the process variable hits 0 or close to it, the PID inverts and needs to go negative and thus spins the motor at full speed in the opposite direction. The two pictures below show the certain cases of when it happened. The red line is the set point and the blue line is the process variable.

The code controlling the device and PID is below.

I am having a hard time understanding why the PID would run away like this. Any help would be amazing. Thank you!

enter image description here

enter image description here

Main Control

int dir = FORWARD; //Controls direction of motor

motorPID.setpoint = vehicleSpeed;

motorPID.input = SM_GetRPM();

motorPID.input = motorPID.input * speedConversion;

UART_SendPID((uint8_t)motorPID.input, (uint8_t)motorPID.setpoint);

PID_Compute(&motorPID);

if(motorPID.output < 0){
    dir = BACKWARD;
}

if(motorPID.setpoint == 0){
    motorPID.output = 0;
}

if(motorPID.input > 60){
    MC_SetMotorSpeed(0, dir);
    int test = 0;
}

MC_SetMotorSpeed(abs(motorPID.output),dir);

PID Code

//Find all error variables
self->lastError = self->error;
double input = self->input;                         //Only so input can't change during compute
self->error = self->setpoint - input;
self->integral += self->error;
double derivative = self->error - self->lastError;

//Anti-integral Windup
if(self->integral > self->Outmax)
    self->integral = self->Outmax;
else if(self->integral < self->Outmin)
    self->integral = self->Outmin;

//Calculate PID
self->output = (self->Kp*self->error) + (self->Ki * self->integral) + (self->Kd * derivative);

//Set limits
if(self->output > self->Outmax)
    self->output = self->Outmax;
else if(self->output < self->Outmin)
    self->output = self->Outmin;

EDIT: Turns out this was a combined error of the problem described and a hardware issue.

\$\endgroup\$
  • \$\begingroup\$ Please post the code as text rather than an un-cropped screengrab of the code. That way it will be legible and we can copy and paste it for editing into the answers. Make sure you use the code tag button and format / format it correctly. You might also mention what the development environment and programming language is. \$\endgroup\$ – Transistor Nov 13 '18 at 18:06
  • \$\begingroup\$ Sudden huge jumps in a signal like this are often due to problems with integer overflow. If there's any integers in the signal path (MC_SetMotorSpeed, perhaps?) then inspect them or show them. \$\endgroup\$ – TimWescott Nov 13 '18 at 18:24
  • \$\begingroup\$ Your PID code looks OK, except that you should be incrementing self->integral with error * self->Ki (and not doing that multiplication in the self->output line), or you should be comparing self->integral * self->Ki with self->Outmin and self->Outmax. \$\endgroup\$ – TimWescott Nov 13 '18 at 18:25
  • \$\begingroup\$ @TimWescott Limiting the integral after multiplying it by Ki looks like this may have been the problem. I appreciate your help and it I should have caught that before. Thank you. If you could put your comment in an answer, it would be the solution for now. \$\endgroup\$ – Drew Fowler Nov 13 '18 at 19:04
1
\$\begingroup\$

Try changing the line that reads

self->integral += self->error;

to

self->integral += self-> Ki * self->error;

and match that by changing the line that reads

self->output = (self->Kp*self->error) + (self->Ki * self->integral) + (self->Kd * derivative);

to

self->output = (self->Kp*self->error) + self->integral + (self->Kd * derivative);

That will scale the integral term correctly for your integrator limiting step.

\$\endgroup\$
0
\$\begingroup\$

judging by your graphs, I dont think you are sampling at a high enough rate to achieve stability... This may be the arduino's limitation though, as I am not familiar with the MCUExpresso.

Ideally, you should have a mathematical description of your system, such that you have a transfer function, then you can just read off your bandwidth from the transfer function. This bandwidth is also the Nyquist frequency, which is the minimum "acceptable" (using that word loosely) frequency the sample a signal. Normally, you want to sample at around 30-40 times the Nyquist frequency. If this ins't possible, I suggest changing sensors.

I encountered similar problems last semester at uni on my project, hope this helps!

\$\endgroup\$
  • \$\begingroup\$ The graphs are only showing the rate at which data is sent to the arduino. I am not worried about bandwidth in this solution as the sample time on the PID can be adjusted. \$\endgroup\$ – Drew Fowler Nov 13 '18 at 18:54
  • \$\begingroup\$ So, if I understood the graph and yourself correctly, the sample number is in the x-axis, and the PWM value is in the y-axis? \$\endgroup\$ – Thefoilist Nov 13 '18 at 18:59
  • \$\begingroup\$ My apologies. I should have made that clear. The x axis is time and the y axis is speed of the motor. \$\endgroup\$ – Drew Fowler Nov 13 '18 at 19:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.