Forum: Compiler & IDEs Programmablaufplan Drehgeber


von Udo S. (Firma: allround) (1udo1)


Lesenswert?

Hallo, ich möchte für meine Drehgeber das Programm von Peter Dannegger 
benutzen. Bin aber in C noch nicht soweit, alles zu verstehen. Lese aber 
tagtäglich viel in den Tutorials! Zum Verständnis des Codes würde mir 
ein Programmablaufplan helfen. Hat schon jemand einen solchen Plan 
erstellt?

Wenn es für dieses Programm von Peter einen Plan gäbe, wäre mir am 
liebsten. Mir hilft es aber auch weiter, wenn es einen PAP gibt, der 
allgemein das Einlesen des Graycodes mit Interrupt aufzeigt.

Hier noch einmal der Code von Peter, damit ihr nicht solange suchen 
müüss

Danke

Udo
1
#include <avr\io.h>
2
#include <avr\interrupt.h>
3
4
#define  XTAL    8e6      // 8MHz
5
#define PHASE_A    (PINA & 1<<PA1)
6
#define PHASE_B    (PINA & 1<<PA3)
7
 #define LEDS_DDR  DDRC
8
#define  LEDS    PORTC    // LEDs against VCC
9
volatile int8_t enc_delta;        // -128 ... 127
10
static int8_t last;
11
12
void encode_init( void )
13
{
14
int8_t new;
15
 new = 0;
16
    if( PHASE_A )      // wenn Bit PORTA1 gesetzt
17
        new = 3;
18
        if( PHASE_B )    // wenn Bit PORTA3 gesetzt
19
          new ^= 1;    // convert gray to binary
20
        last = new;    // power on state
21
        enc_delta = 0;
22
23
       TCCR0 = 1<<WGM01^1<<CS01^1<<CS00;  // CTC, XTAL / 64
24
        OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);  // 1ms
25
       TIMSK |= 1<<OCIE0;
26
}
27
  
28
ISR( TIMER0_COMP_vect )  // 1ms for manual movement
29
{
30
int8_t new, diff;
31
new = 0;
32
    if( PHASE_A )    // wenn Bit PORTA1 gesetzt
33
        new = 3;      // 
34
        if( PHASE_B )    // wenn Bit PORTA3 gesetzt
35
                 new ^= 1;    // convert gray to binary
36
              diff = last - new;  // difference last - new
37
               if( diff & 1 )       // bit 0 = value (1)
38
   {  
39
            last = new;        // store new as next last
40
           enc_delta += (diff & 2) - 1;  // bit 1 = direction (+/-)
41
  }
42
}
43
 
44
int8_t encode_read1( void )    // read single step encoders
45
{
46
  int8_t val;
47
 
48
  cli();
49
  val = enc_delta;
50
  enc_delta = 0;
51
  sei();
52
  return val;        // counts since last call
53
}
54
55
int main( void )
56
{
57
  int32_t val = 0;
58
 
59
  LEDS_DDR = 0xFF;
60
  encode_init();
61
  sei();
62
 
63
  for(;;){
64
    val += encode_read1();      // read a single step encoder
65
    LEDS = val;
66
  }
67
}

von Karl H. (kbuchegg)


Lesenswert?

Udo Scharnitzki wrote:
> Hallo, ich möchte für meine Drehgeber das Programm von Peter Dannegger
> benutzen. Bin aber in C noch nicht soweit, alles zu verstehen. Lese aber
> tagtäglich viel in den Tutorials! Zum Verständnis des Codes würde mir
> ein Programmablaufplan helfen.

Das bezweifle ich.

Der Trick in diesem Code besteht darin, wie sich einzelne Bits des 
Drehgebers beim Drehen verändern und wie diese mit logischen Operationen 
so miteinander verknüpft werden, dass am Ende das Richtige rauskommt.

Da hilft dir ein Ablaufplan auch nicht viel.

Wenn du den Code verstehen willst, dann besorg dir eine Tabelle mit 
Gray-Codes, nimm an dass der Geber auf einem bestimmten Wert steht, 
schnapp dir Papier und Bleistift und spiel selbst Computer und arbeite 
das Programm ab. Danach denkst du dir, dass der Geber einmal verdreht 
wurde und daher einen neuen Code generiert, den du wieder mit Papier und 
Bleistift (und deiner bisherigen Variablenbelegung) durchspielst.

Danach heist es: sich zurücklehnen und über das Nachdenken, was man 
soeben als menschlicher Copmuter simuliert hat. Schluesse ziehen; 
Vorhersagen treffen, was wohl beim nächsten Gray Code passieren müsste; 
das ganze dann tatsächlich auf dem Papier anhand des vorhandenen Codes 
durchspielen usw. bis man verstanden hat, wie das ganze funktioniert.

von 1udo1 (Gast)


Lesenswert?

Danke Karl-heinz,

habe schon Computer gespielt und genau das gemacht, was du empfohlen 
hast. Es war sehr viel Arbeit. Gut, wenn es nur so geht, dann spiele ich 
die Graycode Werte durch und beobachte, was sich tut.

Also nochmal Danke für deine schnelle Antwort. Ich werde so verfahren.

Gruß

Udo

von egberto (Gast)


Lesenswert?

ich habe das mal(für ein anderes Problem) so gelöst, das ich mir die 
Signale für jeden Ausgang einzeln auf eine Papierschlange gemalt habe - 
die konnte ich dann gegeneinander Verschieben und so die möglichen 
Bit-Kombinationen sehen - also richtiges Low-Tec.

Viele Grüße,

egberto

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Udo Scharnitzki wrote:

> Mir hilft es aber auch weiter, wenn es einen PAP gibt, der
> allgemein das Einlesen des Graycodes mit Interrupt aufzeigt.

Hi, man kann die entstehenden Zustände auch über eine kleinen Tabelle 
auf den Code abbilden, wie in

http://www.gjlay.de/pub/c-code/drehgeber.html

Ist vielleicht besser zu verstehen, weil es durch die Tabelle nicht zu 
Fallunterscheidungen kommt. Es erklärt allerdings nicht den Code, den du 
gerne verwenden möchtest...

von ein Schlaumeier (Gast)


Lesenswert?

Ich weiss zwar nicht, was du genau machen möchtest, aber wenn du einfach 
nur einen Drehwinkelgeber simulieren willst, dann kann ich Dir den 
80C167 empfehlen! Deren Timer haben einen Speziellen 
Drehwinkelgeber-Mode.

Gruß

von 1udo1 (Gast)


Lesenswert?

@Johann,

meine Kenntnisse sind noch nicht soweit, das Zustandekommen der Werte
0,1,-1 und DREH_INVALID zu rekonstruieren. Ich glaube 1 ist vorwärts und 
-1 ist rückwärts. Aber welche Codezeilen oder logische Verknüpfungen 
sind diejenigen, mit denen diese Tabelle erstellt wird. Eine, - für 
Anfänger,-- grundlegende Erklärung habe ich bisher noch nicht gefunden. 
Vielleicht ist es auch sehr aufwändig einem Anfänger die Generierung zu 
erklären. Ich möchte es aber gerne wissen.

Frage:

Kann man das in wenigen Worten erklären? Rest mache (versuche)ich dann 
selber. Im Forum gibt es manche Beiträge,  ist aber für mich im 
Augenblick noch nicht nachvollziehbar.
1
char drehgeber_step (void)
2
{
3
    static const char drehgeber_trainsitions[] PROGMEM =
4
    {
5
         0,                       1,           -1, DREH_INVALID,
6
        -1,                       0, DREH_INVALID,            1,
7
         1,            DREH_INVALID,            0,           -1,
8
         DREH_INVALID,           -1,            1,            0
9
    };

@ein Schlaumeier,

zum Testen habe ich gerade einen manuellen alps Drehgeber angeschlossen, 
um das Prizip des Einlesens zu verstehen. Danach werde ich die Encoder 
Scheibe mit der 2-Kanal Lichtschranke anschließen. Also die Hardware 
optischer Encoder auf der Motorwelle wird zum Einsatz kommen.

Gruß

Udo

von 1udo1 (Gast)


Lesenswert?

Hallo,

habe den Code in meinem MEGA16 und der alps-Drehgeber (Raster24) zeigt 
pro Raster 4 Schritte an. Läuft, wie es der Graycode bestimmt.

Jetzt verstehe ich aber eins nicht:

Wenn ich den Geber 1x betätige erzeugen Kanal A und auch Kanal B je 2 
Flankenwechsel. In der Variablen val steht dann der binäre Wert 4 und 
wird an PORTC ausgegeben. Ich möchte pro Raster aber nur einen Schritt 
anzeigen lassen und teile val durch 4. Das ist aber falsch,oder!?
1
val /= teiler;

Ergebnis ist bei jedem Raster immer Null. Ist wahrscheinlich ein grober 
Syntax-Fehler? Habe im Tutorial nix gefunden, was mir weiterhilft. 
Dividiere ich falsch?
1
int main( void )
2
{
3
  int32_t val = 0;
4
  char teiler=4;
5
 
6
  LEDS_DDR = 0xFF;
7
  encode_init();
8
  sei();
9
 
10
  for(;;){
11
    val += encode_read1();    // read a single step encoder
12
    
13
    val /= teiler;
14
    LEDS = val;
15
  }
16
}

von Peter D. (peda)


Lesenswert?

1udo1 wrote:
> Ich möchte pro Raster aber nur einen Schritt
> anzeigen lassen und teile val durch 4. Das ist aber falsch,oder!?

Ja, das ist falsch.
Du mußt
1
     val += encode_read4();    // read a 4 step encoder
nehmen.


Peter

von 1udo1 (Gast)


Lesenswert?

DANKE Peter für deine Hilfe. Anlage läuft!!

Gruß

Udo

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1udo1 wrote:
> @Johann,
>
> meine Kenntnisse sind noch nicht soweit, das Zustandekommen der Werte
> 0,1,-1 und DREH_INVALID zu rekonstruieren. Ich glaube 1 ist vorwärts und
> -1 ist rückwärts. Aber welche Codezeilen oder logische Verknüpfungen
> sind diejenigen, mit denen diese Tabelle erstellt wird. Eine, - für
> Anfänger,-- grundlegende Erklärung habe ich bisher noch nicht gefunden.
> Vielleicht ist es auch sehr aufwändig einem Anfänger die Generierung zu
> erklären. Ich möchte es aber gerne wissen.
>
> Frage:
>
> Kann man das in wenigen Worten erklären? Rest mache (versuche)ich dann
> selber. Im Forum gibt es manche Beiträge,  ist aber für mich im
> Augenblick noch nicht nachvollziehbar.

1 ist entgegengesetzt zu -1.

Wenn es an den Ports zB den Übergang gibt
   HH -> HL
dann wirs daraus zB eine 1, bei
   HH -> LH
wird eine -1 daraus (oder umgekehrt). Und
   HL -> LH
etc. ist ungültig, also bei 2 Änderungen. Bei keiner Änderung wie
   LH -> LH
passiert eben nix.

Insgesant gibt es 16 der H/L-Kombinationen, die als Index in die Tabelle 
dienen. Die hab ich nicht "berechnet", ich hab's mir einfach aufgemalt 
und eingetippt.

Wenn der Wert durch 4 zu teilen ist weil pro Raster 4 Flanken gibt, dann 
geht das ebenso, wie ich den Wert durch 2 "teile" falls der Geber 2 
Flanken/Raster liefert -- eben nur mit einer 4 anstatt einer 2 ;-)

von 1udo1 (Gast)


Lesenswert?

@Johann,

Danke für deine Erläuterung. Ich werde deine Gedankengänge mal 
aufzeichnen. Dann wird mir das Prinzip wohl klar sein.

Meine Versuchsanlage läuft ...... aaaber:

Auf der Motorwelle sitzt eine Encoderscheibe, die durch eine 2 Kanal 
Lichtschranke geführt wird. Der MEGA16 zählt fehlerfrei alle Impulse,die 
die beiden Kanäle generieren. Ab jetzt fehlt mir folgende Info.

Der Motor soll jetzt zum Test 3 Sekunden links laufen, stehen bleiben, 3 
Sekunden rechts laufen. Die Zeit definiere ich mit der delay.h --- und 
das ist wahrscheinlich falsch: denn solange die delay.h in Aktion ist, 
wird mein Timerinterrupt nicht mehr angesprungen, sodass als Folge die 
Ports des Encoders nicht mehr abgefragt werden.

Nun würde ich gerne wissen:

Muss ich meine Zeitschleife einzig und allein innerhalb des 
Timerinterrupts per Zählregister realisieren? Das heißt jede 
Millisekunde ein Register incrementieren. Oder gibt es etwas 
Eleganteres. Ich bin sicher, das gibt es. Aber meine bisherigen 
C-Kenntnisse lassen mich verhungern. Bitte helft mir doch mal.

Hier mein FALSCHER Versuchscode:
1
int main( void )
2
{
3
  int32_t val = 0;
4
5
     PORTA = 0xFF;           // an PORTA2 ist der MOTOR angeschlossen
6
     DDRA=0xff;
7
  
8
     DDRD=0x00;              // alles auf EINGANG
9
     PORTD = 0xFF;           //Pullups aktiv
10
11
     LEDS_DDR = 0xFF;        // PORT B
12
     encode_init();
13
     sei();
14
15
// ####### das funktioniert nicht, soll nur zeigen, was ich will#######
16
// ####### Motor soll Drehrichtung wechseln und es sollen auch noch die Schritte gezählt werden ####
17
 
18
        for(;;){
19
20
        PORTA &= ~(1<<PA2);     //     MOTOR läuft
21
22
        long_delay(25000);  
23
24
        PORTA |= (1<<PA2);      //     MOTOR stoppt
25
26
        long_delay(25000);  
27
28
        PORTA &= ~(1<<PA2);     //     MOTOR läuft
29
30
        long_delay(25000);  
31
32
        val += encode_read4();   // read a single step
33
        encoder
34
  
35
        LEDS = val;              // r18
36
  }
37
}

von Karl H. (kbuchegg)


Lesenswert?

1udo1 wrote:

> Der Motor soll jetzt zum Test 3 Sekunden links laufen, stehen bleiben, 3
> Sekunden rechts laufen. Die Zeit definiere ich mit der delay.h --- und
> das ist wahrscheinlich falsch

Als Faustregel kannst du dir merken.
Die delay Funktionen sind eigentlich fast immer falsch.
Ausser man braucht nur sehr kurze Wartezeiten, wie zb beim Warten auf
ein LCD und diese Wartezeiten stehen in keinem Verhältnis zu den Timing-
Anforderungen des restlichen Programms.

> Muss ich meine Zeitschleife einzig und allein innerhalb des
> Timerinterrupts per Zählregister realisieren? Das heißt jede
> Millisekunde ein Register incrementieren. Oder gibt es etwas
> Eleganteres.

Das ist eigentlich ziemlich elegant.
Der Timerinterrupt realisiert dir innerhalb deines Programms sowas wie 
einen gemeinsamen Zeittakt auf den alle Programmkomponenten aufbauen 
können.

von Peter D. (peda)


Lesenswert?

@1udo1,

man kann "enc_delta" und "encode_read4" auch auf int16_t oder int32_t 
erweitern, dann braucht man nicht so oft den Wert abzufragen.

Besser ist allerdings mit nem Timer das Delay zu machen und 
zwischendurch den Encoderwert auszulesen.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1udo1 wrote:
> @Johann,
>
> Nun würde ich gerne wissen:
>
> Muss ich meine Zeitschleife einzig und allein innerhalb des
> Timerinterrupts per Zählregister realisieren? Das heißt jede
> Millisekunde ein Register incrementieren. Oder gibt es etwas
> Eleganteres. Ich bin sicher, das gibt es. Aber meine bisherigen
> C-Kenntnisse lassen mich verhungern. Bitte helft mir doch mal.

Für solche Timing-Aufgaben verwende ich über meine Projekte ein 
Countdown-Modul. Hier die inzwischen etwas angestaubte WEB-Version:

http://www.gjlay.de/pub/c-code/countdown.html

Die Zeiten die es liefert sind nicht sooo genau, der Sekunden-Zähler hat 
zB nur eine Auflösung von 1 Sekunde. 2 Sekunden zu warten macht man also 
besser über 200*10ms als über 2*1s wenn es genauer sein soll.

Aber für normale Anwendungen wie

-- Taster entprellen (per 10ms)
-- Drehgeber entprellen (per 10ms)
-- LED blinken (per 10ms)
-- Bildschirmschoner (per 1sec oder 1min)
-- DCF-Port abfragen (per 10ms)

tut das prima.

Überleg dir einfach folgendes:

Du hast n LEDs.

No. 1 soll im 1s-Takt blinken,
No. 2 soll im 1.1s-Takt blinken,
No. 3 soll im 1.2s-Takt blinken, etc.

wie würdest du das machen für 5 LEDs?

von 1udo1 (Gast)


Lesenswert?

Hallo, ihr Aktiven

um Praxis zu bekommen, übe ich mit meinen bisherigen Kenntnissen das 
eine oder andere aus. Euren Anregungen gehe ich nach. Vielen Dank!! Habe 
mich schon mal in folgender Version ausgetobt. Es funktioniert, --- bin 
mir aber sicher,dass man mit diesen Programmzeilen keinen Blumentopf 
gewinnen kann.

Würde mir jemand mal eine effektivere Lösung aufschreiben?

Noch mal kurz zur Erinnerung:

Auf der Welle des Motors sitzt eine Encoderscheibe, die die Schritte 
zählt. Der Motor dreht nach Ablauf von Zeitschleifen links und rechts.

Ich lasse in diesem Programm Timer 2 laufen. Nach 300 Überläufen wird 
die switch - Anweisung durchlaufen und dort geprüft, ob der Motor steht, 
links läuft oder rechts läuft.

Bin sicher, dass man das einfacher programmieren kann. Möchte gerne 
wissen, wie man den Code besser schreiben kann.

Danke für eure Anregungen.

Das komplette Programm habe ich in den vorhergehenden Beiträgen schon 
gepostet.

Hier nur die Ergänzung:
1
//###################### TIMER 2 Interrupt ################################
2
3
4
volatile uint16_t countTimer2=0;  // Speichert den aktuellen Zählerwert
5
6
// ISR zum Auffangen der Interrupts:
7
8
ISR(TIMER2_OVF_vect)
9
10
   {
11
   countTimer2++;
12
   }
13
14
// ###### Timer initialisieren ########################
15
16
   TIMSK |= 1<<OCIE0 ^ 1<<TOIE2;
17
18
   TCCR2 = 1<<CS12 ^ 1<<CS10;  // CTC, XTAL / 1024
19
20
21
// ############### innerhalb von main ###########################
22
23
int main( void )
24
{
25
        int32_t val = 0;
26
27
        char merker=0;
28
29
        PORTA = 0xff;
30
        DDRA=0xff;
31
  
32
        DDRD=0x00;
33
        PORTD = 0xFF;// Drehgeber an PORTD4 und PORTD5
34
35
        LEDS_DDR = 0xFF;// PORT B
36
        encode_init();
37
        sei();
38
39
        for(;;)
40
        {
41
        val += encode_read4();  // read a single step encoder
42
  
43
        LEDS = val; // r18
44
45
        if( countTimer2==300  )    
46
        {
47
          switch(merker)      
48
          {
49
          case 0: PORTA |= (1<<PA2); merker=1;break; //  motor rechts
50
51
          case 1: PORTA &= ~((1<<PA2)|(1<<PA3));merker=2;break; // motor stopp
52
53
          case 2: PORTA |= (1<<PA3);pinna2=3;break;   //   motor links
54
55
          case 3: PORTA &= ~((1<<PA2)|(1<<PA3));merker=0;break; //  motor stopp
56
57
          } // Ende switch
58
      
59
          LEDS = val; // r18
60
       
61
          countTimer2=0;
62
        
63
       }// Ende count Abfrage
64
65
       }// Ende for schleife
66
67
}// Ende main

von Matthias L. (Gast)


Lesenswert?

Ich würde das mit CASE eher anders rum machen:

mal als pseudocode:
1
ISR(TIMER2_OVF_vect)
2
{
3
  _u16TimerTick++;
4
}
5
6
main
7
{
8
  uint8_t    _u8State     = 0;
9
  uint8_t    _u8StateLast = 0xFF;
10
  uint16_t   _u16TimeStamp;
11
  ...
12
13
  while(1)
14
  {
15
    ...
16
    switch (_u8State)
17
    {
18
    //--  motor rechts --------------------------------------------------
19
    case 0:
20
      // -- entry ----------------------------------
21
      if ( _u8StateLast != _u8State )
22
      {
23
        _u8StateLast  = _u8State;
24
        _u16TimeStamp = _u16TimerTick;
25
        PORTA |= (1<<PA2);
26
      }
27
      //-- action ----------------------------------
28
      //-- transition ------------------------------
29
      if (   (u16TimerTick-_u16TimeStamp) > 1234 )  u8State = 1;
30
      break; 
31
    //--  motor stopp ---------------------------------------------------
32
    case 1:
33
     // -- entry ----------------------------------
34
      if ( _u8StateLast != _u8State )
35
      {
36
        _u8StateLast  = _u8State;
37
        _u16TimeStamp = _u16TimerTick;
38
        PORTA &= ~((1<<PA2)|(1<<PA3));
39
      }
40
      //-- action ----------------------------------
41
      //-- transition ------------------------------
42
      if (   (u16TimerTick-_u16TimeStamp) > 1234 )  u8State = 2;
43
      break; 
44
    //--  motor links --------------------------------------------------
45
    case 2:
46
     //-- entry ------------------------------------
47
     ...
48
49
    }
50
  }
51
}

Hier nochmal ne Erklärung der switch-case:
Beitrag "Re: Programm bzw. Ablaeufe steuern"

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.