Go Down

Topic: [gelöst:] PID library - funktioniert [-nicht-] (Read 14855 times) previous topic - next topic

HaWe

ah - also SetOutputLimits(-255, 255)
Das muss wschl dann aber in die loop(), vermute ich...

- teste ich sofort aus.

erstmal  tausend dank!

HaWe

#46
Jul 06, 2014, 06:56 pm Last Edit: Jul 06, 2014, 06:58 pm by HaWe Reason: 1
nö -
SetOutputLimits(-255, 255);
akzeptiert er nicht.
Quote
Arduino: 1.5.6-r2 (Windows XP), Board: "Arduino Duemilanove or Diecimila, ATmega168"

pid1007.ino: In function 'void loop()':
pid1007:175: error: 'SetOutputLimits' was not declared in this scope
pid1007.ino: At global scope:
pid1007:186: error: expected constructor, destructor, or type conversion before '=' token
pid1007:187: error: expected constructor, destructor, or type conversion before '.' token
pid1007:190: error: expected constructor, destructor, or type conversion before '(' token
pid1007:192: error: expected constructor, destructor, or type conversion before '.' token
pid1007:194: error: expected declaration before '}' token


ist aber auch ausgesprochen schlecht dokumentiert - noch nicht mal nen Beispiel-Code findet man dazu  :(

Rabenauge

Hab grad mal in der Segway-Software nachgesehen: ich habs in der setup() stehen.

Ich versuchs mal, dir zusammenzubasteln:

Code: [Select]

PID radAchse(&genullterWinkel,&pwmRegler,&sollWert,reglerP,reglerI,reglerD,DIRECT);

void setup()
{
radAchse.SetMode(AUTOMATIC);
  radAchse.SetOutputLimits(-230,230);
  radAchse.SetSampleTime(30);
}

void regelungAchse()//************PID-Regler Achse *********************************************
{
    double abw = abs(sollWert+genullterWinkel);
   if (abw<3)                                 // bei kleineren Abweichungen
   {
    radAchse.SetTunings(reglerP,reglerI,reglerD);
    digitalWrite(ledGelb,LOW);
   }
  else                                          // bei grösseren Abweichungen
   {
    radAchse.SetTunings(reglerAggP,reglerAggI,reglerAggD);
    digitalWrite(ledGelb,HIGH);
   }
  radAchse.Compute(); // Regler berechnen

  if (pwmRegler<0)
  {
    drehRichtung=0;
  }
  else
  {
    drehRichtung=1;
  }
pwmMotoren=abs(pwmRegler)+10;
}


Ist etwas erweitert (ich benutze mehr als einen Satz Tuningparameter) aber- es funktioniert.
Du wirst allerdings deine Motoransteuerung anders aufdröseln müssen, ich brauche pro Motor nur zwei Steuerleitungen: PWM und Drehrichtung.
Ausserdem sind da noch andere Anpassungen drin, aber ich denke, dir wird klar, wie es ungefähr geht..
------------
Grüssle, Sly

HaWe

#48
Jul 06, 2014, 08:11 pm Last Edit: Jul 06, 2014, 11:04 pm by HaWe Reason: 1
sehr schön, danke, das hat jetzt weitergeholfen. 8-)

Den regler jetzt auf Encoderwerte zu tunen, ist ntl eine Lebensaufgabe.

Gibt es eine Tabelle mit Erfahrungswerten für Encoder, Reg.-Time ca 10ms?

mit  PID 2,5,1  schwankt er sehr langsam um den Zielwert (100),

mit höheren PID Werten ist er schneller, aber zittert mächtig.

Code: [Select]

//***********************************************************************************
// PID controller
// Reading motor encoder[0] to control PWM motor [0]
// ver 1.007
//************************************************************************************

#include <PID_v1.h>

//Define Variables we'll be connecting to
double PID0setpoint, PID0input, PID0output;

//Specify the links and initial tuning parameters
// PID myPID(&PIDInput, &PIDOutput, &PIDSetpoint,2,5,1, DIRECT);
PID PID0(&PID0input, &PID0output, &PID0setpoint,20,5,3, DIRECT);

#define  PID_REGTIME_MS 10


//*************************************************************

#define sensortouch(pinHIGH) !digitalRead(pinHIGH)

#define startpin  12
#define MAXMOTORS  2

#define    OUT_RUNSTATE_IDLE     0
#define    OUT_RUNSTATE_ACTIVE   1

// set motor pins
// motor 0
#define pinmenc0A  2  // encA yellow
#define pinmenc0B  3  // encB blue
#define pinmot0d1  4  // dir1
#define pinmot0d2  5  // dir2
#define pinmot0pwm 6  // enable
// motor 1
#define pinmenc1A  7  // encA yellow
#define pinmenc1B  8  // encB blue
#define pinmot1d1  9  // dir1
#define pinmot1d2  10 // dir2
#define pinmot1pwm 11 // enable


//************************************************************************************
// Encoder functions courtesy of / entnommen aus: http: //www.meinDUINO.de //
//
// Globale Variablen zur Auswertung in der Interrupt-Service-Routine (ISR)
//************************************************************************************
volatile int8_t altAB[MAXMOTORS]  = {0,0};
volatile long   motenc[MAXMOTORS] = {0,0},
                oldenc[MAXMOTORS] = {0,0};

// Die beiden Schritt-Tabellen für 1/1, 1/2 oder 1/4-Auflösung/resolution
// 1/1 Auflösung/resolution
//int8_t schrittTab[16] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};

// 1/2 Auflösung/resolution
  int8_t schrittTab[16] = {0, 0,0,0,1,0,0,-1, 0,0,0,1,0,0,-1,0};

// 1/4 Auflösung/resolution
//int8_t schrittTab[16] = {0,0,0,0,0,0,0,-1,0,0,0,0,0,1,0,0};

//*************************************************************
// Interrupt Service Routine: wenn Interrupt ausgelöst wird
//*************************************************************
ISR(TIMER1_COMPA_vect) {
  altAB[0] <<= 2;
  altAB[0] &= B00001100;
  altAB[0] |= (digitalRead(pinmenc0A) << 1) | digitalRead(pinmenc0B);
  motenc[0] += schrittTab[altAB[0]];           //

  altAB[1] <<= 2;
  altAB[1] &= B00001100;
  altAB[1] |= (digitalRead(pinmenc1A) << 1) | digitalRead(pinmenc1B);
  motenc[1] += schrittTab[altAB[1]];           //
}

//************************************************************************************

byte pinmotdir[MAXMOTORS][2]={ {pinmot0d1, pinmot0d2},
                               {pinmot1d1, pinmot1d2} };
                                         
int  pinmotpwm[MAXMOTORS]={pinmot0pwm, pinmot1pwm};



//************************************************************************************
inline void motoron(byte motnr, int power) {
  if(power>0) {
     digitalWrite( pinmotdir[motnr][0],HIGH);
     digitalWrite( pinmotdir[motnr][1],LOW);
  }
  else
  if(power<0) {
     digitalWrite( pinmotdir[motnr][0],LOW);
     digitalWrite( pinmotdir[motnr][1],HIGH);
  }
  else
  if(power==0) {
     digitalWrite( pinmotdir[motnr][0],LOW);
     digitalWrite( pinmotdir[motnr][1],LOW);
  }

  power = abs(power);
  if(power>254) power=254;
  analogWrite(pinmotpwm[motnr],power);
}

//------------------------------------------------------------------------------------

inline void motorbrake(byte motnr) {  // to do list !!!
  motoron(motnr, 0);
}

//------------------------------------------------------------------------------------

inline void motorcoast(int motnr) {
  motoron( motnr, 0);
}

#define  motoroff    motorcoast

//************************************************************************************

void setup()
{
   pinMode(startpin,INPUT_PULLUP);   // btn to start
   
   pinMode(pinmot0d1,OUTPUT);        // dir1
   pinMode(pinmot0d2,OUTPUT);        // dir2
   pinMode(pinmenc0A,INPUT_PULLUP);  // encA
   pinMode(pinmenc0B,INPUT_PULLUP);  // encB
   pinMode(pinmot0pwm,OUTPUT);       // enable
   
   pinMode(pinmot1d1,OUTPUT);        // dir1
   pinMode(pinmot1d2,OUTPUT);        // dir2
   pinMode(pinmenc1A,INPUT_PULLUP);  // encA
   pinMode(pinmenc1B,INPUT_PULLUP);  // encB
   pinMode(pinmot1pwm,OUTPUT);       // enable
 
   // time interrupt for encoder readings
   noInterrupts(); // Jetzt keine Interrupts / disable
       TIMSK1 |= (1<<OCIE1A);  // Timer 1 PIDOutput Compare A Match Interrupt Enable
       TCCR1A = 0;             // "Normaler" Modus
       // WGM12: CTC-Modus einschalten (Clear Timer on Compare match)
       //        Stimmen OCR1A und Timer überein, wird der Interrupt ausgelöst
       // Bit CS12 und CS10 setzen
       // => Prescaler=1024:
       // TCCR1B = (1<<WGM12) | (1<<CS12) | (1<<CS10);
       
       // => Prescaler=8:
       TCCR1B = (1<<WGM12) | (1<<CS11);
       // Frequenz = 16000000 / 1024 / 15 = rd. 1042Hz = 1kHz
       // Frequenz = 16,000,000 / 8 / 64 = rd. 31 kHz
       // Frequenz = 16,000,000 / 8 / 512 = rd. 4 kHz
       OCR1A =511;
       
   interrupts(); // Interrupts wieder erlauben / enable
   
   
   //initialize the variables we're linked to
   PID0input = motenc[0];
   PID0setpoint = 100;

   //turn the PID on
   PID0.SetMode(AUTOMATIC);
   PID0.SetOutputLimits(-255, 255);
   PID0.SetSampleTime(PID_REGTIME_MS);
 
   Serial.begin(115200);
}

//************************************************************************************

void loop()
{
     
   PID0input = motenc[0];
   PID0.Compute(); 
   motoron(0, PID0output);
 
   Serial.println(motenc[0]);
 
}


Nur mit sehr kurzen  Reg.-Time von ca 2ms ist er halbwegs schnell und zuckt nicht und schwingt nicht. Derart kurze Regzeiten will ich aber vermeiden.

Erfahrungswerte wären also gut.

Rabenauge

Kann man nur raten, wenn man deinen konkreten Aufbau nicht in den Fingern hat.
Ich z.B. brauche im Segway extreme Werte, da die Motoren sehr stark sind: Kp unter 10 (ich habs nicht im Kopf, glaub, um die 5 herum), Ki dagegen deutlich über 100, Kd wiederum eher sehr klein (einstelliger Kommabereich).

An deiner Stelle würd ich mal bissel mit I spielen.

Kleiner Trick (falls noch paar Analogpins vorübergehend frei sind: mal einfach drei Potis anschliessen, dann kannst du Kp,Ki und Kd in "sinnvollen" Wertebereichen einfach mal on the fly verstellen, wenn das Ergebnis leidlich passt, Werte auslesen (Konsole) und dann fix einstellen in der Software. Das geht manchmal schneller als ewig rumzuprobieren und hunderte Male neu zu flashen.
------------
Grüssle, Sly

sven222


Kleiner Trick (falls noch paar Analogpins vorübergehend frei sind: mal einfach drei Potis anschliessen, dann kannst du Kp,Ki und Kd in "sinnvollen" Wertebereichen einfach mal on the fly verstellen, wenn das Ergebnis leidlich passt, Werte auslesen (Konsole) und dann fix einstellen in der Software. Das geht manchmal schneller als ewig rumzuprobieren und hunderte Male neu zu flashen.

Die Idee ist so simpel, aber so gut! Danke. 

HaWe

ja, danke, werd ich auch so probieren! 8-)

HaWe

#52
Jul 07, 2014, 12:07 pm Last Edit: Jul 07, 2014, 12:11 pm by HaWe Reason: 1
habe jetzt mit 20,80,1 für p,i,d eine ganz gute Einstellung zum Annähern gefunden.

Nun reguliert das Programm aber immer weiter, auch wenn der Zielwert erreicht ist -
wie sagt man ihm am besten, dass der PID-controller nach einer erfolgten Annäherung  gestoppt werden soll (Motoren stromlos)?

Und erst nach einem neuen entsprechenden Befehl (Tastendruck, analoger globaler Wert, was auch immer) dann wieder annähern soll, dann nach Erfolg wieder stoppen, usw.?

Rabenauge

Ein Regler läuft normalerweise immer.
Er regelt ja nix, wenn nix zu regeln ist. Also solange Sollwert==Istwert, tut sich da nix. Falls doch, steuert der Regler nicht komplett aus (sollte er aber können).

Hm, um ihn stoppen bzw. zuschalten zu können, würd ich mal versuchen, pid.compute() von ner Flag-Variablen (die du dann sowohl per Button als auch per Software bearbeiten kannst) abhängig zu machen.
Einfacher ist es vielleicht, die Ausgabe des Regler-Ergebnisses einfach ins Nirvana zu schicken, statt zur Motorsteuerung.
Was auch geht: wenn die Regelung "unerwünscht" ist, einfach Kp,Ki,Kd auf Null setzen (dann regelt der gar nix mehr). Das klappt mit dem unter "adaptive Tunings" beschriebenen Vorgehen auf jeden Fall.
------------
Grüssle, Sly

HaWe

#54
Jul 07, 2014, 02:53 pm Last Edit: Jul 07, 2014, 04:17 pm by HaWe Reason: 1
edit: 
das mit 0,0,0 habe ich auch schon gemacht, das funktioniert nicht, er gibt weiter output.
das mit .compute() und output nur bei Fallunterscheidung habe ich auch probiert, hier auch völlig erfolglos.

Der Sinn dahinter ist:
Ich will den PID-Regler auf dem Arduino fernsteuern.
Er bekommt i2c-Nachrichten, welcher Wert mit welchem Motor angefahren werden soll (2 Achsen von einen Roboterarm, auf einem Mega auch mehr als diese 2), und die steuert der Arduino dann solange per PID an, bis die Encoder-Counter die entsprechenden Werte haben.
Das Erreichen der Stellwerte meldet er an den Master zurück, und der kann dann bei Bedarf neue  Stellbefehle schicken - dazwischen ist aber nichts zu tun, er kann daher die Motoren abschalten.
Es braucht auch keine Position gegen passive Kräfte gehalten zu werden, da es Schneckengetriebe sind, so spare ich Batteriestrom.

HaWe

#55
Jul 07, 2014, 04:22 pm Last Edit: Jul 07, 2014, 05:05 pm by HaWe Reason: 1
update: hatte mich vertan, das mit 0,0,0 funktioniert auch nicht:

Code: [Select]

void loop()
{  
  cnt++;
  if (!started) {Serial.println(motenc[0]); delay(500); started=1; }
 
  PID0input = motenc[0];
 
  float gap = abs(PID0setpoint-PID0input); //distance away from setpoint
  if(gap>40)            { PID0.SetTunings(15, 50, 1);   }
  else
  if(gap<=PID_REG_PREC) { PID0.SetTunings( 0,  0, 0);   }
  else                  { PID0.SetTunings(20, 80, 1);   }
 
  PID0.Compute();  
 
  if(gap>2)  motoron(0, PID0output);

  if(cnt>=9) {Serial.print(motenc[0]);Serial.print(" - "); Serial.println(PID0output); cnt=0; }
 
  delay(10);
 
}



obwohl er nach Erreichen des Ziels (gap<=2) als pid 0,0,0, gestellt bekommt, liefert er weiter output an den Motor (Ziel =3600):

Code: [Select]

enc  -  output

2125 - 255.00
2226 - 255.00
2327 - 255.00
2427 - 255.00
2528 - 255.00
2628 - 255.00
2729 - 255.00
2829 - 255.00
2931 - 255.00
3032 - 255.00
3132 - 255.00
3234 - 255.00
3335 - 255.00
3436 - 255.00
3537 - -175.00
3591 - -65.00
3607 - -24.08
3606 - 208.16
3606 - 47.32
3606 - 12.76
3606 - -21.80
3606 - -56.36
3606 - -90.92
3606 - -125.48
3606 - -160.04
3606 - -194.60
3602 - -92.52
3600 - -97.00
3601 - -97.00
3601 - -97.00
3601 - -97.00
3601 - -97.00
3601 - -97.00
3601 - -97.00



Man hört den Motor am Schluss auch deutlich dauerhaft piepen (1000 Hz oder so), und wenn ihn manuell verstellen will, merkt man auch deutlich eine Rückstellkraft.
(Diese Nachregulierung soll aber nach Erreichen des Ziels aber eigentlich dauerhaft ausgeschaltet bleiben, nicht nur zeitweise innerhalb der Fehlertoleranz).

da stimmt also was nicht...


Man müsste den PID controller auch jederzeit komplett abschalten können.

Rabenauge

Hm: so vielleicht: http://playground.arduino.cc/Code/PIDLibrarySetMode ?

Einfach, wenn er sich raushalten soll, den Modus auf manuell stellen, sollte eigentlich klappen.
------------
Grüssle, Sly

HaWe

#57
Jul 07, 2014, 05:27 pm Last Edit: Jul 07, 2014, 05:33 pm by HaWe Reason: 1
das versteh ich nicht, das ist aber auch grottenschlecht dokumentiert!

ps, edit:
heisst denn "auto" = on und "manual" = off?
nichts anderes?

und was bedeutet "The PID defaults to the off position when created. " ?

Rabenauge

Es bedeutet, dass der Regler _gar_ nicht arbeitet, wenn er nicht durch Aufruf von setMode(AUTOMATIC) zugeschalten wird.
Ich glaub, das war mir nämlich beim Segay anfangs passiert: die Anmerkung überlesen und eben das nicht getan.
Da regelte sich nix.

Also: manual=off (Regler aus).
Da das Ganze _nur_ Sinn macht, wenn man es im Programm mehrmals aufrufen kann (sonst brächt mans ja nicht...), dürfts klappen.
------------
Grüssle, Sly

Serenifly


und was bedeutet "The PID defaults to the off position when created. " ?

Genau was es sagt. Die Standardeinstellung im Konstruktor. Also Aus nachdem das Objekt erstellt wurde.

Go Up