Längenmessgerät TTL-Signal mit 100 kHz auslesen

Hallo Leute,

mein Name ist Bastian und ich bin neu hier im Forum. Ich steige grade erst in die Welt des Arduinos bzw. des Programmierens ein, deshalb habe ich noch nicht viel Ahnung. Aber ich gebe mir Mühe. :wink:

Ich möchte mit einem Arduino UNO ein TTL Signal eines Längenmessgeräts auslesen. Eine Schaltung interpoliert das eigentliche Sinussignal und gibt ein TTL-Quadratursignal aus. Über ein geschirmtes Kabel werden die beiden Signale A und B und die entsprechend invertierten Signale A’ und B’ zur Auswertelektronik geführt, wo aus den Signalen jeweils wieder die reinen Signale A und B gemacht werden. Diese kommen an meinem Arduino über Pin 9 und 10 an.
Laut der Angaben des Herstellers sollen die beiden Eingänge mit 100 kHz abgefragt werden.

Dazu habe ich diesen Code übernommen und die Timereinstellungen entsprechend angepasst. Die Abfrage findet nun mit 250 kHz statt, was ein bisschen viel ist. Allerdings weiß ich nicht, wie ich die Clock Select Bits wählen muss um mit einem entsprechenden Teiler auf die 100 HZ zu kommen. Wobei eine höhere Abfragefrequenz ja eigentlich nicht schaden kann. Oder? :wink:

Hier der verwendete Code:

// Copyright (C) 2014 Ralf Hübben - www.meinDUINO.de

// An die Pins 9 und 10 ist der Encoder angeschlossen
#define encoderA 9
#define encoderB 10

// Globale Variablen zur Auswertung in der
// Interrupt-Service-Routine (ISR)
volatile int8_t altAB = 0;
volatile int encoderWert = 0;


// 1/1 Auflösung
int8_t schrittTab[16] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; 



/*************************************************************
*
* Interrupt Service Routine
*
* Wird aufgerufen, wenn der entsprechende Interrupt
* ausgelöst wird
*
*************************************************************/
ISR(TIMER1_COMPA_vect) {
  altAB <<= 2;
  altAB &= B00001100;
  altAB |= (digitalRead(encoderA) << 1) | digitalRead(encoderB);
  encoderWert += schrittTab[altAB];
}

/*************************************************************
* 
* void setup()
*
* Wird einmal beim Programmstart ausgeführt
*
*************************************************************/
void setup() {
  pinMode(encoderA, INPUT);
  pinMode(encoderB, INPUT);
  
  noInterrupts(); // Jetzt keine Interrupts
  TIMSK1 |= (1<<OCIE1A);  // Timer 1 Output Compare A Match Interrupt Enable

  TCCR1A = 0; // "Normaler" Modus

  // WGM12: CTC-Modus einschalten (Clear Timer on Compare match) 
  //        Stimmen OCR1A und Timer überein, wird der Interrupt
  //        ausgelöst
  // Bit CS11<<1 setzen, Teiler ist 64
  TCCR1B = (1<<WGM12) | (1<<CS11);

  // Frequenz = 16.000.000 / (64) Teiler aus CSxx = 250.000 Hz
  // Überlauf bei 19, weil die Zählung bei 0 beginnt
  OCR1A = 19; 
  
  interrupts(); // Interrupts wieder erlauben

  Serial.begin(115200);
}


/*************************************************************
* 
* void loop()
*
* Wird immer wieder durchlaufen
*
*************************************************************/
void loop() {


  while(true) {
    
    Serial.println(encoderWert);
    delay(1000);
  }
}

Zunächst sollen einfach nur die gezählten Änderungen mit Hilfe des Serial Displays dargestellt werden (später dann auf einem LCD). Das funktioniert auch schon ganz gut, die Änderungen werden addiert wenn ich den Weg verändere, gehe ich wieder auf die Ursprungsposition zurück, landet auch der Zähler wieder bei Null.
Aber: Das funktioniert nur bei sehr langsamen Verfahrgeschwindigkeiten (irgendwo um 5 mm pro Sekunde). Laut Hersteller sollen aber 1000 mm pro Sekunde bei 100 kHz Abfragefrequenz gut möglich sein. Verfahre ich schneller, dann gehen Schritte verloren und der Zähler landet nicht wieder bei Null.
Habt ihr Ideen? Was ich so gelesen habe sollte der Arduino UNO mit 100 kHz noch klarkommen, oder?
Sind irgendwo Fehler oder ein “Flaschenhals”?

Wie gesagt, ich habe noch nicht viel Ahnung und ein solches Projekt ist auch wahrscheinlich eher nichts für Anfänger. Aber man lernt ja nur dazu, wenn man sich den Herausforderungen stellt. :wink:
Ich denke, dass ich die Funktionsweise des obigen Codes halbwegs verstanden habe.

Vielen Dank schon mal für eure Hilfe!

Gruß
Bastian

Allerdings weiß ich nicht, wie ich die Clock Select Bits wählen muss um mit einem entsprechenden Teiler auf die 100 HZ zu kommen.

Die Formel ist: OCR1A = (16 MHz / Prescaler / gewünschte Frequenz) - 1

Also z.B. Prescaler = 8 und OCR1A = 19. Oder Prescaler = 1 (d.h. Timer läuft mit Systemtakt) und OCR1A = 159

Serenifly:
Die Formel ist:
OCR1A = (16 MHz / Prescaler / gewünschte Frequenz) - 1

Also z.B. Prescaler = 8 und OCR1A = 19. Oder Prescaler = 1 (d.h. Timer läuft mit Systemtakt) und OCR1A = 159

Hallo Serenifly,

danke für deine Antwort.
Dann stimmen meine Timereinstellungen ja (auch wenn es in dem Kommentar falsch steht). 1<<CS11 macht einen Prescaler von 8 und mein 0CR1A ist 19.

Gruß Bastian

Olomolo: Habt ihr Ideen? Was ich so gelesen habe sollte der Arduino UNO mit 100 kHz noch klarkommen, oder?

Ja klar, die Arduino-Hardware schafft das. Aber nicht bei Verwendung der allerlangsamsten Arduino "Komfort"-Softwarebefehle, die für die einfache Verwendung durch ungeübte Programmierer gemacht aber nicht auf maximale Ausführungsgeschwindigkeit getrimmt sind.

Mit Deinen bescheidenen Programmierkenntnissen und 'digitalRead()' klappt das auf keinen Fall!

Jeder Interrupt im Arduino bedeutet schon mal einen Overhead beim Sichern des Controllerzustands vor dem Interrupt und Wiederherstellen des Controllerzustands nach dem Interrupt von ca. 4 Mikrosekunden.

Läßt Du also einen Timerinterrupt mit 100 kHz laufen, dann gehen pro Sekunde bereit 4µs*100000 = 400000 Mikrosekunden nur für das Aufrufen und Beenden der Interrupts drauf - ohne dass dabei auch nur eine einzige Zeile nützlichen Codes ausgeführt worden wäre.

Wenn Du nun die allerlangsamste Möglichkeit zum Lesen von Pinzuständen nimmst und die Funktion "digitalRead()" dazu verwendest, dann gehen dafür pro digitalRead() auf einem Arduino UNO zwischen ca. 4 und 5µs drauf, abhängig von der verwendeten Pinnummer. Also nochmal 400000 bis 500000 Mikrosekunden bei 100 kHz - pro Pin!

Also so wie Du programmierst, brauchst Du bei 100 kHz bereits - 400000 Mikrosekunden Interrupt-Overhead - 400000 bis 500000 Mikrosekunden zum Auslesen des einen Pins - 400000 bis 500000 Mikrosekunden zum Auslesen des anderen Pins = 1200000 bis 1400000 Mikrosekunden Und zwar OHNE dass dabei auch irgendwas gezählt, ausgewertet, formatiert und auf LCD ausgegeben wird.

Weil eine einzelne Sekunde aber nur 1000000 Mikrosekunden hat, Du aber für Deine lahme Programmierung viel mehr als 1200000 Mikrosekunden pro Sekunde haben müßtest, kann Dein Programm so vorne und hinten und mittendrin niemals funktionieren.

Wenn Dein Programm mit 100 kHz Ausleserate funktionieren soll, kannst Du das lahme "digitalRead()" komplett vergessen. Dafür brauchst Du schnelle "direkte Registerprogrammierung" für den Atmega Deiner Wahl. Im Fall des UNO wäre das der Atmega328.

Ohne direkte Registerprogrammierung beim Auslesen der Pinzustände wird das mit 100 kHz Ausleserate nix. Also mußt Du so wie Du den Timer1 bereits über die entsprechenden Timer-Register "direkt" programmierst, auch die entsprechenden PORT/PIN-Register direkt programmieren - und auf so komfortabel verwendbare, jedoch in der Ausführung extrem lahme - Befehle wie 'digitalRead()' in Deinem Programm komplett verzichten.

Alternativ, das hier: https://code.google.com/p/digitalwritefast/downloads/list

Das geht wenn die Pins zur Compilezeit bekannt sind (was hier der Fall ist) genauso schnell

Ich würde einen Pin Change Interrupt (PCInt) probieren. Der tritt nur auf wenn sich ein Signal ändert, reduziert also den Overhead.

Einen Verlust von Schritten kann man zwar nicht verhindern, aber entdecken wenn sich die Signale nicht so ändern wie sie sollten. Wenn sowas passiert, dann weißt Du zumindest, daß irgendwas faul ist und verbessert werden sollte.

CHANGE auch mit den richtigen Hardware Interrupts. PinChange Interrupts haben mehr Overhead als diese, da sich 8 Pins einen Interrupts-Vektor teilen. Man braucht also noch eine Abfrage welcher Pin genau ausgelöst hat.

Per Timer Pollen geht schon. Man muss wie oben gesagt nur den Zustand schnell genug auslesen. Mit der digitalWriteFast Library geht das in 2-3 Takten pro Pin.

Hallo Leute,

danke für eure Antworten! Jurs hat mir mit seinem super Beitrag die Augen etwas weiter geöffnet. Danke!

jurs: [...] Weil eine einzelne Sekunde aber nur 1000000 Mikrosekunden hat, Du aber für Deine lahme Programmierung viel mehr als 1200000 Mikrosekunden pro Sekunde haben müßtest, kann Dein Programm so vorne und hinten und mittendrin niemals funktionieren.

Wenn Dein Programm mit 100 kHz Ausleserate funktionieren soll, kannst Du das lahme "digitalRead()" komplett vergessen. Dafür brauchst Du schnelle "direkte Registerprogrammierung" für den Atmega Deiner Wahl. Im Fall des UNO wäre das der Atmega328.

Ohne direkte Registerprogrammierung beim Auslesen der Pinzustände wird das mit 100 kHz Ausleserate nix. Also mußt Du so wie Du den Timer1 bereits über die entsprechenden Timer-Register "direkt" programmierst, auch die entsprechenden PORT/PIN-Register direkt programmieren - und auf so komfortabel verwendbare, jedoch in der Ausführung extrem lahme - Befehle wie 'digitalRead()' in Deinem Programm komplett verzichten.

Dein einfaches Vorrechnen hat mir gut verdeutlicht, dass es so wohl nicht funktionieren kann. Super Erklärung! Erfreulich finde ich zumindest, dass es überhaupt funktionieren kann. Ich werde jetzt denke ich verschiedene Methoden ausprobieren. Sollte ich softwareseitig dann bei beispielsweise 50 kHz landen, dann habe ich halt nur bis zu einer verfahrgeschwindigkeit von 500 mm pro Sekunde zuverlässige Ergebnisse. So kann ich mich ein wenig herantasten. "Direkt" mit der Registerprogrammierung zu starten ist dann wohl doch zu heftig... ;)

@ Serenifly Danke für den Link, werde ich mir mal anschauen. Du meinst ich sollte die beiden Signale an die beiden hardwareseitigen Interrupt-Pins anschließen und damit in mein Programm eingreifen und nicht (wie im Code oben) die Zustände immer wieder abfragen. Bei 3 Takten pro Pin, also 6 Takten für beide Pins wären dann theoretisch (ohne zu Zählen, oder etwas anderes auszuführen) 2,6 MHz möglich. Mir ist klar, dass das völlig utopisch ist, ich schreibe das nur um mich zu vergewissern, dass ich deinen Gedanken richtig verstanden habe. Oder? :)

Ist es denn mit vertretbarem Aufwand und dem Arduino UNO überhaupt möglich mit ca. 100 kHz die Zustände von zwei Pins abzufragen, zu Zählen und die Richtung festzustellen und dann den Wert als Kommazahl in der Einheit mm auf einem LCD auszugeben? Oder sollte ich besser gleich zu leistungsstärkerer Hardware greifen?

Danke auch DrDiettrich für deine Hinweise!

Gruß Bastian

Du meinst ich sollte die beiden Signale an die beiden hardwareseitigen Interrupt-Pins anschließen und damit in mein Programm eingreifen und nicht (wie im Code oben) die Zustände immer wieder abfragen

Nein. Das Timer Prinzip passt schon. Das ist vor allem schön wenn man mehr als einen Drehgeber hat. Dann kann man in dem einen Interrupt mehrere Drehgeber pollen.

Du musst aber die Pin-Abfrage schneller machen. Also das lahme digitalRead() raus und statt dessen die Library und digitalReadFast() verwenden. Dann sparst du es dir direkt mit den Registern zu hantieren, aber der Code der im Hintergrund rauskommt ist praktisch identisch.

Bei 3 Takten pro Pin, also 6 Takten für beide Pins wären dann theoretisch (ohne zu Zählen, oder etwas anderes auszuführen) 2,6 MHz möglich

Weniger. Du hast ja noch den Overhead des Interrupts selbst (siehe jurs) und dann machst du neben dem Auslesen noch ein paar andere Operationen wie das Schieben und den Zugriff auf die Tabelle

Außerdem darfst du das auch nicht zu oft Pollen. Du willst ja sonst noch was mit dem Controller machen und nicht nur ständig im Interrupt hängen.

Ich würde sowas gar nicht direkt mit einem Arduino erledigen. Einfach einen Quadratur Decoder vor den Arduino packen und danach hast Du alle Zeit der Welt. Wenn es unbedingt in Software sein soll schaust Du hier: http://www.arduino.cc/playground/Main/RotaryEncoders

Hallo Leute!

@ Serenfly: Danke, ich habe jetzt denke ich verstanden, was Du meinst. Werde die Library mit dem digitalFastRead mal versuchen zu integrieren. Aber es kommen bestimmt noch einige Fragen auf. ;) Wie Du siehst fehlt mir leider noch das Grundverständnis für die internen Prozesse um sowas wie zum Beispiel den zeitlichen Bedarf einschätzen zu können. Blöd, dass mich das erste "richtige" Projekt direkt an die Geschwindigkeitsgrenzen der einfachen Arduino-Programmierung bringt... ;)

@ Udo Klein: Danke für Deine Antwort, Udo! Der von Dir angesprochene Quadratur Decoder macht aus meinen beiden Rechtecksignalen ein Step und ein Direction Signal. Sozusagen eine vor den Arduino geschaltete Flankenauswertung. Verstehe ich das richtig? Damit würde dann die am Eingangspin des Arduino erforderliche Abfragefrequenz auf 25 kHz sinken. Oder was genau meintest Du mit "alle Zeit der Welt"?

Gruß Bastian

Serenifly: CHANGE auch mit den richtigen Hardware Interrupts. PinChange Interrupts haben mehr Overhead als diese, da sich 8 Pins einen Interrupts-Vektor teilen. Man braucht also noch eine Abfrage welcher Pin genau ausgelöst hat.

Die Pin-Abfrage ist immer nötig, egal ob gepollt oder per interrupt. Ein Interrupt spart aber viele Abfragen, die beim Pollen auch dann durchlaufen werden müssen, wenn sich nichts geändert hat. Je nach Anforderung kann man man die Pins auf einen oder zwei Ports verteilen, mit entsprechend einem oder zwei Interrupt-Handlern.

Wer mag, der kann ja mal abzählen, wieviel Zeit mit einer solchen Abfrage ohne Interrupt bereits verdödelt wird, bis man weiß, ob sich was geändert hat: if (digitalRead(pinA)!=pinA_alt || digitalRead(pinB)!=pinB_alt) ...

BTW der Overhead ist für die externen Interrupts höher als für PCInt, weil diese zur Laufzeit geändert werden können.

Die Frequenz geht runter auf 50 kHz. D.h. man kann pro Interrupt 320 Takte verpulvern. Step kommt an den Interrupt und Direktion wird ausgewertet:

#include <util/atomic.h>

const uint8_t  dir_pin = ...

volatile int16_t count = 0;

void isr() {
    // Interruptroutine für step, durch geigneten Interrupt auslösen
    count += digitalRead(dir_pin)*2 - 1;
}

void loop() {
    int16_t l_count;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { l_count = count; }

    do something with l_count ...
}

Hallo Leute,

so, nachdem meine “digitalWriteFast” Library endlich funktioniert, habe ich das Ganze mal damit umgesetzt. Sieht dann so aus:

#include <digitalWriteFast.h>


// Code hauptsächlich von (C) 2014 Ralf Hübben - www.meinDUINO.de


// An die Pins 7 und 8 ist der Encoder angeschlossen
#define encoderA 7
#define encoderB 8

// Globale Variablen zur Auswertung in der
// Interrupt-Service-Routine (ISR)
volatile int8_t altAB = 0;
volatile int encoderWert = 0;


// jede Flanke wird ausgewertet
int8_t schrittTab[16] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; 

// jede 4. Flanke wird ausgewertet
//b[16] = {0,0,0,0,0,0,0,-1,0,0,0,0,0,1,0,0}; 

/*************************************************************
*
* Interrupt Service Routine
*
* Wird aufgerufen, wenn der entsprechende Interrupt
* ausgelöst wird
*
*************************************************************/
ISR(TIMER1_COMPA_vect) {
  altAB <<= 2;
  altAB &= B00001100;
  altAB |= (digitalReadFast(encoderA) << 1) | digitalReadFast(encoderB);
  encoderWert += schrittTab[altAB];
}

/*************************************************************
* 
* void setup()
*
* Wird einmal beim Programmstart ausgeführt
*
*************************************************************/
void setup() {
  pinMode(encoderA, INPUT);
  pinMode(encoderB, INPUT);
  
  noInterrupts(); // Jetzt keine Interrupts
  TIMSK1 |= (1<<OCIE1A);  // Timer 1 Output Compare A Match Interrupt Enable

  TCCR1A = 0; // "Normaler" Modus

  // WGM12: CTC-Modus einschalten (Clear Timer on Compare match) 
  //        Stimmen OCR1A und Timer überein, wird der Interrupt
  //        ausgelöst
  
  // Bit CS11<<1, Teiler ist 8
  TCCR1B = (1<<WGM12) | (1<<CS11);

  // Frequenz = 16.000.000 / 8 Teiler aus CSxx = 2.000.000 Hz
  // Überlauf bei 19, weil die Zählung bei 0 beginnt, dann wird ISR 2.000.000/20=100.000 Mal pro Sekunde aufgerufen
  OCR1A = 19; 
  
  interrupts(); // Interrupts wieder erlauben

  Serial.begin(115200);
}


/*************************************************************
* 
* Wird immer wieder durchlaufen
*
*************************************************************/
void loop() {


  while(true) {
  
    Serial.println(encoderWert);
    
    
    delay(200);
  }
}

Nun ja, leider nicht wesentlich schneller als mit den herkömmlichen Befehlen, ich vermute mal, dass woanders noch ein Flaschenhals existiert. Leider habe ich zu wenig Ahnung um diesen zu erkennen.

Ich hätte noch ein paar (Anfänger-)Fragen, seid bitte geduldig mit mir:

Meine Abfrage findet ja -zumindest die Timer betreffend, theoretisch- mit 100 kHz statt, der Hersteller gibt dann eine maximal Verfahrgeschwindigkeit ohne Messverluste von 1,2 m/s an. Davon bin ich mit 0,012 m/s (Faktor 100) ziemlich weit entfernt. Verfahre ich schneller, so komme ich nicht mehr auf den Wert “0” zurück, sprich es müssen Daten verloren gehen. Bedeutet das, dass mein Code viel zu lange zum Durchlaufen benötigt und ich in Wirklichkeit eher mit 1 kHz (besagter Faktor 100) abtaste?

Woher weiß ich denn, welche Operationen wie viele Takte in Anspruch nehmen? Gibt es irgendwo eine Tabelle, die ich mir mal anschauen könnte?

Gibt es überhaupt eine realistische Chance (ich denke den bisherigen Beiträgen nach eher nicht), dass ich mit meinem Arduino Uno und Standardcodes und -libraries:

  • zwei Pins mit -für mich ausreichenden- 25 kHz abfrage,
  • die Flanken zähle und entsprechend der Richtung aufsummiere,
  • das Ganze in Millimeter umrechne und
  • dieses Ergebnis mit zusätzlichem Text, alle 0,5s aktualisiert, auf einem Display ausgebe?

Falls 3) bejaht werden kann: Wo ist der Flaschenhals (oder sogar mehrere)?

Falls 3) verneint wird: Welche alternativen gibt es? Schnellerer Microcontroller? Registerprogrammierung? (irgendwie machen es die Hersteller ja auch…)

Welche Informationen bezüglich des Themas könnt ihr mir ans Herz legen? Ich habe inzwischen denke ich ziemlich viel zum Thema “rotary Encoder” durch, das Problem ist, dass es da eher so bis 1 kHz geht, sprich für meinen Fall untauglich ist.

Puh, das ist einiges…danke für eure Hilfe!

Gruß Bastian

Olomolo:
2)
Woher weiß ich denn, welche Operationen wie viele Takte in Anspruch nehmen? Gibt es irgendwo eine Tabelle, die ich mir mal anschauen könnte?

Den Assembler Code im Disassembler ansehen. Da wirst du aber nicht durchblicken

Registerprogrammierung?

Ist eine Option. Aber das sollte eigentlich die digitalWriteFast Bibliothek für dich machen. Wenn die Pins zur Compilezeit bekannt ist, springt da sowie ich es verstehe der gleiche Code raus.

Wenn du es per Hand probieren willst:
https://www.arduino.cc/en/Reference/PortManipulation
UNO Pinout:

Pin 7 ist Port D, Bit 7
Pin 8 ist Port B, Pin 0

Damit erhält man:

altAB |= ((PIND & _BV(PD7)) << 1) | (PINB & _BV(PB0));

Theoretisch. Keine Garantie dass es passt. Aber ein Bit liest man ein wenn man das PINx Register einliest und dann das gewünschte Bit mit UND maskiert. _BV() ist ein Makro für (1 << n). Der Compiler setzt dass dann schon in schnelle Befehle um.

Eine Optimierung die sich anbietet ist den Encoder auf zwei Pins zu legen die Bit 1 und Bit 0 entsprechen im Port Register entsprechen. Dann kannst du beide Pins auf einmal einlesen und muss nichts schieben. Das wären Pin 8 und Pin 9. Damit kann man das machen:

altAB |= PINB & 0x03;

Also einfach Port B einlesen und die oberen 6 Bits löschen. Das sollte in 2 Takten erledigt sein.

Du könntest die Encoder-Signale auf zwei bits eines Ports legen und diese dann beide in 1 Befehl auslesen, shiften, maskieren, mit vorigem Wert verknüpfen, zugehörigen schrittTab Wert holen ( -1, 0, +1 ) und den auf ein volatile int8_t encoderWert summieren. Dann brauchst du in loop keine Klimmzüge mit no'Interrupts/ATOMIC_BLOCK Das sollte in c praktisch genauso schnell sein wie in Assembler.

2) Du kannst dir das Disassembly der ISR ansehen. Die meisten Befehle brauchen nur 1 Takt. objdump -S MySketch.cpp.elf > disasm.txt

michael_x: Du könntest die Encoder-Signale auf zwei bits eines Ports legen und diese dann beide in 1 Befehl auslesen, shiften, maskieren

Das ist genau was ich am Ende beschrieben hatte

Wenn man da Bits 0 und 1 verwendet spart man sich auch das Schieben :)

Olomolo: 3) Gibt es überhaupt eine realistische Chance (ich denke den bisherigen Beiträgen nach eher nicht), dass ich mit meinem Arduino Uno und Standardcodes und -libraries: - zwei Pins mit -für mich ausreichenden- 25 kHz abfrage, - die Flanken zähle und entsprechend der Richtung aufsummiere, - das Ganze in Millimeter umrechne und - dieses Ergebnis mit zusätzlichem Text, alle 0,5s aktualisiert, auf einem Display ausgebe?

Ich habe Dir doch oben die Rechnung aufgemacht. Und Dir dabei vorgerechnet, dass es mit 100 kHz und dem lahmen digitalRead() nicht funktionieren kann.

Mit derselben Rechnung kannst Du Dir aber ausrechnen, dass es mit einer niedrigeren Geschwindigkeit von z.B. 32 kHz durchaus funktionieren kann. Ich rechne es Dir gerne nochmal mit 32000 Hz vor:

Also so wie Du programmierst, brauchst Du bei 32000 Hz bereits - 32000*4 Mikrosekunden Interrupt-Overhead = 128000 µs - 32000* 4 bis 5 Mikrosekunden zum Auslesen des einen Pins = 160000 µs - 32000* 4 bis 5 Mikrosekunden zum Auslesen des anderen Pins = 160000 µs = 448000 Mikrosekunden

Da würden Dir pro Sekunde noch 1000000-448000 = 552000 Mikrosekunden übrigbleiben, zum Zählen, Auswerten und Anzeigen auf LCD.

Und wenn Du statt des sehr langsamen digitalRead() ein fastDigitalRead() verwendest, das statt 4-5 nur eine Mikrosekunde braucht, hast Du noch mehr Zeit übrig.

Und wenn Du "richtig" programmierst, z.B. für den Drehgeber zwei nebeneinanderliegende Pins aus demselben Pinregister verwendest, kann es nochmal schneller werden.

Also was mit 100000 Interrupts pro Sekunde und digitalRead() nicht funktioniert, kann mit 32000 Interrupts pro Sekunde und direkter Registerprogrammierung kein Problem sein. Und wenn man es richtig macht und alle Takte des Controllers möglichst sinnvoll nutzt, geht wohl auch etwas mehr als 32000 Hz Ausleserate.

Die Alternativen für möglichst hohe Zählraten mit Drehgebern sind: - direkte Registerprogrammierung statt Nutzung von Komfortfunktionen wie digitalRead()/fastDigitalRead() - schnellerer Controller (und auch bei diesem dann wieder direkte Registerprogrammierung) - Verwendung spezieller "Quadrature Decoder" als Externe Hardware zum Zählen mit abfragbarem Zählerstand

Olomolo: 2) Woher weiß ich denn, welche Operationen wie viele Takte in Anspruch nehmen? Gibt es irgendwo eine Tabelle, die ich mir mal anschauen könnte?

Das mußt Du für beliebige Funktionen üblicherweise selbst ausmessen. Und dazu benötigst Du ein Oszilloskop. Im Datenblatt steht vielleicht drin, dass es mit der schnellsten Art, einen Pin HIGH oder LOW zu setzen zwei Takte des Controllers braucht. Das wären also bei 16 MHz 2/16000000s = 125 Nanosekunden.

Und um die Zeitdauer einer Funktion zu messen, würdest Du Dein Oszilloskop z.B. auf HiGH triggern und im Code folgendes schreiben (Pseudocode): - setze Pin HIGH - führe die zu messende Funktion aus - setzen Pin LOW

Auf dem Oszilloskop liest Du dann die breite des Zackens ab, wie lange der Pin HIGH war, und ziehst vom abgelesenen Wert die oben genannten 125 Nanosekunden ab, die das Setzen des Pins dauert. Und dann hast Du herausbekommen, wie lange die Funktion läuft.

Das ist genau was ich am Ende beschrieben hatte Wenn man da Bits 0 und 1 verwendet spart man sich auch das Schieben

Serenifly, du warst schneller und ausführlicher, einfach besser. 8) zufrieden ?

Da wirst du aber nicht durchblicken

Wenn man die c Quelle hat, ist das Disassembly jedenfalls interessant.

Serenifly: Aber das sollte eigentlich die digitalWriteFast Bibliothek für dich machen. Wenn die Pins zur Compilezeit bekannt ist, springt da sowie ich es verstehe der gleiche Code raus.

Wenn du es per Hand probieren willst

Ach, warum schwer, wenn es auch einfacher geht... ;-)

Serenifly: Eine Optimierung die sich anbietet ist den Encoder auf zwei Pins zu legen die Bit 1 und Bit 0 entsprechen im Port Register entsprechen. Dann kannst du beide Pins auf einmal einlesen und muss nichts schieben.

Ok, da verstehe ich jetzt erstmal ziemlich wenig. Mit "schieben" meinst du, dass ich in der ISR die Bits verschiebe, oder? Was bewirkt die von dir vorgeschlagene Änderung? Ich muss doch dann trotzdem beide Pins auswerten und letztendlich wieder "zusammenführen".

Gruß Bastian