Large Error Instability

Hello,

I am using quadrature (rotary) encoders for motion control via external interupts for a master/slave(DC motor) setup. When I implement a large error (ie quickly moving my master encoder - or - step response). The PWM signal go crazy and unstable. The error between the master and slave encoder increases rather than decreases - which makes my PID controller output infinitely growing values. Also my output rate serial.print (in my main loop) decreases. When I have a small error (moving slowly with master) everything is great.

Motor has a gear box would makes the scale between the master and slave encoders very imbalanced. I compensated this by scalling the increments on the encoder count of the master and slave.

ex:

masterEncoder (+/-)= 2.4
slaveEncoder (+/-)= 0.2

I feel that when there is a large error present, something happens, perhaps a change in bit on my data type or ISR issue. I am using all floats.

If anyone has suggestions to why I am having instability with a large error, I would appreciate the help.
Let me know if this was confusing to understand.
Daniel

One possibility is that your ISR function for handling encoder interrupts is too long, complex, or too slow to keep up with the interrupt rate. Without posting your code it's very difficult for anyone to help with your symptoms.

Lefty

Please post your code then we can deduce what is happening.
What is the number of interrupts you get approx.

Sorry about that, below is my code.

/*
Project:
Name: Daniel G
Date: 10/15/2012
Company:
*/

/*
EnA | In1 | In2 | Description

0 | X | X | Free Running Motor Stop
1 | 1 | 0 | Forward
1 | 0 | 1 | ReverslaveEncoder
1 | 1 | 1 | Fast Motor Stop

CHA: ____EEEE____EEEE____EEEE____EEEE
CHB: EE____EEEE____EEEE____EEEE____EE
When CHA rislaveEncoders check the state of CHB. From here you can determine the
direction of the encoder corresponding to the Master or Slave
*/

#include <stdio.h>
int In1 = 8; // Red
int In2 = 9; // Orange
int EnA = 10; // Yellow
int encoderPower = 7; // Red
int Master_CHA = 0; // Green - Interrupt INT0 on Pin 2
int Master_CHB = 4; // White
int Slave_CHA = 1; // Purple - Interrupt INT1 on Pin 3
int Slave_CHB = 6; // Teal
int LOOPCOUNTER = 0;

int Master_CHB_val = 0;
int Slave_CHB_val = 0;
volatile float masterEncoder = 0;
volatile float slaveEncoder = 0;

// Control Signal and Error
float u = 0;
float err = 0;

// PID Control
float Kp = 8;
float Ki = .002;
float Kd = .2;

// Error Variables
float err_old = 0;
float err_intgrl = 0;
float err_dot = 0;
int max_int = 200;
int max_der = 200;

void setup()
{
// Serial.begin(9600); // Begin serial communication

// slaveEncodertting Motor Pins
pinMode(In1, OUTPUT); // Setting In1 (direction indicator)
pinMode(In2, OUTPUT); // Setting In2 (direction indicator)
pinMode(EnA, OUTPUT); // Setting PWM output

// Temperary HIGH to encoderPower Pin to give Vcc
pinMode(encoderPower, OUTPUT);
digitalWrite(encoderPower, HIGH);

// Set Encoder Pins & Interrupt
pinMode(Master_CHB, INPUT);
pinMode(Slave_CHB, INPUT);
attachInterrupt(Master_CHA, Master_checkDirection, RISING); // CHA causes interrupt service routine
attachInterrupt(Slave_CHA, Slave_checkDirection, RISING); // CHA causes interrupt service routine

TCCR1B = TCCR1B & 0b11111000 | 0x02; // PWM frequency changed from 500Hz to 3.922KHz - L298 typically can support 25KHz.
}

// ISR for Master (Controller)
void Master_checkDirection()
{
Master_CHB_val = digitalRead(Master_CHB); // When CHA RAISES check CHB
if (Master_CHB_val == 0) masterEncoder += 1.4; // Increment Master count for a direction
else masterEncoder -= 1.4; // Decrement Master count for the other direction
}

// ISR for Slave (Motor)
void Slave_checkDirection()
{
Slave_CHB_val = digitalRead(Slave_CHB); // When CHA RAISES check CHB
if (Slave_CHB_val == 0) slaveEncoder += 0.6; // Increment Slave count for a direction
else slaveEncoder -= 0.6; // Decrement Slave count for the other direction
}

// Function to Set Direction of PWM
void setDirection(boolean In1_State, boolean In2_State)
{
digitalWrite(In1, In1_State);
digitalWrite(In2, In2_State);
}

void loop()
{
err = slaveEncoder - masterEncoder;
// LOOPCOUNTER++;

if (err < 0) setDirection(LOW, HIGH);
else if (err > 0) setDirection(HIGH, LOW);

err_dot = err - err_old;
if (err_dot > max_der) err_dot = max_der;
else if (err_dot < -max_der) err_dot = -max_der;

err_intgrl = err + err_intgrl;
if (err_intgrl > max_int) err_intgrl = max_int;
else if (err_intgrl < -max_int) err_intgrl = -max_int;

u = (float)(abs(Kperr + Kderr_dot + Ki*err_intgrl));
constrain(u, 0, 255);

analogWrite(EnA, u); // Write PWM value as the encoder count of the Master (controller)
// if (LOOPCOUNTER == 1200)
// {
// Serial.print(masterEncoder);
// Serial.print(" || ");
// Serial.print(slaveEncoder);
// Serial.print(" || ");
// Serial.println(err);
// LOOPCOUNTER = 0;
// }
err_old = err;
}

  constrain(u, 0, 255);

Since constrain() returns the constrained value you probably want:

  u = constrain(u, 0, 255);

It might work better is you calculate the PID before you set the direction:

  err_dot = err - err_old;

  err_intgrl = err + err_intgrl;

  int PID = Kp*err + Kd*err_dot + Ki*err_intgrl;
  constrain(PID, -255, 255);

 if (PID < 0) {
    setDirection(LOW, HIGH);
    PID = -PID;
  }
  else
    setDirection(HIGH, LOW); 

  analogWrite(EnA, PID);

Hello John,

Thank you for your input. Your approach seems much cleaner but unfortunately, I am still getting the same results.

I feel that if I exceed my PID control signal to much, it goes unstable. By providing a step response the PID signal is too high (high err) in which causes instability. Contraining the signal is not helping for some reason. During instability (after a step response - my serial monitor just shows a repeating 255 or -255, never decreasing.

I constrained the maximum errors but I still am getting the same weird results.

  err_dot = err - err_old;
  err_dot = constrain(err_dot, -max_der, max_der);

  err_intgrl = err + err_intgrl;
  err_intgrl = constrain(err_intgrl, -max_int, max_int);

The problem I feel is the gear box on the DC motor. Because the scaling is not 1:1 - as the maximum PWM signal is set, the slaveEncoder goes to infinite because the gearbox is sending encoder positions in the thousands. I am trying to compensate for this with the scaling the encoder count in Slave_checkDirection() and Master_check Direction() but it is only efficient until a certain point.

Your thoughts?

What values of 'err' are you getting?

Notice below the err values are just fine when I move slowly, but applying a large instantaneous motion to my masterEncoder (essentially a step response). The err builds up exponentially starting at -12668.67.

-6.23
-1.12
-1.12
-1.12
6.37
6.96
2.14
-5.98
-0.60
8.68
15.87
5.08
-2.71
-3.31
-1.20
0.60
4.20
7.80
5.70
-0.30
-0.30
-0.30
-1.80
-1.80
-2.10
-5.70
-12.01
-11.12
6.57
-4.51
-11.69
-9.27
-8.96
-0.85
-0.85
-0.85
-35.35
-12668.67
-25233.88
-33518.90
-42608.51
-52169.74
-61758.05
-71490.49
-81504.08
-91523.61
-101471.30
-111362.58

u = (float)(abs(Kperr + Kderr_dot + Ki*err_intgrl));

I would take the PID signals apart, and see which one is causing the trouble.

IE, set Kd=Ki=0 initially.

oric_dan(333):

u = (float)(abs(Kperr + Kderr_dot + Ki*err_intgrl));

I would take the PID signals apart, and see which one is causing the trouble.

IE, set Kd=Ki=0 initially.

Since it is the error term itself (err) that is running away I don't think the PID parameters will matter.

Looks like the error term is jumping about 10,000 to 12,000 at a time. How many pulses per second are you expecting? Since the Master counts up by 1.4 you are either getting 9,000 pulses in a small fraction of a second (there are no delays in the PID loop) or the floating-point math is going wrong.

About the fact that there are no delays... perhaps it would work better if you updated the PID loop at fixed intervals. Say 100 times a second (every 10 milliseconds).

I would also consider working in integers, particularly in the ISR's, because floating-point math is so time consuming.

Since it is the error term itself (err) that is running away I don't think the PID parameters will matter.

Probably so, but it's all a big feedback loop, so everything may contribute.

Even with Kp, I am experiencing this problem. I do not think it has something to do with my PID parameters. I personally think it has something to do with the scaling between master and slave. The gearhead on the motor has a reduction radio of 377:1 while my master is 1:1. This difference in scales (I believe) should cause some issue.

But, I also feel it could be ISR and floating point math - because even with a scaling ratio the PWM shouldn't go on to infinite.

In regards to your question, "How many pulses per second are you expecting?" I believe that is coming from the large amount of pulses from the slave end with the gearbox. Because of the gearbox, there are tons of pulses coming in - especially when the motor exceeds maximum speed.

This is unnecessary frustration :frowning:

Daniel

EDIT: johnwasser, maximum PWM signal is given (100%) - as this happens when the err goes through the roof. I get pulses coming back at ~43Khz. The Arduino should be able to process the ISR at this frequency, can it not? Perhaps givening maximum PWM signal produces too quick of a interupt frequency in which the processor cannot keep up?

if (err_intgrl > max_int) 

err_intgrl = constrain(err_intgrl, -max_int, max_int);

I suppose these both work properly with mixed data types, but I probably wouldn't do it that way,
rather make all quantities = floats, or use casts.

This is unnecessary frustration

LOL, the longer you're an engineer, the more you realize it's really more a matter of 'delayed gratification',
and not something else.

Think of the guys who built the Curiosity mars rover. They worked on that thing open-loop for 10 years,
with only a wing and a prayer that the thing wouldn't crash on landing, due to the hyper complicated
landing scheme they came up with. Or else die in flight for one of 10,000 other reasons.