Go Down

Topic: Controlling an animatronics bust (Read 5 times) previous topic - next topic

laadams85

You may be able to send a higher PWM when you start and then immediately send a lower PWM frequency.  This could be done using state programming.  If just starting to move or reversing direction send a higher PWM, short delay, check PID, then send the lower required PWM.

PeterH

The effects you're describing sound a lot like mechanical stiction (it will take less torque to keep the motor moving than to start it moving) and also the inherent variations in the current/torque relationship of the motor as the motor moves. (Try turning a motor by hand - the motor has to overcome that lumpiness before it will move, and if you maintain constant current and then turn the spindle, you can feel how much the torque fluctuates.)

The Integral term of the PID should prevent the system from settling with a non-zero error value.

You should be able to tune the Differential term so that the system slows down and stops at the target position without any overshoots or undershoots, as long as your mechanical system is linear. (If you have non-linear movements then it might be difficult to tune the PID correctly across all positions.)
I only provide help via the forum - please do not contact me for private consultancy.

Retroplayer

I've now got it "sorta" working. I switched over to the lower frequency PWM pins (10 and 11), removed the integral (caused WILD oscillations) and I have been trying to follow along with various tutorials on PID tuning and it still oscillates. I've got it oscillating less... but still crazy amounts.

The problem, I think is that the potentiometer changes value very quickly. I am dealing with a very small range of motion here. Just kicking the motor a little can way overshoot. I am so close to giving up on PD control (remember, I zeroed the Integral).

Raising the integral even to 1 caused the motor to go absolutely nuts. A P of 2 and a D of 1 seems to be working the best.

This is what I think I understand:

P = How much force to move towards zero position
D = Pre-act (predicting what the output will be over time)

I am just not positive that this is right for me. What I am getting out of right now is no different than my simple If currpos > pos, then move left etc... routine I was using. In fact, even that actually got it right ocassionally!

Retroplayer

This code mostly works. It is modified from the DIY-Servo code (unfortunately I forget the author):

Quote

int PWMPin = 5;
int PWMPin2 = 6;
int potPin = A0;

float KP = 2;             // PID: position multiplier (gain) was 2 Adjust to get more aggressive or conservative control
float KI = 0;              // PID: Intergral multiplier (gain) was .05 set about the same as your manual response time Seconds/4
float KD = 1;              // PID: Derivative multiplier (gain) http://www.expertune.com/tutor.html

int lastError = 0;
int sumError = 0;

//int iMax = 1;
//int iMin = 0;

int setPos = 0;
int dutyMax = 254;

void setup() {   

  pinMode(PWMPin, OUTPUT);
  pinMode(PWMPin2, OUTPUT);
  setPwmFrequency(PWMPin, 255);
  Serial.begin(9600);

}

void loop() {
 
  int val = analogRead(potPin);
  val = map(val,0,1023,0,255);

  checkInput();
 
  int error = val - setPos;

  int ms = KP * error + KD * (error - lastError) + KI * (sumError);


  lastError = error;
  sumError += error;

//  if(sumError > iMax){
//    sumError = iMax;
//  }
//  else if(sumError < iMin){
//    sumError = iMin;
//  }


  // BACKWARD DIRECTION
  if(ms > 0){                                     
    int motorSpeed = ms;
    if(motorSpeed > dutyMax){                         
      motorSpeed = dutyMax;
    }
    PWMPin = 5;                                 
    PWMPin2 = 6;

    analogWrite(PWMPin2, 0);
    analogWrite(PWMPin, motorSpeed);
  }


  // FORWARD DIRECTION
  if(ms < 0){                                     
    ms = -1 * ms;
    int motorSpeed = ms;
    if(motorSpeed > dutyMax){                   
      motorSpeed = dutyMax;
    }

    PWMPin = 6;                                   
    PWMPin2 = 5;

    analogWrite(PWMPin2, 0);
    analogWrite(PWMPin, motorSpeed);
  }

}

void checkInput(){
  if(Serial.available()){
    byte ch = Serial.read();

    if (ch == '+'){
      setPos = setPos+5;
    }
    if (ch == '-'){
      setPos = setPos-5;
    }
    if(ch == 's'){
      analogWrite(PWMPin,0);

    }

  }

  setPos=constrain(setPos,0,255);

}


void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) { // Timer0 or Timer1
    switch(divisor) {
    case 1:
      mode = 0x01;
      break;
    case 8:
      mode = 0x02;
      break;
    case 64:
      mode = 0x03;
      break;
    case 256:
      mode = 0x04;
      break;
    case 1024:
      mode = 0x05;
      break;
    default:
      return;
    }
  }
  else if(pin == 3 || pin == 11) {
    switch(divisor) {
    case 1:
      mode = 0x01;
      break;
    case 8:
      mode = 0x02;
      break;
    case 32:
      mode = 0x03;
      break;
    case 64:
      mode = 0x04;
      break;
    case 128:
      mode = 0x05;
      break;
    case 256:
      mode = 0x06;
      break;
    case 1024:
      mode = 0x7;
      break;
    default:
      return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode; // Timer2
  }
}




But it makes a ton of noise, even at idle. And the difference between setpos and currpos needs to be very large before the motor will even move. However, it will fairly accurately move to that position and "stop." Well, I say stop, but more like it sits there and whines. It does undershoot a little, but I think that is because it is setting the pwm so low that the motor stops turning and can't make it that last bit.

Retroplayer

I just don't think this is going to work unless I figure out to control it exactly the same way it was controlled in the original.

Go Up