kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Verwendung eines inkrementellen Drehgebers mit einem Arduino Micro

(EN google-translate)

(PL google-translate)

Hardware

  • Es kommt ein "Rotary Encoder LPD3806-600BM-G5-24C" zum Einsatz.
Beschreibung, siehe beispielsweise: https://roboparts.ru/upload/iblock/798/798878d187fd802f525d217c0af961c1.pdf
  • Anschlüsse:
  1. ROT: +5V
  2. SCHWARZ: Ground
  3. GRÜN: Phase A
  4. WEISS: Phase B
  5. SILBER: Abschirmung

Ordnen Sie die Kabel auch bei der Klemmverbindung in dieser Reihenfolge an.



Verwenden Sie für die Weiterleitung möglichst die gleichen Kabelfarben.


  • Auflösung: 600 Schritte pro Umdrehung, wenn auch die Richtungsinformation benötigt wird.
  • Die Phasenausgänge (grün und weiß) sind offene Kollektorausgänge von NPN-Transistoren und müssen jeweils mit einem Pullup-Widerstand versehen werden, um einen eindeutigen Spannungspegelwechsel abgreifen zu können.
  • Dies kann durch INTERNE Pullups geschehen.
Anordnung NPN-Transistor mit Pullup für Signal A und phasenverschogenes Signal B.

Bild 0-1: Anordnung NPN-Transistor mit Pullup für Signal A und phasenverschogenes Signal B.

Test

Zum einfachen Testen wird der Drehgeber mit Strom versorgt und dessen Ausgänge werden mit digitalen Eingängen beim Arduino verbunden, bei denen die Pullup-Widerstände aktiviert sind.

  • Um später auch die Drehgeberbewegung mittels Interrupts erfassen zu können, wird
  • Phase A (GRÜN) mit INT0 verbunden (Digital Pin 3 / PD0) und
  • Phase B (WEISS) mit INT1 (Digital Pin 2 / PD1).

Es handelt sich um die gleichen Anschlüsse, wie bei I2C, weshalb beispielsweise der MPU6050 so nicht gleichzeitig betrieben werden kann.


  • Zur Kontrolle wird A mit einer grünen LED bei Digital Pin 12 (PD6) und
  • B mit einer roten LED bei Digital Pin 6 (PD7) angezeigt.

Bevor nicht die Schaltung fertig aufgebaut und das Programm übertragen wurde, sollte der Drehgeber nicht mit Spannung versorgt werden (ROT gekappt).


Testaufbau.

Bild 0-2: Testaufbau.

Stromlaufplan des Testaufbaus.

Bild 0-3: Stromlaufplan des Testaufbaus.

void setup() 
{
    DDRD&=~((1<<PD0)|(1<<PD1)); //PD0 und PD1 Input
    PORTD|=((1<<PD0)|(1<<PD1)); //PD0 und PD1 interne Pullup-Widerstände aktiviert.

    DDRD|=((1<<PD6)|(1<<PD7));  //PD6==DIO12...A anzeigen, PD7==DIO6...B anzeigen
    PORTD&=~((1<<PD6)|(1<<PD7));//beide aus  
}

void loop() 
{
    //Wenn A LOW => PD6 HIGH, sonst PD6 LOW
    if((PIND&(1<<PD0))==0)//DOPPELTE KLAMMERUNG NOTWENDIG!!!!
    {
         PORTD|=(1<<PD6);
    }
    else
    {
         PORTD&=~(1<<PD6);      
    }

    //Wenn B LOW => PD7 HIGH, sonst PD7 LOW
    if((PIND&(1<<PD1))==0)//DOPPELTE KLAMMERUNG NOTWENDIG!!!!
    {
         PORTD|=(1<<PD7);
    }
    else
    {
         PORTD&=~(1<<PD7);      
    }    
}

Code 0-1: Drehgeber001: Testansteuerung mit normalen Digitalen Eingängen.

Beobachtung bei Draufsicht auf die Achse:

A...PD0...GRÜN
B...PD1...ROT

Drehen gegen Uhrzeigersinn 1==LED AN, PD0/1 LOW, 0==LED AUS, PD0/1 HIGH:
AB
00
01
11
10

Drehen im Uhrzeigersinn:
AB
00
10
11
01


Code 0-2: Protokoll Testlauf.

Nun soll die Phaseninformation benutzt werden, um Drehrichtungen erfassen zu können. Man kann sich die dazu nötigen Bedingungen für einen Zähler Z zunächst verbal formulieren:

  • Wenn ZUVOR A_PD0==B_PD1==1 und jetzt fallende Flanke nur bei B_PD1 => mathematisch positive Drehung Z++.
  • Wenn ZUVOR A_PD0==B_PD1==1 und jetzt fallende Flanke nur bei A_PD0 => mathematisch negative Drehung Z--.

Der Zählerstand wird an über die serielle Schnittstelle an den PC gesendet, so, dass man sich ihn im Seriellen Monitor der Arduino IDE ansehen kann:

bool AB_LOW=false;
int Z=0;
int ZALT=0;
unsigned char IN=0;

void setup() 
{
    DDRD&=~((1<<PD0)|(1<<PD1)); //PD0 und PD1 Input
    PORTD|=((1<<PD0)|(1<<PD1)); //PD0 und PD1 interne Pullup-Widerstände aktiviert.

    DDRD|=((1<<PD6)|(1<<PD7));  //PD6==DIO12...A anzeigen, PD7==DIO6...B anzeigen
    PORTD&=~((1<<PD6)|(1<<PD7));//beide aus  

    Serial.begin(115200);//ACHTUNG: Höhere Baudrate als Default!
}

void loop() 
{
    IN=PIND&0b00000011;
    if( IN==0 )
    {
        AB_LOW=true;
    }
    else if( IN==1 && AB_LOW) //A HIGH
    {
        AB_LOW=false;
        Z--;            
    }
    else if( IN==2 && AB_LOW) //B HIGH
    {
        AB_LOW=false;
        Z++;              
    }

    if(Z!=ZALT)
    {
       Serial.println(Z);
       ZALT=Z;
    }
    //Wenn A LOW => PD6 HIGH, sonst PD6 LOW
    if((IN&0b00000001)==0)//DOPPELTE KLAMMERUNG NOTWENDIG!!!!
    {
         PORTD|=(1<<PD6);
    }
    else
    {
         PORTD&=~(1<<PD6);      
    }

    //Wenn B LOW => PD7 HIGH, sonst PD7 LOW
    if((IN&0b00000010)==0)//DOPPELTE KLAMMERUNG NOTWENDIG!!!!
    {
         PORTD|=(1<<PD7);    
    }
    else
    {
         PORTD&=~(1<<PD7);      
    }            
}

Code 0-3: Drehgeber002: Um einen Zähler erweitertes Programm.

Verwendung mit Interrupts

In der jetzigen Form des Programms kann nicht viel mehr als die Drehgebererfassung mit dem Mikrocontroller umgesetzt werden.

Mit jeder Codezeile mehr, die in dem Loop-Bereich hinzu kommt, muss man befürchten, dass ein Durchlauf so langsam wird, dass eine Pegeländerung bei A und B "verpasst" wird und die Drehgebererfassung nicht mehr richtig arbeitet.

Um einerseits die Ressourcen des loop-Bereichs wieder frei zu bekommen und andererseits sicher zu gehen, dass kein Pegelwechsel bei A oder B verpasst wird, können die Anschlüsse bei PD0 und PD1 so konfiguriert werden, dass sie als externe Interrupt-Eingänge funktionieren und jeweils die Ausführung einer Interrupt-Service-Routine ausgelöst wird.

Eine Interrupt-Service-Routine ist eine kleine Funktion im Quelltext, die genau dann abgearbeitet wird, wenn ein definierter Pegelwechsel auf einem definierten Eingang stattfindet. Der Hauptloop wird für die Dauer der Ausführung unterbrochen und unmittelbar danach wieder fortgesetzt. Dazu werden die Arbeitsregister und der Programmzeiger gemerkt und bei Fortsetzen von loop() wieder eingelesen, so, dass das Programm genau an der Stelle fortgesetzt wird, wo es unterbrochen wurde.

Interrupts mit Arduino-Befehlen

  • Arduino stellt die Funktion attachInterrupt(..) zur Verfügung.
  • Sie benötigt drei Übergabeparameter:
  1. Die Nummer des Interrupts
  2. Einen Zeiger auf eine selbst geschriebene Funktion, die im Falle des Interrupts aufgerufen werden soll.
  3. Eine Angabe dazu, bei welcher Art Pegeländerung am Interrupt-Eingang die eigene Funktion aufgerufen werden soll.

Als Möglichkeiten für den Typ der Änderung stehen zur Verfügung: LOW, CHANGE, RISING, FALLING, HIGH.

Siehe auch: https://www.arduino.cc/reference/de/language/functions/external-interrupts/attachinterrupt/
bool AB_LOW=false;
int Z=0;
int ZALT=0;
unsigned char IN=0;

void fallendeFlankeBeiA()
{
    if((PIND&0b00000011)>0)
        Z--;
}

void fallendeFlankeBeiB()
{
    if((PIND&0b00000011)>0)
        Z++;
}

void setup() 
{
    DDRD&=~((1<<PD0)|(1<<PD1)); //PD0 und PD1 Input
    PORTD|=((1<<PD0)|(1<<PD1)); //PD0 und PD1 interne Pullup-Widerstände aktiviert.

    DDRD|=((1<<PD6)|(1<<PD7));  //PD6==DIO12...A anzeigen, PD7==DIO6...B anzeigen
    PORTD&=~((1<<PD6)|(1<<PD7));//beide aus  

    Serial.begin(115200);

    cli();//Verbiete zunächst Interrupts
    attachInterrupt(0, fallendeFlankeBeiA, FALLING);
    attachInterrupt(1, fallendeFlankeBeiB, FALLING);
    sei();//Erlaube Interrupts
}

void loop() 
{
    IN=PIND&0b00000011;

    if(Z!=ZALT)
    {
       Serial.println(Z);
       ZALT=Z;
    }
    //Wenn A LOW => PD6 HIGH, sonst PD6 LOW
    if((IN&0b00000001)==0)
    {
         PORTD|=(1<<PD6);
    }
    else
    {
         PORTD&=~(1<<PD6);      
    }

    //Wenn B LOW => PD7 HIGH, sonst PD7 LOW
    if((IN&0b00000010)==0)
    {
         PORTD|=(1<<PD7);    
    }
    else
    {
         PORTD&=~(1<<PD7);      
    }            
}

Code 0-4: Drehgeber003: Verwendung von Interrupts mittels Arduinio-Befehlen.

Nun kann die loop-Methode wieder für andere Aufgaben verwendet werden.

Interrupts mit Register-Befehlen

Das vorangehende Programm soll nun so modifiziert werden, dass die Interrupt-Konfiguration mittels Register-Befehlen läuft und die nativen ISR (Interrupt-Service-Routinen) verwendet werden:

#include <avr/interrupt.h>

bool AB_LOW=false;
int Z=0;
int ZALT=0;
unsigned char IN=0;

ISR(INT0_vect)
{
    if((PIND&0b00000011)>0)
        Z--;
}

ISR(INT1_vect)
{
    if((PIND&0b00000011)>0)
        Z++;
}

void setup() 
{
    DDRD&=~((1<<PD0)|(1<<PD1)); //PD0 und PD1 Input
    PORTD|=((1<<PD0)|(1<<PD1)); //PD0 und PD1 interne Pullup-Widerstände aktiviert.

    DDRD|=((1<<PD6)|(1<<PD7));  //PD6==DIO12...A anzeigen, PD7==DIO6...B anzeigen
    PORTD&=~((1<<PD6)|(1<<PD7));//beide aus  

    Serial.begin(115200);

    cli();//Verbiete zunächst Interrupts
    //Falling Edge => Interrupt: 10 bei ISCn1 und ISCn0, siehe Datenblatt S. 89:
    EICRA=( (1<<ISC31)|(0<<ISC30)|(1<<ISC21)|(0<<ISC20)|(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00) );
    //External Interrupt Request 6, 3 - 0 Enable, S.90:
    EIMSK=( (1<<INT1)|(1<<INT0) );
    sei();//Erlaube Interrupts
}

void loop() 
{
    IN=PIND&0b00000011;

    if(Z!=ZALT)
    {
       Serial.println(Z);
       ZALT=Z;
    }
    //Wenn A LOW => PD6 HIGH, sonst PD6 LOW
    if((IN&0b00000001)==0)
    {
         PORTD|=(1<<PD6);
    }
    else
    {
         PORTD&=~(1<<PD6);      
    }

    //Wenn B LOW => PD7 HIGH, sonst PD7 LOW
    if((IN&0b00000010)==0)//DOPPELTE KLAMMERUNG NOTWENDIG!!!!
    {
         PORTD|=(1<<PD7);    
    }
    else
    {
         PORTD&=~(1<<PD7);      
    }            
}

Code 0-5: Drehgeber004: Interrupts mittels Register-Befehlen aktivieren und verwenden.

Drehgeber-Test mit Interrupts: https://youtu.be/70_TosqT0qU
ÜBUNGEN

Aufgabe 1

  • Bauen Sie obige Schaltung nach und testen die vier oben besprochenen Programm-Varianten.

Aufgabe 2

  • Entwickeln Sie ein Programm, mit dem es möglich ist, den Winkel bei einem Servo proportional zu der Winkel-Einstellung bei dem Drehgeber zu steuern.
  • Verwenden Sie den Ausgang OC1A / Digital Pin 9 zur Ansteuerung des Servos.

Aufgabe 3

  • Wie Aufgabe 2 aber:
  • Verwenden Sie nur Registerbefehle zur Konfiguration und Verwendung von Servo und Drehgeber.
  • Sorgen Sie für eine möglichst hohe Servowinkelauflösung.
  • Kapseln Sie die Servo- und die Drehgeberfunktionalität jeweils in eine Klasse mit passenden Objektmethoden.