Forum: Mikrocontroller und Digitale Elektronik Hall Encoder Richtungswechsel Problem


von Rudolf.Z (Gast)


Lesenswert?

Hi,

Ich versuche von folgendem Motor den Encoder auszulesen:

http://www.robotshop.com/media/files/pdf/datasheet-1442.pdf

Bisher habe ich folgendes auf einem Arduino Uno programmiert:
1
//Macros
2
#define pwm 5
3
#define en1 6
4
#define en2 7
5
6
#define encoder1 2
7
#define encoder2 3
8
9
volatile int encoderPosA=0;
10
volatile uint8_t encoderOld1;
11
volatile uint8_t encoderOld2;
12
13
void setup() {
14
  //Pins
15
  pinMode(pwm,LOW);
16
  pinMode(en1,OUTPUT);
17
  pinMode(en2,OUTPUT);
18
19
  pinMode(encoder1,INPUT);
20
  pinMode(encoder2,INPUT);
21
22
  digitalWrite(pwm,LOW);
23
  digitalWrite(en1,HIGH);
24
  digitalWrite(en2,LOW);
25
26
  //Encoder
27
  pinMode(encoder1, INPUT); 
28
  digitalWrite(encoder1, HIGH);       // turn on pullup resistor
29
  pinMode(encoder2, INPUT); 
30
  digitalWrite(encoder2, HIGH);       // turn on pullup resistor
31
  attachInterrupt(0, doEncoder1, CHANGE);  // encoder pin on interrupt 0 - pin 2
32
33
  encoderOld1=digitalRead(encoder1);
34
  encoderOld2=digitalRead(encoder2);
35
  
36
  //Serial
37
  Serial.begin (9600);
38
  Serial.println("start"); 
39
}
40
41
void loop() {
42
  // put your main code here, to run repeatedly:
43
  
44
  
45
}
46
47
void doEncoder1() { //interrupt through PIN2
48
  if(encoderOld1==0 && encoderOld2==0 || encoderOld1==1 && encoderOld2==1) { //Left
49
    digitalWrite(en1,HIGH);
50
    digitalWrite(en2,LOW);
51
    encoderPosA++;
52
  } else {
53
    digitalWrite(en1,LOW);
54
    digitalWrite(en2,HIGH);
55
    encoderPosA--;
56
  }
57
  encoderOld1=digitalRead(encoder1);
58
  encoderOld2=digitalRead(encoder2);
59
60
  Serial.println(encoderPosA);
61
}

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

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Und wenn schon, dann willst du hier
1
void doEncoder1() { //interrupt through PIN2
2
....
3
  encoderOld1=digitalRead(encoder1);
4
  encoderOld2=digitalRead(encoder2);

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.

: Bearbeitet durch User
von Rudolf.Z (Gast)


Lesenswert?

Ok, danke, mache das mal so wie von MaWin vorgeschlagen. Das Serial war 
nur zum sehen, ob es funktioniert (bei Drehzahl von Hand).

Gruss Rudolf

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Rudolf.Z (Gast)


Lesenswert?

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
volatile int encoderPosA=0;
19
int pos_des=0;            //desired position in 304/360°*pos ->1216/4=304 resolution of encoder
20
const int encoder_max=304;
21
22
//PID init
23
volatile int e=0;
24
volatile int ealt=0;
25
volatile int esum=0;
26
volatile int out=0;       //current error
27
const uint8_t out_max=255;    //error max
28
const uint16_t integral_max_err=1600;
29
30
void setup() {
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
void loop() {
62
  // trajectory just for testing
63
  for(int i=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(int i=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
void serialEvent() {
82
  while (Serial.available()) {
83
    // get the new byte:
84
      int temp = 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
void doEncoder1() { //interrupt through PIN2
94
  if(digitalRead(encoder2)==HIGH) { //Left
95
    encoderPosA++;
96
  } else {
97
    encoderPosA--;
98
  }
99
}
100
101
void doEncoder2() { //interrupt through PIN2
102
  if(digitalRead(encoder1)==LOW) { //Left
103
    encoderPosA++;
104
  } else {
105
    encoderPosA--;
106
  }
107
}
108
109
void controller() { //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
131
}

von DA (Gast)


Lesenswert?

>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?

von Thorsten O. (Firma: mechapro GmbH) (ostermann) Benutzerseite


Lesenswert?

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

von Rudolf.Z (Gast)


Lesenswert?

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?

von Rudolf.Z (Gast)


Lesenswert?

Niemand eine Idee?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

: Bearbeitet durch User
von Rudolf.Z (Gast)


Lesenswert?

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
volatile int timeMeasure=0; //in uS
21
volatile int currentSpeed=0;
22
int speed_des=100;  //in °/s          
23
24
//PID init
25
volatile int e=0;
26
volatile int esum=0;
27
volatile int out=0;       //current error
28
const uint8_t out_max=150;    //error max
29
const uint16_t integral_max_err=1000;
30
31
void setup() {
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
void loop() {
65
  
66
}
67
68
void serialEvent() {
69
  while (Serial.available()) {
70
    // get the new byte:
71
      int temp = Serial.parseInt();
72
      if(temp<=360 && temp>=20) {
73
          speed_des=temp;
74
          esum=0;
75
      }
76
  }  
77
}
78
79
void doEncoder1() { //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
void controller_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
107
}

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

: Bearbeitet durch User
von msx (Gast)


Lesenswert?

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.

von Rudolf.Z (Gast)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.