www.mikrocontroller.net

Forum: Compiler & IDEs Programmablaufplan Drehgeber


Autor: Udo Scharnitzki (Firma: allround) (1udo1)
Datum:

Bewertung
0 lesenswert
nicht 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
#include <avr\io.h>
#include <avr\interrupt.h>

#define  XTAL    8e6      // 8MHz
#define PHASE_A    (PINA & 1<<PA1)
#define PHASE_B    (PINA & 1<<PA3)
 #define LEDS_DDR  DDRC
#define  LEDS    PORTC    // LEDs against VCC
volatile int8_t enc_delta;        // -128 ... 127
static int8_t last;

void encode_init( void )
{
int8_t new;
 new = 0;
    if( PHASE_A )      // wenn Bit PORTA1 gesetzt
        new = 3;
        if( PHASE_B )    // wenn Bit PORTA3 gesetzt
          new ^= 1;    // convert gray to binary
        last = new;    // power on state
        enc_delta = 0;

       TCCR0 = 1<<WGM01^1<<CS01^1<<CS00;  // CTC, XTAL / 64
        OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);  // 1ms
       TIMSK |= 1<<OCIE0;
}
  
ISR( TIMER0_COMP_vect )  // 1ms for manual movement
{
int8_t new, diff;
new = 0;
    if( PHASE_A )    // wenn Bit PORTA1 gesetzt
        new = 3;      // 
        if( PHASE_B )    // wenn Bit PORTA3 gesetzt
                 new ^= 1;    // convert gray to binary
              diff = last - new;  // difference last - new
               if( diff & 1 )       // bit 0 = value (1)
   {  
            last = new;        // store new as next last
           enc_delta += (diff & 2) - 1;  // bit 1 = direction (+/-)
  }
}
 
int8_t encode_read1( void )    // read single step encoders
{
  int8_t val;
 
  cli();
  val = enc_delta;
  enc_delta = 0;
  sei();
  return val;        // counts since last call
}

int main( void )
{
  int32_t val = 0;
 
  LEDS_DDR = 0xFF;
  encode_init();
  sei();
 
  for(;;){
    val += encode_read1();      // read a single step encoder
    LEDS = val;
  }
}


Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: 1udo1 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: egberto (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: ein Schlaumeier (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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ß

Autor: 1udo1 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
char drehgeber_step (void)
{
    static const char drehgeber_trainsitions[] PROGMEM =
    {
         0,                       1,           -1, DREH_INVALID,
        -1,                       0, DREH_INVALID,            1,
         1,            DREH_INVALID,            0,           -1,
         DREH_INVALID,           -1,            1,            0
    };

@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

Autor: 1udo1 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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!?
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?
int main( void )
{
  int32_t val = 0;
  char teiler=4;
 
  LEDS_DDR = 0xFF;
  encode_init();
  sei();
 
  for(;;){
    val += encode_read1();    // read a single step encoder
    
    val /= teiler;
    LEDS = val;
  }
}

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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
     val += encode_read4();    // read a 4 step encoder
nehmen.


Peter

Autor: 1udo1 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
DANKE Peter für deine Hilfe. Anlage läuft!!

Gruß

Udo

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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 ;-)

Autor: 1udo1 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:

int main( void )
{
  int32_t val = 0;

     PORTA = 0xFF;           // an PORTA2 ist der MOTOR angeschlossen
     DDRA=0xff;
  
     DDRD=0x00;              // alles auf EINGANG
     PORTD = 0xFF;           //Pullups aktiv

     LEDS_DDR = 0xFF;        // PORT B
     encode_init();
     sei();

// ####### das funktioniert nicht, soll nur zeigen, was ich will#######
// ####### Motor soll Drehrichtung wechseln und es sollen auch noch die Schritte gezählt werden ####
 
        for(;;){

        PORTA &= ~(1<<PA2);     //     MOTOR läuft

        long_delay(25000);  

        PORTA |= (1<<PA2);      //     MOTOR stoppt

        long_delay(25000);  

        PORTA &= ~(1<<PA2);     //     MOTOR läuft

        long_delay(25000);  

        val += encode_read4();   // read a single step
        encoder
  
        LEDS = val;              // r18
  }
}


Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: 1udo1 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:

//###################### TIMER 2 Interrupt ################################


volatile uint16_t countTimer2=0;  // Speichert den aktuellen Zählerwert

// ISR zum Auffangen der Interrupts:

ISR(TIMER2_OVF_vect)

   {
   countTimer2++;
   }

// ###### Timer initialisieren ########################

   TIMSK |= 1<<OCIE0 ^ 1<<TOIE2;

   TCCR2 = 1<<CS12 ^ 1<<CS10;  // CTC, XTAL / 1024


// ############### innerhalb von main ###########################

int main( void )
{
        int32_t val = 0;

        char merker=0;

        PORTA = 0xff;
        DDRA=0xff;
  
        DDRD=0x00;
        PORTD = 0xFF;// Drehgeber an PORTD4 und PORTD5

        LEDS_DDR = 0xFF;// PORT B
        encode_init();
        sei();

        for(;;)
        {
        val += encode_read4();  // read a single step encoder
  
        LEDS = val; // r18

        if( countTimer2==300  )    
        {
          switch(merker)      
          {
          case 0: PORTA |= (1<<PA2); merker=1;break; //  motor rechts

          case 1: PORTA &= ~((1<<PA2)|(1<<PA3));merker=2;break; // motor stopp

          case 2: PORTA |= (1<<PA3);pinna2=3;break;   //   motor links

          case 3: PORTA &= ~((1<<PA2)|(1<<PA3));merker=0;break; //  motor stopp

          } // Ende switch
      
          LEDS = val; // r18
       
          countTimer2=0;
        
       }// Ende count Abfrage

       }// Ende for schleife

}// Ende main


Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich würde das mit CASE eher anders rum machen:

mal als pseudocode:
ISR(TIMER2_OVF_vect)
{
  _u16TimerTick++;
}

main
{
  uint8_t    _u8State     = 0;
  uint8_t    _u8StateLast = 0xFF;
  uint16_t   _u16TimeStamp;
  ...

  while(1)
  {
    ...
    switch (_u8State)
    {
    //--  motor rechts --------------------------------------------------
    case 0:
      // -- entry ----------------------------------
      if ( _u8StateLast != _u8State )
      {
        _u8StateLast  = _u8State;
        _u16TimeStamp = _u16TimerTick;
        PORTA |= (1<<PA2);
      }
      //-- action ----------------------------------
      //-- transition ------------------------------
      if (   (u16TimerTick-_u16TimeStamp) > 1234 )  u8State = 1;
      break; 
    //--  motor stopp ---------------------------------------------------
    case 1:
     // -- entry ----------------------------------
      if ( _u8StateLast != _u8State )
      {
        _u8StateLast  = _u8State;
        _u16TimeStamp = _u16TimerTick;
        PORTA &= ~((1<<PA2)|(1<<PA3));
      }
      //-- action ----------------------------------
      //-- transition ------------------------------
      if (   (u16TimerTick-_u16TimeStamp) > 1234 )  u8State = 2;
      break; 
    //--  motor links --------------------------------------------------
    case 2:
     //-- entry ------------------------------------
     ...

    }
  }
}

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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.