Drehzahlmesser am Auto

Guten Abend in die Runde. Ich habe mir einen Drehzalmesser für meinen alten Fiat Ducato gebaut. Auslesung der Impulse erfolgt über den originalen Kurbelwellensensor der mit dem Arduino Nano verbunden ist und der dann die Drehzahl auf einen kleinen Display anzeigt. Der entsprechende Programmcode ist unten angehangen. Das funktioniert auch soweit ganz toll. Nur halt nicht im Drehzahlbereich 850 (Leerlauf) bis etwa 1200 U/min. Also wenn ich im Standgas bin zeit er 850 an und wenn ich langsam Gas gebe zeigt das Display erst ab etwa 1200 U/min auch die Drehzahl an. Über 1200 funktioniert alles wie es soll. Vielleicht hat jemand ne Idee woran es liegt?

unsigned long myTimeDZMImpuls=0;  
unsigned long myTimeZeitIm=0;  
double Drehzahl=0;
int impulszz=0;
int zeitimpuls=0;


void setup()
{
pinMode(3, INPUT); 
attachInterrupt(digitalPinToInterrupt(3), ImpulsLesen, FALLING);
]

void ImpulsLesen(){

  if(micros()-myTimeDZMImpuls>2){
impulszz=impulszz+1;
myTimeDZMImpuls = micros();

  }

void Loop()
{
if (impulszz>1610)
{ 
zeitimpuls=millis()-myTimeZeitIm; 
        
 Drehzahl=(60000/zeitimpuls);
Drehzahl=Drehzahl*10;
impulszz=0;
myTimeZeitIm=millis();
}
}

AVR?

Dann: avr-libc: <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks

Ich sehe keine Ausgaben!

Was ist dieses für eine Magie?

if (impulszz>1610)

Moin @ardi902010 ,

anscheinend ist der von Dir gepostete Code nicht komplett. Mindestens der Anteil zur Ausgabe auf dem Display fehlt. Dort könnte durchaus auch die Ursache zu finden sein...

Ansonsten hat @combie bereits auf kritische Schwachstellen im veröffentlichten Sketch hingewiesen.

ec2021

Ich habe den Code auf das nötigste heruntergebrochen hier. Im Display wird dann nur die Variable "Drehzahl" angezeigt.

Der Kurbelwellensensor generiert 161 Impulse pro Umdrehung (161 Zähne am Starterzahnkranz). Es wird also gewartet bis 10 Umdrehungen der Kurbelwelle absolviert sind und dann wird anhand der benötigten Zeit dafür auf die Umdrehungen pro Minute hochgerechnet.

Schon verstanden ...

Es wäre allerdings nicht der erste Sketch, bei dem die Ursache für Probleme im nicht veröffentlichten Teil des Codes zu finden ist. :slight_smile:

Auf jeden Fall hilft das komplette Posten, diese Möglichkeit auszuschließen.

Die im Interrupt und in der loop() verwendete Variable sollte als volatile deklariert werden, um Fehler durch Compiler-Optimierungen (Halten des letzten Wertes in einem Register) zu vermeiden. Zusätzlich besteht die Gefahr, dass der Interrupt gerade beim Auswerten oder Ändern der Variablen in der loop() zuschlägt. Um das zu Vermeiden gibt es die bereits oben verlinkten Mechanismen.

Man könnte das Messverfahren ggf. auch von fixen 10 Umdrehungen auf eine vorgegebene Messzeit umstellen. Also die Anzahl der Impulse z.B. alle 100 ms auswerten.

Bei max. 3000 U/min wären das 50 U/s = 8050 Impulse/s. Damit also 805 pro 0.1 s. Im Leerlauf 228 in 0.1s.

Du könntest erstmal prüfen, ob die Rohdaten so stimmen.

volatile

Ich setze schon lange, in solchen Fällen, kein volatile mehr ein.
Weil es eben die Optimierung unterbindet.
Ein Stock in die Speichen des Optimizers.

Die oben genannten Macros setzen "Memory Barriers".
Soweit mir bekannt, eine "Full Barrier".

Das habe ich lange getestet, den generierten ASM Code untersucht.
Die Empfehlung solche Variablen volatile zu machen scheint doch eher historisch oder übervorsichtig zu sein.

Teste das doch mal!

Übrigens:
volatile ist beim AVR-Gcc weiterhin nötig, wenn auf Hardware Register zugegriffen wird, denn die können sich wirklich außerhalb des Programms ändern, was sich auch nicht durch Interrupt sperren verhindern lässt.

Danke für die Auffrischung :wink:

Habe zu dem Thema aktuell noch mal recherchiert, hier die Ergebnisse

Volatile und ATOMIC_BLOCK

Deine Erkenntnisse bzgl. des Einsatzes der avr-lib werden hier bestätigt und die Zusammenhänge zwischen dem ATOMIC_BLOCK-Makro und der memory barrier erläutert:

https://www.mikrocontroller.net/topic/570524

Die memory-Barriere wird bei der AVR Library sowohl in dem Makro sei() wie auch bei cli() eingesetzt:

https://github.com/avrdudes/avr-libc/blob/main/include/avr/interrupt.h

Die Verwendung von sei() und cli() im atomic.h ruft damit automatisch die memory barrier Funktionen auf:

https://github.com/avrdudes/avr-libc/blob/main/include/util/atomic.h

Die Entwickler der atomic.h haben die volatile Deklaration in ihrem Beispiel dann allerdings inkonsequent beibehalten; siehe atomic.h Zeile 154 (Stand der letzten Änderung vom 29. März 2025)

Unabhängig von der derzeit unsicheren Umsetzung bzgl. der Variablen impulszz kann ich im obigen Code keine unmittelbare Ursache erkennen, die erklären würde, warum nun gerade Drehzahlwerte zwischen 850 und 1200 nicht ermittelt werden sollten.

Beim Reduzieren Deines Sketches haben sich ein paar Fehler eingeschlichen:

  • setup() { ] endet mit einer eckigen anstatt einer geschweiften Klammer
  • ImpulsLesen() -> hier fehlt die abschließende geschweifte Klammer
  • Loop() sollte sicher loop() heißen

Spricht etwas dagegen, dass wir mal einen Blick auf den kompletten Code werfen dürfen? Manchmal verstecken sich Fehlerursachen hinter scheinbar unverdächtigen Code-Anteilen ...

Ich danke dir für diese Untersuchung.

Ach ja, die Untersuchung des Programms habe ich abgebrochen, als ich bemerken musste, dass das untestbarer Code ist.

Zudem hätte ich sicherlich Timer1 im ZählerMode verwendet und so wohl ganz auf die ISR verzichtet. Würde also auch aus dem Grund (für mich) keinen Sinn machen.

mit diesen Sketch bekomm ich auch (handgedrückte) kleinere Werte z.B. 60, 100, 110:

unsigned long myTimeDZMImpuls = 0;
unsigned long myTimeZeitIm = 0;
double Drehzahl = 0;
unsigned int impulszz = 0;
unsigned int zeitimpuls = 0;

void ImpulsLesen() {
  if (micros() - myTimeDZMImpuls > 2) {
    impulszz = impulszz + 1;
    myTimeDZMImpuls = micros();
  }
}

void setup() {
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(3), ImpulsLesen, FALLING);
  Serial.begin(115200);
}

void loop() {
  if (impulszz > 1610) {
    zeitimpuls = millis() - myTimeZeitIm;
    Drehzahl = (60000 / zeitimpuls);
    Drehzahl = Drehzahl * 10;
    Serial.println(Drehzahl);
    impulszz = 0;
    myTimeZeitIm = millis();
  }
}

Hier mal ein Sketch, der die erforderlichen Maßnahmen für im Interrupt veränderte Variable berücksichtigt und die Drehzahlpulse per Timer simuliert, so dass man den Code auch ohne die Hardware testen kann:

Siehe auch hier: https://wokwi.com/projects/437089934882609153

/*
  Forum: https://forum.arduino.cc/t/drehzahlmesser-am-auto/1396866/8
  Wokwi: https://wokwi.com/projects/437089934882609153

  Version mit ATOMIC_BLOCK und Simulation der Drehzahlpulse per Timer
  auf Basis des Originalsketches

  2025/07/22
  ec2021

*/

#include <util/atomic.h>

constexpr byte inPin {3};
constexpr unsigned long pulseProRotation {161};
unsigned long letzterPuls = 0;
unsigned long letzteBerechnung = 0;
uint16_t Drehzahl = 0;
uint16_t impulsZaehler = 0;
unsigned long zeitFuerZehnRotationen = 0;
boolean zehnRotationen = false;


void setup() {
  Serial.begin(115200);
  pinMode(inPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(inPin), ImpulsLesen, FALLING);
  starteSimulation(990);
}

void ImpulsLesen() {
  if (micros() - letzterPuls > 2) {
    letzterPuls = micros();
    impulsZaehler++;
  }
}

void drehzahlMessung() {
  ATOMIC_BLOCK(ATOMIC_FORCEON)
  {
    if (impulsZaehler >= pulseProRotation * 10) {
      zehnRotationen = true;
      impulsZaehler = 0;
    }
  }
  if (zehnRotationen) {
    zeitFuerZehnRotationen = millis() - letzteBerechnung;
    letzteBerechnung = millis();
    zehnRotationen = false;
    Drehzahl = round(60000.0 / zeitFuerZehnRotationen) * 10;
    Serial.println(Drehzahl);
  }
}

void loop() {
  drehzahlMessung();
}

/*************************************************************************************/
/* Dieser Teil ist für das Simulieren der Pulse für die Drehzahlmessung erforderlich */
#include <TimerOne.h>
constexpr byte outPin {4};
unsigned long interval;
float zielDrehzahl = 1260;
byte togglePin = 0;

void starteSimulation(float ziel) {
  pinMode(outPin, OUTPUT);
  togglePin = 1 << outPin;
  zielDrehzahl = ziel;
  interval = round(1000000.0 / ((zielDrehzahl / 60.0) * pulseProRotation));
  Serial.println(interval);
  Timer1.initialize(interval);
  Timer1.attachInterrupt(timer_isr);
}

void timer_isr() {
  PIND = togglePin;
  delayMicroseconds(3);
  PIND = togglePin;
}

/*************************************************************************************/

Die Berechnung der Drehzahl auf diese Weise

Drehzahl = round(60000.0 / zeitFuerZehnRotationen) * 10;

sorgt dafür, dass sie auf Zehnerwerte gerundet wird.

Die Variablenbezeichnungen habe ich ein wenig angepasst, um die Nachvollziehbarkeit (hoffentlich) zu erleichtern.

Bei der Funktion starteSimulation() im setup() kann man die zu simulierende Drehzahl als Parameter einsetzen; kommentiert man sie aus, sollte der Sketch auch in Deinem Umfeld funktionieren. Die Ausgabe erfolgt allerdings momentan per serieller Schnittstelle.

Viel Erfolg!
ec2021

P.S.: Da wie oben angegeben ATOMIC_BLOCK die Deklaration als volatile überflüssig macht. habe ich darauf verzichtet.

Und hier Deine Version inkl. der Drehzahlsimulation zum Testen auf Wokwi:

https://wokwi.com/projects/437099518400803841

Sketch
/*
  Forum: https://forum.arduino.cc/t/drehzahlmesser-am-auto/1396866/8
  Wokwi: https://wokwi.com/projects/437099518400803841

  Version auf Basis des Originalsketches zzgl.
  -  der Simulation der Drehzahlpulse per Timer
  -  der seriellen Ausgabe der Drehzahl


  2025/07/22
  ec2021

*/

unsigned long myTimeDZMImpuls = 0;
unsigned long myTimeZeitIm = 0;
double Drehzahl = 0;
int impulszz = 0;
int zeitimpuls = 0;


void setup()
{
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(3), ImpulsLesen, FALLING);
  starteSimulation(1000);
}

void ImpulsLesen() {
  if (micros() - myTimeDZMImpuls > 2) {
    impulszz = impulszz + 1;
    myTimeDZMImpuls = micros();
  }
}

void loop()
{
  if (impulszz > 1610)
  {
    zeitimpuls = millis() - myTimeZeitIm;

    Drehzahl = (60000 / zeitimpuls);
    Drehzahl = Drehzahl * 10;
    impulszz = 0;
    myTimeZeitIm = millis();
    Serial.println(Drehzahl);
  }
}

/*************************************************************************************/
/* Dieser Teil ist für das Simulieren der Pulse für die Drehzahlmessung erforderlich */
#include <TimerOne.h>
constexpr byte outPin {4};
constexpr unsigned long pulseProRotation {161};
unsigned long interval;
float zielDrehzahl = 1260;
byte togglePin = 0;

void starteSimulation(float ziel) {
  Serial.begin(115200);
  pinMode(outPin, OUTPUT);
  togglePin = 1 << outPin;
  zielDrehzahl = ziel;
  interval = round(1000000.0 / ((zielDrehzahl / 60.0) * pulseProRotation));
  Serial.println(interval);
  Timer1.initialize(interval);
  Timer1.attachInterrupt(timer_isr);
}

void timer_isr() {
  PIND = togglePin;
  delayMicroseconds(3);
  PIND = togglePin;
}

/*************************************************************************************/


Eine Funktion sollte nur das machen, was ihre Aufgabe ist.
Und, wenn möglich, keine globalen Variablen verwenden.

Die Serielle Ausgabe - oder was sonst auch immer - gehört in die Testumgebung.

Vorschlag:

bool drehzahlMessung(uint16_t& ergebnis) {
// liefert true, falls neues Ergebnis vorliegt
  ATOMIC_BLOCK(ATOMIC_FORCEON)
  {
    if (impulsZaehler >= pulseProRotation * 10) {
      zehnRotationen = true;
      impulsZaehler = 0;
    }
  }
  if (zehnRotationen) {
    zeitFuerZehnRotationen = millis() - letzteBerechnung;
    letzteBerechnung = millis();
    zehnRotationen = false;
    ergebnis = round(60000.0 / zeitFuerZehnRotationen) * 10;
    return true;
  }
  return false; // kein neuer Wert
}

void loop() {
  if (drehzahlMessung(Drehzahl)) Serial.println(Drehzahl);
}

Drehzahl muss keine globale Variable sein, wenn sie (per Referenz) übergeben wird.

Sehr schön, aber das Problem des TO liegt tatsächlich wohl an ganz anderer Stelle.

Da die serielle Schnittstelle in der Originalanwendung sowieso raus fliegen wird und der Sketch außerhalb der Meßroutine aus Post 1 unbekannt ist, halte ich weitere Detailoptimierungen derzeit für begrenzt hilfreich. Das kann ja alles noch kommen, wenn die Ursache für das geschilderte Problem gefunden und behoben wurde ...

Die Wokwi-Versionen zeigen jedenfalls, dass es in der Messroutine keine Begrenzungen für Werte zwischen 850 und 1200 U/min gibt.

:wink:

Das tritt übrigens nur beim Gas geben auf. Wenn man Gas weg nimmt wird die Drehzahl auch zwischen 850 und 1200 angezeigt. Ich werde die Vorschläge hier mal im Code umsetzen vielleicht bringt es etwas.

Das klingt ja irgendwie nach einem Schwellwert, der erst einmal überschritten werden muss, damit wieder Anzeigen erscheinen ... ?!?

Ist das Verhalten so:

  • START
  • Leerlauf-Anzeige mit 850 U/min
  • Anzeige bei Drehzahlerhöhung erst ab 1200 U/min
  • Danach sowohl aufwärts wie auch abwärts bis Leerlauf
  • Ab 850 U/min --> weiter mit START

?

Ja genau so. Die angezeigte Drehzahl sollte auch stimmen wenn ich sie mit dem Bericht der Hauptuntersuchung abgleiche: Leerlauf 862 (HU Bericht) und 854 (Anzeige), Max Drehzahl 4892 (HU Bericht) und 4908 (Anzeige).

Ich habe unten mal den Testsketch angehangen. Der bringt genau beschriebenes Verhalten. Der Testsketch ist aus dem eigentlichen Hauptsketch herauskopiert, das Ergebnis ist aber das selber. Der Hauptsketch ist aber noch wesentlich umfangreicher da dort noch Wegfahrperre, Scheibenwischerintervall und diverse Funktionen für das Wohnmobil einprorammiert sind. Ich glaube nicht das hier einer durchsieht wenn ich das hier poste. Aber der Testsketch produziert ja das selbe Ergebnis.

#include "MFRC522.h"
#include <Arduino.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif





unsigned long myTimeDZM=0;  //Zeit Drehzahlmesser
int timerDZM=500; //Zeit für Drehzahlmesser 
unsigned long myTimeDZMImpuls=0;  //Zeit Drehzahlmesser
unsigned long myTimeZeitIm=0;  //Zeit für Impuls DZM

//Pins
#define pinImpuls 3 // Pin für Drehzahlimpuls


//OLED Display
#define pinOSDA 5  
#define pinOSLK 6 
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ pinOSLK, /* data=*/ pinOSDA, /* reset=*/ U8X8_PIN_NONE);   // Adafruit Feather M0 Basic Proto + FeatherWing OLED

// Drehzahlmesser
double Drehzahl=0;
int impulszz=0;
int zeitimpuls=0;



  
void setup()
{
//Pin In Output
pinMode(pinImpuls, INPUT); //Pin Impuls
attachInterrupt(digitalPinToInterrupt(pinImpuls), ImpulsLesen, FALLING);

  SPI.begin();
  u8g2.begin(); //OLED Display
  
}


void loop()
{


if (impulszz>1610){ //Führe aus wenn x Impulse gemessen wurden

zeitimpuls=millis()-myTimeZeitIm; 
        
 Drehzahl=(60000/zeitimpuls);
Drehzahl=Drehzahl*10;
impulszz=0;
myTimeZeitIm=millis();
}
    
   


    if((millis()-myTimeDZM)>timerDZM){
 
u8g2.clearBuffer();
//u8g2.clearDisplay();
// u8g2.setFont(u8g2_font_9x18_mn);
//u8g2.setFont(u8g2_font_chargen_92_mr);
u8g2.setFont(u8g2_font_fub30_tr);
 u8g2.setFontRefHeightExtendedText();
 u8g2.setDrawColor(1);
 u8g2.setFontPosTop();
 u8g2.setFontDirection(0);
 u8g2.setCursor(20,1); 
u8g2.print(Drehzahl,0);
u8g2.sendBuffer();


/*
 u8g2.setDrawColor(1);
 u8g2.drawBox(0, 15, (impuls*128)/6000, 17);
u8g2.setDrawColor(0);
u8g2.drawBox((impuls*128)/6000, 15, 129-(impuls*128)/6000, 17);
*/
/*
u8g2.clearBuffer();
u8g2.clearDisplay();
u8g2.setFont(u8g2_font_9x18_tf);
 u8g2.setFontRefHeightExtendedText();
 u8g2.setDrawColor(1);
 u8g2.setFontPosTop();
 u8g2.setFontDirection(0);
 u8g2.setCursor(5,20); // set cursor position lcd
 //u8g2.print(zeitimpuls);
 u8g2.print(impuls);
*/
//u8g2.sendBuffer();
              myTimeDZM=millis();
      }
 } 

 

 



void ImpulsLesen(){
        
  if(micros()-myTimeDZMImpuls>2){
 impulszz=impulszz+1;            
myTimeDZMImpuls = micros();
  }
}

Oha, das ist insofern schon mal interessant, weil die Anzeigeroutine


  if (millis() - myTimeDZM > timerDZM) {
    unsigned long start = millis();

    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_fub30_tr);
    u8g2.setFontRefHeightExtendedText();
    u8g2.setDrawColor(1);
    u8g2.setFontPosTop();
    u8g2.setFontDirection(0);
    u8g2.setCursor(20, 1);
    u8g2.print(Drehzahl, 0);
    u8g2.sendBuffer();

    myTimeDZM = millis();
    Serial.println(myTimeDZM - start);
  }

den Controller schon ca. eine Drittelsekunde in Beschlag nimmt.

In dieser Zeit einlaufende Pulse, die dazu führen, dass impulszz deutlich größer wird als 1610, werden bei der Abfrage "if (impulszz > 1610)" und der folgenden Umrechnung ignoriert. Es wird so gearbeitet, als wenn jeder Wert über 1610 genau 10 Umdrehungen entspricht.

Das kann man hier bei Wokwi sehr gut sehen:
https://wokwi.com/projects/437119968369247233

Ausgabe bei simulierten 850 U/min

Start
U8G2 [ms] 325
Impulse 1611
U8G2 [ms] 331
Impulse 1611
U8G2 [ms] 331
Impulse 1611
U8G2 [ms] 331
Impulse 1611
U8G2 [ms] 331
Impulse 1898
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 329
Impulse 1896
U8G2 [ms] 328
Impulse 1894
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 329
Impulse 1896
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 329
Impulse 1895
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 329
Impulse 1895
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1896
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 329
Impulse 1896
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 329
Impulse 1895
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1894
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 329
Impulse 1896
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 329
Impulse 1895
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 329
Impulse 1896
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 329
Impulse 1895
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1893
U8G2 [ms] 328
Impulse 1893
U8G2 [ms] 329
Impulse 1894
U8G2 [ms] 329
Impulse 1896
U8G2 [ms] 329

wobei sich hier noch zugunsten der Daten auswirkt, dass die Zeit myTimeDZM erst nach dem Abarbeiten der Anzeige gesetzt wird. Das führt dazu, dass bei höheren Drehzahlen zwischen den Anzeigen der Schwellwert zweimal überschritten wird. Hier für simulierte 1250 U/min, man beachte die letzte Impulsanzahl vor der Anzeige U8G2:

Impulse 2419
Impulse 1611
U8G2 [ms] 340
Impulse 1611
U8G2 [ms] 340
Impulse 2421
Impulse 1611
U8G2 [ms] 340
Impulse 1536
U8G2 [ms] 339
Impulse 2490
Impulse 1611
U8G2 [ms] 340
Impulse 1611

Ich habe Deinen Sketch jetzt im Wesentlichen wie folgt angepasst:

  • Verwendung des ATOMIC_BLOCK-Makros
  • Berechnung der Drehzahl und Anzeige in der selben Funktion
  • Ermittlung der tatsächlich vergangenen Zeit nach dem letztem Rücksetzens des Zählers.
  • Berechnung der Drehzahl aus der Anzahl der Pulse, die in diesem so ermittelten Zeitraum eingegangen sind
  • Berechnung der Drehzahl weitgehend mit float-Werten, um Rundungsfehler beim Dividieren zu minimieren
  • Ein delay() in der loop(), um die zeitliche Auswirkung weiterer Funktionen zu simulieren.
Sketch
/*
   Forum: https://forum.arduino.cc/t/drehzahlmesser-am-auto/1396866
   Wokwi: https://wokwi.com/projects/437123436443317249

  2025/07/22
  ec2021

*/

//#include "MFRC522.h"
#include <Arduino.h>
#include <U8g2lib.h>
#include <util/atomic.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif





unsigned long myTimeDZM = 0; //Zeit Drehzahlmesser
int timerDZM = 500; //Zeit für Drehzahlmesser
unsigned long myTimeDZMImpuls = 0; //Zeit Drehzahlmesser
unsigned long myTimeZeitIm = 0; //Zeit für Impuls DZM

//Pins
#define pinImpuls 3 // Pin für Drehzahlimpuls


//OLED Display
#define pinOSDA A4
#define pinOSLK A5
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ pinOSLK, /* data=*/ pinOSDA, /* reset=*/ U8X8_PIN_NONE);   // Adafruit Feather M0 Basic Proto + FeatherWing OLED

// Drehzahlmesser
double Drehzahl = 0;
uint16_t impulszz = 0;
uint16_t impulszz_copy = 0;
int zeitimpuls = 0;
constexpr uint16_t PulseProRotation {161};



void setup()
{
  //Pin In Output
  pinMode(pinImpuls, INPUT); //Pin Impuls
  attachInterrupt(digitalPinToInterrupt(pinImpuls), ImpulsLesen, FALLING);

  SPI.begin();

  u8g2.begin(); //OLED Display
  starteSimulation(1200);


}


void loop()
{
  if (millis() - myTimeDZM >= timerDZM) {
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
      impulszz_copy = impulszz;
      impulszz     = 0;
    }
    uint16_t messIntervall = millis() - myTimeDZM;
    myTimeDZM = millis();
    Drehzahl = (1.0 * impulszz_copy / PulseProRotation) * (60000.0 / messIntervall) ;

    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_fub30_tr);
    u8g2.setFontRefHeightExtendedText();
    u8g2.setDrawColor(1);
    u8g2.setFontPosTop();
    u8g2.setFontDirection(0);
    u8g2.setCursor(20, 1);
    u8g2.print(Drehzahl, 0);
    u8g2.sendBuffer();
  }
  delay(300);
}

void ImpulsLesen() {

  if (micros() - myTimeDZMImpuls > 2) {
    impulszz = impulszz + 1;
    myTimeDZMImpuls = micros();
  }
}

/*************************************************************************************/
/* Dieser Teil ist für das Simulieren der Pulse für die Drehzahlmessung erforderlich */
#include <TimerOne.h>
constexpr byte outPin {4};
constexpr unsigned long pulseProRotation {161};
unsigned long interval;
float zielDrehzahl = 1200;
byte togglePin = 0;

void starteSimulation(float ziel) {
  pinMode(outPin, OUTPUT);
  togglePin = 1 << outPin;
  zielDrehzahl = ziel;
  interval = round(1000000.0 / ((zielDrehzahl / 60.0) * pulseProRotation));
  Timer1.initialize(interval);
  Timer1.attachInterrupt(timer_isr);
}

void timer_isr() {
  PIND = togglePin;
  delayMicroseconds(3);
  PIND = togglePin;
}

/*************************************************************************************/


Auf Wokwi zum Testen: https://wokwi.com/projects/437123436443317249

Ich habe dabei so wenig wie möglich an Deinem Sketch geändert, da ist sicher noch Luft nach oben ...

Gerne mal in der originalen Anwendung ausprobieren!

Viel Erfolg!
ec2021

Warum Software I2C die ist doch auch sehr langsam, nim HW_I2C.

Danke, werde ich so mal probieren.