Beispiel 1: Regelung eines Motorpendels
(EN google-translate)
(PL google-translate)
Anordnung
Bild 0-1: Aufbau des Systems.
Bild 0-2: Schematischer Aufbau und Stromlaufplan des Systems.
pendelregler.ogg.zip - Film.
Programmvarianten
REGLER.zip - Programmvarianten
Experiment einfacher P-Regler
int sensor;
int motor;
void setzeMotor(int wert)
{
if(wert>255)
wert=255;
if(wert<-255)
wert=-255;
if(wert>0)
{
digitalWrite(7,1);
digitalWrite(8,0);
analogWrite(9,wert);
}
else if(wert<0)
{
digitalWrite(7,0);
digitalWrite(8,1);
analogWrite(9,-wert);
}
else
{
digitalWrite(7,0);
digitalWrite(8,0);
analogWrite(9,0);
}
}
void setup()
{
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7,0);
digitalWrite(8,0);
Serial.begin(9600);
}
void loop()
{
sensor = analogRead(0)-300;
Serial.println(sensor);
delay(200);
setzeMotor(-sensor);
}
Code 0-1: REGLER001 - Experiment einfacher P-Regler
Experiment einfacher PI-Regler
int sensor;
int motor;
int integral = 0;
void setzeMotor(int wert)
{
if(wert>255)
wert=255;
if(wert<-255)
wert=-255;
if(wert>0)
{
digitalWrite(7,1);
digitalWrite(8,0);
analogWrite(9,wert);
}
else if(wert<0)
{
digitalWrite(7,0);
digitalWrite(8,1);
analogWrite(9,-wert);
}
else
{
digitalWrite(7,0);
digitalWrite(8,0);
analogWrite(9,0);
}
}
void setup()
{
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7,0);
digitalWrite(8,0);
Serial.begin(9600);
}
void loop()
{
sensor = analogRead(0)-300;
integral+=sensor;
Serial.println(sensor);
delay(200);
setzeMotor(-sensor-integral);
}
Code 0-2: REGLER002 - Experiment einfacher PI-Regler.
Experiment einfacher PID-Regler
int sensor=0;
int sensor_alt=0;
int motor=0;
int integral = 0;
int differenz = 0;
void setzeMotor(int wert)
{
if(wert>255)
wert=255;
if(wert<-255)
wert=-255;
if(wert>0)
{
digitalWrite(7,1);
digitalWrite(8,0);
analogWrite(9,wert);
}
else if(wert<0)
{
digitalWrite(7,0);
digitalWrite(8,1);
analogWrite(9,-wert);
}
else
{
digitalWrite(7,0);
digitalWrite(8,0);
analogWrite(9,0);
}
}
void setup()
{
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7,0);
digitalWrite(8,0);
Serial.begin(9600);
}
void loop()
{
sensor_alt = sensor;
sensor = analogRead(0)-300;
integral+=sensor;
differenz = 4*(sensor-sensor_alt);
if(integral>200)
integral=200;
if(integral<-200)
integral=-200;
// Serial.println(sensor);
delay(4);
setzeMotor(-2*sensor-integral-differenz);
}
Code 0-3: REGLER004 - Experiment einfacher PID-Regler.
Experiment Sprungantwort
#define ARRSIZE 300
int sensor=0;
int sensor_alt=0;
int motor=0;
int integral = 0;
int differenz = 0;
int ZUSTAND = 0;
int arr[ARRSIZE];
int index=0;
void setzeMotor(int wert)
{
if(wert>255)
wert=255;
if(wert<-255)
wert=-255;
if(wert>0)
{
digitalWrite(7,1);
digitalWrite(8,0);
analogWrite(9,wert);
}
else if(wert<0)
{
digitalWrite(7,0);
digitalWrite(8,1);
analogWrite(9,-wert);
}
else
{
digitalWrite(7,0);
digitalWrite(8,0);
analogWrite(9,0);
}
}
void setup()
{
pinMode(12, INPUT);
digitalWrite(12,1); //Pullup
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7,0);
digitalWrite(8,0);
Serial.begin(9600);
}
void loopA()
{
sensor_alt = sensor;
sensor = analogRead(0)-300;
integral+=sensor;
differenz = 4*(sensor-sensor_alt);
if(integral>200)
integral=200;
if(integral<-200)
integral=-200;
delay(4);
setzeMotor(-2*sensor-integral-differenz);
if(digitalRead(12)==0)
{
index=0;
ZUSTAND = 1;
}
}
void loopB()
{
setzeMotor(-255);
arr[index] = analogRead(0)-300;
index++;
if(index>=ARRSIZE)
{
setzeMotor(0);
index=0;
ZUSTAND=2;
}
delay(1);
}
void loopC()
{
Serial.print(index); //Millisekunden
Serial.print(" ");
Serial.println(arr[index]); //Winkel
index++;
if(index>=ARRSIZE)
{
index=0;
ZUSTAND=0;
}
}
void loop()
{
switch(ZUSTAND)
{
case 0:
loopA();
break;
case 1:
loopB();
break;
case 2:
loopC();
break;
}
}
Code 0-4: REGLER005sprung - Experiment Sprungantwort.
Auswertung
daten.zip - mess3_roh.sce Rohdaten als Scilab-Matrix, mess3c.sce Glättung / zeitl. Ableitung, Totzeit-Annahme: 20ms.
Bild 0-3: Sprungantwort Bewegungsgeschwindigkeit, Einheiten/Sekunde gegen Zeit in Sekunden, Totzeit nicht dargestellt.
System-Identifikation
anzahl_schritte = 400;
delta = ones(2,2)*0.1;
for i=1:1:anzahl_schritte
pnumber = 1+ floor(grand(1, 1, "unf", 0, 1.999999999));
prichtung = 2*floor(grand(1, 1, "unf", 0, 1.999999999))-1;
prichtung_index = (prichtung+1)/2 + 1;
param_neu = param;
param_neu(pnumber) = param_neu(pnumber) + prichtung*delta(pnumber,prichtung_index);
fehler_neu = berechneFehler(param_neu);
if ( fehler_neu <= fehler ) then
fehler = fehler_neu;
param = param_neu;
delta(pnumber,prichtung_index) = delta(pnumber,prichtung_index)*1.1;
else
delta(pnumber,prichtung_index) = delta(pnumber,prichtung_index)*0.9;
end
end
Code 0-5: Einfacher Optimierungsalgorithmus (modifiziertes Gradientenverfahren) als mögliche Grundlage.
Bild 0-4: Sensorwert-Verlauf bei der Sprungantwort.
PROBLEME: Die Daten, die bei der Sprungantwort geliefert werden sind mit Störungen behaftet. Ursachen könnten sein:
|
Neuer Ansatz FAST PWM: fpwm = fclock / ( N*(1+TOP) ) Vorteilung N = 1 fclock = 16.000.000 Hz fpwm = soll 50kHz (maximale Wandlungsfrequenz des AD-Wandlers: 60kHz, etwas drunter bleiben) Allerdings ist die PWM-Frequenz des Motortreibers auf 5kHz begrenzt, also wird die genommen: Deshalb: fpwm = 5kHz TOP = fclock/(fpwm*N) - 1 = 16000000 / (5000*1) - 1 = 3199 (relativ klein!) => TOP = 3199 Konfiguration von Timer1 mit Mode 14, Seite 123 Datenblatt ATmega32U4 fpwm = fclock / ( N*(1+TOP) ) = 16.000.000 / ( 1*(4999+1) )
Code 0-6: Überlegungen zur neuen Version
ÜBUNGSAUFGABEN
|
-
VERSION 2.0
-
Bild 0-5: Veränderte Fragestellung: Welche Regelstrecke liegt zugrunde, wenn sie PID-geregelt diese Rohdaten liefert?
mess15.sce.zip - Neue Rohdaten.
//5000Hz: Schafft D-Anteil nicht mehr:
#define MOTORMAX 3199
#define MAXARR 1000
#define SOLLSTART -200
volatile long int sensor=0;
volatile long int inx=0;
volatile long int SOLLWERT=SOLLSTART;
//Für ADW:
volatile unsigned int akku, akku2;
volatile bool toggle = false;
volatile unsigned int ZEITSCHRIITE = 0;
volatile long int e=0,e_alt=0,e_diff=0,e_integ=0,stellsignal=0;
//volatile long int P = 40;
volatile long int P = 1500;
volatile long int I = 4;
volatile long int D = 1500;
volatile long int HAFT=0;
int arr[MAXARR];
int index=0;
int ZUSTAND=0;
//dt = 0.0002; //5000Hz == 0.0002s
void initTimer1()
{
//Timer1, Mode 14 WGM13..0 1110
//Vorteilung 1, CS12..0 001
TCCR1B = (1<<WGM13) | (1<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10) ;
TCCR1A = (1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (1<<WGM11) | (0<<WGM10);
//TOP=ICR1=3199
//N=1
//fpwm = fclock / ( N*(1+TOP) ) = 16000000/3200==5000Hz
ICR1 = MOTORMAX;
DDRB |= (1<<PB5); //ist D9 / OC1A
//ISR aufgrund Überlauf Timer 1:
TIMSK1 |= (1<<OCIE1A);
//TIMSK1 |= (1<<ICIE1); //Match mit ICR1
//Interrupts aktivieren
sei();
}
void initADW()
{
//A0 ist ADC7 auf dem Arduino Micro:
//MUX:000111
//Vorteilung bei 16Mhz soll 60kHz ergeben, deshalb: 16000000/60000 = 266,67
//Nächster Wert: 256, also 16000000/256 = 62500Hz (o.k.)
//ADLAR==1 => oberen 8 Bit komplett in ADCH, unteren 2 in oberen von ADCL
//ADEN==1 aktiv
//ADSC==1 start conversion / start 1st bei free running
//ADTS3..0 ==0 free running mode
//REFS1..0 00==VREF intern , extern ausgeschaltet
//ADHSM 1==High speed , mehr Stromverbrauch
//ADTS3..0 Koppeln an Timer 1 Overflow: 0110
ADCSRA = (1<<ADEN) | (1<<ADSC) | (0<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
ADCSRB = (1<<ADHSM) | (0<<ACME) | (0<<MUX5) | (0<<ADTS3) | (1<<ADTS2) | (1<<ADTS1) | (0<<ADTS0);
ADMUX = (1<<REFS1) | (0<<REFS0) | (1<<ADLAR) | (0<<MUX4) | (0<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0);
}
void setzeMotor(long int wert) // +/- 3199
{
if(wert>MOTORMAX)
wert=MOTORMAX;
if(wert<-MOTORMAX)
wert=-MOTORMAX;
if(wert>0)
{
digitalWrite(7,1);
digitalWrite(8,0);
analogWrite(9,wert);
}
else if(wert<0)
{
digitalWrite(7,0);
digitalWrite(8,1);
analogWrite(9,-wert);
}
else
{
digitalWrite(7,0);
digitalWrite(8,0);
analogWrite(9,0);
}
}
void setup()
{
initADW();
initTimer1();
pinMode(12,INPUT); //Taster
digitalWrite(12,1);
pinMode(11,OUTPUT); //Lautsprecher
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7,0);
digitalWrite(8,0);
Serial.begin(9600);
}
void aktualisiereSensorwert()
{
akku2 = ADCL; //muss erst ausgelesen werden, damit ADCL verfügbar ist
akku = ADCH;
ADCSRA |= (1<<ADSC); //Neue Wandlung anfordern, ist (hoffentlich) fertig bei nächstem Durchlauf
sensor = (akku<<2) | (akku2>>6);
sensor-=608;
}
void loopA()
{
aktualisiereSensorwert();
e_alt = e;
e = SOLLWERT-sensor;
e_integ+=e;
e_diff = (e-e_alt);
if(e_integ>MOTORMAX)
e_integ = MOTORMAX;
if(e_integ<-MOTORMAX)
e_integ = -MOTORMAX;
//P-Regler:
//stellsignal = P*e;
//PI-Regler:
//stellsignal = P*e + I*e_integ;
//PID-Regler:
/*
if(sensor>0)
HAFT=-1500;
else if(sensor<0)
HAFT=1500;
else
HAFT=0;
*/
stellsignal = (P*e)/100 + (D*e_diff)/100 + (I*e_integ)/100 + HAFT;
setzeMotor(stellsignal);
//if(ZEITSCHRIITE%100==0)
// Serial.println(sensor);
if(digitalRead(12)==0)
{
index=0;
ZUSTAND=1;
SOLLWERT = 0;
}
}
void loopB()
{
aktualisiereSensorwert();
e_alt = e;
e = SOLLWERT-sensor;
e_integ+=e;
e_diff = (e-e_alt);
if(e_integ>MOTORMAX)
e_integ = MOTORMAX;
if(e_integ<-MOTORMAX)
e_integ = -MOTORMAX;
stellsignal = (P*e)/100 + (D*e_diff)/100 + (I*e_integ)/100 + HAFT;
setzeMotor(stellsignal);
if(ZEITSCHRIITE%10==0)
{
arr[index]=sensor;
index++;
}
if(index>=MAXARR)
{
ZUSTAND=2;
index=0;
setzeMotor(0);
SOLLWERT = SOLLSTART;
}
}
void loopC()
{
Serial.print(index);
Serial.print(" ");
Serial.println(arr[index]);
index++;
if(index>=MAXARR)
{
ZUSTAND=0;
index=0;
SOLLWERT=SOLLSTART;
}
}
ISR(TIMER1_COMPA_vect) //Ein Durchgang
{
switch(ZUSTAND)
{
case 0:
loopA();
break;
case 1:
loopB();
break;
case 2:
loopC();
break;
}
if(toggle)
digitalWrite(11,1);
else
digitalWrite(11,0);
toggle=!toggle;
ZEITSCHRIITE++;
ZEITSCHRIITE%=100;
//theoretisch überflüssig, da an Timer 1 Overflow gekoppelt.
}
void loop()
{
//leer, alles passiert zeitlich getaktet in der ISR (Interrupt Service Routine)
}
Code 0-7: Zugrunde liegendes Programm.
-
VERSION 3.0
-
|
Bild 0-6: Sprungantwort des P-geregelten Systems,
mess25.sce.zip - Scilab-Daten
Regler012sprung.zip - Arduino-Projekt
//5000Hz: Schafft D-Anteil nicht mehr:
//TEST mit 50000Hz !!!!:
//5000Hz
//#define MOTORMAX 3199
//50000Hz
//#define MOTORMAX 319
//20000Hz
#define MOTORMAX 799
#define MAXARR 200
volatile int arr[MAXARR];
volatile unsigned int ZEITSCHRITTE = 0;
int ZUSTAND=0;
//dt = 0.0002; //5000Hz == 0.0002s
void setzeMotor(long int wert) // +/- 3199
{
if(wert>MOTORMAX)
wert=MOTORMAX;
if(wert<-MOTORMAX)
wert=-MOTORMAX;
if(wert>0)
{
digitalWrite(7,1);
digitalWrite(8,0);
// analogWrite(9,wert);
OCR1A = wert;
}
else if(wert<0)
{
digitalWrite(7,0);
digitalWrite(8,1);
// analogWrite(9,-wert);
OCR1A = -wert;
}
else
{
digitalWrite(7,0);
digitalWrite(8,0);
// analogWrite(9,0);
OCR1A = 0;
}
}
#include "MODULE.h"
#include "LOOP.h"
LOOP* loops[3] = {&loopA,&loopB,&loopC};
void setup()
{
pinMode(12,INPUT); //Taster
digitalWrite(12,1);
pinMode(11,OUTPUT); //Lautsprecher
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7,0);
digitalWrite(8,0);
Serial.begin(9600);
adw.start();
timer1.start();
}
ISR(TIMER1_COMPA_vect) //Ein Durchgang
{
loops[ZUSTAND]->exec();
if(ZEITSCHRITTE%10==0)
digitalWrite(11,1);
if(ZEITSCHRITTE%10==5)
digitalWrite(11,0);
pinMode(12,INPUT); //Taster
digitalWrite(12,1);
ZEITSCHRITTE++;
ZEITSCHRITTE%=500; //0..499 == 0.01 Sekunde
//theoretisch überflüssig, da an Timer 1 Overflow gekoppelt.
}
void loop()
{
//leer, alles passiert zeitlich getaktet in der ISR (Interrupt Service Routine)
}
Code 0-8: Hauptprogramm
class TIMER1
{
public:
TIMER1()
{
}
void start()
{
//Timer1, Mode 14 WGM13..0 1110
//Vorteilung 1, CS12..0 001
TCCR1B = (1<<WGM13) | (1<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10) ;
TCCR1A = (1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (1<<WGM11) | (0<<WGM10);
//TOP=ICR1=3199
//N=1
//fpwm = fclock / ( N*(1+TOP) ) = 16000000/3200==5000Hz
ICR1 = MOTORMAX;
DDRB |= (1<<PB5); //ist D9 / OC1A
//ISR aufgrund Überlauf Timer 1:
TIMSK1 |= (1<<OCIE1A);
//TIMSK1 |= (1<<ICIE1); //Match mit ICR1
//Interrupts aktivieren
sei();
}
} timer1;
class ADW
{
public:
long int sensor=0;
unsigned int akku, akku2;
ADW()
{
}
void start()
{
//A0 ist ADC7 auf dem Arduino Micro:
//MUX:000111
//Vorteilung bei 16Mhz soll 60kHz ergeben, deshalb: 16000000/60000 = 266,67
//Nächster Wert: 256, also 16000000/256 = 62500Hz (o.k.)
//ADLAR==1 => oberen 8 Bit komplett in ADCH, unteren 2 in oberen von ADCL
//ADEN==1 aktiv
//ADSC==1 start conversion / start 1st bei free running
//ADTS3..0 ==0 free running mode
//REFS1..0 00==VREF intern , extern ausgeschaltet
//ADHSM 1==High speed , mehr Stromverbrauch
//ADTS3..0 Koppeln an Timer 1 Overflow: 0110
ADCSRA = (1<<ADEN) | (1<<ADSC) | (0<<ADATE) | (0<<ADIF) | (0<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
ADCSRB = (1<<ADHSM) | (0<<ACME) | (0<<MUX5) | (0<<ADTS3) | (1<<ADTS2) | (1<<ADTS1) | (0<<ADTS0);
ADMUX = (1<<REFS1) | (0<<REFS0) | (1<<ADLAR) | (0<<MUX4) | (0<<MUX3) | (1<<MUX2) | (1<<MUX1) | (1<<MUX0);
}
void aktualisieren()
{
akku2 = ADCL; //muss erst ausgelesen werden, damit ADCL verfügbar ist
akku = ADCH;
ADCSRA |= (1<<ADSC); //Neue Wandlung anfordern, ist (hoffentlich) fertig bei nächstem Durchlauf
sensor = (akku<<2) | (akku2>>6);
//sensor-=608;
}
long int holeWert()
{
return sensor;
}
} adw;
Code 0-9: MODULE.h
class LOOP
{
public:
virtual void exec() = 0;
};
class LoopA : public LOOP
{
public:
unsigned long inx;
unsigned long index;
LoopA()
{
inx=0;
index=0;
}
void exec()
{
adw.aktualisieren();
setzeMotor( 2*(-300-(adw.holeWert()-608)) );
if(inx%100==0)
index++;
inx++;
if(index>=MAXARR)
{
inx=0;
index=0;
ZUSTAND=1;
}
}
} loopA;
class LoopB : public LOOP
{
public:
unsigned long index,inx;
LoopB()
{
index=0;
inx=0;
}
void exec()
{
adw.aktualisieren();
setzeMotor( 2*(0-(adw.holeWert()-608)) );
if(inx%100==0) //20000Hz, %100 => 200Hz
{
arr[index]=(adw.holeWert()-608);
index++;
}
inx++;
if(index>=MAXARR)
{
index=0;
inx=0;
ZUSTAND=2;
setzeMotor(0);
}
}
} loopB;
class LoopC : public LOOP
{
public:
unsigned long index,inx;
LoopC()
{
index=0;
inx=0;
}
void exec()
{
if(inx%100==0)
{
Serial.print(index);
Serial.print(" ");
Serial.println(arr[index]);
index++;
}
inx++;
if(index>=MAXARR)
{
ZUSTAND=0;
index=0;
inx=0;
}
}
} loopC;
Code 0-10: LOOP.h
Lösungsvarianten der System-Indentifikation
regelsysteme_20160504.zip
Bild 0-7: Ordner "identifikation_siehe_Kap21_unten" - PT3-System
Bild 0-8: Ordner "identifikation_VERBESSERT5" - PT2-System mit richtungsabhängiger Gleitreibung, [a,b,c,d]=[362.51078 0.3013469 88.277654 23399.98].