Das ganze klappt, aber beim Richtungswechsel gibt es immer 2 Schritte
Verlust, was einen Fehler akkumuliert. Wie kann man den Richtungswechsel
berücksichtigen?
Gruss Rudolf
Fang doch erstmal eine Nummer kleiner an, indem du den Interrupt nur
z.B. bei fallender Flanke ausführst. Liest du dann den anderen Kanal,
ist er bei einer Drehrichtung high und bei der anderen low.
Gut, dabei verlierst du die Hälfte der Auflösung. Der nächste Schritt
wäre dann, bei beiden Flanken die ISR zu triggern und abhängig von der
Flanke und dem Zustand des anderen Kanals die Drehrichtung zu bestimmen
- beide Kanäle gleich ist dann Drehrichtung eins - Kanäle verschieden
ist Drehrichtung zwei.
Das Zwischenspeichern des Wertes, denn du in EncoderOld machst,
erschliesst sich mir nicht - erklär mal bitte. Genauso die Sache mit en1
und en2?
Und serielle Ausgaben in der ISR sind tabu, da sie sehr viel Zeit
beanspruchen. Mach das lieber in der Hauptschleife.
Rudolf.Z schrieb:> //interrupt through PIN2
Was soll der Qustch ?
Man kann Drehencoder nicht zuverlässig durch Flankenwechsel an einem Pin
erkennen.
Das wurde inzwischen hinreichend oft beschrieben.
http://www.dse-faq.elektronik-kompendium.de/dse-faq.htm#F.29
Rufe deine Routine in einem ausreichend schnellen Zeitgeberinterrupt auf
und alles ist gut.
In so einer Interrupt-Funktion ein Serial-println aufzurufen, halte ich
aber für gewagt. Auch ist nicht ganz klar, was du nach nach en1 und en2
schreibst.
die Encoder Eingänge so schnell wie möglich nach betreten der ISR
abfragen. Nicht, dass sich die in der Zwischenzeit geändert haben.
Überhaupt auf einem Arduino, bei dem ein digitalRead nicht unbedingt zum
schnellsten gehört, willst du das so handhaben. Zur Not müssen dann eben
ein paar Hilfsvariablen herhalten, in denen du die Werte zwischendurch
parkst, wenn du die xOldx Werte noch benötigst. Aber du willst und musst
berücksichtigen, dass ein Programm auch auf einem µC nicht in 0-Zeit
abläuft.
MaWin schrieb:> Man kann Drehencoder nicht zuverlässig durch Flankenwechsel an einem Pin> erkennen.
Ich bin sonst auch kein Freund der ISR Methode, aber hier ist sie
vermutlich doch die richtige.
Lt. Datenblatt liefern die Hallgeber prellfreie Impulse und das auch
noch recht schnell, bei 12V am Motor kommt alle 200µs eine Flanke, die
bei einer Timer Abfrage den Arduino(!) ganz schön auf Trab hält. Die
sch**ss Library Routinen sind ja schnarch langsam.
Das ganze funktioniert jetzt und ich habe einen PID Regler entworfen,
der mir auch genau den Ziel Winkel anfährt. Das Problem ist jetzt, dass
ich einer Trajektorie mit einer vorgegebenen Geschwindigkeit folgen
möchte. Momentan fahre ich in meinem Code einfach ein Grad nach dem
anderen ab und warte eine Zeitverzögerung lang, was auch funktioniert,
aber natürlich gar nicht sauber ist. Wie folgt man einer Trajektorie und
hält die Geschwindigkeit konstant? Ich kann ja keinen zweiten PID Regler
entwerfen, da sonst das System instabil wird. Daher muss ich doch die
Geschwindigkeit in den Regler integrieren?
Code:
1
//Libraries
2
#include"TimerOne.h"
3
4
//Macros
5
#define pwm 5
6
#define en1 6
7
#define en2 7
8
9
#define encoder1 2
10
#define encoder2 3
11
12
//PID Macros
13
#define k_p 30 //30
14
#define k_i 1/10 //1/10
15
#define k_d 120 //120
16
17
//Encoder init
18
volatileintencoderPosA=0;
19
intpos_des=0;//desired position in 304/360°*pos ->1216/4=304 resolution of encoder
20
constintencoder_max=304;
21
22
//PID init
23
volatileinte=0;
24
volatileintealt=0;
25
volatileintesum=0;
26
volatileintout=0;//current error
27
constuint8_tout_max=255;//error max
28
constuint16_tintegral_max_err=1600;
29
30
voidsetup(){
31
//Pins
32
pinMode(pwm,LOW);
33
pinMode(en1,OUTPUT);
34
pinMode(en2,OUTPUT);
35
36
pinMode(encoder1,INPUT);
37
pinMode(encoder2,INPUT);
38
39
digitalWrite(pwm,LOW);
40
digitalWrite(en1,HIGH);
41
digitalWrite(en2,LOW);
42
43
//Encoder
44
pinMode(encoder1,INPUT);
45
digitalWrite(encoder1,HIGH);// turn on pullup resistor
46
pinMode(encoder2,INPUT);
47
digitalWrite(encoder2,HIGH);// turn on pullup resistor
48
49
attachInterrupt(0,doEncoder1,FALLING);
50
//attachInterrupt(1,doEncoder2,FALLING);
51
52
//Timer1 for controller
53
Timer1.initialize(2000);//500 Hz
54
Timer1.attachInterrupt(controller);
55
56
//Serial
57
Serial.begin(9600);
58
Serial.println("start");
59
}
60
61
voidloop(){
62
// trajectory just for testing
63
for(inti=1;i<=encoder_max;i=i+1){
64
pos_des=i;
65
esum=0;
66
delay(16);
67
}
68
69
delay(1000);
70
71
for(inti=encoder_max;i>=1;i=i-1){
72
pos_des=i;
73
esum=0;
74
delay(16);
75
}
76
77
delay(1000);
78
79
}
80
81
voidserialEvent(){
82
while(Serial.available()){
83
// get the new byte:
84
inttemp=Serial.parseInt();
85
if(temp<=304&&temp>=0){
86
pos_des=temp;
87
ealt=encoderPosA-pos_des;
88
esum=0;
89
}
90
}
91
}
92
93
voiddoEncoder1(){//interrupt through PIN2
94
if(digitalRead(encoder2)==HIGH){//Left
95
encoderPosA++;
96
}else{
97
encoderPosA--;
98
}
99
}
100
101
voiddoEncoder2(){//interrupt through PIN2
102
if(digitalRead(encoder1)==LOW){//Left
103
encoderPosA++;
104
}else{
105
encoderPosA--;
106
}
107
}
108
109
voidcontroller(){//500Hz call frequency
110
e=(encoderPosA-pos_des);
111
if(esum<integral_max_err)//Integral windup
112
esum=esum+e;
113
out=e*k_p+(e-ealt)*k_d+esum*k_i;
114
ealt=e;
115
116
if(out>out_max)
117
out=out_max;
118
119
if(out<-out_max)
120
out=-out_max;
121
122
if(out>0){//update direction
123
digitalWrite(en1,HIGH);
124
digitalWrite(en2,LOW);
125
}else{
126
digitalWrite(en1,LOW);
127
digitalWrite(en2,HIGH);
128
}
129
130
analogWrite(pwm,abs(out));//Value between 0 and 255
>Momentan fahre ich in meinem Code einfach ein Grad nach dem>anderen ab und warte eine Zeitverzögerung lang, was auch funktioniert,>aber natürlich gar nicht sauber ist.
Was machst du? Weißt du das?
Normalerweise legt man die Geschwindigkeit bei der Berechnung der
Sollwerte fest. Der Regler hat dann nur die Aufgabe, diesen Sollwerten
mit möglichst wenig Abweichung zu folgen. Wenn der Regler gut
eingestellt ist und die Sollwerte nichts fordern, was der Antrieb und
die Mechanik nicht umsetzen können (Stichwort Beschleunigungs- und
Ruckbegrenzung), ergibt sich eine gute Übereinstimmung zwischen Soll-
und Istwerten.
Mit freundlichen Grüßen
Thorsten Ostermann
Thorsten O. schrieb:> Normalerweise legt man die Geschwindigkeit bei der Berechnung der> Sollwerte fest.
Wie macht man das? Wenn ich z.B 30° in 5s verfahren will, kann ich die
30° z.B in 100 Punkte zerlegen (Trajektorie) wo ich dem Regler immer
0.05s Zeit lasse um zwischen 2 Punkten auszuregeln, also so wie in
meiner Loop function?
Mit einem PID gibt es m.E. zwei Ansätze:
* Du nimmst den numerischen Endpunkt (in Hallgeberzählungen) als
Sollwert und die Istposition aus dem Ergebnis deiner Sensormessung. Die
Geschwindigkeit klemmst (begrenzt) du am Ausgang des PID auf den
gewünschten Wert.
* Die Zeit zwischen den Sensorwechseln liefert direkt die
1/Geschwindigkeit als Istwert für den PID, Sollwert gibst du vor und die
Zählerei der Sensoren gibt dir die Differenz zum Sollpunkt. Wenn
Differenz = 0 dann Motorstop.
Aber es gehen auch zwei PID. Der eine regelt über die Sensorwechselzeit
= 1/Geschwindigkeit und der andere kriegt wie gehabt Sollwert in
Sensorposition von dir und Istwert von der Sensorzählerei. Dieser PID
kann geklemmt auf True und False den ersten PID sperren.
Ok, danke, werde deine zweite Methode anwenden. Ich stehe aber vor dem
Problem, wie ich die Geschwindigkeit ermitteln soll.
Momentan ermittle ich die Geschwindigkeit über einen Timer, denn ich
nach jedem Encoderimpuls neu starte:
-> °/s=360°/[(°/Schritt Encoder)*gemessenes Zeitintervall]
Als Regler verwende ich einen PI-Regler. Es funktioniert solange, bis
der Fehler negativ wird, dann fährt der Motor nur noch mit voller
Geschwindigkeit.
Wie macht man das normalerweise?
Gruss Rudolf.Z
1
//Libraries
2
#include"TimerOne.h"
3
#include"MsTimer2.h"
4
5
6
//Macros
7
#define pwm 5
8
#define en1 6
9
#define en2 7
10
11
#define encoder1 2
12
#define encoder2 3
13
14
//PD Macros for speed, since d not needed for speed
15
#define k_p 6/7 //30
16
#define k_i 0 //1/10
17
18
19
//Encoder init
20
volatileinttimeMeasure=0;//in uS
21
volatileintcurrentSpeed=0;
22
intspeed_des=100;//in °/s
23
24
//PID init
25
volatileinte=0;
26
volatileintesum=0;
27
volatileintout=0;//current error
28
constuint8_tout_max=150;//error max
29
constuint16_tintegral_max_err=1000;
30
31
voidsetup(){
32
//Pins
33
pinMode(pwm,LOW);
34
pinMode(en1,OUTPUT);
35
pinMode(en2,OUTPUT);
36
37
pinMode(encoder1,INPUT);
38
pinMode(encoder2,INPUT);
39
40
digitalWrite(pwm,LOW);
41
digitalWrite(en1,HIGH);
42
digitalWrite(en2,LOW);
43
44
//Encoder
45
pinMode(encoder1,INPUT);
46
digitalWrite(encoder1,HIGH);// turn on pullup resistor
47
pinMode(encoder2,INPUT);
48
digitalWrite(encoder2,HIGH);// turn on pullup resistor
49
50
attachInterrupt(0,doEncoder1,FALLING);
51
52
//Timer1 for controller
53
Timer1.initialize(1000000);//high time interval, since should not overflow
54
55
//MsTimer2
56
MsTimer2::set(4,controller_speed);//every 4 ms call controller_speed ->250Hz
57
MsTimer2::start();
58
59
//Serial
60
Serial.begin(9600);
61
Serial.println("start");
62
}
63
64
voidloop(){
65
66
}
67
68
voidserialEvent(){
69
while(Serial.available()){
70
// get the new byte:
71
inttemp=Serial.parseInt();
72
if(temp<=360&&temp>=20){
73
speed_des=temp;
74
esum=0;
75
}
76
}
77
}
78
79
voiddoEncoder1(){//interrupt through PIN2
80
timeMeasure=Timer1.read();//big overhead
81
if(timeMeasure>0)//->no overflows
82
currentSpeed=1184210/timeMeasure;//1'184'210=1'000'000*360/resolution_encoder ->speed in °/s
83
Timer1.restart();
84
}
85
86
voidcontroller_speed(){//500Hz call frequency -> get immediately speed from encoderIntervallCounter
87
e=speed_des-currentSpeed;
88
if(esum<integral_max_err)//Integral windup
89
esum=esum+e;
90
out=e*k_p+esum*k_i;
91
92
if(out>out_max)
93
out=out_max;
94
95
if(out<-out_max)
96
out=-out_max;
97
98
if(e>0){//update direction
99
digitalWrite(en1,LOW);
100
digitalWrite(en2,HIGH);
101
}else{
102
digitalWrite(en1,HIGH);
103
digitalWrite(en2,LOW);
104
}
105
106
analogWrite(pwm,abs(out));//Value between 0 and 255
Rudolf.Z schrieb:> Es funktioniert solange, bis> der Fehler negativ wird, dann fährt der Motor nur noch mit voller> Geschwindigkeit.
Möglicherweise liegt das an der internen Repräsentation von negativen
Zahlen (Zweierkomplement), dabei ist das höchste Bit gesetzt und
fungiert als Minuszeichen.
https://de.wikipedia.org/wiki/Zweierkomplement
Lass dir 'e' und 'out' mal auf die Konsole werfen, dann wirds vermutlich
klarer.
Du kannst die Sache auch etwas übersichtlicher machen, wenn du für die
Abfrage auf negative Werte gar nicht e, sondern out nimmst.
> Timer1.initialize(1000000); //high time interval, since should not> overflow
Da wäre ich vorsichtig. Check mal in den Arduino Libs, ob sowas geht,
denn selbst Timer 1 hat nur 16 bit.
Rudolf.Z schrieb:> currentSpeed=1184210/timeMeasure;> //1'184'210=1'000'000*360/resolution_encoder ->speed in °/s
Lass dir das auch mal auf der Konsole anzeigen. Mögl. kommt da nur
Unsinn raus.
Rudolf.Z schrieb:> Wie macht man das? Wenn ich z.B 30° in 5s verfahren will, kann ich die> 30° z.B in 100 Punkte zerlegen
Wie ich das Datenblatt lese, hat der Encoder eine Auflösung von 64
Impulsen/Umdrehung und das Getriebe eine Untersetzung von 19. Das ergibt
1216 Imp./Umdrehung oder 3,37.. Impulse/Grad. Ob Du jetzt 1-, 2- oder
4-fach auswertest, habe ich mir nicht weiter angesehen.
Das ist m.E. einfach zu wenig oder zu langsam, um etwas zu regeln. Ein
Schrittmotor wäre hier sinnvoll.
Die Positionsregelung funktioniert nun tiptop, ich habe mittlerweile die
Methode nach MaWin angewandt und jetzt 1216 Impulse/Umdrehung. Die
Geschwindigkeitsregelung kann natürlich nicht genau erfolgen, so wie ich
es momentan habe nur in 15°/s Schritten, aber das reicht mir. Ich denke,
dass ich vielleicht doch nur mit dem PID einer Trajektorie mit
Verzögerung folgen werde, was sehr wahrscheinlich genauer ist als eine
Geschwindigkeits- und Positionsregelung.
Gruss Rudolf.Z