Incrementalgeber auslesen


© August 2023, letzte Änderung am 21.10.23

--- still under construction ---

Übersicht

Ein Incrementalgeber ist ein Drehwinkel Meßinstrument, welches
die relative Winkelposition ausgiebt.
Relativ, da ich keinen Absolutwinkel erhalte, sondern nur deren Änderung.
Das bedeutet, solange ich nicht eine Referenzposition angefahren habe,
kenne ich nicht den Winkel.
Nachdem ich die Referenz  N  angefahren habe,
muss ich entsprechend der Phase der Signale  A  und  B
entweder hoch oder runter zählen um die jetzige Position zu bestimmen.

Ausgang eines Incrementalgebers bei einer Drehrichtung

Das N-Signal habe ich an beliebiger Stelle eingezeichnet
entsprechend der Bedingung:   N = 1   →   A = 1  &  B = 1

Python Incrementalgeber-Test auf dem Zero W

Wie man den Raspberry startet, konfiguriert, remote darauf zugreift, etc.
habe ich bereits unter  Raspberry Zero W  beschrieben.

Zunächst habe ich nur einen Incrementalgeber an PGIO14, 15 und 23
angeschlossen und gucke ob der funktioniert.
Entsprechende Testschaltung findet sich unter  Motortest.
Die Pinbelegung habe ich unter  Raspberry Zero W IO  beschrieben.

Nun nehme ich an, wir befinden uns im Benutzer-Ordner z.B.  /home/pi
Hier schreibe ich mit einen Editor z.B. nano, ein paar Zeilen Python.

nano inctst01.py

#!/usr/bin/python
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)                  # GPIO.BOARD für Pins
GPIO.setwarnings(False)
nRES = 12                               # reset low active
A = 23                                  # A, B, N from incremental sensor
N = 15
B = 14
GPIO.setup(nRES, GPIO.OUT)
GPIO.output(nRES, GPIO.LOW)             # reset DRV8825 (Motor deaktiviert)
GPIO.setup(A, GPIO.IN)
GPIO.setup(B, GPIO.IN)
GPIO.setup(N, GPIO.IN)

inAnew = GPIO.input(A)
inBnew = GPIO.input(B)
cnt = 0

while abs(cnt) < 10000:                 # never reached
    inAold = inAnew
    inBold = inBnew

    inAnew = GPIO.input(A)
    inBnew = GPIO.input(B)

    if GPIO.input(N): # == True:
        cnt = 0
        print ( 'N=1  A= '+str(inAnew)+'  B= '+str(inBnew) )
    elif inAnew > inAold and inBnew == 0 and inBold == 0:
        cnt += 1
        print ( 'counter= '+str(cnt) )
    elif inAnew > inAold and inBnew == 1 and inBold == 1:
        cnt -= 1
        print ( 'counter= '+str(cnt) )
    elif inAnew < inAold and inBnew == 0 and inBold == 0:
        cnt -= 1
        print ( 'counter= '+str(cnt) )
    elif inAnew < inAold and inBnew == 1 and inBold == 1:
        cnt += 1
        print ( 'counter= '+str(cnt) )
    elif inAnew == 0 and inAold == 0 and inBnew < inBold:
        cnt += 1
        print ( 'counter= '+str(cnt) )
    elif inAnew == 0 and inAold == 0 and inBnew > inBold:
        cnt -= 1
        print ( 'counter= '+str(cnt) )
    elif inAnew == 1 and inAold == 1 and inBnew < inBold:
        cnt -= 1
        print ( 'counter= '+str(cnt) )
    elif inAnew == 1 and inAold == 1 and inBnew > inBold:
        cnt += 1
        print ( 'counter= '+str(cnt) )
    elif inAnew != inAold  and inBnew != inBold:
        print ( "---lost step---" )
print ("fertig")                        # never reached

Danach mache ich den Code noch ausführbar und starte Ihn.
Ausgeführt wird er dann über den python-Interpreter.

chmod u+x inctst01.py

./inctst01.py

Nun sollten beim Drehen des Incrementalgebers unterschiedliche Zahlen auf den Bildschirm erscheinen.
Interessant ist, dass ich bei dieser Art der Auswertung bis ±4096 (-4065 - 4085) komme.
Leicht sind ein paar Veränderungen nicht erfasst, da ich zu schnell drehte.
Das könnte aber auch an den print Befehlen liegen, welche über WLAN übertragen werden.
Zumindest zeigt der erste Versuch, das python auf einem Zero W zu langsam für diese Anwendung, ist.
Mit  CTRL-C kann man den Code unterbrechen.

Von python gibt es verschiedene Derivate (2.x versus 3.x) und Bibliotheken.
z.B. readthedocs.io   gpiozero (verwendet PCB Pins)
      abyz.me.uk   pigpio (Steuerung über Netzwerk möglich, verwendet GPIO Bezeichnung)
      pypi.org   RPi.GPIO (Klassiker mit GPIO Bezeichnung oder PCB Pins)

Zum Anfang

Incrementalgeber-Test in C auf dem Zero W

C wird im Gegensatz zu python schon vor dem Start kompiliert und nicht zur Laufzeit interpretiert.
Somit ist C wesentlich schneller als python.

Vergleichbar mit python, gibt es hier auch verschiedene Bibliotheken oder
sogar die Möglichkeit direkt auf die Register zuzugreifen, was aufgrund der asnchronen Abarbeitung,
nicht zu empfehlen ist.
Asynchron, weil die schnelle CPU in verschiedene FIFOs schreibt, deren Inhalt wird dann langsam
von verschiedenen "IO-state machines" abgearbeitet.

Wenn man die WiringPi Bibliothek verwendet, muss man aufpassen, das die Bezeichnungen anders sind
und nach Pin 28 Schluss ist, vom 40 poligen Stecker.
Also habe ich mich für die schlankeste Bibliothek  bcm2835  entschieden.
Diese muss zunächst runtergeladen, entpackt und kompiliert werden.

wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.73.tar.gz
…
2023-08-26 21:29:05 (307 KB/s) - ‘bcm2835-1.73.tar.gz’ saved [281850/281850]

tar -xvzf bcm2835-1.73.tar.gz

cd bcm2835-1.73/
./configure
checking for a BSD-compatible install... /usr/bin/install -c
…

make
make  all-recursive
…

sudo make install
Making install in src
…

Nun kann ich die Bibliothek verwenden und erstelle wieder ein Programm.
Zur besseren Wiedererkennung, habe ich es möglichst identisch zum Python Programm aufgebaut.

nano inctst.c

#include <stdio.h>
#include <bcm2835.h>                    // #include <wiringPi.h>
#include <time.h>

#define nRes RPI_V2_GPIO_P1_32          // dieses Mal die Pins und nicht die Ports
#define inA  RPI_V2_GPIO_P1_16          // #define inA 16
#define inB  RPI_V2_GPIO_P1_08
#define inN  RPI_V2_GPIO_P1_10

/*****************************************************
 * DELAY FOR # uS WITHOUT SLEEPING
 * Using delayMicroseconds lets the linux scheduler decide to jump to another process.  
 * Using this function avoids letting the scheduler know 
 * we are pausing and provides much faster operation if you are needing to use lots of delays.
 * 
 */
void delayMicrosecondsNoSleep (int delay_us)
{  long int start_time;
   long int time_difference;
   struct timespec gettime_now;
   clock_gettime(CLOCK_REALTIME, &gettime_now);
   start_time = gettime_now.tv_nsec;               // Get nS value
   while (1)
   {  clock_gettime(CLOCK_REALTIME, &gettime_now);
      time_difference = gettime_now.tv_nsec - start_time;
      if (time_difference < 0)
         time_difference += 1000000000;            // Rolls over every 1 second
      if (time_difference > (delay_us * 1000))     // Delay for # nS
         break;
   }
}


/*********************
 * main
 */
int main( void )
{ if( !bcm2835_init() )    { printf("no lib found\n"); return 1; } 
  printf("inctst.c started\n");         // if( wiringPiSetup() == -1 ) {return 1;}

  uint8_t inanew, inbnew, inaold, inbold;
  int cnt;

  bcm2835_gpio_fsel(  nRes, BCM2835_GPIO_FSEL_OUTP);  // pinMode( nRes, OUTPUT);
  bcm2835_gpio_write( nRes, LOW  );                   // digitalWrite(nRes,0);                   
  // reset DRV8825
  bcm2835_gpio_fsel( inA,   BCM2835_GPIO_FSEL_INPT);  // pinMode( inA, INPUT);
  bcm2835_gpio_fsel( inB,   BCM2835_GPIO_FSEL_INPT);
  bcm2835_gpio_fsel( inN,   BCM2835_GPIO_FSEL_INPT);

  inanew = bcm2835_gpio_lev(inA);                     // bcm2835_gpio_lev returns uint8_t
  inbnew = bcm2835_gpio_lev(inB);                     // digitalRead(inB)
  cnt = 0;

  while( abs(cnt) < 10000 )
  { inaold = inanew;
    inbold = inbnew;
    inanew = bcm2835_gpio_lev(inA);
    inbnew = bcm2835_gpio_lev(inB);

    if( bcm2835_gpio_lev( inN ) ) { cnt = 0; }
    else if( inanew > inaold && inbnew == 0 && inbold == 0 )
    { cnt++; printf( " %d\n", cnt );
    }
    else if( inanew > inaold && inbnew == 1 && inbold == 1 )
    { cnt--; printf( " %d\n", cnt );
    }
    else if( inanew < inaold && inbnew == 0 && inbold == 0 )
    { cnt--; printf( " %d\n", cnt );
    }
    else if( inanew < inaold && inbnew == 1 && inbold == 1 )
    { cnt++; printf( " %d\n", cnt );
    }
    else if( inanew == 0 && inaold == 0 && inbnew < inbold )
    { cnt++; printf( " %d\n", cnt );
    }
    else if( inanew == 0 && inaold == 0 && inbnew > inbold )
    { cnt--; printf( " %d\n", cnt );
    }
    else if( inanew == 1 && inaold == 1 && inbnew < inbold )
    { cnt--; printf( " %d\n", cnt );
    }
    else if( inanew == 1 && inaold == 1 && inbnew > inbold )
    { cnt++; printf( " %d\n", cnt );
    }
    else if( inanew != inaold && inbnew != inbold )
    { printf( "lost step\n" );
      // return 2;                       // auskommentiert, da sonst zu schnell beendet
    }
    
    delayMicrosecondsNoSleep( 100 );     // mit 100 µs Pause → 19% CPU
  } // while( abs(cnt) < 10000; )     
  return 0;
}

Um mir das Leben zu vereinfachen, kompiliere und starte ich in einen 2. Fenster.
Dummerweise sind die Pins 27 und 28 nicht in der Bibliothek enthalten.
Daher muss ich die Platine nocheinmal ändern und den Widerstand für nRes auf Pin 32 legen.

gcc -o tst inctest.c -lbcm2835 

./tst

sudo  war übrigens zum Start des Programms nicht nötig.
Abbruch des Programms erfolgt wieder über CTRL-C.
Beim Kompilieren bekam ich natürlich auch Fehlermeldungen.

gcc mottst.c -o tst -lbcm2835
mottst.c: In function ‘main’:
mottst.c:10:17: error: ‘RPI_V2_GPIO_PI_10’ undeclared (first use in this function); 
did you mean ‘RPI_V2_GPIO_P1_10’?
 #define inN     RPI_V2_GPIO_PI_10
                 ^~~~~~~~~~~~~~~~~

Nach vielen Versuchen, habe ich exakt den Text aus der Webseite  airspayce.com  kopiert
und dann lief es ???

Auch dieses Programm konnte ich leicht beenden, indem ich etwas schneller gedreht habe,
was ich so nicht erwartet hätte.
Die 100% CPU-Last haben offensichtlich zum "throttling" der CPU im Sekunden-Takt geführt,
wo dann Schritte verloren gingen. Daher habe ich eine Pause ergänzt und der CPU einen Kühlkörper spendiert.

Es gibt aber noch ein Problem mit delayMicroseconds( 100 )
was ich auf der Seite raspberry-projects.com fand.
raspberry-projects.com   clock_gettime() For Acurate Timing
Leider verliere ich immer noch Schritte.

Zum Anfang

Python Incrementalgeber-Test per Interrupt

Die bisherigen Programme setzten auf sogenanntes Polling.
Viel eleganter ist es allerdings nur bei Änderungen via Interrupts zu reagieren.

Zum Anfang

Quellen

python.org   python
mint-first.de   python Übungsaufgaben
w3schools.com   Python Tutorial
github.com   Simple Electronics with GPIO Zero.PDF

Zum Anfang