Forum: Mikrocontroller und Digitale Elektronik Hilfe zu Drehencoder-Auswertung nach Wiki


von Wurzel (Gast)


Lesenswert?

Hallo,

ich versuche den Pollin Drehencoder von ALPS auszuwerten. Ich habe mich 
genau nach dem C-Code im Wiki hier auf der Website gehalten. 
http://www.mikrocontroller.net/articles/Drehgeber

Die Auswertung läuft in folgender Funktion ab:
1
char decoder_getc(uint8_t x_position, uint8_t y_position, unsigned char min_char, unsigned char max_char)
2
{
3
  unsigned char zahl = '0';
4
  
5
  while( !(ENCODER_BOTTON_PORT & (1<<ENCODER_BOTTON_PIN)) ); //warten bis Taster losgelassen wird
6
7
  while( ENCODER_BOTTON_PORT & (1<<ENCODER_BOTTON_PIN) )
8
  {
9
    zahl += encode_read2();
10
  
11
12
    lcd_gotoxy(x_position, y_position);
13
    lcd_putc(zahl);
14
15
  }
16
  return zahl;
17
}

Das Problem ist, dass manchmal Drehungen nicht erkannt werden, oder ich 
erst zig mal drehen muss bis sich was auf dem Display tut. Auch werden 
manchmal Zeichen übersprungen.
:-(

Ich habe die Abtastrate auf 0,5ms verringert und auf 4 bzw. 8ms erhöht. 
Hat irgendwie nichts gebracht. Ich bin jetzt etwas ratlos.

Hat jemand mit dem Pollin ALPS Drehencoder Erfahrungen? Wer kann helfen?
danke.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> Ich habe mich genau nach dem C-Code im Wiki hier auf der Website gehalten.
Gut. Der Code funktioniert super.

> Das Problem ist, dass manchmal Drehungen nicht erkannt werden
Dann zeig mal deinen restlichen Code. Bei dem, was du schon gepostet 
hast, erwarte ich Schlimmes... :-/

von Karl H. (kbuchegg)


Lesenswert?

Wurzel schrieb:

> Hat jemand mit dem Pollin ALPS Drehencoder Erfahrungen?

Welcher ist es denn genau?
(Hintergrund: Ich habe hier auch Drehgeber, die verhalten sich aber ganz 
anders. Die geben beim Drehen von einer Rastposition zur nächsten nur 
Pulse aus)

von Wurzel (Gast)


Lesenswert?

ATmega16 @ 8MHz Quarz

Hier der Code für den Timer0.
1
#define XTAL    8000000
2
//Drehencoder Pinbelegung
3
#define PHASE_B    (PIND & 1<<PD2)
4
#define PHASE_A    (PIND & 1<<PD7)
5
 
6
volatile int8_t enc_delta;      // -128 ... 127
7
static int8_t last;
8
9
void timer0_init(void)
10
{
11
  int8_t recent;
12
13
  recent = 0;
14
  if( PHASE_A )
15
    recent = 3;
16
  if( PHASE_B )
17
    recent ^= 1;          // convert gray to binary
18
  last = recent;            // power on state
19
  enc_delta = 0;
20
  TCCR0 |= (1<<WGM01)|(1<<CS01)|(1<<CS00);    // CTC, XTAL / 64
21
  OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);  // 1ms
22
  TIMSK |= 1<<OCIE0;
23
}
24
25
void timer0_comp_irx(unsigned char enable)
26
{
27
  if( enable )
28
    TIMSK |= (1<<OCIE0);
29
  else
30
    TIMSK &= ~(1<<OCIE0);
31
}
32
33
ISR( TIMER0_COMP_vect )        
34
{
35
  int8_t recent, diff;
36
37
  recent = 0;
38
  if( PHASE_A )
39
    recent = 3;
40
  if( PHASE_B )
41
    recent ^= 1;          // convert gray to binary
42
  diff = last - recent;        // difference last - new
43
  if( diff & 1 )            // bit 0 = value (1)
44
  {        
45
    last = recent;          // store new as next last
46
    enc_delta += (diff & 2) - 1;  // bit 1 = direction (+/-)
47
  }
48
}

Drehencoder:
1
#define ENABLE  1
2
#define DISABLE  0
3
4
int8_t encode_read1( void )      // read single step encoders
5
{
6
  int8_t val;
7
8
  timer0_comp_irx(DISABLE);
9
  val = enc_delta;
10
  enc_delta = 0;
11
  timtimer0_comp_irx(ENABLE);
12
  return val;          // counts since last call
13
};
14
 
15
 
16
int8_t encode_read2( void )      // read two step encoders
17
{
18
  int8_t val;
19
20
  timer0_comp_irx(DISABLE);
21
  val = enc_delta;
22
  enc_delta = val & 1;
23
  timer0_comp_irx(ENABLE);
24
  return val >> 1;
25
};
26
 
27
 
28
int8_t encode_read4( void )      // read four step encoders
29
{
30
  int8_t val;
31
32
  timer0_comp_irxDISABLE);
33
  val = enc_delta;
34
  enc_delta = val & 3;
35
  timer0_comp_irxENABLE);
36
  return val >> 2;
37
};

Folgendes muss ich noch sagen:
Die Funktion decoder_getc(..) sieht etwas chaotisch aus, weil die 
Auswertung nicht über den Timerinterrupt geschehen ist. Die zwei 
fehlenden Parameter min_char und max_char wurden zu testzwecken 
entfernt.
Außerdem Timer0 Overflow Interrupt läuft noch ein Interrupt vom Timer0 
ca. alle 4ms.
In der main wird nach Initalisierung (Ports, Timer) die Funktion 
decoder_getc() aufgerufen und das ASCII Zeichen auf dem Display 
ausgegeben. Es handelt sich bei der Funktion nur um eine Testroutine.

@Karl heinz
ALPS 11mm Size Metal Shaft Encoder EC11 Series
Model No. EC11E15244B2

von Wurzel (Gast)


Lesenswert?


von Rene K. (draconix)


Lesenswert?

Also ich muß sagen, ich habe hier drei verschiedene, ausgebaute, 
Drehimpulsgeber liegen... alle haben eine andere Art der Ansteuerung.

von Karl H. (kbuchegg)


Lesenswert?

Ich kann jetzt aus dem Kopf nicht sagen, welchen ALPS Typ ich genau 
habe. Aber wenn ich abends nach Hause komme, werd ich das mal eruieren 
und meine Codebasis rausgeben.

von Wurzel (Gast)


Lesenswert?

@Karl heinz

Das wäre sehr freundlich von dir, danke!

von Karl H. (kbuchegg)


Lesenswert?

Hmm.
Der letzte Encoder, den ich noch nicht verbaut hatte, versteckt sich 
hartnäckig.
Jetzt kann ich nicht sagen, was das genau für ein Typ ist. Aber ich weiß 
noch, wie ich ihn das erste mal ausprobiert hatte: Ich hab einen Kanal 
(A) an den Durchgangspiepser gehängt und war erstaunt, dass derbeim 
Drehen von einer Rastposition in die nächste nur ganz kurz gepiepst hat. 
Erwartet hatte ich eigentlich, dass sich mit dem Drehen von einer 
Rastposition in die nächste ein Piepsen/nicht_piepsen ergibt.

Wie auch immer.
Ich habe die Auswertung in die Tastenentprellroutine vom PeDa 
eingebunden.
D.h. Timer-Overflow der alle paar Millisekunden ausgelöst wird.
1
#define ENCODER_DDR     DDRA
2
#define ENCODER_PORT    PORTA
3
#define ENCODER_PIN     PINA
4
#define ENCODER_A       (1<<PA5)
5
#define ENCODER_B       (1<<PA4)
6
 
7
8
uint8_t prevEncoderA = ENCODER_A;
9
uint8_t prevEncoderB = ENCODER_B;
10
uint8_t encoderA = ENCODER_A;
11
uint8_t encoderB = ENCODER_B;
12
13
ISR( TIMER0_OVF_vect )                            // every 10ms
14
{
15
  ...
16
17
  // Drehendcoder
18
  prevEncoderA = encoderA;
19
  encoderA = ENCODER_PIN & ENCODER_A;
20
  encoderB = ENCODER_PIN & ENCODER_B;
21
22
  if( encoderA != prevEncoderA ) {      // Pegelwechsel A
23
    if( !encoderA )               // 1 -> 0  Startflanke
24
      prevEncoderB = encoderB;
25
    else {                        // 0 -> 1  Endflanke
26
      if( prevEncoderB != encoderB ) {  // gilt nur, wenn es an B ebenfalls einen Pegelwechsel gab
27
                                        // da die Wechsel phasenverschoben sind
28
                                        // ist das gleichzeitig eine Entprellung
29
30
        if( encoderB )
31
          key_press |= KEY_NEXT;
32
        else
33
          key_press |= KEY_PREV;
34
35
      }
36
    }
37
  }
38
}

Das Ergebnis wird in key_press abgeliefert und wird mit den normalen 
Entprellroutinen für die Tasten mit abgefragt.
1
///////////////////////////////////////////////////////////////////
2
//
3
// check if a key has been pressed. Each pressed key is reported
4
// only once
5
//
6
uint8_t get_key_press( uint8_t key_mask )
7
{
8
  cli();                                          // read and clear atomic !
9
  key_mask &= key_press;                          // read key(s)
10
  key_press ^= key_mask;                          // clear key(s)
11
  sei();
12
  return key_mask;
13
}

und natürlich die Intialisierung
1
int main( void )
2
{
3
  KEY_DDR  &= ~ALL_KEYS;                // konfigure key port for input
4
  KEY_PORT |=  ALL_KEYS;                // and turn on pull up resistors
5
6
  ENCODER_DDR &= ~( ENCODER_A | ENCODER_B );
7
  ENCODER_PORT |= ENCODER_A | ENCODER_B;
8
 
9
  ...
10
11
  TCCR0 = (1<<CS01)|(1<<CS00);      // divide by 1024
12
  TIMSK |= 1<<TOIE0;        // enable timer interrupt
13
14
  ...
15
16
  sei();
17
18
  while ....


Ich hätte eigentlich gerne ein komplettes Beispiel zusammengestellt, 
aber das Teil ist hartnäckig weg :-)

von alpshasser (Gast)


Lesenswert?

Alle Alps Drehencoder sind anders.
Auch die mit derselben Partnummer.
Insbesondere die Phase B kann bei der Rastung stabil 1 oder 0 sein oder 
- meistens- undefiniert irgendwas sein.
Auch kann es 2 oder 1 Impuls zwischen Rastungen geben. Eine allgemein 
definierte Aussage zu diesem Murks ist nicht möglich.

von Wurzel (Gast)


Lesenswert?

Ich habe leider kein Oszi und kann daher nur den Zustand in den 
Rasterstellungen erfassen. Beide "Phasen" wechseln zwischen 11 und 00.

Ich probiere mal meinen Code an deine Vorlage anzupassen.

von Hannes L. (hannes)


Lesenswert?

Wurzel schrieb:
> Ich habe leider kein Oszi und kann daher nur den Zustand in den
> Rasterstellungen erfassen.

Wenn Du langsam genug drehst, dann siehst Du auch an LEDs, wie die 
beiden Phasen schalten.

> Beide "Phasen" wechseln zwischen 11 und 00.

So ist es bei meinem Alps von Pollin (Bestellnummer 240399, vor einigen 
Jahren gekauft) auch, ist eine Ausführung mit kurzer geriffelter 1/4 
Zoll-Welle, 30 Rastungen pro Umdrehung (15 mal 00 und 15 mal 11) und 
sehr sauberen Rastpunkten ohne Wackler an einer der beiden Phasen (im 
Gegensatz zum Panasonic-Drehgeber Bestellnummer 240313, dessen Phase B 
genau im Rastpunkt umschaltet).

>
> Ich probiere mal meinen Code an deine Vorlage anzupassen.

Ich frage die Dinger im Timer-synchronisierten Job im Abstand von 1 ms 
ab, wobei ich aus (um 2 Bit geshiftetem) Alt-Wert und Neuwert einen 
Index bilde, über den ich den Increment-Wert (-1, 0, +1) aus einem 
Flash-Array hole (LUT). Bei Drehgebern mit anderen Eigenschaften wird 
einfach nur die LUT angepasst.

Mit C-Code kann ich nicht dienen, ich werkele in ASM.

...

von Rene K. (draconix)


Lesenswert?

Wurzel schrieb:
> Ich habe leider kein Oszi und kann daher nur den Zustand in den
> Rasterstellungen erfassen. Beide "Phasen" wechseln zwischen 11 und 00.
>
> Ich probiere mal meinen Code an deine Vorlage anzupassen.

Jep ist richtig so, er schaltet nicht direkt 11 und 00 sondern schaltet

in die eine Richtung

P1: 0-1-1   /  1-0-0
P2: 0-0-1   /  1-1-0

in die andere Richtung

P1: 0-0-1  /  1-1-0
P2: 0-1-1  /  1-0-0

von Falk B. (falk)


Lesenswert?

@  alpshasser (Gast)

>Alle Alps Drehencoder sind anders.

Oh wie schön.

>Insbesondere die Phase B kann bei der Rastung stabil 1 oder 0 sein oder
>- meistens- undefiniert irgendwas sein.

Es ist ja auch selten dämlich, die Rastpunkte genau auf den 
Flankenwechsel eines Kanals zu legen.

MfG
Falk

von Wurzel (Gast)


Lesenswert?

@Falk brunner

ALPS hat es wohl geschafft den Rastpunkt einer Phase auf die Flanke zu 
legen (siehe Datenblatt). ;-)

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

Falk Brunner schrieb:
> @  alpshasser (Gast)
>
>>Alle Alps Drehencoder sind anders.
>
> Oh wie schön.
>
>>Insbesondere die Phase B kann bei der Rastung stabil 1 oder 0 sein oder
>>- meistens- undefiniert irgendwas sein.
>
> Es ist ja auch selten dämlich, die Rastpunkte genau auf den
> Flankenwechsel eines Kanals zu legen.

Das weißt Du und das weiß ich, aber Panasonic weiß das nicht...
http://www.pollin.de/shop/downloads/D240313D.PDF
Schau Dir das Impulsdiagramm an, Spur B hat die Flanke (den 
Pegelwechsel) genau auf dem Rastpunkt.

@Wurzel:
Beim Alps ist das nicht der Fall, siehe Datenblatt im Anhang.

>
> MfG
> Falk

...

von Wurzel (Gast)


Lesenswert?

@Lux

Doch! Zumindest bei den Typen EC09E/EC11E/EC11J/EC11K. Schau mal auf 
Seite 9 des Datenblatts. ;-)
http://www.alps.com/products/WebObjects/catalog.woa/E/PDF/Switch/Encoder/EC11/EC11.PDF

von Hannes L. (hannes)


Lesenswert?

Wurzel schrieb:
> @Lux
>
> Doch!

Der Alps-Drehencoder, den Pollin unter Bestellnummer 240339 verkauft 
hat, hat seine 30 Rastungen allesamt im stabilen Bereich beider Spuren. 
Und dies nicht nur laut Datenblatt, sondern auch real. Um dies zu 
überprüfen, habe ich gestern Abend extra mein Steckbrett hervorgeholt 
und das dort verbaute Exemplar (unten ganz rechts) mit LEDs 
durchgespielt.
http://www.hanneslux.de/avr/tipps/brett/index.html

> Zumindest bei den Typen EC09E/EC11E/EC11J/EC11K. Schau mal auf
> Seite 9 des Datenblatts. ;-)
> 
http://www.alps.com/products/WebObjects/catalog.woa/E/PDF/Switch/Encoder/EC11/EC11.PDF

Ja, das ist wie bei dem Panasonic-Drehgeber (Bestellnummer 240313), den 
Pollin derzeit noch vertreibt und dessen Datenblatt ich oben verlinkt 
habe.

Wenn schon (mindestens) zwei Hersteller solche Drehgeber anbieten, dann 
muss es doch auch Kunden geben, die so "selten dämlich" (TM Falk) sind, 
solche Konstrukte in Auftrag zu geben.

Diese "selten dämliche" Konstruktion ist zwar nicht gerade vorteilhaft, 
lässt sich aber auch zuverlässig und sauber auswerten, ich habe in 
verschiedenen Basteleien die Panasonic-Drehgeber (Pollin 240313) im 
Einsatz.

Bei Drehgebern, die ihre Rastung auf 00 und 11 haben, gibt es pro 
Rastung auf jeder Spur eine Flanke. Um diese Drehgeber auszuwerten, 
prüft man ja eine Spur auf Flanke und die andere Spur auf Zustand. Bei 
symmetrischen Drehgebern ist es egal, welche Spur man auf Flanke prüft. 
Bei diesen "selten dämlichen" Drehgebern prüft man Spur A (also die 
Spur, die im eingerasteten Zustand stabilen Pegel liefert) auf Flanke 
und Spur B auf Zustand. Da der Zustand nur relevant ist, wenn eine 
Flanke erkannt wurde, spielt der (eingerastet) undefinierte Zustand der 
Spur B keine Rolle.

Zum Abtasten des Drehgebers wird das Bitmuster (der Zustand der beiden 
Spuren) eingelesen und auf die beteiligten Bits maskiert. Dies wird dann 
zu dem "gemerkten" und um 2 Bits verschobenen Bitmuster der letzten 
Abtastung geORT. Es entsteht eine 4-Bit-Zahl, die als Index auf die LUT 
genutzt wird. Von diesen 16 möglichen Zuständen sind bei diesen 
Drehgebern aber nur 4 Zustände relevant.

In die LUT werden also nur dort Incremente eingetragen, wo Spur A den 
Pegel wechselt (also eine Flanke hat).

Eine Drehrichtung:
Rastung alt neu
        A B A B
        -------
 -->    0 0 0 1   1
 |  >   0 1 1 1   7  Flanke an A
 |      1 1 1 0  14
 |  >   1 0 0 0   8  Flanke an A
 |      0 0 0 1   1
 |  >   0 1 1 1   7  Flanke an A
 |      1 1 1 0  14
 |  >   1 0 0 0   8  Flanke an A
 --<    0 0 0 1   1

Andere Drehrichtung:
Rastung alt neu
        A B A B
        -------
 -->    0 1 0 0   4
 |  >   0 0 1 0   2  Flanke an A
 |      1 0 1 1  11
 |  >   1 1 0 1  13  Flanke an A
 |      0 1 0 0   4
 |  >   0 0 1 0   2  Flanke an A
 |      1 0 1 1  11
 |  >   1 1 0 1  13  Flanke an A
 --<    0 1 0 0   4

Die eine Drehrichtung ergibt also bei Flanken an Spur A die Zahlenwerte 
(als Index auf die LUT) 7 und 8, die andere Drehrichtung 2 und 13. 
Daraus ergibt sich, dass bei Index 7 und 8 der Wert +1 (als Increment) 
in die LUT eingetragen wird und bei Index 2 und 13 der Increment-Wert 
-1. Alle anderen Elemente des Arrays (der LUT) werden mit dem Wert 0 
aufgefüllt.

Wird Spur A und B vertauscht, so ergeben sich andere Index-Werte. Bei 
symmetrischen Drehgebern ist das egal, die "selten dämlichen" spinnen 
dann aber.

Somit wird der Zählerstand nur verändert, wenn eine Flanke an Spur A 
erkannt wurde. Der Zustand von B ist in diesem Zeitpunkt ja stabil.

Dies alles läuft im Timer-Int oder einem per Timer synchronisierten Job 
ab. Die Aufruf-Frequenz ist ein Kompromiss zwischen CPU-Last und maximal 
möglicher Drehgeschwindigkeit. Bei Verwendung des Drehgebers als 
manuelles Eingabegerät hat sich bei mir eine Abtastfrequenz von 1 kHz 
bewährt.

Die Mainloop (bzw. ein Job davon) addiert nun diesen Zählerstand auf den 
zur Bearbeitung anstehenden Wert und löscht ihn danach. Somit gehen 
keine Drehbewegungen verloren, wenn es in Main mal länger dauert.

C-Code kann ich Dir nicht geben, ich mach' sowas in ASM.

...

von Falk B. (falk)


Lesenswert?

@  Hannes Lux (hannes)

>Wenn schon (mindestens) zwei Hersteller solche Drehgeber anbieten, dann
>muss es doch auch Kunden geben, die so "selten dämlich" (TM Falk) sind,
>solche Konstrukte in Auftrag zu geben.

Sicher, was aber nicht automatisch bedeutet, dass das sonderlich klug 
ist. Wie es scheint, ist der Drehgeber auch heutzutage noch sehr mit 
mystischen Vorstellungen behaftet.

>Diese "selten dämliche" Konstruktion ist zwar nicht gerade vorteilhaft,
>lässt sich aber auch zuverlässig und sauber auswerten,

Nöö, das ist mehr oder minder Glückssache, jenachdem wie "klapprig" der 
individuelle Drehgeber ist. Warum das so ist, steht im Artikel 
Drehgeber.

>Rastung auf jeder Spur eine Flanke. Um diese Drehgeber auszuwerten,
>prüft man ja eine Spur auf Flanke und die andere Spur auf Zustand.

Genau DAS ist Billigmurks, der aber leider selbst bei Markenherstellern 
zu finden ist. Kann jeder leicht prüfen. Einfach einen Drehknopf fest 
anfassten und immer auf einem Rastpunkt leicht hin- und herdrehen. 
Mechanisch dreht man immer vor und zurück, ein Menu darf dabei nicht 
kontinuierlich weiterblättern bzw. wenn es eine Zahleneinstellung ist, 
muss sie vor und zurück springen. Tut sie das nciht, ist die Auswertung 
Schrott.

MfG
Falk

von Hannes L. (hannes)


Lesenswert?

Falk Brunner schrieb:
> @  Hannes Lux (hannes)
>
>>Wenn schon (mindestens) zwei Hersteller solche Drehgeber anbieten, dann
>>muss es doch auch Kunden geben, die so "selten dämlich" (TM Falk) sind,
>>solche Konstrukte in Auftrag zu geben.
>
> Sicher, was aber nicht automatisch bedeutet, dass das sonderlich klug
> ist.

Da gebe ich Dir schon mal unbestritten recht. Aber...
Nicht alles, was ich nicht (auf Anhieb) verstehe, ist von vornherein 
Schrott.

> Wie es scheint, ist der Drehgeber auch heutzutage noch sehr mit
> mystischen Vorstellungen behaftet.

Bei mir nicht.

>
>>Diese "selten dämliche" Konstruktion ist zwar nicht gerade vorteilhaft,
>>lässt sich aber auch zuverlässig und sauber auswerten,
>
> Nöö, das ist mehr oder minder Glückssache,

Nöö, ist es nicht. Es sei denn, Du meinst mit Glücksache das Vertauschen 
der Spuren, dann hättest Du natürlich recht. Aber dann ist auch der 
(richtig gepolte) Anschluss der Versorgungsspannung Glücksache... ;-)

> jenachdem wie "klapprig" der
> individuelle Drehgeber ist.

Wenn er wirklich "klapperig" (auf beiden Spuren) ist, dann gehört er in 
den Schrott.

> Warum das so ist, steht im Artikel
> Drehgeber.

In diesem Artikel steht eine Menge theoretisches Blabla, aber nichts 
Konkretes praktisch Verwertbares (zumindest vor einiger Zeit, aktuell 
nachgesehen habe ich nicht).

>
>>Rastung auf jeder Spur eine Flanke. Um diese Drehgeber auszuwerten,
>>prüft man ja eine Spur auf Flanke und die andere Spur auf Zustand.
>
> Genau DAS ist Billigmurks,

Du musst es ja wissen...

> der aber leider selbst bei Markenherstellern
> zu finden ist. Kann jeder leicht prüfen. Einfach einen Drehknopf fest
> anfassten und immer auf einem Rastpunkt leicht hin- und herdrehen.
> Mechanisch dreht man immer vor und zurück, ein Menu darf dabei nicht
> kontinuierlich weiterblättern bzw. wenn es eine Zahleneinstellung ist,
> muss sie vor und zurück springen. Tut sie das nciht, ist die Auswertung
> Schrott.

Diesem Test hält meine Auswertung locker stand.

Anscheinend hast Du meinen Text nicht richtig gelesen und bist auf 
"Flanke" angesprungen. Mit "Auswertung der Flanke" meinte ich natürlich 
keinen externen Interrupt, sondern einen Vergleich zwischen Neuwert und 
Altwert bei zyklischer Abfrage (per Timer-Interrupt).

>
> MfG
> Falk

...

von Karl H. (kbuchegg)


Lesenswert?

Falk Brunner schrieb:

> anfassten und immer auf einem Rastpunkt leicht hin- und herdrehen.
> Mechanisch dreht man immer vor und zurück, ein Menu darf dabei nicht
> kontinuierlich weiterblättern bzw. wenn es eine Zahleneinstellung ist,
> muss sie vor und zurück springen.

Noch nicht mal das.
Eine saubere Auswertung erkennt, dass nicht beide Flanken in einer 
korrekten Reihenfolge gekommen sind und tut nichts.

von Hannes L. (hannes)


Lesenswert?

Karl heinz Buchegger schrieb:
> Falk Brunner schrieb:
>
>> anfassten und immer auf einem Rastpunkt leicht hin- und herdrehen.
>> Mechanisch dreht man immer vor und zurück, ein Menu darf dabei nicht
>> kontinuierlich weiterblättern bzw. wenn es eine Zahleneinstellung ist,
>> muss sie vor und zurück springen.
>
> Noch nicht mal das.
> Eine saubere Auswertung erkennt, dass nicht beide Flanken in einer
> korrekten Reihenfolge gekommen sind und tut nichts.


Auch das funktioniert ohne Probleme, solange man den Drehgeber nicht zu 
schnell dreht, was bei Benutzung als manuelles Eingabegerät aber egal 
ist.

Von den 16 möglichen Kombinationen aus Alt und Neu werden ja 12 
ignoriert (0 in der LUT) und nur 4 genutzt (2 ma sorum und 2 mal rosum 
(TM Paul)).

...

von Wurzel (Gast)


Lesenswert?

@Hannes Lux

Es funktioniert!
Ich habe den Code von Karl Heinz Buchegger probiert, aber auch kein 
brauchbares Ergebnis erhalten. Ich weiss nicht wieso.
Dann habe ich die Funktion mit Hilfe der LUT getestet und damit 
funktioniert es jetzt schon viel besser. :-)
Nur ganz selten wenn ich den Knopf ganz langsam zwischen zwei 
Rastpunkten drehe zählt er manchmal eine Stellung falsch (+1 oder -1). 
Aber kein Vergleich zu meinen vorherigen Versuchen, da ging ja nichts 
zuverlässig. Das Problem was ich noch habe ist, dass Phase A und Phase B 
vertauscht sind. D.h. Phase A am Pin des Encoders ist Phase B im 
Programm. Also müsste die LUT noch angepasst werden.

Hier der Code in C, Verbesserungsvorschläge nehme ich gerne entgegen.
ATmega16 @ 8Mhz

Initialisierung Timer0
1
#define XTAL    8000000
2
//Drehencoder Pinbelegung
3
#define PHASE_B    ((PIND & 1<<PD2)>>PD2)
4
#define PHASE_A    ((PIND & 1<<PD7)>>PD7)
5
6
void timer0_init(void)
7
{
8
  recentA = PHASE_A;
9
  recentB = PHASE_B;
10
  enc_delta = 0;
11
  TCCR0 |= (1<<WGM01)|(1<<CS01)|(1<<CS00);    // CTC, XTAL / 64
12
  OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);  // 1ms
13
  TIMSK |= 1<<OCIE0;
14
}

Timer Compare ISR
1
volatile int8_t enc_delta;      // -128 ... 127
2
static int8_t lastA, lastB, recentA, recentB;
3
const int8_t decoder_lut[]={  0,
4
                -1, //2
5
                0,
6
                0,
7
                0,
8
                0,
9
                1, //7
10
                1, //8
11
                0,
12
                0,
13
                0,
14
                0,
15
                -1, //13
16
                0,
17
                0,
18
                0 
19
              };
20
                
21
22
ISR( TIMER0_COMP_vect )        
23
{
24
  uint8_t index;
25
  
26
  lastA = recentA;
27
  lastB = recentB;
28
29
  recentA = PHASE_A;
30
  recentB = PHASE_B;
31
32
  index = ((lastA<<3)|(lastB<<2)|(recentA<<1)|(recentB)) & 0b00001111; //Zustände zu 4Bit zusammenfassen und maskieren
33
  enc_delta += decoder_lut[index-1]; //Wert aus der LUT entnehmen
34
35
}

von Hannes L. (hannes)


Lesenswert?

Die LUTs für die beiden möglichen Polungen sehen in ASM so aus:
1
/*
2
dgtab:      ;Tabelle mit Drehgeber-Werten (alt-alt-neu-neu als Index)
3
                    ;aa nn,     aa nn
4
.db     0, 1        ;00 00,     00 01
5
.db     0, 0        ;00 10,     00 11
6
.db    -1, 0        ;01 00,     01 01
7
.db     0, 0        ;01 10,     01 11
8
.db     0, 0        ;10 00,     10 01
9
.db     0,-1        ;10 10,     10 11
10
.db     0, 0        ;11 00,     11 01
11
.db     1, 0        ;11 10,     11 11
12
*/
13
14
dgtab:      ;Tabelle mit Drehgeber-Werten (alt-alt-neu-neu als Index)
15
                    ;aa nn,     aa nn
16
.db     0, 0        ;00 00,     00 01
17
.db     1, 0        ;00 10,     00 11
18
.db     0, 0        ;01 00,     01 01
19
.db     0,-1        ;01 10,     01 11
20
.db    -1, 0        ;10 00,     10 01
21
.db     0, 0        ;10 10,     10 11
22
.db     0, 1        ;11 00,     11 01
23
.db     0, 0        ;11 10,     11 11

Eine ist auskommentiert. Es stehen immer 2 Bytes in einer Zeile, weil 
die ASM-Direktive ".db" das bei Flash-Daten (die ja wordadressiert sind) 
so verlangt (geradzahlig).

...

von Falk B. (falk)


Lesenswert?

@Hannes Lux (hannes)

>Wenn er wirklich "klapperig" (auf beiden Spuren) ist, dann gehört er in
>den Schrott.

Nöö, auch wenn er keinen Wackelkontakt hat, ist das Ding nicht 
wasserdicht. Und nur weil das Ding bei dir in Einzelstückzahlen auf dem 
Steckbrett läuft, heisst das nicht automatisch, dass die Lösung 
wasserdicht ist.

>> Warum das so ist, steht im Artikel
>> Drehgeber.

>In diesem Artikel steht eine Menge theoretisches Blabla, aber nichts
>Konkretes praktisch Verwertbares (zumindest vor einiger Zeit, aktuell
>nachgesehen habe ich nicht).

Klasse, so kann man sich natürlich auch rausreden. Und nur weil du was 
nicht (auf Anhieb) verstehst, ist es noch lange kein theoretisches 
BlaBla . . . ;-)

>Anscheinend hast Du meinen Text nicht richtig gelesen und bist auf
>"Flanke" angesprungen. Mit "Auswertung der Flanke" meinte ich natürlich
>keinen externen Interrupt, sondern einen Vergleich zwischen Neuwert und
>Altwert bei zyklischer Abfrage (per Timer-Interrupt).

Ok, hab deinen Post nochmal genau gelesen.
Hast Recht ;-)
Das passt in dem Fall. Vielleicht sollte man das in den Artikel 
Drehgeber aufnehmen. Denn die ALPS & Co "Murksdrehgeber" sind ja nun mal 
recht verbreitet. Und der Verlust/Halbierung der Auflösung ist in diesem 
Fall sogar positiv, denn man erhält nur einen Puls pro Rastung, was im 
allgemeinen erwünscht ist.

MfG
Falk

von Wurzel (Gast)


Lesenswert?

So ich habe die LUT und die ISR noch etwas angepasst. Aber manchmal 
"springt" der Encoder vor und zurück wenn man in eine Richtung bewegt. 
Also macht er quasi 1,-1 vielleicht auch 1,0,-1 beim drehen in ein und 
die selbe Richtung.

Wo ist hier noch der Fehler? **ratlos**

1
#define PHASE_A    ((PIND & 1<<PD2)>>PD2)
2
#define PHASE_B    ((PIND & 1<<PD7)>>PD7)
3
#define KEY      ((PIND & 1<<PD6)>>PD6)
4
#define BOUNCE    20 //BOUNCE*Abtastrate der ISR=Prellzeit
5
6
volatile int8_t enc_delta;
7
volatile uint8_t key_pressed, key_state;
8
static int8_t lastA, lastB, recentA, recentB;
9
const int8_t decoder_lut[]={  0,
10
                -1,
11
                0,
12
                0,
13
                1,
14
                0,
15
                0,
16
                0,
17
                0,
18
                0,
19
                0,
20
                1,
21
                0,
22
                0,
23
                -1,
24
                0 
25
              };
26
                
27
28
ISR( TIMER0_COMP_vect )        
29
{
30
  uint8_t index;
31
  static uint8_t count;
32
  
33
  lastA = recentA;
34
  lastB = recentB;
35
36
  key_state = KEY;
37
  if(!key_state )
38
  {
39
    count++;
40
    if( count >= BOUNCE )
41
      key_pressed = 1;
42
  }  
43
  else
44
  {
45
    count = 0;
46
    key_pressed = 0;
47
  }
48
49
  
50
51
  recentA = PHASE_A;
52
  recentB = PHASE_B;
53
54
  index = ((lastA<<3)|(lastB<<2)|(recentA<<1)|(recentB)) & 0b00001111;
55
  enc_delta += decoder_lut[index];
56
}

P.S. in der ISR ist noch eine Entprellung des Encoder-Tasters eingefügt 
worden.

von Hannes L. (hannes)


Lesenswert?

Nimm mal als LUT:
0, 0, 1, 0, 0, 0, 0,-1,-1, 0, 0, 0, 0, 1, 0, 0
bzw. das Gegenstück:
0, 0,-1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,-1, 0, 0

oder bei anderer Polung:
0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0,-1, 0, 0, 1, 0
bzw. das Gegenstück:
0,-1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,-1, 0

In Deinem oberen Beispiel hast Du Dich vermutlich beim Index verhaspelt, 
Du musst von 0 bis 15 zählen, Deinen Kommentaren nach hast Du von 1 bis 
16 gezählt... ;-)
Beitrag "Re: Hilfe zu Drehencoder-Auswertung nach Wiki"

Dein unteres Beispiel entspricht ja meinem unteren Vorschlag.
Beitrag "Re: Hilfe zu Drehencoder-Auswertung nach Wiki"
Wenn der Murks ist, dann müsstest Du mal meinen oberen Vorschlag 
umsetzen.

...

von Rambo (Gast)


Lesenswert?

ähm oben, unten... ich bin jetzt etwas durcheinander.

Ja ich habe den Code und die LUT geändert.

 enc_delta += decoder_lut[index*+1*];

Die Erhöhung des Index um 1 war ja falsch, weil ich ja dadurch nie das 
Element 0 aus der LUT erhalten kann (und 16te Element existiert ja nicht 
wie du schon erwähnt hast).

Also ich gehe jetzt mal davon aus, dass ich die vier LUTs in der letzten 
Codevariante ausprobieren soll. 
Beitrag "Re: Hilfe zu Drehencoder-Auswertung nach Wiki"

von Rambo (Gast)


Lesenswert?

Also ich habe jetzt alle LUTs getestet, keine Besserung. :-(

P.S. ich meine natürlich das Element mit dem Index 16
>(und 16te Element existiert ja nicht wie du schon erwähnt hast)

von Wurzel (Gast)


Lesenswert?

Ich habe auch mit der Abtastzeit gespielt, hat aber auch nichts 
gebracht.(Name stimmt jetzt wieder ;-))

von Hannes L. (hannes)


Lesenswert?

Deinen C-Code kann (und will) ich nicht nachvollziehen. Meine Routine 
sieht so aus (Auszug, kein vollständiges Programm) und funktioniert:
1
;Drehgeber-Routine in AVR-Assembler für Panasonic-Drehgeber von Pollin.
2
3
;vereinbarte Konstanten:
4
.equ dgpin=pina             ;Drehgeberport-Eingänge
5
.equ dgneumsk=3             ;untere 2 Bits
6
.equ dgaltmsk=15            ;untere 4 Bits
7
8
;genutzte Register (Auszug):
9
.def null=r2                ;immer Null
10
.def dgz=r4                 ;Drehgeber-Zähler 
11
.def dgalt=r21              ;Drehgeber-Bitmuster alt + neu
12
.def wl=r24                 ;Working L
13
.def wh=r25                 ;Working H
14
15
16
drehgeber:          ;wertet Drehgeberbewegungen aus, Aufruf mit 1 kHz
17
 in wl,dgpin                ;Drehgeber-Port einlesen
18
 andi wl,dgneumsk           ;nur die benutzten 2 Bits werten
19
 lsl dgalt                  ;altes Drehgeber-Bitmuster
20
 lsl dgalt                  ;nach oben schieben
21
 or dgalt,wl                ;neue Drehgeberbits einblenden
22
 andi dgalt,dgaltmsk        ;Index auf 4 Bit begrenzen (uralt löschen) 
23
 ldi zl,low(dgtab*2)        ;Tabelle mit
24
 ldi zh,high(dgtab*2)       ;Dregheber-Increment-Werten
25
 add zl,dgalt               ;Index aufaddieren
26
 adc zh,null                ;Übertrag auch
27
 lpm wl,z                   ;Inkrement-Wert holen (0, +1 oder -1)
28
 add dgz,wl                 ;Drehgeber-Increment aufaddieren
29
 ret                        ;fertig, zurück...
30
31
32
/* ;Diese Polung ist deaktiviert...
33
dgtab:      ;Tabelle mit Drehgeber-Werten (alt-alt-neu-neu als Index)
34
                    ;aa nn,     aa nn
35
.db     0, 1        ;00 00,     00 01
36
.db     0, 0        ;00 10,     00 11
37
.db    -1, 0        ;01 00,     01 01
38
.db     0, 0        ;01 10,     01 11
39
.db     0, 0        ;10 00,     10 01
40
.db     0,-1        ;10 10,     10 11
41
.db     0, 0        ;11 00,     11 01
42
.db     1, 0        ;11 10,     11 11
43
*/
44
45
;Diese Polung ist aktiv:
46
dgtab:      ;Tabelle mit Drehgeber-Werten (alt-alt-neu-neu als Index)
47
                    ;aa nn,     aa nn
48
.db     0, 0        ;00 00,     00 01
49
.db     1, 0        ;00 10,     00 11
50
.db     0, 0        ;01 00,     01 01
51
.db     0,-1        ;01 10,     01 11
52
.db    -1, 0        ;10 00,     10 01
53
.db     0, 0        ;10 10,     10 11
54
.db     0, 1        ;11 00,     11 01
55
.db     0, 0        ;11 10,     11 11

...

von Wurzel (Gast)


Lesenswert?

Also wenn ich mir den Assembler-Code ansehe, entspricht dies genau 
meiner Vorgehensweise im C-Code. :-/

Vielleicht liegt's doch am Drehencoder selbst. ???

von screwdriver (Gast)


Lesenswert?

Hallo Hannes,

ich habe deinen Assemblercode nach Bascom portiert. Das Ergebnis 
arbeitet sehr sauber. Nur mit viel Mühe geht da mal eine Rastung 
verloren. Das hat mir so gut gefallen, das ich den Bascom-Quelltext dann 
hier (http://rn-wissen.de/index.php/Drehencoder) veröffentlicht habe.

Danke.

von Falk B. (falk)


Lesenswert?


von screwdriver (Gast)


Lesenswert?

Auch der C-Code von Peter Dannegger hat mit diesen hier per Datenblatt 
vorgestellten "wackligen" Drehgebern, die in der Raststellung 
Signalwechsel haben, keine Probleme. Diese "Wackler" aber auch das 
sogenannte Pendeln wird durch die zyklich aufzurufende Anpassungsroutine
1
int8_t encode_read2( void )
herausgerechnet.

mfg
screwdriver

von Falk B. (falk)


Lesenswert?

@  screwdriver (Gast)

>Signalwechsel haben, keine Probleme. Diese "Wackler" aber auch das
>sogenannte Pendeln wird durch die zyklich aufzurufende Anpassungsroutine

Nöö, das ist in deinem Fall Zufall. Denn die Umschaltung zwischen zwei 
Zuständen kann nämlich auch genau auf der Flanke liegen, auch bei read2 
oder read4.

MfG
Falk

von screwdriver (Gast)


Lesenswert?

Falk Brunner schrieb:
> Denn die Umschaltung zwischen zweiZuständen kann nämlich auch genau auf
> der Flanke liegen, auch bei read2 oder read4.

Das spielt keine Rolle.

Durch einen Pegelwechsel eines Encoder-Signals wechselt in PeDas 
Timer-ISR wohlwahr die Variable enc_delta zwischen +1 und -1. Und eine 
Auswertung des Encoders mit der Routine encode_read1 im Hauptprogramm 
würde dann auch dieses Pendelverhalten in der Variable val zeigen.
Die o.a. Encoder sind jedoch mit der Routine encode_read2 auszuwerten. 
Hier muß die Variable enc_delta jedoch größer 1 oder kleiner -1 sein, 
damit sich die Variable val im Hauptprogramm ändert.

von Wurzel (Gast)


Lesenswert?

Nachtrag:

Ich habe dem ALPS Encoder unrecht getan, er funktioniert jetzt sehr 
zuverlässig! Ursache war ein defekter Pin am ATmega16.

von Wurzel (Gast)


Lesenswert?

Hi,

ich bin's nochmal. Mit einer neuen Problemstellung:

Ich verwende den integrierten Taster des Drehencoders. Das Entprellen 
wie in meinem obigen funktioniert auch. Aber ich möchte jetzt 
unterscheiden zwischen kurz und lang gedrückt. Leider komme ich da nicht 
weiter. Den Code von PeDa 
http://www.mikrocontroller.net/articles/Entprellung#Komfortroutine_.28C_f.C3.BCr_AVR.29 
kapiere ich irgendwie wie nicht und ich bräuchte den ja auch nur für 
einen Taster und nicht einen ganzen port. :-(

Kann mir jemand helfen wie ich vorgehen sollte. Es muss kein fertiger 
Code sein, ich möchte ja auch noch was lernen. Danke euch im Vorraus.

von Hannes L. (hannes)


Lesenswert?

Wurzel schrieb:
> Hi,
>
> ich bin's nochmal. Mit einer neuen Problemstellung:
>
> Ich verwende den integrierten Taster des Drehencoders. Das Entprellen
> wie in meinem obigen funktioniert auch. Aber ich möchte jetzt
> unterscheiden zwischen kurz und lang gedrückt. Leider komme ich da nicht
> weiter.

So, Unterschied kurz/lang. Den erfährt man also erst beim Loslassen, 
denn aus kurz könnte ja noch lang werden... ;-)

Man erhöht also bei gedrückter einen Zähler, sorgt dafür, dass er nicht 
überläuft und wertet ihn beim Loslassen aus. Nach der Auswertung wird er 
natürlich wieder auf 0 gesetzt.

Betrachtet man jetzt die Zeiten und Wertebereiche so stellt man 
Folgendes fest:

Der Drehgeber wird im Zeitabstand von 1 ms abgefragt. Fragt man im 
selben Raster auch den Taster ab, so kann man mit einem Byte als Zähler 
maximal 255 ms als Tastendruckzeit darstellen. Das ist zuwenig. Man 
könnte nun 16-bittig zählen, dann müsste man aber auch 16-bittig 
auswerten. Das ist zwar kein Akt, aber nicht nötig. Denn man kann auch 
einen Vorteiler laufen lassen, der z.B. nur jede 16. Runde zuschlägt. 
Dann reicht das Byte des Zeitzählers bis zu 4 Sekunden.
Also etwa:
1
 inc vorteiler
2
 andi vorteiler,15
3
 brne weg_hier
4
5
 ;Tastenabfrage...
weg_hier:

Damit wird die Tastenabfrage dann nur noch alle 16 ms aufgerufen.

Beim Abfragen des Tasters gibt es zwei Zustände, Taster betätigt (L) und 
Taster unbetätigt (H). Die Tastenabfrage läuft ja auch im Interrupt. Um 
dem Hauptprogramm einen erfolgreichen Tastendruck mitzuteilen, werden 
noch zwei Bit als Merker gebraucht, das eine signalisiert den langen 
Tastendruck, das andere den kurzen. Gelöscht werden die Bits dann von 
der Mainloop, wenn zu zu dem Programmteil verzweigt, der den Tastendruck 
(den zugehörigen Job) abarbeitet.
1
 ;Tastenabfrage        ;Teil der Timer-ISR, Aufruf alle 16 ms
2
 inc tastenzeit            ;erstmal auf Verdacht erhöhen
3
 cpi tastenzeit,255        ;Endwert erreicht?
4
 brne tastenabfrage1       ;nöö, weiter...
5
 dec tastenzeit            ;ja, letzten Schritt zurücknehmen
6
                           ;der Zähler Tastenzeit kann somit nicht
7
                           ;überlaufen...
8
Tastenabfrage1:
9
 sbis tastenpin,tastenbit  ;ist Taste betätigt? - nein, auswerten...
10
 rjmp Tastenabfrage_end    ;ja, weg hier, denn Zählen u. Begrenzen ist
11
                           ;ja fertig...
12
 cpi tastenzeit,taste_lang ;Zeit für lang überschritten?
13
 brlo Tastenabfrage2       ;nein, weiter...
14
 sbr merker,1<<t_lang      ;ja, Merker für langen Tastendruck setzen
15
 rjmp Tastenabfrage3       ;und weiter...
16
Tastenabfrage2:
17
 cpi tastenzeit,taste_kurz ;Zeit für kurz (Entprellzeit) überschritten?
18
 brlo Tastenabfrage2       ;nein, weiter...
19
 sbr merker,1<<t_kurz      ;ja, Merker für kurzen Tastendruck setzen
20
Tastenabfrage3:
21
 clr tastenzeit            ;Tastenzeit löschen
22
Tastenabfrage_end:

Der Zähler "tastenzeit" wird also bei gedrückter Taste erhöht, wobei ein 
Überlauf verhindert wird. Bei ungedrückter Taste wird er zwar auch 
erhöht, fällt aber durch die Lang- und Entprell-Prüfung und wird wieder 
gelöscht. War zuvor die Taste lang genug gedrückt, so wird vor dem 
Löschen noch der zugehörige Merker aktiviert. Dieser wird vom 
Hauptprogramm geprüft und gelöscht.

> Den Code von PeDa
> 
http://www.mikrocontroller.net/articles/Entprellung#Komfortroutine_.28C_f.C3.BCr_AVR.29
> kapiere ich irgendwie wie nicht und ich bräuchte den ja auch nur für
> einen Taster und nicht einen ganzen port. :-(

Da der in C ist, ... ;-)

>
> Kann mir jemand helfen wie ich vorgehen sollte.

Ich hoffe, es ist verständlich genug erklärt.

> Es muss kein fertiger
> Code sein,

Naja, ist einfach so drauflos geschrieben, ist also nicht geprüft, es 
können durchaus Tippfehler drin sein.

> ich möchte ja auch noch was lernen. Danke euch im Vorraus.

...

von Wurzel (Gast)


Lesenswert?

Also ich denke so wie du beschrieben hast, bin ich vorgegangen.
1
#define BOUNCE 20
2
#define LONG   100
3
#define KEY    ((PIND & 1<<PD6)>>PD6)
4
5
volatile uint8_t key_pressed, key_pressed_long;
6
7
//alle 1ms Interrupt
8
ISR( TIMER0_COMP_vect )        
9
{
10
  uint8_t index;
11
  static uint8_t count, count2;
12
  
13
  lastA = recentA;
14
  lastB = recentB;
15
16
17
  if(!KEY )
18
  {
19
    count++;
20
    if( count >= BOUNCE ) //20ms
21
    {
22
      key_pressed = 1;
23
      count = 0;
24
      count2++;
25
    }
26
    if( count2 >= LONG ) //20ms*100=2s
27
    {
28
      key_pressed_long = 1;
29
      count2 = 0;
30
    }
31
  }  
32
  else
33
  {
34
    count = 0;
35
    count2 = 0;
36
  }
37
38
39
  recentA = PHASE_A;
40
  recentB = PHASE_B;
41
42
  index = ((lastA<<3)|(lastB<<2)|(recentA<<1)|(recentB)) & 0b00001111;
43
  enc_delta += decoder_lut[index];
44
}
45
46
//Funktionen zum abfragen der Merker
47
48
uint8_t encoder_keypressed( void )
49
{
50
  uint8_t temp = key_pressed;
51
  key_pressed = 0;
52
  return temp;
53
}
54
55
uint8_t encoder_keypressedlong( void )
56
{
57
  
58
  uint8_t temp = key_pressed_long;
59
  key_pressed_long = 0;
60
  return temp;
61
}

Ich weiss ja das wir nicht die selbe Sprache sprechen, daher umschreibe 
ich die Tastenabfrage. ;-)

Die ISR wird alle 1ms aufgerufen. Wenn Taster gedrückt (L) dann wird 
count um 1 erhöht. Wenn count den Wert zum Entprellen erreicht hat, wird 
der Merker für kurze Tastenbetätigung gesetzt, count wieder null, und 
gleichzeitig der zweite Zähler count2 um 1 erhöht. Erreicht count2 den 
Wert für langes Drücken, wird auch hier der Merker gesetzt und count 
zurückgesetzt. Falls zwischendurch der Taster losgelassen wird, werden 
beide Zähler auf null gesetzt, d.h. um sicher zu gehen, dass die Zeiten 
eingehalten werden.
Die Abfrage der Merker erfolgt in der main() über die zwei Funktionen 
encoder_keypressed() und encoder_keypressedlong(). innerhalb dieser 
Funktionen werden die Merker wieder zurück gesetzt.

Soweit mein Code, aber irgendwie funktioniert dieser noch nicht richtig. 
:-/

von Hannes L. (hannes)


Lesenswert?

Wurzel schrieb:
> Also ich denke so wie du beschrieben hast, bin ich vorgegangen.

Den C-Text kommentiere ich nicht...

> Ich weiss ja das wir nicht die selbe Sprache sprechen,

Naja, ich spreche die Sprache, die der AVR auch spricht (ASM ist eine 1 
zu 1 Umsetzung in Maschinencode) und die der Architektur des AVRs 
entspricht.

> daher umschreibe
> ich die Tastenabfrage. ;-)

Das ist gut...

>
> Die ISR wird alle 1ms aufgerufen. Wenn Taster gedrückt (L) dann wird
> count um 1 erhöht.

Gut.

> Wenn count den Wert zum Entprellen erreicht hat, wird
> der Merker für kurze Tastenbetätigung gesetzt, count wieder null, und
> gleichzeitig der zweite Zähler count2 um 1 erhöht.

Wenn man sich dabei bewusst ist, dass dies im Interrupt passiert, also 
(zeitlich) zwischendurch immer das Hauptprogramm läuft, dann erkennt 
man, dass das so nix wird. Denn das Hauptprogramm fragt ja die Merker ab 
und erkennt den Merker für kurzen Tastendruck, bevor die Taste wieder 
losgelassen wird. Der Merker für den kurzen Tastendruck darf also erst 
gesetzt werden, nachdem der Taster losgelassen wurde (also nachdem 
sicher ist, dass es kein langer Tastendruck mehr werden kann).

> Erreicht count2 den
> Wert für langes Drücken, wird auch hier der Merker gesetzt und count
> zurückgesetzt.

Der Count-Merker wurde indessen von Main erkannt, abgearbeitet und 
zurückgesetzt.

> Falls zwischendurch der Taster losgelassen wird, werden
> beide Zähler auf null gesetzt, d.h. um sicher zu gehen, dass die Zeiten
> eingehalten werden.

Zu umständlich, bei strikter Trennung in Vorteiler und Tastenzeit wird 
alles viel einfacher und als angenehmen Nebeneffekt funktioniert es 
sogar.

> Die Abfrage der Merker erfolgt in der main() über die zwei Funktionen

Ob man das in Funktionen auslagern muss, weiß ich nicht, darüber möchte 
ich auch nicht diskutieren. In ASM würde ich die Merker in der Mainloop 
abfragen
1
 sbrc merker,t_lang        ;trat ein langer Tastendruck auf? - nein...
2
 rcall langer_tastendruck  ;ja, abarbeiten...
3
 sbrc merker,t_kurz        ;trat ein kurzer Tastendruck auf? - nein...
4
 rcall kurzer_tastendruck  ;ja, abarbeiten...
(Je nach Struktur des Programms verzweige ich auch mal über "rjmp" und 
springe statt mit "ret" mit "rjmp mainloop" zurück. Aber das ist eine 
andere Baustelle und muss den C-Programmiierer nicht interessieren.)

... und im Unterprogramm zurücksetzen
1
 cbr merker,t_lang         ;Jobauftrag löschen, ist ja in Arbeit
und
1
 cbr merker,t_kurz         ;Jobauftrag löschen, ist ja in Arbeit

> encoder_keypressed() und encoder_keypressedlong(). innerhalb dieser
> Funktionen werden die Merker wieder zurück gesetzt.

Nur doof, dass der kurze Tastendruck bereits voreilig abgearbeitet 
wurde, ehe der lange Tastendruck überhaupt erkannt werden konnte.

>
> Soweit mein Code, aber irgendwie funktioniert dieser noch nicht richtig.
> :-/

Das ist auch kein Wunder, Dein Algorithmus entspricht ja nicht dem 
meinen und ist irgendwie auch nicht zu Ende gedacht. Sieh es aber bitte 
nicht als persönliche Beleidigung, nur irgendwie muss ich es ja (etwas 
direkt) formulieren, damit es verständlich wird.

Wenn mit Interrupts gearbeitet wird, sollte man Programme aus einer 
anderen Sicht betrachten. Die Tasten/Drehgeberabfrage erfolgt ja nicht 
in einer Schleife, in der verweilt wird, bis das Ergebnis vorliegt, 
sondern wird durch zyklusches "Vorbeischauen" und Erledigen nur eines 
Schrittes (entsprechend eines Schleifendurchlaufes) abgearbeitet. 
Zwischendurch ist immer wieder die Mainloop aktiv, bzw. einer ihrer 
Jobs. Gut, es wäre vermessen, dies bereits als "Multitasking" zu 
bezeichnen, aber es ist schon ein kleiner Schritt in diese Richtung...

...

von Wurzel (Gast)


Lesenswert?

Hallo Hannes,

ich nehme deine Antwort nicht als beleidigend auf. ;-) Ich bin mir ja im 
klaren, dass mein Code fehlerhaft und wahrscheinlich Murks ist.

Ich möchte daher nochmal von vorne beginnen und meine Erkenntnisse aus 
deinen Beiträgen in einen Pseudocode zusammen fassen.
1
vorteiler = 8Bit
2
tastzeit = 8Bit
3
4
ISR alle 1ms:
5
{
6
vorteiler um eins erhöhen;
7
  wenn vorteiler gleich 16:
8
     dann tastenzeit um eins erhöhen
9
      und danach prüfen ob tastzeit = 255:
10
        falls ja: von tastzeit eins abziehen;
11
12
...
13
}
Bis hier hin bin ich noch gekommen, aber danach blicke ich bei deinem 
Assemblercode nicht mehr ganz durch. Wo kommt jetzt der Zustand 
(gedrückt/nicht gedrückt) des Tasters ins Spiel?
Sorry aber ich muss mir das Stück für Stück herleiten.

von Hannes L. (hannes)


Lesenswert?

Wurzel schrieb:
> Hallo Hannes,
>
> ich nehme deine Antwort nicht als beleidigend auf. ;-) Ich bin mir ja im
> klaren, dass mein Code fehlerhaft und wahrscheinlich Murks ist.
>
> Ich möchte daher nochmal von vorne beginnen und meine Erkenntnisse aus
> deinen Beiträgen in einen Pseudocode zusammen fassen.
>
1
> vorteiler = 8Bit
2
> tastzeit = 8Bit
3
> 
4
> ISR alle 1ms:
5
> {
6
> vorteiler um eins erhöhen;
7
>   wenn vorteiler gleich 16:
8
>      dann tastenzeit um eins erhöhen
9
>       und danach prüfen ob tastzeit = 255:
10
>         falls ja: von tastzeit eins abziehen;
11
> 
12
> ...
13
> }
14
>
> Bis hier hin bin ich noch gekommen, aber danach blicke ich bei deinem
> Assemblercode nicht mehr ganz durch.

Du hast recht, da ist auch noch (mindestens) ein logischer Fehler drin. 
Ich hätte nur dann hochzählen dürfen, wenn der Taster betätigt ist. Das 
passiert nunmal, wenn man mal schnell etwas Code aus dem Hut schreibt 
und nicht vorher testet. - Sorry...

> Wo kommt jetzt der Zustand
> (gedrückt/nicht gedrückt) des Tasters ins Spiel?

Durch die Abfrage des Tastenpins:
 sbis tastenpin,tastenbit  ;ist Taste betätigt? - nein, auswerten...
 rjmp Tastenabfrage_end    ;ja, weg hier, denn Zählen u. Begrenzen ist
                           ;ja fertig...

SBIS überspringt den RJMP dann, wenn der Tastenpin H-Pegel hat, also der 
Taster nicht betätigt ist. Ist er betätigt, wirkt der Sprung (RJMP) zum 
Ende der Routine.

> Sorry aber ich muss mir das Stück für Stück herleiten.

In BASIC würde es in etwa so aussehen:
1
vorteiler = vorteiler + 1
2
if vorteiler >= 16 then             'nur jedes 16. mal
3
 vorteiler = 0
4
 if taste = 0 then                  'ist Taste betätigt?
5
  tastenzeit = tastenzeit + 1       'ja, hochzählen und
6
  if tastenzeit = 255 then tastenzeit = 254  'begrenzen
7
 else                               'Taste ist unbetätigt
8
  if tastenzeit > lang then         'war Taste lange betätigt?
9
   merker = set merker_lang         'ja, Merker "lang" setzen
10
  elseif tastenzeit > kurz then     'nein, nicht lang, war dann kurz?
11
   merker = set merker_kurz         'ja, Merker "kurz" setzen
12
  endif
13
  tastenzeit = 0                    'nach Auswertung immer löschen
14
 endif
15
endif

Ich hoffe, das ist verständlicher...

...

von Rainier (Gast)


Lesenswert?

Ich habe den Code umgeschrieben nach C.
Allerdings klappt's noch nicht. Nutzt du nur einen merker dem du 
unterschiedliche werte (kurz und lang) zuweist?

Der merker wird ja bei der Abfrage vom Hauptprogramm wieder gelöscht, 
oder?

von Hannes L. (hannes)


Lesenswert?

Rainier schrieb:

Wer nun? Rainer, Rambo oder Wurzel?

> Ich habe den Code umgeschrieben nach C.
> Allerdings klappt's noch nicht.

Dann hast Du etwas verändert. Ich vermute, Du hast das ELSEIF falsch 
interpretiert und setzt bei langem Tastendruck beide Merker...

> Nutzt du nur einen merker dem du
> unterschiedliche werte (kurz und lang) zuweist?

Ich nutze derzeit keinen Merker, da ich die Kurz/Lang-Unterscheidung 
bisher noch nicht brauchte. ;-)

Natürlich sollen für lang und kurz unterschiedliche Merker 
(Bitvariablen) genutzt werden, ansonsten könnte man sie ja nicht 
unterscheiden. In ASM nutze ich dazu ein Register, deren 8 Bits 
unterschiedliche Funktion haben. Jeder dieser Bits bekommt einen eigenen 
Namen und wird separat gesetzt und gelöscht. In C wird das nicht viel 
anders sein. Natürlich muss man sie so deklarieren, dass sowohl 
Hauptprogramm als auch ISR darauf zugreifen können.

>
> Der merker wird ja bei der Abfrage vom Hauptprogramm wieder gelöscht,
> oder?

Ja sicher doch. Die ISR setzt bei "Auftreten des Ereignisses" den zum 
Ereignis gehörenden Merker, das Hauptprogramm prüft und löscht dann die 
Merker bei der Ausführung des zugehörigen Jobs. Man kann die Merker in 
diesem Falle auch als "Jobflag" sehen, also ein Flag (Semaphore, Merker, 
Bitvariable, Schalter, RS-Flipflop), das anzeigt, das ein bestimmter Job 
zu erledigen ist (das Gegenstück dazu sind Merker, die einen Status 
anzeigen, der darüber entscheidet, wie ein Job erledigt werden soll).

...

von Wurzel (Gast)


Lesenswert?

Sry, ich wurschtel hier an mehreren Rechnern rum. -> Wurzel

>Dann hast Du etwas verändert. Ich vermute, Du hast das ELSEIF falsch
>interpretiert und setzt bei langem Tastendruck beide Merker...

Nein, mache ich nicht:
1
  prescaler++;
2
  if( prescaler >= 16 )
3
  {
4
    prescaler = 0;
5
    if( !KEY )
6
    {
7
      button_down++;
8
      if( button_down == 255 )
9
        button_down = 254;
10
    }
11
    else
12
    {
13
      if( button_down >= LONG )
14
        key_pressed_long = 1;
15
      else if( button_down >= SHORT )
16
        key_pressed = 1;
17
18
      button_down = 0;
19
    }
20
  }

Ich verwende natürlich auch zwei Merker: key_pressed_long, key_pressed.
zurückgesetzt werden sie nachdem sie abgefragt wurden.

Hm, irgendwas ist noch fehlerhaft.

von Hannes L. (hannes)


Lesenswert?

> Hm, irgendwas ist noch fehlerhaft.

Gültigkeitsbereich der Variablen?

...

von Wurzel (Gast)


Lesenswert?

prescaler und button_down sind static variablen in der ISR deklariert.
Die Marker key_pressed_long und key_pressed sind global definiert.

von Hannes L. (hannes)


Lesenswert?

Wurzel schrieb:
> prescaler und button_down sind static variablen in der ISR deklariert.
> Die Marker key_pressed_long und key_pressed sind global definiert.

Volatile?

Achnee, ich nehm's zurück, ich habe ja keine Ahnung von C...

...

von Wurzel (Gast)


Lesenswert?

Ja, die Globalen sind volatile.

von Peter D. (peda)


Lesenswert?


von Wurzel (Gast)


Lesenswert?

@Peter

>Den Code von PeDa
>http://www.mikrocontroller.net/articles/Entprellun...
>kapiere ich irgendwie wie nicht und ich bräuchte den ja auch nur für
>einen Taster und nicht einen ganzen port. :-(

von screwdriver (Gast)


Lesenswert?

@hannes

Dein Code arbeitet wirklich gut. Er hat aber bekanntermaßen den 
Nachteil, das die Signalzuordnung nicht beliebig ist.

Wie wärs denn, wenn du nicht nur zwei Signalzustände auswertest, sondern 
drei. Bei drei Zuständen muß ja mindestens einmal der stabile 
dabeigewesen sein. Somit wäre deine Sprungtabelle um 2bit länger, also 
4mal so lang, aber was solls, die Abarbeitungszeit bleibt ja gleich. Das 
Wackeln und Zappeln hätte dann auch bei falscher Signalzuordnung ein 
Ende! Dann sollte doch die Auswertung unabhängig von der Signalzuordnung 
sein, oder?

mfg
screwdriver

von Rudi D. (rulixa)


Angehängte Dateien:

Lesenswert?

Hannes Lux schrieb:
> lsl dgalt                  ;altes Drehgeber-Bitmuster
> lsl dgalt                  ;nach oben schieben

Ich habe mit sehr gutem Erfolg deine Routine verwendet.
Ich verwende jedoch 2x lsr, da dann "urold" von selbst im Nirvana 
verschwindet. Die 2. Tabelle habe ich noch selbst abgeleitet.
Sie verwendet ja Übergänge zwischen den Rastpunkten und funktioniert 
deshalb so perfekt.

Siehe attachments

LG Rudi

von Hannes L. (hannes)


Lesenswert?

Rudi D. schrieb:
> Ich verwende jedoch 2x lsr, da dann "urold" von selbst im Nirvana
> verschwindet.

Da bei mir der Drehgeber an den Bits 0 und 1 liegt, wäre Rechtsschieben 
recht sinnfrei. Bei Dir liegt der Drehgeber an Bit 2 und 3, da ist 
Rechtsschieben natürlich besser.

> Die 2. Tabelle habe ich noch selbst abgeleitet.
> Sie verwendet ja Übergänge zwischen den Rastpunkten und funktioniert
> deshalb so perfekt.

Es gibt viele Varianten, wie man die Drehgeberbits anordnen kann. Jede 
Variante erfordert natürlich eine eigene Tabelle. Für 
Copy&Paste-Programmierer ist das natürlich nix, aber wenn man die 
Funktion verstanden hat, dann ist das ja kein Problem. Anschlussbelegung 
und Tabelle ist für mich kein Dogma, ich habe da auch verschiedene 
Varianten im Einsatz. Auch Varianten, die mit nur einem Shift auskommen 
(Bit 0 und 2 oder 1 und 3).

Es gibt da auch eine Variante, bei der zwei Drehgeber angeschlossen sind 
(Bit 0 und 1, sowie 4 und 5). Jedes Nibble enthält die Bits "seines" 
Drehgebers. Die Überträge beim Schieben werden vor dem ORen der neuen 
Bits ausgeANDet.

>
> Siehe attachments
>
> LG Rudi

Noch 'n Tipp: Wenn Du gute preiswerte Alps-Drehgeber suchst, dann schau 
mal hier vorbei:
http://stores.ebay.de/Logo-s-Elektronik-Kiste_Encoder-inkremental_W0QQfsubZ366805719

Dagegen ist der Pollin-Drehgeber Wucher... ;-)

...

von Rudi D. (rulixa)


Angehängte Dateien:

Lesenswert?

Hannes Lux schrieb:
> Auch Varianten, die mit nur einem Shift auskommen
> (Bit 0 und 2 oder 1 und 3).

Verstehe ich in der Eile nicht.
Danke für die Alps Quelle.
Da ist die Anwendung mit 2x t2313.

LG Rudi

von Hannes L. (hannes)


Lesenswert?

Rudi D. schrieb:

> Verstehe ich in der Eile nicht.

Anschluss von Spur A an Bit 0 und Spur B an Bit 2:

Bit 0: Spur A neu
Bit 1: Spur A alt
Bit 2: Spur B neu
Bit 3: Spur B alt

...

von Rudi D. (rulixa)


Lesenswert?

Hannes Lux schrieb:
> Rudi D. schrieb:
>
>> Verstehe ich in der Eile nicht.
>
> Anschluss von Spur A an Bit 0 und Spur B an Bit 2:
>
> Bit 0: Spur A neu
> Bit 1: Spur A alt
> Bit 2: Spur B neu
> Bit 3: Spur B alt
>
> ...

Danke, bis zum nächsten mal
LD Rudi

von rudi (Gast)


Lesenswert?

was stimmt an der routine nicht?

void CheckEncoder(void)
{
  stateA = ENCODER_A;
  stateB = ENCODER_B;

  if ((stateA_old == 0) && (stateB_old == 1) && (stateA == 1) && (stateB 
== 1)//CW
    ||(stateA_old == 1) && (stateB_old == 0) && (stateA == 0) && (stateB 
== 0))
  {
    counter++;
  }
  if ((stateA_old == 0) && (stateB_old == 0) && (stateA == 1) && (stateB 
== 0)//CCW
    ||(stateA_old == 1) && (stateB_old == 1) && (stateA == 0) && (stateB 
== 1))
  {
    counter--;
  }

  ENC_change=1;
  upd_lcd =1;

  stateA_old = stateA;
  stateB_old = stateB;
}


verwende den panasonic encoder und habe schon timer und 
irq_on_pin_change versucht. beides liefert unbrauchbare ergebnisse.

gruß rudi

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.