Forum: Projekte & Code Drehimpulsgeber mit Rasterstellung bei 00/11 auswerten


von Christian K. (christiank)


Angehängte Dateien:

Lesenswert?

Hier mal ein neuer Ansatz zum Auswerten von Drehimpulsgebern....

In der Praxis hat man häufig Drehimpulsgeber (rotary switches) die
Raststellungen bei 00 und 11 haben. Die Zwischenstellungen 01 der 10
treten nur dynamisch während der Drehung auf. Solche Geber sind z.B.
als Eingabeinstrument üblich.

Der sehr gute Code aus diesem Thread
http://www.mikrocontroller.net/forum/read-4-37992.html#new
hat passt für diesen Typ nicht so gut denn:

a) Bei den oben beschriebenen Drehimpulsgebern zählt er doppelt weil er
bei jedem Wechsel inc- oder decrementiert. D.h. wird um eine
Raststellung weitergedreht, zählt der Zähler +/-2.

b) Wärend des Kontaktprellens ist der Ausgangswert nicht konstant
sondern kann +/-1 wechseln. Das kann ein Problem sein, wenn das
Anwenderprogram ungünstig ausliest.

c) Er ist nicht unbedingt einfach zu verstehen.

Hier nun mein Code für AVR:

  static uint8_t last_state = 0;
  static uint8_t last_cnt = 0;
  uint8_t new_state;

  new_state=PINE & (_BV(PINE4) | _BV(PINE3));
  if ((new_state^last_cnt)==(_BV(PINE4) | _BV(PINE3)) )
  {
    if ((new_state ^ last_state)==_BV(PINE4))
      enc_delta+=1;
    else
      enc_delta-=1;
    last_cnt=new_state;
  }
  last_state=new_state;

Er funktioniert folgendermassen:
- Am Anfang werden die zwei Input-Ports eingelesen.
- Dann wird geprüft, ob sich beide Bit's unterscheiden
- Wenn nein wars das schon
- Wenn ja, schauen wir woher wir kommen
  kommen wir von rechts (je nach Beschaltung) zählen wir hoch, sonst
runter
- Dann speichern wir uns noch den State bei dem wir gezählt haben
- Am Ende merken wir uns den ausgewerteten State damit wir nächstes mal
wissen woher wir kommen

Dabei wird natürlich automatisch entprellt.

Have Fun.

von Techniker (Gast)


Lesenswert?

Hallo Christian!

Bin in C noch nicht so bewandert.
Kannst du mit erklären, was "_BV" bedeutet?

Danke!

von MartinS (Gast)


Lesenswert?

Ich schreibe das hier mal auf basis von Port B Bit 0 und 1.
1. Zwischenspeichern von PB0 & PB1
2. PB0 XOR PB1 = 0 -> nix passiert nix tun
               = 1 -> es wurde gedreht
3. PB0 XOR Zwischenspeicher PB0 = 1 es wurde nach links gedreht
                                 = 0 es wurde nach rechts gedreht
4. (Entprellung)
   wenn bei 3. das Ergebnis 1 war dann nicht neu bei 1. anfangen bevor
  nicht auch PB1 den status gewechselt hat,
   wenn bei 3. das Ergebnis 0 war dann nicht neu bei 1. anfangen bevor
  nicht PB0 den status gewechselt hat.

von Andre (Gast)


Lesenswert?

@Techniker:

_BV ist letztlich nur ein Makro:(1 << (x)).

Schöner wäre es, wenn Du _BV nicht nimmst, sondern das Makro
ausschreibst. Dann ist es portabler (andere Compiler).
Mit _BV wird der Code allenfalls ein bißchen leserlicher.

von Andreas K. (andi_k)


Lesenswert?

Kann mir jemand den übersetzten C-Code von oben geben (AVR-ASM)?
Habe mit meinen Unkenntnissen in C versucht, das in ASM zu wandeln aber
ich komme auf keinen Nenner.
Kann leider nur ASM.

Danke im Voraus!

MfG
Andi

von Christian K. (christiank)


Lesenswert?

Hier, nur kopiert, nicht näher angeschaut....

a) Optimization for size

70:         new_state=PINE & (_BV(PINE4) | _BV(PINE3));
+000003A4:   91800000    LDS     R24,0x0000       Load direct from data
space
+000003A6:   7188        ANDI    R24,0x18         Logical AND with
immediate
+000003A7:   8389        STD     Y+1,R24          Store indirect with
displacement
71:         if ((new_state^last_cnt)==(_BV(PINE4) | _BV(PINE3)) )  //
we are either in main state 00 or 11
+000003A8:   8199        LDD     R25,Y+1          Load indirect with
displacement
+000003A9:   9180014C    LDS     R24,0x014C       Load direct from data
space
+000003AB:   2789        EOR     R24,R25          Exclusive OR
+000003AC:   3188        CPI     R24,0x18         Compare with
immediate
+000003AD:   F4A1        BRNE    PC+0x15          Branch if not equal
73:           if ((new_state ^ last_state)==_BV(PINE4))
+000003AE:   8189        LDD     R24,Y+1          Load indirect with
displacement
+000003AF:   9190014B    LDS     R25,0x014B       Load direct from data
space
+000003B1:   2789        EOR     R24,R25          Exclusive OR
+000003B2:   3180        CPI     R24,0x10         Compare with
immediate
+000003B3:   F431        BRNE    PC+0x07          Branch if not equal
74:             enc_delta+=1;
+000003B4:   9180014A    LDS     R24,0x014A       Load direct from data
space
+000003B6:   5F8F        SUBI    R24,0xFF         Subtract immediate
+000003B7:   9380014A    STS     0x014A,R24       Store direct to data
space
+000003B9:   C005        RJMP    PC+0x0006        Relative jump
76:             enc_delta-=1;
+000003BA:   9180014A    LDS     R24,0x014A       Load direct from data
space
+000003BC:   5081        SUBI    R24,0x01         Subtract immediate
+000003BD:   9380014A    STS     0x014A,R24       Store direct to data
space
77:           last_cnt=new_state;
+000003BF:   8189        LDD     R24,Y+1          Load indirect with
displacement
+000003C0:   9380014C    STS     0x014C,R24       Store direct to data
space
79:         last_state=new_state;
+000003C2:   8189        LDD     R24,Y+1          Load indirect with
displacement
+000003C3:   9380014B    STS     0x014B,R24       Store direct to data
space
+000003C5:   9621        ADIW    R28,0x01         Add immediate to
word

b) Optimization for speed
70:         new_state=PINE & (_BV(PINE4) | _BV(PINE3));
+000003A4:   91800000    LDS     R24,0x0000       Load direct from data
space
+000003A6:   7188        ANDI    R24,0x18         Logical AND with
immediate
+000003A7:   8389        STD     Y+1,R24          Store indirect with
displacement
71:         if ((new_state^last_cnt)==(_BV(PINE4) | _BV(PINE3)) )  //
we are either in main state 00 or 11
+000003A8:   8199        LDD     R25,Y+1          Load indirect with
displacement
+000003A9:   9180014C    LDS     R24,0x014C       Load direct from data
space
+000003AB:   2789        EOR     R24,R25          Exclusive OR
+000003AC:   3188        CPI     R24,0x18         Compare with
immediate
+000003AD:   F4A1        BRNE    PC+0x15          Branch if not equal
73:           if ((new_state ^ last_state)==_BV(PINE4))
+000003AE:   8189        LDD     R24,Y+1          Load indirect with
displacement
+000003AF:   9190014B    LDS     R25,0x014B       Load direct from data
space
+000003B1:   2789        EOR     R24,R25          Exclusive OR
+000003B2:   3180        CPI     R24,0x10         Compare with
immediate
+000003B3:   F431        BRNE    PC+0x07          Branch if not equal
74:             enc_delta+=1;
+000003B4:   9180014A    LDS     R24,0x014A       Load direct from data
space
+000003B6:   5F8F        SUBI    R24,0xFF         Subtract immediate
+000003B7:   9380014A    STS     0x014A,R24       Store direct to data
space
+000003B9:   C005        RJMP    PC+0x0006        Relative jump
76:             enc_delta-=1;
+000003BA:   9180014A    LDS     R24,0x014A       Load direct from data
space
+000003BC:   5081        SUBI    R24,0x01         Subtract immediate
+000003BD:   9380014A    STS     0x014A,R24       Store direct to data
space
77:           last_cnt=new_state;
+000003BF:   8189        LDD     R24,Y+1          Load indirect with
displacement
+000003C0:   9380014C    STS     0x014C,R24       Store direct to data
space
79:         last_state=new_state;
+000003C2:   8189        LDD     R24,Y+1          Load indirect with
displacement
+000003C3:   9380014B    STS     0x014B,R24       Store direct to data
space

von Andreas K. (andi_k)


Lesenswert?

Danke Christian!

Funktioniert jetzt.
Allerdings scheint es sich genau so wie mein bisheriger Algorythmus zu
verhalten (siehe Datenblatt des DDM427 von ALTRON unten rechts).
Erst ab 2KHz Abtastrate geht beim schnellen Drehen nichts verloren.

MfG
Andi

von Christian K. (christiank)


Lesenswert?

Hallo Andi,

ich verwende 500Hz Abtastrate. Läuft allerdings nur als manuelles
Eingabegerät. Dass heisst, es stört eigentlich nicht, wenn beim
schnellen drehen per Hand Pulse verloren gehen. Der User kann beim
Kurbeln sowieso nicht mitzählen... Bei der langsamen Feineinstellung -
solange man mitzählen kann - reichen die 500Hz.

Grüße
  Christian

von MartinS (Gast)


Lesenswert?

Oder mach die ganze geschichte mit externen Interrupts, oder in
Hardware.

von The Daz (Gast)


Lesenswert?

@Christian

Aeh, das kapier ich nicht :

 if ((new_state^last_cnt)==(_BV(PINE4) | _BV(PINE3)) )
 {
   if ((new_state ^ last_state)==_BV(PINE4))

Nur einer dieser beiden Ausdruecke kann wahr sein. Folglich kann die
Routine nur rueckwaerts zaehlen, oder ?

von Christian K. (christiank)


Lesenswert?

Wieso?

last_cnt und last_state sind verschieden, kann man leicht übersehen
:-)

last_cnt ist die Bitkombination bei der zuletzt gezählt wurde (00) oder
(11)

last_state ist der letzte Zustand (10)/(01) d.h. woher ich komme

Gruesse
  Christian

von The Daz (Gast)


Lesenswert?

Damn, du hast recht. Lesen hilft ungemein :)

von Felix J. (feejai)


Lesenswert?

Ich weis net, wies euch da geht, aber ich krieg immer PINEx nicht
deklariert und damit bricht der gcc ab!

von SuperUser (Gast)


Lesenswert?

Hallo Felix,

ist das dein erstes Program für AVR?

Ein

#include <avr/io.h> (beim gcc)

Sollte natürlich nicht fehlen und dein µC im makefile definiert sein...

von Felix J. (feejai)


Lesenswert?

Sorry, mein oberes Posting is absolouter Mist, habs jetzt schon kapiert
was PINE sein soll. Brett vorm Kopf.

von Tobi T. (tubbu-)


Lesenswert?

Hallo,

habe den Code so erweitert, dass man auch mehrere Drehgeber einlesen
kann.
1
void evalRots(unsigned int rotData)
2
//Erkennt die Umdrehungen der Encoder
3
{
4
  static unsigned int last_state = 0;
5
  static unsigned int last_cnt = 0;
6
  
7
  for(unsigned char i=0; i< 12; i+=2)    //Encoder Anzahl: 6
8
  {
9
    if (((rotData^last_cnt) & (1 << i))  && ((rotData^last_cnt) & (1 <<
10
(i+1))))
11
    {
12
      if ((rotData ^ last_state) & (1 << i))
13
        enc_delta[(i>>1)]--;
14
      else
15
        enc_delta[(i>>1)]++;
16
      
17
      if((last_cnt^rotData) & (1 << i))      //bits verschieden?
18
        last_cnt^=(1 << i);            //togglen
19
      if((last_cnt^rotData) & (1 << (i+1)))    //bits verschieden?
20
        last_cnt^=(1 << (i+1));          //togglen
21
22
    }
23
    if((last_state^rotData) & (1 << i))        //bits verschieden?
24
      last_state^=(1 << i);            //togglen
25
    if((last_state^rotData) & (1 << (i+1)))      //bits verschieden?
26
      last_state^=(1 << (i+1));          //togglen
27
  }
28
}

Bei mir befinden sich die Graycode werte in einem Integer, da ich die
Encoder über zwei schieberegister einlese. rotData kann aber auch
einfach in Port des uc sein. Der Code macht im prinzip genau das
gleiche wie der Orginalcode des Threads, wahrscheinlich lässt sich da
noch einiges vereinfachen, laufen und funktionieren tut er aber.

Tubbu

von Peter D. (peda)


Lesenswert?

Hallo Christian,

kannst Du mal das hier ausprobieren ?

http://www.mikrocontroller.net/forum/read-4-37992.html#360188


Peter

von Lurch (Gast)


Lesenswert?

Habe einen Drehimpulsgeber von Alps (STEC12E06 von Conrad) der mit jeder
Rasterstellung den Zähler um 4 weiterzählt, was für meinen Fall nicht
gewünscht ist. Ich habe das Beispiel von Christian ausprobiert, leider
nicht mit dem gewünschten Erolg. Der Code aus dem dem Thread

http://www.mikrocontroller.net/forum/read-4-37992.html#

von Peter Dannegger lässt sich auch dann prima verwenden wenn zwischen
den Rasterstellungen 2 oder 4 Impulse kommen, aber nur einer gezählt
werden soll.

ISR(TIMER0_OVF_vect)
{
  static char enc_last = 0x01;
  char i = 0;

    if( PHASE_A )
      i = 1;

    if( PHASE_B )
      i ^= 3;        // convert gray to binary

    i -= enc_last;      // difference new - last
    if( i & 1 ){      // bit 0 = value (1)
      enc_last += i;      // store new as next last
    enc_delta += (i & 2) - 1;  // bit 1 = direction (+/-)
    if (!(enc_delta % 4)) {  // nur jeden 4. Schritt zählen
    if ((i & 2)) {    // prüfen ob auf oder ab
      count++;    // 0-255
    }
    else {
      count--;    // 255-0
    }
    }
    }
}

Läuft super.

Arne

von Marco Beffa (Gast)


Lesenswert?

Ich habe erfolglos versucht den Code hier zu verwenden.

Wäre jemand so freundlich mir das ganze nochmals klar und deutlich zu
erklären? Die Codeschnippsel oben sind eher verwirrend!!


last_cnt ist die Bitkombination bei der zuletzt gezählt wurde (00) oder
(11)

last_state ist der letzte Zustand (10)/(01) d.h. woher ich komme

wo wird bei 00 und 11 etwas gezählt? da soll ja gerade nichts
passieren...


Mit freundlichen Grüssen

von Marco Beffa (Gast)


Lesenswert?

Dies ist mein Code:

void encoder(void)
{

struct
{
 unsigned int speicher_A :1;
 unsigned int speicher_B :1;
}encoder;

if(PF.PORT.BIT.B1 == PF.PORT.BIT.B2)
{
 encoder.speicher_A = PF.PORT.BIT.B1;
 encoder.speicher_B = PF.PORT.BIT.B2;
}

if(PF.PORT.BIT.B1 ^ PF.PORT.BIT.B2)  //Bits unterschiedlich?
 {
   if(encoder.speicher_B ^ PF.PORT.BIT.B2) // Hat Bit2 geändert?
    {
      while(PF.PORT.BIT.B1 == encoder.speicher_A); //warten
      LCD_clear(0);
      LCD_writeString("Links", 0, 0,0);

    }

   else
    {
      while(PF.PORT.BIT.B2 == encoder.speicher_B);
      LCD_clear(0);
      LCD_writeString("Rechts", 0, 60,0);
    }
 }
}


90 % der Fälle funtkioniert er, jedoch werden manchmal Drehungen nicht
erkannt (auch wenn sie sehr langsam gemacht wurden). Es muss also
irgendwo ein Fehler sein! Ausserdem finde ich das mit der while
schleife ebenfalls nicht gut, aber irgendwie muss ich ja die Änderung
des andern Bits abwarten, oder?

MFG

von SuperUser (Gast)


Lesenswert?

Irgendwie kann ich bei deinem Code keine grosse Ähnlichkeit mit dem
Original erkennen...

Vielleicht ist wichtig, dass _BV(PINE4) und _BV(PINE3) verschieden
sind. _BV(PINE4) bedeutet z.B. (1<<3) d.h. 8, und _BV(PINE3) = 1<<2 =
4. Ist bei deinem PF.PORT.BIT.B1 vermutlich nicht so?

Ausserdem kostet dein LCD_clear() und LCD_write() vmtl. soviel Zeit,
dass die Drehpulse verloren gehen. Der Code sollte schon mit einer
gewissen Poll-Frequenz laufen (bei mir 400Hz).

Hier nocheinmal der Original-Code damit man nicht scrollen muss...

void encoder(void)
{
  static uint8_t last_state = 0;
  static uint8_t last_cnt = 0;
  uint8_t new_state;

  new_state=PINE & (_BV(PINE4) | _BV(PINE3));
  if ((new_state^last_cnt)==(_BV(PINE4) | _BV(PINE3)) )
  {
    if ((new_state ^ last_state)==_BV(PINE4))
      enc_delta+=1;
    else
      enc_delta-=1;
    last_cnt=new_state;
  }
  last_state=new_state;
}

von Dirk F. (dirk-frerichs)


Lesenswert?

hallo

ich versuche gerade das zum laufen zu bekommen
scheitere aber kläglich

hier mal ein paar schnipsel:



// belegung des Rotary Encoders
#define ROTARY_DDR      DDRD
#define ROTARY_PIN      PIND
#define ROTARY_PORT     PORTD
#define ROTARY_A        5
#define ROTARY_B        6
#define ROTARY_P        7

int enc_delta=0;

void encoder(void)
{
  static uint8_t last_state = 0;
  static uint8_t last_cnt = 0;
     uint8_t new_state;

  new_state=ROTARY_PIN & ((1<<ROTARY_A) | (1<<ROTARY_B));

  if ((new_state^last_cnt)== (1<<ROTARY_A) | (1<<ROTARY_B))
    {

  if ((new_state ^ last_state)==(1<<ROTARY_A))
    enc_delta+=1;
  else
    enc_delta-=1;

  last_cnt=new_state;
  }
  last_state=new_state;
}


int main (void)
{
     ROTARY_DDR &=~ (1<<ROTARY_A)|(1<<ROTARY_B)|(1<<ROTARY_P);
     ROTARY_PORT |= (1<<ROTARY_A)|(1<<ROTARY_B)|(1<<ROTARY_P);



  for(;;)
  {

        encoder();

        itoa(enc_delta,out,10);
  lcd_gotoxy(13,0);
  lcd_puts(out);

        }

}



es kommt aber leider nur ein extrem schnelles zählen auf das LCD
new_state und last_state sind immer auf 0
es passiert nix bei drehung

könnte mir bitte jemand sagen warum ?
hab schin gesucht , finde aber nix

Danke

von Gast (Gast)


Lesenswert?

Vielleicht enc_delta als volatile deklarieren, damit im Hauptprogramm 
Änderungen an der Variable erkannt werden...

von Dirk F. (dirk-frerichs)


Lesenswert?

leider keine besserrung

von Erazer (Gast)


Angehängte Dateien:

Lesenswert?

Im Anhang ein Code der bei mir bestens funktioklappt.

von Erazer (Gast)


Angehängte Dateien:

Lesenswert?

Nachtrag:
PullUpwiderstände sind bei mir am Drehgeber.
Daher im Code keine eingeschaltet.

von Dirk F. (dirk-frerichs)


Lesenswert?

hi

also ich habe es nun zum laufen bekommen
zwar mit anderem code

da dieser aber immer 2 schritte zählt  teile ich das ganze immer durch 2


#define ROTARY_DDR      DDRD
#define ROTARY_PIN      PIND
#define ROTARY_PORT     PORTD
#define ROTARY_A        5
#define ROTARY_B        6
#define ROTARY_P        7

volatile int enc_delta =0 ;


SIGNAL (SIG_OVERFLOW0)
{
  static char enc_last  = 0x01;
  char     i       = 0;
  if( (ROTARY_PIN & (1<<ROTARY_B) ))
    i = 1;
  if( (ROTARY_PIN & (1<<ROTARY_A) ))
    i ^= 3;
    i -= enc_last;
  if( i & 1 )
    {
    enc_last += i;
      enc_delta += (i & 2) - 1;
      }
}

von Erazer (Gast)


Angehängte Dateien:

Lesenswert?

Hier der entgültige Code

von tomgr (Gast)


Lesenswert?

Hallo,

habe hier nen Drehimpulsgeber (noname), der immer in der Raststellung 00 
hat.
Dadurch kann ich mit dem hier vorhandenen Code keine Auswertung machen.

Das Ding zählt halt nur in eine Richtung.

Also, R ist die Raststellung :
links  (R)00 - 01 - 11 - 01 - (R)00
rechts (R)00 - 10 - 11 - 10 - (R)00

Ich bitte mal um ne kleine hilfe.

gruss tomgr

von SuperUser (Gast)


Lesenswert?

Warum sollte der Code mit deinem Drehgeber nicht funktionieren? Er wird 
halt nur doppelt zählen...

von wilfried mühlenhoff (Gast)


Lesenswert?

Ich verwende Encoder mit 30 oder 40iger Auflösung.
Da kriegt der AVR ganz schön was zu tun.
Sollte man den kleinsten Eingang über IRQ aufnehmen,
anstatt mit hoher Samplerate zu arbeiten?

Das Softwareentprellen ist auch nicht ganz ohne.
Habe ich das auch bei Drehgebern mit HALL-IC?

P.S.
Habe meine Drehgeberreste jetzt in EBay eingestellt.
Wer noch welche braucht...

von Harry (Gast)


Lesenswert?

Hi folks,

I´m very sorry but when I see this : "_BV(PINE4) | _BV(PINE3" . I´m just 
wondering how it functions.

Because you cannot write in the register PINX.

Or I´m wrong ? or I´m messed up ? somebody has to explain me that...

von Christian (Gast)


Lesenswert?

That are defines for the port register and the register bits. _BV() is a 
macro that gives the bit vector for the given bit.

von Falk B. (falk)


Lesenswert?

@  wilfried mühlenhoff (Gast)

>Ich verwende Encoder mit 30 oder 40iger Auflösung.
>Da kriegt der AVR ganz schön was zu tun.

Nö.

>Sollte man den kleinsten Eingang über IRQ aufnehmen,
>anstatt mit hoher Samplerate zu arbeiten?

Nein, siehe Artikel Drehgeber.

>Habe ich das auch bei Drehgebern mit HALL-IC?

Ja. Siehe Artikel.

von Falk B. (falk)


Lesenswert?

@  Harry (Gast)

>Because you cannot write in the register PINX.

Yes, you can! ;-)
In the new AVRs, writing a PINX bit will toggle the corresponding PORTX 
Bit!

Regards
Falk

von Wuhu W. (wuhuuu2)


Lesenswert?

Hallo,

ich habe den Code verwendet, nur ergibt sich bei mir bei der Abfrage

new_state ^ last_state = 11

Also ein unerwünschte Ergebnis, da ja nur 10 oder 01 erwartet wird.

Kann mir jemand helfen?
1
ISR( TIMER2_OVF_vect )
2
{
3
4
new_state = PINL & ((1<<5) | (1<<3))  ;  // save state
5
6
// Change in State on both important positions?
7
if (  (new_state ^ last_count) == ((1<<5 )|(1<<3))    ) 
8
  {
9
    // right or left turn?
10
    if(  (new_state ^ last_state) == (1<<3 ) )
11
      {enc_delta += 1;}
12
    else                               // Falle für unerwünschten Zustand
13
      {enc_delta -= 1;}
14
15
    enc_last_count = enc_new_state;    // save Bitcombination of last count
16
  }
17
enc_last_state = enc_new_state;        // save last state
18
19
}

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.