1.6 🚧 Frequenz und Periodendauer messen

[de.wikipedia.org/wiki/Frequenz 🔗]

Die Frequenz f eines Signals ist die Anzahl der Wiederholungen pro Sekunde.
Im Beispiel ein Rechtecksignal. Die Periodendauer T ist dabei z.B. die Zeit von steigender zur steigender Flanke.

Um die Frequenz zu messen kann z.B. die Anzahl der steigenden Taktflanken in einer Sekunde gezählt werden.
Bei niedrigen Frequenzen wird eher die Periodendauer T gemessen.
Oft ist auch die Länge des positiven und negativen Impulses von Interesse.

Auf dem Baseshield ist ein Frequenzgenerator mit NE555 verbaut, dessen Frequenz wir nun messen wollen. Dazu muss neben der Taste PA10 ein Jumper gesetzt werden, damit der Ausgang mit PA1 verbunden wird. Mit dem Poti unter dem LCD-Display kann die Frequenz verändert werden.

Frequenz und Periodendauer
Frequenz und Periodendauer

Frequenz messen

  • Eine ISR zählt in einer Variablen zaehler die Positiven Taktflanken
  • Ein Timer löst jede Sekunde eine ISR aus:
    • Der Wert von zaehler enthält die Frequenz und wird gespeichert
    • zaehler wird wieder auf 0 gesetzt.

Vervollständigen Sie den Vorgabecode…

Frequenz messen
Frequenz messen
#define FREQUENZ_PIN PA1 // Frequenz zu messen
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl
volatile unsigned long zaehler=0;  // Flanken zählen
volatile unsigned long frequenz=0; // ermittelte Frequenz

void isr_Messen(){    // Jede Sekunde aufrufen
  ...
  Serial.printf("Frequenz %d Hz \n",frequenz);
}
void isr_Zaehlen(){   // zählt bei jeder steigenden Taktflake
  ...
}
void setup(){
  Serial.begin (9600); //Serielle kommunikation starten
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Zaehlen, RISING); // steigende Taktflanken zählen
  ...                                  // ISR jede Sekunde
  mytimer.attachInterrupt(isr_Messen); // Timer ISR einstellen
  mytimer.resume();  // Timer aktivieren
}
void loop(){}
Lösung
#define FREQUENZ_PIN PA1 // Frequenz zu messen
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl
volatile unsigned long zaehler=0;  // Flanken zählen
volatile unsigned long frequenz=0; // ermittelte Frequenz

void isr_Messen(){    // Jede Sekunde aufrufen
  frequenz = zaehler; // gemessene Frequenz sichern
  zaehler=0;
  Serial.printf("Frequenz %d Hz \n",frequenz);
}
void isr_Zaehlen(){   // zählt bei jeder steigenden Taktflake
  zaehler++;
}
void setup(){
  Serial.begin (9600); //Serielle kommunikation starten
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Zaehlen, RISING); // steigende Taktflanken zählen
  mytimer.setOverflow(1000000, MICROSEC_FORMAT);// ISR jede Sekunde
  mytimer.attachInterrupt(isr_Messen); // Timer ISR einstellen
  mytimer.resume();  // Timer aktivieren
}
void loop(){}

Periodendauer messen

Bei niedrigen Frequenzen wird die Zeit zwischen den Flanken gemessen.
Dazu wäre eine gute Zeitbasis z.B. µs zählen praktisch. Ich möchte 2 Lösungsansätze vorstellen.

  1. Einen Timer mit 1 MHz laufen lassen und dessen Zählwert verwenden.
  2. Die Arduino-Funktion micros() verwenden, wie funktioniert sie genau?
  3. Die Hardware die Periodendauer messen lassen, kenne ich von AVR, müsste mit STM32 auch möglich sein?
Periodendauer messen
Periodendauer messen

P1. Timer mit 1 MHz zum Messen laufen lassen

Notwendige Befehle stehen nicht in der Formelsammlung. Infos siehe:
[github.com/stm32duino/Arduino_Core_STM32/wiki/HardwareTimer-library]

  • Ein Timer zählt mit 1 MHz die Mikrosekunden (CNT).
  • Der Overflowwert (ARR) ist maximal eingestellt, damit er nicht vorzeitig auf 0 gesetzt wird. (Prüfen, ob das notwendig ist)
  • Eine ISR wird bei steigender Taktflanke von PA1 ausgelöst.
    • Dabei wird der Timerwert in einer Variable periode gespeichert.
    • Der Timerwert wird wieder auf 0 gesetzt.

❓ Welche maximale Periodendauer kann so gemessen werden?

Lösung

Bei einem 16Bit Counter 216-1 µs = 65535 µs

#define FREQUENZ_PIN PA1 // Frequenz zu messen
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl
volatile unsigned long periode=0; // ermittelte Periodendauer

void isr_Periode(){ // bei steigendenr Taktflake aufrufen
  periode= mytimer.getCount(); // Timer Wert speichern
  mytimer.setCount(0); // Timer auf 0 setzen
}
void setup(){
  Serial.begin (9600); //Serielle Kommunikation starten
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Periode, RISING); // steigende Taktflanken zählen
  mytimer.setOverflow(0xFFFF,TICK_FORMAT);// auf Maximum setzen
  mytimer.setPrescaleFactor(32); // 32MHz Frequenz durch 32 Teilen -> 1µs
  mytimer.resume(); // Timer starten
}
void loop(){
  Serial.printf("Periodendauer %d µs \n",periode);
  delay(1000);
}

P2. Mit micros() Zeiten messen

micros() steht nicht in der Formelsammlung Info: [arduino.cc/reference/en/language/functions/time/micros/]

micros() zählt die Mikrosekunden seit Systemstart in einer uint32_t Variablen. Beim STM32 werden dabei SystemTicks verwendet. [www.mystm32.de/doku.php?id=systemtickc 🔗]

Info: getCurrentMicros()
uint32_t getCurrentMicros(void){
  uint32_t m0 = HAL_GetTick();
  __IO uint32_t u0 = SysTick->VAL;
  uint32_t m1 = HAL_GetTick();
  __IO uint32_t u1 = SysTick->VAL;
  const uint32_t tms = SysTick->LOAD + 1;

  if (m1 != m0) {
    return (m1 * 1000 + ((tms - u1) * 1000) / tms);
  } else {
    return (m0 * 1000 + ((tms - u0) * 1000) / tms);
  }
}
  1. ❓Nach welcher Zeit springt der micros() wieder auf 0?
  2. ❓Welche Auswirkung hat dies auf die Messung wenn der Sprung auf 0 während der Messung geschieht?
Lösung
  1. Datentyp unsigned long ist uint32_t: 32 Bit ohne Vorzeichen. 232 µs / 106 / 60 = 71,58 min = 1,19 h
  2. Es wäre zeitneu < zeitmarke, somit würde eine negative periode ausgerechnet aber als vorzeichenlose Zahl interpretiert werden.
#define FREQUENZ_PIN PA1 // Frequenz zu messen
volatile unsigned long periode = 0; // Periodendauer

void isr_Periode(){ // bei steigendenr Taktflake aufrufen
  static unsigned long zeitmarke = 0; // persistente Variable
  unsigned long zeitneu = micros();   // Zeit jetzt
  periode = zeitneu-zeitmarke; // Periodendauer speichern
  zeitmarke = zeitneu;
}
void setup(){
  Serial.begin (9600); // Serielle Kommunikation starten
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Periode, RISING); // steigende Taktflanken
}
void loop(){
  Serial.printf("Periodendauer %d µS \n",periode);
  delay(1000);
}

Impulsdauer messen

Nun soll die Impulsdauer des positiven und negativen Impulses gemessen werden, also wie lange ist das Signal High und wie lange ist es Low.
Es gibt 4 Möglichkeiten dies zu tun:

  1. Arduino pulseIn() Funktion
  2. Impulsdauer mit ext. ISR und micros() messen
  3. Impulsdauer mit ext. ISR und Timer messen
  4. 🤯 Impulsdauer mit Timer-Hardwarefunktion messen

I1. Arduino pulseIn() Funktion

Die Arduino-API bietet eine Funktion pulseIn() an:
www.arduino.cc/reference/en/language/functions/advanced-io/pulseIn/ 🔗
Arduino pulseIn() function | roboticsbackend🔗
Hier ein Programm, dass mit pulseIn() die positive und negative Impulslänge im Sekundentakt auf der seriellen Schnittstelle ausgibt.

#define FREQUENZ_PIN PA1 // Frequenz zu messen
unsigned long impulsdauerPos=0; // positive Impulsdauer
unsigned long impulsdauerNeg=0; // negative Impulsdauer

void setup(){
  Serial.begin (9600);
  pinMode(FREQUENZ_PIN, INPUT);
}
void loop(){
  impulsdauerPos=pulseIn(FREQUENZ_PIN,HIGH);
  impulsdauerNeg=pulseIn(FREQUENZ_PIN,LOW);
  Serial.printf("Impulsdauer pos %d µs neg %d µs\n",impulsdauerPos,impulsdauerNeg);
  delay(1000);
}

Schauen Sie sich die Doku und Definition von pulseIn() an (rechte Maus und „Go to Definition“).

  1. ❓ Blockiert pulseIn() den Programmablauf?
  2. ❓ Es gibt einen optionalen dritten Parameter timeout. Welcher Wert wird beim Überschreiten der timeout-Zeit zurück gegeben?
    Welchen Default-Wert hat dieser Parameter und wie lange ist dadurch die längste Zeit, die ohne Angabe dieses Parameters gemessen werden kann?
  3. ❓ Welche zeitbestimmende Funktion wird in pulseIn() verwendet und welche Auswirkung hat dies auf die Genauigkeit der Messung?
Lösungen
  1. Ja, solange die Messung stattfindet.
  2. Beim Überschreiten von timeout wird 0 zurück gegeben.
    Der Default-Wert von timeout ist 1000000. Die längste Messzeit ist dabei 1 Sekunde.
  3. pulseIn() verwendet micros() und ist dabei auf die Genauigkeit dieser Funktion angewiesen, hängt vom µC ab
    https://docs.arduino.cc/language-reference/en/functions/time/micros/🔗

I2. Impulsdauer mit ext. ISR und micros() messen

Die Impulsdauern eines Signals ohne Unterbrechungen zu messen ist nicht schwer:
Ein externer Interrupt wird bei jeder Flanke des Signals ausgelöst und die vergangene Zeit seit der letzen Flanke der jeweiligen Impulsdauer zugeordnet.
Vervollständigen Sie den vorgegebenen Code:

#define FREQUENZ_PIN PA1 // Frequenz zu messen
volatile unsigned long impulsdauerPos=0; // Dauer positiver Impuls
volatile unsigned long impulsdauerNeg=0; // Dauer negativer Impuls

void isr_Flankenwechsel(){ // bei jeder Flanke aufgerufen
  static unsigned long zeitmarke = 0;   // lokale persistente Zeitmarke
  unsigned long zeitneu = micros();     // Zeit jetzt gleich festhalten
  ...
}
void setup(){
  Serial.begin (9600);
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Flankenwechsel, CHANGE); // wechselnde Flanken
}
void loop(){
  Serial.printf("Impulsdauer pos %d µs neg %d µs\n",impulsdauerPos,impulsdauerNeg);
  delay(1000);
}
Lösung
#define FREQUENZ_PIN PA1 // Frequenz zu messen
volatile unsigned long impulsdauerPos=0; // Dauer positiver Impuls
volatile unsigned long impulsdauerNeg=0; // Dauer negativer Impuls

void isr_Flankenwechsel(){ // bei jeder Flanke aufgerufen
  static unsigned long zeitmarke = 0;   // lokale persistente Zeitmarke
  unsigned long zeitneu = micros();     // Zeit jetzt gleich festhalten
  if(digitalRead(FREQUENZ_PIN)){        // war steigende Flanke
    impulsdauerNeg = zeitneu-zeitmarke; // Dauer negativer Impuls
  }else{                                // war fallende Flanke
    impulsdauerPos=zeitneu-zeitmarke;   // Dauer positiver Impuls
  }
  zeitmarke=zeitneu;
}
void setup(){
  Serial.begin (9600); //Serielle kommunikation starten
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Flankenwechsel, CHANGE); // wechselnde Flanken
}
void loop(){
  Serial.printf("Impulsdauer pos %d µs neg %d µs\n",impulsdauerPos,impulsdauerNeg);
  delay(1000);
}
  1. ❓ Angenommen, die Werte der Impulsdauern schwanken innerhalb einer Sekunde, welche Werte werden jeweils ausgegeben?
  2. ❓ Wie genau sind die Werte der Impulsdauern, wovon hängt die Genauigkeit ab?
Lösungen
  1. Es wird jede Sekunde jeweils der letzte Wert der Messung ausgegeben.
  2. Die Genauigkeit hängt der Zeit bis in die ISR gesprungen wird und von der Genauigkeit der micros() Funktion ab.

I3. Impulsdauer mit ext. ISR und Timer messen

Um die Ungenauigkeit von micros() zu vermeiden kann als Zeitbasis ein Timer mit 1 µs Takt verwendet werden.
🖥 Erstellen Sie eine Lösung dafür. Tipp: Timer mit 1 MHz zum Messen laufen lassen

Lösungsvorschlag
#define FREQUENZ_PIN PA1 // Frequenz zu messen
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl
volatile unsigned long periode = 0;      // Periodendauer
volatile unsigned long impulsdauerPos=0; // Dauer positiver Impuls
volatile unsigned long impulsdauerNeg=0; // Dauer negativer Impuls

void isr_Flankenwechsel(){ // bei jeder Flanke aufgerufen
  static unsigned long zeitmarke = 0;      // lokale persistente Zeitmarke
  unsigned long zeit = mytimer.getCount(); // Zeit jetzt gleich festhalten
  if(digitalRead(FREQUENZ_PIN)){           // war steigende Flanke
    mytimer.setCount(0);                   // Timer auf 0 setzen
    impulsdauerNeg = zeit-zeitmarke;       // Dauer negativer Impuls
    periode = zeit;                        // Periodendauer
  }else{                                   // war fallende Flanke
    impulsdauerPos=zeit;                   // Dauer positiver Impuls
    zeitmarke=zeit;                        // Zeit merken
  }
}
void setup(){
  Serial.begin (9600); //Serielle Kommunikation starten
  pinMode(FREQUENZ_PIN, INPUT);
  attachInterrupt (digitalPinToInterrupt (FREQUENZ_PIN), isr_Flankenwechsel, CHANGE); // steigende Taktflanken zählen
  mytimer.setOverflow(0xFFFF,TICK_FORMAT);// auf Maximum setzen
  mytimer.setPrescaleFactor(32); // 32MHz Frequenz durch 32 Teilen -> 1µs
  mytimer.resume(); // Timer starten
}
void loop(){
  Serial.printf("Impulsdauer pos %d µs neg %d µs Summe %d Periodendauer %d\n",impulsdauerPos,impulsdauerNeg,(impulsdauerPos+impulsdauerNeg),periode);
  delay(1000);
}
  1. ❓ Welche maximalen Werte können ermittelt werden?
  2. ❓ Welche Faktoren beeinträchtigen die Genauigkeit der Messung?
Lösungen
  1. Bei einem 16Bit-Timer 216 µs.
  2. Die Zeit zwischen Flankenwechsel und Einlesen des Timers, die Zeit bis zum Zurücksetzen des Timers bei steigender Flanke.

I4. 🤯 Impulsdauer mit Timer-Hardwarefunktion messen

Zum genauen Messen von Frequenzen und Periodendauern kann bei vielen µC die Timerhardware in einen entsprechenden Modus gebracht werden. Dabei wird ohne Umweg über ISRs der Zählerstand durch den Zustand eines Eingangspins von der Timerhardware gesichert und kann dann in der Software ausgewertet werden.
In der Doku der STM32-API findet sich Frequency_Dutycycle_measurement.ino🔗 wie funktioniert das?

🔊 Entfernung messen mit Ultraschallsensor HC-SR04 (HP17-2)

ToDo: Bilder von den Sensoren und der Rückseite machen!
Auf Funduino.de gibt es einen Sketch Nr.11 Entfernung messen, diesen habe ich für unser Board mit dem STM32 angepasst. Auf Seite 10 der Boardbeschreibung wird gezeigt wie ein Ultraschallmodul HC-SR04 verwendet werden kann. An einem Trigger-Pin wird ein mindestens 10µs langes Startsignal angelegt und nach kurzer Zeit erhält man am Echo-Pin ein Signal dessen Dauer proportional zur Entfernung ist. Links auf mezdata.de zum Sensor.

Oszillogramm Funduino-Modul
Oszillogramm Funduino-Modul
Oszillogramm Amazon-Modul
Oszillogramm Amazon-Modul

Messung mit dem Funduinomodul:
C1 (Channel1) , Gelb ist das Triggersignal, C2, blau das Echosignal. Entfernung ca. 1m.
Gefahr für den STM durch zu hohe Ausgangsspannung des Echo-Signals? Der STM arbeitet mit 3,3V und das Signal hat 5V, es könnte der Eingang beschädigt werden? Entwarnung: Habe zwischen Modul und Eingang einen 10kΩ Widerstand geschaltet und das Signal zeigt die Spannung am Eingang: 5,14V also fließt kein gefährlicher Strom.
Messung mit dem Amazonmodul:
Echosignal kommt später und hat nur 3,4V
Ist wohl ein anderer µC verbaut..

Sensor testen mit Seriellem Plotter

#define TRIGGER PA10 // Pin zum Auslösen der Messung
#define ECHO PC9     // Rückgabepin für Impulslänge
long dauer=0;        // long sind 32 Bit für Messung der Impulslänge in µs
long entfernung=0;   // in mm
void setup(){
  Serial.begin (9600);
  pinMode(TRIGGER, OUTPUT); // Trigger-Pin ist ein Ausgang
  pinMode(ECHO, INPUT);     // Echo-Pin ist ein Eingang
}
void loop(){
  digitalWrite(TRIGGER, HIGH);
  delayMicroseconds(20);       // 10 µs minimal, 20 µs zur Sicherheit
  digitalWrite(TRIGGER, LOW);  // Jetzt fängt die Messung an
  dauer = pulseIn(ECHO, HIGH); // Mit dem Befehl „pulseIn“ zählt der Mikrokontroller die Zeit in Mikrosekunden, bis der Schall zum Ultraschallsensor zurückkehrt.
  //entfernung = (dauer/2) * 0.03432; // numerisch nicht geschickte Berechnung in cm
  entfernung = dauer*0.3434/2; // Berechnung in mm
  //Serial.printf("Dauer: %5d µs, Entfernung: %d mm\n",dauer,entfernung); // Ausgabe serieller Monitor
  Serial.printf("Dauer:%d,Entfernung:%d\n",dauer,entfernung); // Ausgabe serieller Plotter
  delay(500); // 0,5s
}
Ausgabe auf Seriellem Plotter
Ausgabe auf Seriellem Plotter

Wenn im zerklüftetem Zimmer gemessen wird, können die Ultraschallimpulse wild reflektiert werden und führen an manchen Positionen zu unsteten Ergebnissen.

Wie ist der Minimalwert von dauer?

Weitere Infos zu dem Modul:

Theorie hinter der Messung

Schallgeschwindigkeit🔗 in trockener Luft bei 20 °CWegZeit
$ c_{s} = 343,4 m/s $$ s = c_{s} * t $$ t = s / c_{s} $

pulseIn(ECHO, HIGH) gibt die Länge des positiven Impulses in µs zurück. Die Entfernung zum Objekt ist der halbe Wert der Strecke, die der Schall zurücklegt.

1 cm5 cm10 cm1 m2 m5 m10 m
58,2 µs291,2 µs582,4 µs5824,1 µs11648,2 µs29120,5 µs58241,1 µs = 58,2 ms
Impulslänge bei verschiedenen Entfernungen

Den maximalen Wert des Ultraschallsensors habe ich mit Finger auf den Empfänger legen ermittelt (kein Signal kommt zurück): <190000 -> <32,3 m. Solange der Impuls bei pulseIn() gemessen wird blockiert die Funktion weiteren Programmfluss, d.h. das Programm wird bis zum Ende der Messung unterbrochen. Deshalb kann bei pulseIn() als optionaler dritter Parameter noch ein Timeout in µs angegeben werden, wird diese Zeit überschritten wird 0 zurück gegeben.

Numerische Tricks bei der Berechnung der Entfernung

Beim Funduino-Sketch wird die Entfernung sinngemäß so berechnet: entfernung = (dauer/2) * 0.03434; // nach Wikipedia bei 20°C: * 0.03434
Dauer ist der µs Wert des Impulses als ganze Zahl. Wird eine ganze Zahl durch eine ganze Zahl (2) geteilt, werden dabei die Nachkommastellen abgeschnitten, es wird nicht gerundet. Ausserdem wurde auf cm gerechnet und dabei wird auch weiter Genauigkeit verschwendet, der verwendete Datentyp bietet mehr als ausreichend Platz für Millimetergenauigkeit. Beispiel für Entfernung 5 cm:

Berechnung nach Funduino entfernung = (dauer/2) * 0.03434: 291/2 als Ganzzahldivision = 145 *0.03434 = 4,9793 aber in eine Ganzzahl gewandelt -> 4 cm
Berechnung numerisch genauer entfernung = dauer*0.3434/2: 291*0.3434 = 99,9294 / 2 = 49,96 in eine Ganzzahl gewandelt -> 49 mm -> 4,9 cm

Messungen filtern und glätten

Eine Wissenschaft für sich! Wenn man die Messergebnisse mit dem Seriellem Plotter in unterschiedlichen Positionen verfolgt, können wilde Ausreißer beobachtet werden. Für den folgenden Anwendungsfall sollen Entfernungen über 2 m ignoriert und die verbleibenden Messungen gemittelt werden:

  • Bei pulseIn() timeout passend einstellen.
  • Entfernung mitteln mit entfernung = (entfernung + dauer*0.3434/2)/2;
  • Der Serielle Plotter soll nur die Entfernung in mm anzeigen.

🖥 Erstellen Sie den Code und testen Sie ihn.

Lösungsvorschlag
#define TRIGGER PA10 // Pin zum Auslösen der Messung
#define ECHO PC9     // Rückgabepin für Impulslänge
long dauer=0;        // long sind 32 Bit für Messung der Impulslänge in µs
long entfernung=0;   // in mm
void setup(){
  Serial.begin (9600); //Serielle Kommunikation starten, damit man sich später die Werte am serial monitor ansehen kann.
  pinMode(TRIGGER, OUTPUT); // Trigger-Pin ist ein Ausgang
  pinMode(ECHO, INPUT);     // Echo-Pin ist ein Eingang
}
void loop(){
  digitalWrite(TRIGGER, HIGH);
  delayMicroseconds(20);       // 10 µs minimal, 20 zur Sicherheit
  digitalWrite(TRIGGER, LOW);  // Jetzt fängt die Messung an
  dauer = pulseIn(ECHO, HIGH,11648); // Mit dem Befehl „pulseIn“ zählt der Mikrokontroller die Zeit in Mikrosekunden, bis der Schall zum Ultraschallsensor zurückkehrt.
  if (dauer!=0){
    entfernung = (entfernung + dauer*0.3434/2)/2; // Berechnung in mm
  }
  Serial.printf("Entfernung:%d\n",entfernung); // Ausgabe serieller Plotter
  delay(500); // 0,5s
}

Eine stärkere Glättung könnte mit diesem Ausdruck erfolgen: entfernung = (entfernung*4 + dauer*0.3434/2)/5;

🅿️ Einparkhilfe mit Leuchtband und blinkender LED

Entwickeln Sie eine Einparkhilfe mit Leuchtband L0..L7. Was ist besser, je näher desto mehr LED oder desto weniger?

Erweitern Sie ihre Lösung mit der blinkenden Board-LED (PA5), je näher desto schneller blinkt sie, bei der geringsten Entfernung leuchtet sie dauernd.
Verwenden Sie zum Blinken einen Timer-Interrupt.

Lösungsvorschlag
#define trigger PA10
#define echo PC9
#define LED_B PA5 // D13 Led auf dem Board
int dauer=0; // int bei STM sind 32 Bit
int entfernung=0;
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
void isr_blinken(){
  digitalWrite(LED_B,!digitalRead(LED_B));
}
void setup(){
  Serial.begin (9600); //Serielle kommunikation starten, damit man sich später die Werte am serial monitor ansehen kann.
  pinMode(trigger, OUTPUT); // Trigger-Pin ist ein Ausgang
  pinMode(echo, INPUT); // Echo-Pin ist ein Eingang
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(LED_B,OUTPUT); // BoardLED
  mytimer.setOverflow(1, HERTZ_FORMAT); 
  mytimer.attachInterrupt(isr_blinken); //Timer IR aktivieren und Sprung zur ISR
}
#define MAX_ENTFERNUNG 200
void ausgeben(int d){
  int ausgabe=0;
  int blink=0;
  int vergleichswert=MAX_ENTFERNUNG;
  while (d < vergleichswert){
    ausgabe = ausgabe*2 +1;
    //vergleichswert /= 1.5; // type cast, Java würde meckern
    vergleichswert = (int) (vergleichswert / 1.5); 
    blink++;
  }
  if (blink==0){ // zu weit weg
    mytimer.pause();   //Timer stoppen
    digitalWrite(LED_B,LOW);
  }
  else if (blink<=7){
    mytimer.setOverflow(blink, HERTZ_FORMAT); //
    mytimer.resume();   //Timer starten
  }
  else{
    mytimer.pause();   //Timer stoppen
    digitalWrite(LED_B,HIGH);
  }
  GPIOC->ODR =ausgabe;
}
void loop(){
  static int dist=0;
  digitalWrite(trigger, HIGH); 
  delayMicroseconds(10); 
  digitalWrite(trigger, LOW); // Messung starten
  dauer = pulseIn(echo, HIGH); 
  entfernung = (dauer/2) * 0.03432; 
  if (entfernung >= 300 || entfernung <= 0){
    Serial.println("Kein Messwert");
  }
  else{
    dist = (dist+entfernung)/2;
    Serial.printf(" %d cm Mittelwert: %d cm \n",entfernung,dist);
    ausgeben(dist);
  }
  delay(200);
}

Einparkhilfe mit Anzeigenumschaltung

Die Einparkhilfe verwendet für Entfernungen über 16 cm eine zweistellige 7-Segmentanzeige und schaltet bei geringerer Entfernung auf eine Leuchtbandanzeige um. Der entprellte Boardtaster schaltet die Einparkhilfe an und aus. Ausgewertet wird der Tastendruck über einen Interrupt auf die fallende Flanke. Ein Timerinterrupt ruft mit 100 Hz die isr_ausgeben() auf, neben der Ausgabe auf die zwei Anzeigen, bzw. dem Leuchtband wird eine Variable csZaehler (Centi-Sekunden = 100. Sekunden) hochgezählt, mit dieser Variable können Delays vermieden werden.

#define TRIGGER PA10
#define ECHO PC9
int entfernung=0; // entfernung in cm
int csZaehler=0; // Centi-Sekunden
int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung
int leuchtband[]={0b10101010,0b1,0b11,0b111,0b1111,0b11111,0b111111,0b1111111,0b11111111};
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
enum{RUHE,MESSEN} zustand=RUHE;

void setup(){
  pinMode(TRIGGER, OUTPUT); // Trigger-Pin ist ein Ausgang
  pinMode(ECHO, INPUT); // Echo-Pin ist ein Eingang
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(PC11,OUTPUT); // Einer
  pinMode(PC12,OUTPUT); // Zehner
  attachInterrupt (digitalPinToInterrupt (PC13), isr_taster, FALLING);
  mytimer.setOverflow(100, HERTZ_FORMAT); 
  mytimer.attachInterrupt(isr_ausgeben); //Timer IR aktivieren und Sprung zur ISR
  mytimer.resume();   //Timer starten
}
void isr_taster(){ // Taster wurde gedrückt
  switch(zustand){
    case RUHE:
      zustand=MESSEN;
      break;
    case MESSEN:
      zustand=RUHE;  
  }
}
void isr_ausgeben(){  // alle 10ms also 100 Hz
  int ausgabe; // Dezimeter
  switch(zustand){
    case RUHE:
      GPIOC->ODR = 0;
      break;
    case MESSEN:
      if(entfernung <= 16){
        GPIOC->ODR = leuchtband[entfernung/2];
      }
      else{
        ausgabe = entfernung/10; // Dezimeter
        if (csZaehler%2){ // ungerade
          GPIOC->ODR = bcd_7seg[ausgabe%10] | (1<<11); // Einer einschalten
        }
        else{ // gerade
          GPIOC->ODR = bcd_7seg[ausgabe/10] | (1<<7) |(1<<12); // Dezimalpunkt und Zehner einschalten
        }
      }
      if(csZaehler%30==0){ // alle 300 ms Messen
        messung(); // Messe die Entfernung.
      }
  }
  csZaehler++;
}
void messung(){
  int dauer=0; // int bei STM sind 32 Bit
  int mess;
  digitalWrite(TRIGGER, HIGH); 
  delayMicroseconds(10); 
  digitalWrite(TRIGGER, LOW); // Messung starten
  dauer = pulseIn(ECHO, HIGH,20000); // auf 20ms begrenzen für 6m
  mess = (dauer/2) * 0.03432;
  if (mess>300){
    entfernung = 300;
  }
  else{
    entfernung = (entfernung+mess)/2; // Messungen glätten
  }
}
void loop(){
}

Zustandsdiagramm?

Gerne für isr_taster(), aber dabei isr_ausgeben() richtig ein zu bauen macht irre. Ausweg wäre durch geschickte Unterprogramme die Sache zu erleichtern, aber dabei wird der Code unnötig aufgepumpt?

Wozu überhaupt isr_ausgeben() darstellen, führt ja zu keiner Zustandsänderung.

Einparkhilfe Zustandsdiagramm
Einparkhilfe Zustandsdiagramm

Bonus: Lösung ohne pulseIn()

Bei größeren Distanzen flackert die Einerstelle, die Ursache liegt in der Messung der Pulslänge die von der ISR aufgerufen wird. Hier eine Lösung ohne pulseIn() und ohne Verzögerung.
Hinweis: Die Funktion micros() gibt die Zeit seit Systemstart in Mikrosekunden zurück, wird hier als Zeitmarkengeber verwendet. Dank PlatformIO ist die Definition von mircos() nur einen Mausklick entfernt:

extern uint32_t micros(void) ;

Wie lange dauert es, bis nach Systemstart micros() wieder auf 0 überläuft?

#include <Arduino.h>

#define TRIGGER PA10 // Auslösen einer Messung
#define ECHO PC9     // Signal des Sensors
#define LED_B PA5    // D13 Board-LED
#define TASTER_B PC13 // Board-Taster
const int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung
const int leuchtband[]={0b10101010,0b1,0b11,0b111,0b1111,0b11111,0b111111,0b1111111,0b11111111};
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
enum zustandstyp{RUHE,MESSEN};
zustandstyp zustand=RUHE;
int entfernung=0;    // entfernung in cm
int csZaehler=0;     // Centi-Sekunden-Zähler
bool messungLaeuft = false; // Flag zum Synchronisieren

void isr_echo(){ // Messen der Entfernung, Aufruf bei Flankenwechsel
  static unsigned long anfang = micros(); // Anfangszeit merken können
  int dauer;
  int mess;
  if (digitalRead(ECHO) == HIGH) { // 0->1 Messung starten
    anfang = micros();
    messungLaeuft=true;
  }
  else { // 1->0 Messung stoppen und auswerten
    dauer = micros()-anfang;
    mess = (dauer/2) * 0.03432;
    if (mess>300 || mess<=0){
      entfernung = 300;
    }
    else{
      entfernung = (entfernung+mess)/2; // Messungen glätten
    }
    messungLaeuft=false;
  }
}
void isr_taster(){
  switch(zustand){
    case RUHE:
      zustand=MESSEN;
      break;
    case MESSEN:
      zustand=RUHE;  
  }
}
void ausgeben(){
  int ausgabe; // Dezimeter
  if(entfernung <= 16){
    GPIOC->ODR = leuchtband[entfernung/2];
  }
  else{
    ausgabe = entfernung/10; // Dezimeter
    if (csZaehler%2){ // ungerade
      GPIOC->ODR = bcd_7seg[ausgabe%10] | (1<<11); // Einer einschalten
    }
    else{ // gerade
      GPIOC->ODR = bcd_7seg[ausgabe/10] | (1<<7) |(1<<12); // Dezimalpunkt und Zehner einschalten
    }
  }
}
void starteMessung(){ // Messung starten
  digitalWrite(TRIGGER, HIGH); 
  delayMicroseconds(10); 
  digitalWrite(TRIGGER, LOW); 
}
void isr_ausgeben(){ // alle 10 ms
  switch(zustand){
    case RUHE:
      GPIOC->ODR = 0;
      break;
    case MESSEN:
      ausgeben();
      if(!messungLaeuft && csZaehler%30==0){ // alle 300 ms Messung wieder starten
        starteMessung();
      }
  }
  csZaehler++;
}
void setup(){
  pinMode(TRIGGER, OUTPUT); // TRIGGER-Pin ist ein Ausgang  
  pinMode(ECHO, INPUT);     // ECHO-Pin ist ein Eingang
  GPIOC->MODER = 0x5555;    // PC0..PC7 als Ausgang
  pinMode(PC11,OUTPUT);     // Einer
  pinMode(PC12,OUTPUT);     // Zehner
  pinMode(LED_B,OUTPUT);    // Board-LED zum Testen
  pinMode(TASTER_B,INPUT);  // nicht unbedingt erforderlich
  attachInterrupt (digitalPinToInterrupt (TASTER_B), isr_taster, FALLING);
  mytimer.setOverflow(100, HERTZ_FORMAT); 
  mytimer.attachInterrupt(isr_ausgeben); //Timer IR aktivieren und Sprung zur ISR
  mytimer.resume();       //Timer starten
  attachInterrupt(digitalPinToInterrupt(ECHO),isr_echo,CHANGE); // Aufruf bei Flankenwechsel
}
void loop(){
}

Nun habe ich versucht bool messungLaeuft durch zwei Zustände zu ersetzten, dabei musste ich den Echo-Interrupt ein- und ausschalten.

#include <Arduino.h>

#define TRIGGER PA10 // Auslösen einer Messung
#define ECHO PC9     // Signal des Sensors
#define LED_B PA5    // D13 Board-LED
#define TASTER_B PC13 // Board-Taster
const int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung
const int leuchtband[]={0b10101010,0b1,0b11,0b111,0b1111,0b11111,0b111111,0b1111111,0b11111111};
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
enum zustandstyp{RUHE,MESSEN,MESSENFERTIG};
zustandstyp zustand=RUHE;
int entfernung=0;    // entfernung in cm
int csZaehler=0;     // Centi-Sekunden-Zähler

void isr_echo(){ // Messen der Entfernung, Aufruf bei Flankenwechsel
  static unsigned long anfang = micros(); // Anfangszeit merken können
  int dauer;
  int mess;
  if (digitalRead(ECHO)) { // 0->1 Messung starten
    anfang = micros();
  }
  else { // 1->0 Messung stoppen und auswerten
    dauer = micros()-anfang;
    mess = (dauer/2) * 0.03432;
    if (mess>300 || mess<=0){
      entfernung = 300;
    }
    else{
      entfernung = (entfernung+mess)/2; // Messungen glätten
    }
    zustand=MESSENFERTIG;
  }
}
void starteMessung(){ // Messung starten
  digitalWrite(TRIGGER, HIGH); 
  delayMicroseconds(10); 
  digitalWrite(TRIGGER, LOW);
}
void isr_taster(){
  switch(zustand){
    case RUHE:
      attachInterrupt(ECHO,ISR_echo,CHANGE); // Aufruf bei Flankenwechsel
      starteMessung();
      zustand=MESSEN;
      break;
    case MESSEN:
    case MESSENFERTIG:
      detachInterrupt(ECHO);
      zustand=RUHE;  
  }
}
void ausgeben(){
  int ausgabe; // Dezimeter
  if(entfernung <= 16){
    GPIOC->ODR = leuchtband[entfernung/2];
  }
  else{
    ausgabe = entfernung/10; // Dezimeter
    if (csZaehler%2){ // ungerade
      GPIOC->ODR = bcd_7seg[ausgabe%10] | (1<<11); // Einer einschalten
    }
    else{ // gerade
      GPIOC->ODR = bcd_7seg[ausgabe/10] | (1<<7) |(1<<12); // Dezimalpunkt und Zehner einschalten
    }
  }
}
void isr_ausgeben(){ // alle 10 ms
  switch(zustand){
    case RUHE:
      GPIOC->ODR = 0;
      break;
    case MESSEN:
      ausgeben();
      break;
    case MESSENFERTIG:
      ausgeben();
      if(csZaehler%30==0){ // alle 300 ms Messung wieder starten
        starteMessung();
        zustand=MESSEN;
      }
  }
  csZaehler++;
}
void setup(){
  pinMode(TRIGGER, OUTPUT); // TRIGGER-Pin ist ein Ausgang  
  pinMode(ECHO, INPUT);     // ECHO-Pin ist ein Eingang
  GPIOC->MODER = 0x5555;    // PC0..PC7 als Ausgang
  pinMode(PC11,OUTPUT);     // Einer
  pinMode(PC12,OUTPUT);     // Zehner
  pinMode(LED_B,OUTPUT);    // Board-LED zum Testen
  pinMode(TASTER_B,INPUT);  // nicht unbedingt erforderlich
  attachInterrupt (digitalPinToInterrupt (TASTER_B), isr_taster, FALLING);
  mytimer.setOverflow(100, HERTZ_FORMAT); 
  mytimer.attachInterrupt(isr_ausgeben); //Timer IR aktivieren und Sprung zur ISR
  mytimer.resume();       //Timer starten
}
void loop(){
}

Erstellen Sie ein Zustandsdiagramm.

Lösungsvorschlag Zustandsdiagramm
Zustandsdiagramm
Zustandsdiagramm