Geschwindigkeitmessung per Interrupt: Geht das schneller?

Hallo allerseits,

zu dem Projekt aus meinem vorigen Topic noch eine Detailfrage:

Ich will die Zeit einer Umdrehung eines Fahrradrades (1 - 3x pro s) per Hallsensor messen, daran angepasst möchte ich LEDs per 595- Schieberegistern aufleuchten lassen.
Per pulseIn() die Umdrehungszeit zu ermitteln, geht bislang am besten, aber das hält nach einer Ausführung den Code auf, bis das nächste pulseIn() eingeht. Daher dachte ich, dass das per externem Interrupt genauer geht, da dieser doch nur wenige Takte brauchen sollte.
Tatsächlich hält dieser das Programm aber noch mehr auf, so dass es nur bei sehr langsamer Umdrehung überhaupt zum Aufleuchten der LEDs kommt.

Kann mir jemand sagen, woran das liegt? Vielleicht an der Art des Interrupts (ich hörte alternativ vom ISP bei ATMega µCs, doch der ist beim Arduino angebl. nicht belegt?!) oder an der Ausgabe per shiftOut()?

Wie kann ich den Ablauf, bspw. den Interrupt, schneller machen?

Gruß
Moritz

der ganze Code per Cloud

void setup() {
  attachInterrupt(0, interrupt, FALLING);                                   // wenn Hall- Sensor (active- low) an ISR0 ≙ Pin2 betätigt
}


void loop() {

  start = (zeit / 2);                                                           // delay() will Millisekunden
  gap = (zeit / 73);                                                            // delay() will Millisekunden

  if ((zeit > 0) && (currTime < timeout)) {                                     // auto- AUS- Funktion muss noch überarbeitet werden

    delay(start);                                                               // bisschen mehr, um von dem Auslösepunkt wegzukommen

    alpha('E');
    alpha('_');
    delay(gap);
  }
}


void interrupt() {
  currTime = millis();                                                  // aktueller Zeitwert wird genommen
  if (prevTime > 0) {                                                   // falls schon Zeit vergangen (≙ nicht erstes Mal)
    zeit = currTime - prevTime;                                       // Zeit seit letztem Auslösen ermitteln (≙ 1 Umdrehung), als "zeit" speichern
  }
  prevTime = currTime;                                                  // aktuellen Zeitwert als neuen Startwert merken
}

Hier reicht millis() in der loop aus. Jedoch sollten die delays dort verschwinden.

Alle globalen Variablen, welche von der Interruptroutine genutzt werden, müssen volatile sein. Das Auslesen dieser Variablen muss in einem ATOMIC Block erfolgen.

@sschultewolter: Das war's, man dankt.

Ich dachte, delays in der Loop wären nicht problematisch, weil das doch die ISR nicht tangierten sollte (solange darin kein delay() zu finden ist). Werden Interrupts nicht unabhängig vom Restcode ausgeführt?! Ich hatte das so verstanden:

as delay() and millis() both rely on interrupts, they will not work while an ISR is running.

gefunden bei About Interrupt Service Routines Aber das hat das Problem schon verbessert!

@combie: Der Einwand ist berechtigt. Allerdings HAT es so funktioniert, nur eben nicht so schnell wie gedacht. Wie ist das zu erklären?

Danke, Gruß Moritz

Ich dachte, delays in der Loop wären nicht problematisch, weil das doch die ISR nicht tangierten sollte (solange darin kein delay() zu finden ist). Werden Interrupts nicht unabhängig vom Restcode ausgeführt?!

Wenn ich dich richtig verstehe, hast du das schon richtig verstanden. ;)

Während eine ISR läuft, bleibt loop() natürlich kurzzeitig hängen, egal ob innerhalb oder ausserhalb einer delay() Funktion. Wenn die ISR einigermassen schnell genug abläuft, geht auch kein timer0 Interrupt verloren und millis() zählt richtig hoch.


Den Fehler durch nicht-atomic Zugriff auf Variable > 1byte wird man in der Regel selten bis nie erleben, deshalb ist er um so schlimmer (je nach Philosophie und Anwendungsfall)

Für sowas eine ISR, ist in meinen Augen mit Kanonen auf Spatzen schießen. Sicherlich, möglich ist sowas, aber dadurch lernt man es auch nicht, auf delay zu verzichten ;)

Moritz_uno: Wie kann ich den Ablauf, bspw. den Interrupt, schneller machen?

Den Interrupt kannst und brauchst Du nicht schneller machen. Der löst innerhalb von 4µs aus.

Prellt der Interrupt oder läuft er prellfrei? Hast Du das getestet?

Ob vielleicht Dein Sensor bei einem Durchlauf ganz schnell hintereinander mehrach ein Signal abgibt?

Wenn das der Fall sein sollte und Du eine Höchstdrehzahl für das Rad vorgeben kannst, z.B. dass es nie mehr als 10 Signale (=Umdrehungen?) pro Sekunde gibt, könntest Du das Signal per Software mit 100ms entprellen, um es sauber zu bekommen.

Ansonsten bekommst Du eine kleine Unsauberkeit davon, dass der millis() Zeitzähler etwas unrund läuft und sich um 1ms = 1000µs verticken kann. Das würde mit dem micros() Zeitzähler genauer werden.

Aber bei einem typischen Fahrradumfang von 2,20 m und zwei Umdrehungen pro Sekunde, würden 0,001s ja nur eine Ungenauigkeit von 4,40m/s*0,001s= 0,0044 m = 4,4 mm am Radumfang ausmachen.

Wenn Du die Auslösung des Magneten genauer als auf 0,001s bzw. 4,4mm am Randumfang hinbekommen möchtest, müßtest Du also micros() statt millis() verwenden. Aber das wird wohl kaum notwendig sein, denn bis auf eine tausendstel Sekunde genau wirst Du wohl sowieso kaum 2 Radumdrehungen nacheinander hinbekommen.

Und für ein verzögerungsfreies Multitasking müßtest Du natürlich auf die "delay()" Funktion verzichten, und Dein Programm so aufbauen, dass es ohne delay() blockierungsfrei abläuft.

Was soll das werden? Light-Painting mit LEDs am Fahrrad? Die LEDs am drehenden Rad montiert oder fest am Rahmen montiert und die Geschwindigkeitsmessung dient nur der Emittlung, in welchem Tempo die LEDs blinken sollen?

Ist die nur bedingt brauchbare Geschwindigkeitsmessung mit der nicht funktionierenden loop()-Synchronisation das einzige, was Du bisher hast?

Beschreib mal genauer, was das werden soll!

Die Verwendung von 'delay()' in Deinem Programm dürfte jedenfalls eher das Problem an sich in Deinem Programm darstellen, und daher keinesfalls als Lösung für irgendein Problem brauchbar sein.

@combie: Der Einwand ist berechtigt.

Ich weiß... :o

Allerdings HAT es so funktioniert, nur eben nicht so schnell wie gedacht. Wie ist das zu erklären?

Was? Tuts das so, oder tuts das nicht richtig? Es tuts nicht richtig!

Hallo miteinander, hallo jurs, danke für das Interesse!

Ja, Lightpainting am Fahrrad, die Monkeylights waren Vorbild. Und das POV- Prinzip hat mich interessiert.

Ich wäre aus den genannten Gründen mit der Genauigkeit von millis() ganz zufrieden, zumal der Zeitpunkt der Darstellung ja auch einmal pro Umdrehung neu ermittelt würde.

Der Hallsensor ist ein Digitaler, er liefert genau ein Signal pro Auslösung, ich hab’s getestet (zählen und anzeigen lassen).

Die delay()s zu entfernen, hat schon mal Veränderung erbracht.
Ich möchte aber mehrere verschiedene Zeichen nacheinander darstellen, den zeitl. Versatz habe ich bislang mit delay() gemacht.

Ich habe ein Beispiel zu Blink_ohne_delay umgestrickt, um zwei oder drei regelmäßig wiederkehrende Aktionen unabhängig voneinander auszuführen, das hat auch geklappt. Bei der Weiterentwicklung, diese Aktionen zwangsläufig nacheinander auszuführen, um damit die verscheidenen Zeichen meiner Animation darstellen zu können, gibt es Probleme.

Kann mir jemand sagen, wo hier der Fehler liegt? Das ist an sich doch simpel, es kann eigentlich nur funktionieren:
Per Vergleich mit einem selbst festgesetzten Zeitwert und den aktuellen millis() wird die erste Aktion (LED1- Status ändern) ausgeführt, dann ein Status- Bit als “Aktion1 schon ausgeführt” gesetzt.
In der nächsten loop kann diese Aktion dann ja nicht noch einmal erfolgen, da ja "schon ausgeführt). Ein wenig später (etwas größerer Zeitwert) wird die zweite Aktion (LED2- Status ändern) ausgeführt, dann ebenfalls ein Status- Bit als “Aktion2 schon ausgeführt” gesetzt. Genauso die dritte.
Erst, wenn alle ausgeführt, werden die Status-Bits entfernt.

So KANN doch eigentlich nur eine Aktion zur Zeit ausgeführt werden?! Die vorige nicht, weil “schon ausgeführt”, die nächste nicht, weil “Zeitwert noch nicht erreicht”?!
→ Es sollen drei LEDs der Reihe nach an-, dann der Reihe nach ausgehen.
Warum ist das nicht so?

Danke, Gruß

Hier der Problemcode:

#include <TimerOne.h>

/*
 * Timer-/ Rollover- Funktion für zeitverzögerte Aktionen
 *
 * Man braucht eine eigene Timer- Variable für jede Aufgabe! Die Intervalle können mehrfach benutzt werden.
 *
 * von www.baldengineer.com/arduino-how-do-you-reset-millis.html, 15.08.15
 */

const int ledPin1 = 10;
const int ledPin2 = 11;
const int ledPin3 = 12;

unsigned long currentTime;
unsigned long Time1;
unsigned long Time2;
unsigned long Time3;
const int intervall1 = 500;
const int intervall2 = 1000;
const int intervall3 = 1500;
bool led1;
bool led2;
bool led3;
bool state;

void setup() {
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
}

void loop() {
  currentTime = millis();

  //----------------------- LED 1---------------------------
  if (((currentTime - Time1) >= intervall1) && (led1 == HIGH)) {
    // It's time to do something!
    digitalWrite(ledPin1, state); // change LED- State of the LED on Pin 13
    // Use the snapshot to set track time until next event
    led1 = LOW;
    Time1 = currentTime;
  }

  //----------------------- LED 2---------------------------
  if (((currentTime - Time2) >= intervall2) && (led2 == HIGH)) {
    // It's time to do something!
    digitalWrite(ledPin2, state); // change LED- State of the LED on Pin 13
    // Use the snapshot to set track time until next event
    led2 = LOW;
    Time2 = currentTime;
  }

  //----------------------- LED 3---------------------------
  if (((currentTime - Time3) >= intervall3) && (led3 == HIGH)) {
    // It's time to do something!
    digitalWrite(ledPin3, state); // change LED- State of the LED on Pin 13
    // Use the snapshot to set track time until next event
    led3 = LOW;
    Time3 = currentTime;
  }
  if ((led1 == LOW && led2 == LOW) && (led3 == LOW)) {
    led1 = HIGH;
    led2 = HIGH;
    led3 = HIGH;
    state = !state;
  }
}

Moritz_uno: -> Es sollen drei LEDs der Reihe nach an-, dann der Reihe nach ausgehen.

OK, also (vorerst?) nur drei LEDs, synchronisiert auf den Sensor, so dass sich ein "stehendes Bild" am drehenden Rad ergibt.

Z.B. zeitlicher Ablauf so: - Magnetsensor wird erkannt, erste LED geht an - nach einer viertel Radumdrehung geht die zweite LED an - nach einer halben Radumdrehung geht die dritte LED an - nach dreiviertel Radumdrehung gehen alle drei LEDs aus Beispielsweise so?

Also eine viertel Umdrehung eine LED, eine viertel Umdrehung zwei LEDs, eine viertel Umdrehung drei LEDs, eine viertel Umdrehung alle LEDs aus, als "stehendes Bild" dargestellt?

Zeiten bzw. Drehwinkel der Schaltzeitpunkte sollen leicht anpassbar sein?

In Deinem Code aus Reply #8 fehlt völlig die Synchronisation auf den Magnetsensor, so dass sich kein "stehendes Bild" ergeben kann, sondern nur ein wildes Ein- und Ausschalten.

Ich lasse mir mal eine passende Datenstruktur und Programmlogik einfallen und melde mich dann mal mit einem Codevorschlag.

Hi!

Neinnein, es sind 16 LEDs, per 2x 595- Schieberegister angesteuert, den Hall- Takt nehme ich mit pulseIn (was aufhält) oder per Interrupt. Im allerersten Post dieses Threads ist der komplette Code zu finden, ich kann bislang Buchstaben darstellen, die als bytearray gespeichert und spaltenweise ausgegeben werden.

Der Dargestellte ist nur ein Beispielcode, an dem ich die zeitlich aufeinanderfolgende Abfolge von mehreren Aktionen (im Endeffekt dann die Abfolge der Anzeige meiner Buchstaben) ohne delay in den Griff kriegen will.

So, wie’s ist, führt das aktuell zu einem Wechselblinken der ersten beiden mit der letzten LED.
Das ist auch erklärbar durch die Abfrage auf “größer/gleich”, denn wenn Intervall1 < Intervall2 < Intervall3, ist natürlich Intervall3 “größer/gleich” Intervall1 und Intervall2. Aber warum gehen dann LED1 (zugehörig zu Intervall1) und LED2 (zugehörig zu Intervall2) gleichzeitig an, bzw. warum nicht auch mal alle gleichzeitig (weil die Abfrage á la “Ist schon Zeit größer/gleich der Vorgegebenen vergangen?” bei einem Zeitraum größer Intervall3 dann doch beim Vergleich mit allen Intervallen ‘wahr’ sein müsste)?!

Nur für die Akten:

habe den Fehler bei meinem Code nicht gefunden, aber einen anderen aufgetan, der in etwa so funktioniert. Vielleicht hilft’s ja jemandem mit einem ähnlichen Problem.

/*
 * Ramin Soleymani, https://www.rheinwerk-verlag.de/das-arduino-training_3742/?GPP=youtube, 26.08.2015
 */

int LEDPins[] = {2, 3, 4};
unsigned long letzerWechsel;
int zeitProLED = 200;
int aktuelleLED = 0;

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < 4; i++) {
    pinMode(LEDPins[i], OUTPUT);
  }
}

void loop() {
  unsigned long aktuelleZeit = millis();

  //----------------------- LED 1---------------------------
  if ((aktuelleZeit - letzerWechsel) > zeitProLED) {
    
    digitalWrite(LEDPins[aktuelleLED], LOW);
    aktuelleLED = (aktuelleLED + 1) % 6;
    digitalWrite(LEDPins[aktuelleLED], HIGH);

    letzerWechsel = aktuelleZeit;
  }  
}

Moritz_uno: Neinnein, es sind 16 LEDs, per 2x 595- Schieberegister angesteuert,

Also ein 16-LED Array. Und alles viel komplexer als die 3-Linien-Ansteuerung mit den 3 LEDs.

Moritz_uno: den Hall- Takt nehme ich mit pulseIn (was aufhält) oder per Interrupt.

Per Interrupt ist korrekt. PulseIn ist völlig unbrauchbar, weil blockierend.

Moritz_uno: Im allerersten Post dieses Threads ist der komplette Code zu finden, ich kann bislang Buchstaben darstellen, die als bytearray gespeichert und spaltenweise ausgegeben werden.

Den Code habe ich nicht gesehen. Nur irgendwie ein Anmeldefenster, dass ich mich bei einem Dropbox-Dienst im Internet anmelden soll.

Moritz_uno: Der Dargestellte ist nur ein Beispielcode, an dem ich die zeitlich aufeinanderfolgende Abfolge von mehreren Aktionen (im Endeffekt dann die Abfolge der Anzeige meiner Buchstaben) ohne delay in den Griff kriegen will.

Wenn Du Buchstaben oder ähnliche Pixel-Bilder darstellen möchtest, mußt Du im Prinzip einmal eine die Auflösung Deines Bildes festlegen und ein Bitmap-Array aufbauen mit dem Inhalt, der dargestellt werden soll. Sagen wir mal zwecks Einsparung von RAM-Speicher nimmst Du eine Bildauflösung über den Umfang von 256 Pixeln, dann ist Dein gesamtes Bild 16*256 Pixel groß = 4096 Pixel, wobei in einem Byte bei monochromer Darstellung 8 Pixel codiert sein können = 4096 Bytes / 8 = 512 Bytes. Das Bild würde noch locker in den RAM-Speicher eines UNO hineinpassen. Bei Farbbildern würde auf einem UNO aber nur noch die Vorabspeicherung im PROGMEM-Flashspeicher in Frage kommen.

Beim passieren des Sensors rechnest Du dann aus, wie lange jedes Pixel eingeschaltet sein muss. Sagen wir mal, das Rad rotiert aktuell in 500 Millisekunden= 500000 Mikrosekunden, wenn es den Sensor auslöst. Dann ist die Pixeldauer 500000/256 = 1953.125, abgerundet auf den nächsten durch 4 teilbaren Wert (oder niedriger) beispielsweise 1952µs pro Pixel.

Und jetzt brauchst Du nur noch eine delay-freie Routine, nur mit Verwendung von micros(), die ab der Sensorauslösung alle 1952µs eine Spalte Deines Bildes auf den LEDs schaltet. Beim Schalten mit digitalWrite() dauert jeder Schaltvorgang 4µs, so dass 16 LEDs in 16*4= 64µs geschaltet werden. Das würde bedeuten, dass senkrechte Linien in Deinem Bild nicht komplett senkrecht dargestellt werden, sondern um 64/1952=0,03 = ca. 3% einer Pixelbreite gekippt sind. Aber das dürfte in der Praxis wohl kaum groß auffallen.

Mit Deinen Schieberegistern müßtest Du selbst mal ausrechnen, wie lange Du benötigst, um alle 16-LEDs anzusteuern.

Und dann startet in der loop mit jeder Sensorauslösung ein neuer Durchlauf: Pixelbreite in Mikrosekunden ausrechnen, und dann alle x Mikrosekunden eine Spalte des darzustellenden Bildes auf den LEDs ausgeben.

Wenn das Rad dabei langsamer wird, ist am Ende der Bilddarstellung stets ein toter Bereich zu sehen, in dem die LEDs entweder alle AUS oder EIN geschaltet sind. Wenn das Rad schneller wird und der Sensor wieder auslöst, bevor das Bild komplett dargestellt ist, fehlt am Ende etwas vom Bild und die neue Bildausgabe startet noch bevor die alte abgeschlossen ist.

Hast Du Dir schon überlegt, ob Du die darzustellenden Muster, Bilder, Texte komplett "on the fly" im Arduino berechnen möchtest? Oder ob Du die Bilder mit einem Pixel-Malprogramm (z.B. Windows "Paint") erstellen, und die Bilddaten dann als Konstanten im Programm unterbringen möchtest?

Für einen ersten Test würde ich vielleicht etwas einfaches mit einem simplen berechneten Muster machen.

Hier habe ich mal einen Code für 16 LEDs gemacht, den ich selbst allerdings überhaupt nicht testen kann.

Der Sensor ist dabei an Pin-2, die 16 LEDs an Pin-3 bis Pin-A4 angeschlossen. Wenn Du irgendwas mit Schieberegistern machst, müßtest Du die Funktion showLEDs() entsprechend anpassen.

Das anzuzeigende monochrome Muster wird in 256 uint16_t Variablen gespeichert, repräsentiert also ein Bitmap von 16*256 Pixeln Größe.

Bei zu langsamer Raddrehzahl (weniger als 1 Umdrehung in 2 Sekunden) wird die LED-Ausgabe abgeschaltet:

#define SENSOR 2
#define SENSOR_INTERRUPT 0

byte ledPins[]={3,4,5,6,7,8,9,10,11,12,13,A0,A1, A2, A3, A4}; // 16 LEDs
  
uint16_t pixelBild[256]; // Ein Bild aus 16*256 Pixeln (monochrom)

void initBild()
{ // Testmuster für die Ausgabe erzeugen ("Gittermuster")
  int i,j;
  Serial.println("\nCreate Picture:");
  memset(pixelBild,0,sizeof(pixelBild)); // Bild löschen
  for (j=0; j<16; j++)
  {
    for (i=0; i<256; i++)
    {
      if ((i%10==0) || (j%4==0)) bitSet(pixelBild[i],j);
    } 
  }
  // Testmuster auf Serial ausgeben
  for (j=0; j<16; j++)
  {
    for (i=0; i<256; i++)
    {
      Serial.print(bitRead(pixelBild[i],j));
    } 
    Serial.println();
  }
  Serial.println();
}

void showLEDs(uint16_t value)
{ // Funktion zum Setzen der 16 LEDs auf ihren anzuzeigenden Wert
  for (int i=0; i<16; i++)
    digitalWrite(ledPins[i], bitRead(value,i));
}
  
  
void setup() {
  Serial.begin(9600);
  pinMode(SENSOR,INPUT_PULLUP); // oder INPUT?
  for (int i=0; i<16) pinMode(ledPins[i],OUTPUT); // LED pins auf OUTPUT setzen
  initBild(); // Testmuster erzeugen
  attachInterrupt(SENSOR_INTERRUPT, interrupt, FALLING);                                   // wenn Hall- Sensor (active- low) an ISR0 ≙ Pin2 betätigt
}


volatile unsigned long lastInterruptTime_ISR;
unsigned long lastTurningTime, lastTurningStart, bitTime;
byte bitCount;
boolean showBits;
unsigned long bitStartTime;

void loop() {
  while(1) // Beschleunigung der loop-Funktion, die dadurch nie verlassen wird
  {
    if (lastInterruptTime_ISR != lastTurningStart) // Beginn einer neuen Umdrehung festgestellt
    {
      noInterrupts(); // Bei gesperrten Interrupts auslesen
      lastTurningTime= lastInterruptTime_ISR- lastTurningStart;
      interrupts();
      lastTurningStart+= lastTurningTime;
      if (lastTurningTime<2000) // Rad dreht schneller als einmal in 2000ms
      {
        // Zeit für die Darstellung eines Bits errechnen als Vielfaches von 4µs
        bitTime=4*(lastTurningTime*1000/256/4); // in µs, abgerundet auf einen durch 4µs teilbaren Wert
        bitCount=0;
        showBits=true; // Ausgabe einschalten
        bitStartTime=micros();
        showLEDs(pixelBild[bitCount]); // erste Pixelspalte einschalten
      }
      else // bei zu langsamer Radumdrehung alles abschalten
      {
        showBits=false; // Ausgabe abschalten  
        showLEDs(0); // alle LEDs aus
      }
    }
    if (showBits && micros()-bitStartTime>=bitTime)
    {
      bitStartTime+= bitTime;
      if (bitCount<255) // nächste Pixelspalte anzeigen
      {
        bitCount++;
        showLEDs(pixelBild[bitCount]);
      }
      else 
      {
        showBits=false; // Ausgabe abschalten  
        showLEDs(0);
      }  
    }
  }
}


void interrupt() {
  lastInterruptTime_ISR=millis();
}

Das am Anfang des Programms erzeugte Testmuster ist sowas wie ein Gittermuster, zur Verdeutlichung lasse ich das erstellte Muster einmal auf Serial ausgeben.

Vielleicht kannst Du darauf aufbauend ja etwas basteln.

Nachtrag:
Ich habe mal gerade wegen der gewählten Auflösung von 256 Pixeln auf dem Radumfang gerechnet, das ist wohl deutlich zu hoch gegriffen und sollte niedriger gewählt werden. Vielleicht 150. Oder maximal 180. Oder je nach Abstand der LEDs so, dass sich als Auflösung in Radumfangsrichtung ungefähr der Abstand zweier LEDs als Pixelbreite ergibt. Dazu müßte man aber wissen, welchen Abstand die LEDs zueinander haben und welchen Abstand die mittlere LED von der Radachse hat, um eine brauchbare Auflösung für das darzustellende Muster auszurechnen, so dass dann beispielsweise Buchstaben als bitmapped Font weitgehend verzerrungsfrei dargestellt werden können, ohne große Umrechnung.