1.6 ⏱️ Frequenz, Perioden- und Impulsdauer 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 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…
#define P_FREQUENZ 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), 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 P_FREQUENZ 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), 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.
- Einen Timer mit 1 MHz laufen lassen und dessen Zählwert verwenden.
- Die Arduino-Funktion micros() verwenden, wie funktioniert sie genau?
- Die Hardware die Periodendauer messen lassen, kenne ich von AVR, müsste mit STM32 auch möglich sein?
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 P_FREQUENZ 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), isr_Periode, RISING); // steigende Taktflanken zählen
mytimer.setOverflow(0x10000);// 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);
}
}
- ❓Nach welcher Zeit springt der micros() wieder auf 0?
- ❓Welche Auswirkung hat dies auf die Messung wenn der Sprung auf 0 während der Messung geschieht?
Lösung
- Datentyp unsigned long ist uint32_t: 32 Bit ohne Vorzeichen. 232 µs / 106 / 60 = 71,58 min = 1,19 h
- Es wäre zeitneu < zeitmarke, somit würde eine negative periode ausgerechnet aber als vorzeichenlose Zahl interpretiert werden.
#define P_FREQUENZ 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), isr_Periode, RISING); // steigende Taktflanken
}
void loop(){
Serial.printf("Periodendauer %d µS \n",periode);
delay(1000);
}
P3. Mit Timer-Hardwarefunktion in CaptureCompare-Register messen
Beispiel aus der STM32-Lib: InputCapture.ino🔗
HardwareTimer.cpp🔗
Die PWM-Kanäle-Pins des Timers können auch als Auslöser zur Aufnahme (Capture) des aktuellen Zählerstandes in ein CaptureCompare-Register verwendet werden. Wenn z.B. eine steigende Flanke passiert wird ein „Foto“ des aktuellen Zählerstandes gespeichert. Da der Zähler weiter zählt muss sich auch gemerkt werden, ob zwischen zwei Aufnahmen ein Überlauf statt gefunden hat.
Der Frequenzgenerator auf dem SturmBoard ist über einen Jumper an PA1 angeschlossen. In der Formelsammlung oder hier nach dem Timer und dem dazugehörigen Kanal suchen: TIM2 Kanal 2 oder TIM5 Kanal 2 sind möglich.
Mögliche Auslöseeinstellungen für setMode():
- TIMER_INPUT_CAPTURE_RISING
- TIMER_INPUT_CAPTURE_FALLING
- TIMER_INPUT_CAPTURE_BOTHEDGE
- TIMER_INPUT_FREQ_DUTY_MEASUREMENT
Hinweis: In setMode(..) wird pinMode(pin,INPUT) aufgerufen. Falls PullUP/PullDown eingeschaltet werden sollen muss dies danach gemacht werden.
#define P_FREQUENZ PA1 // TIM2 Kanal 2 oder TIM5 Kanal 2
#define KANAL 2
static HardwareTimer mytimer = HardwareTimer(TIM2); // Timerinstanz sowie Timerauswahl
volatile uint32_t periode=0; // ermittelte Periodendauer
volatile uint32_t letzteAufnahme=0; // letzer Wert
volatile uint32_t ueberlauf=0; // gab es Überlauf?
void isr_Capture(){ // bei Auslösung aufrufen
uint32_t aufnahme = mytimer.getCaptureCompare(KANAL);
if (aufnahme > letzteAufnahme){
periode = aufnahme - letzteAufnahme;
}
else{
periode = 0x10000 + aufnahme - letzteAufnahme;
}
letzteAufnahme = aufnahme;
ueberlauf = 0;
}
void isr_Overflow(){ // wenn der Zähler überläuft
ueberlauf++;
if (ueberlauf > 1) periode = 0; // bei mehr Überläufen stimmt die Rechnung nicht mehr
}
void setup(){
Serial.begin (9600); //Serielle Kommunikation starten
mytimer.setOverflow(0x10000);// auf Maximum setzen
mytimer.setPrescaleFactor(32); // 32MHz Frequenz durch 32 Teilen -> 1µs
mytimer.attachInterrupt(isr_Overflow); // ISR für den Timer-Überlauf
mytimer.setMode(KANAL,TIMER_INPUT_CAPTURE_RISING,P_FREQUENZ); // den Auslöser einstellen
mytimer.attachInterrupt(KANAL,isr_Capture); // ISR für Aufnahme bearbeiten
mytimer.resume(); // Timer starten
}
void loop(){
Serial.printf("Periodendauer %d µs \n",periode);
delay(1000);
}
Vorhaben | Syntax | Erläuterungen |
---|---|---|
Kanal für Input Capture einstellen | mytimer.setMode(channel,mode,pin); | channel gibt den Timer-Kanal vor: 1..4 mode gibt die Auslösebedingung vor: TIMER_INPUT_CAPTURE_RISING TIMER_INPUT_CAPTURE_FALLING TIMER_INPUT_CAPTURE_BOTHEDGE TIMER_INPUT_FREQ_DUTY_MEASUREMENT pin gibt den Pinnamen an, Bsp: PA1 |
ISR bei Capture-Ereignis ausführen | mytimer.attachInterrupt(channel,isr); | channel gibt den Timer-Kanal vor: 1..4 isr ist aufzurufende ISR, Bsp: isr_Capture |
Capture-Wert lesen | n = mytimer.getCaptureCompare(channel); | channel gibt den Timer-Kanal vor: 1..4 |
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:
- Arduino pulseIn() Funktion
- Impulsdauer mit ext. ISR und micros() messen
- Impulsdauer mit ext. ISR und Timer messen
- 🤯 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 P_FREQUENZ PA1 // Frequenz zu messen
unsigned long impulsdauerPos=0; // positive Impulsdauer
unsigned long impulsdauerNeg=0; // negative Impulsdauer
void setup(){
Serial.begin (9600);
pinMode(P_FREQUENZ, INPUT);
}
void loop(){
impulsdauerPos=pulseIn(P_FREQUENZ,HIGH);
impulsdauerNeg=pulseIn(P_FREQUENZ,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“).
- ❓ Blockiert pulseIn() den Programmablauf?
- ❓ 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? - ❓ Welche zeitbestimmende Funktion wird in pulseIn() verwendet und welche Auswirkung hat dies auf die Genauigkeit der Messung?
Lösungen
- Ja, solange die Messung stattfindet.
- Beim Überschreiten von timeout wird 0 zurück gegeben.
Der Default-Wert von timeout ist 1000000. Die längste Messzeit ist dabei 1 Sekunde. - 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 P_FREQUENZ 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), isr_Flankenwechsel, CHANGE); // wechselnde Flanken
}
void loop(){
Serial.printf("Impulsdauer pos %d µs neg %d µs\n",impulsdauerPos,impulsdauerNeg);
delay(1000);
}
Lösung Code
#define P_FREQUENZ 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(P_FREQUENZ)){ // 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), isr_Flankenwechsel, CHANGE); // wechselnde Flanken
}
void loop(){
Serial.printf("Impulsdauer pos %d µs neg %d µs\n",impulsdauerPos,impulsdauerNeg);
delay(1000);
}
- ❓ Angenommen, die Werte der Impulsdauern schwanken innerhalb einer Sekunde, welche Werte werden jeweils ausgegeben?
- ❓ Wie genau sind die Werte der Impulsdauern, wovon hängt die Genauigkeit ab?
Lösungen
- Es wird jede Sekunde jeweils der letzte Wert der Messung ausgegeben.
- 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 Code
#define P_FREQUENZ 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(P_FREQUENZ)){ // 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(P_FREQUENZ, INPUT);
attachInterrupt (digitalPinToInterrupt (P_FREQUENZ), 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);
}
- ❓ Welche maximalen Werte können ermittelt werden?
- ❓ Welche Faktoren beeinträchtigen die Genauigkeit der Messung?
Lösungen
- Bei einem 16Bit-Timer 216 µs.
- 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.


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
unsigned long dauer=0; // long sind 32 Bit für Messung der Impulslänge in µs
unsigned 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
}

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 °C | Weg | Zeit |
---|---|---|
$ 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 cm | 5 cm | 10 cm | 1 m | 2 m | 5 m | 10 m |
---|---|---|---|---|---|---|
58,2 µs | 291,2 µs | 582,4 µs | 5824,1 µs | 11648,2 µs | 29120,5 µs | 58241,1 µs = 58,2 ms |
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
unsigned long dauer=0; // long sind 32 Bit für Messung der Impulslänge in µs
unsigned 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;
Ohne pulseIn() und delay() nur mit Timern und ext. Interrupt
Die Messungen werden mit einem Timer startMessung alle 300 ms durch isr_startMessung() ausgelöst. Die Pulslänge wird mit einem Timer messTimer in µs gemessen. Dauert dies zu lange wird eine isr_messTimer_overflow() ausgelöst. Das Echo-Signal wird mit einer ISR isr_echo_change() ausgewertet. Nach dem Auslösen einer Messung können 3 Dinge passieren:

🖥 Ergänzen Sie den vorgegebenen Code und testen Sie das Programm.
#define TRIGGER PA10 // Pin zum Auslösen der Messung
#define ECHO PC9 // Rückgabepin für Impulslänge
#define ECHO_RISE digitalRead(ECHO) // wenn nach CHANGE High ist
static HardwareTimer startMessungTimer = HardwareTimer(TIM3); // Timerinstanz für Messungswiederholung
static HardwareTimer messTimer = HardwareTimer(TIM4); // Timerinstanz für Pulsdauermessung
enum zustandstyp {MESSEN_ENTRY,MESSEN,MESSUNG};
zustandstyp zustand=MESSEN_ENTRY;
unsigned 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
startMessungTimer.setPrescaleFactor(?); // 1ms Takt
startMessungTimer.setOverflow(?); // 300 ms
startMessungTimer.?;
startMessungTimer.?; // Timer starten
messTimer.setPrescaleFactor(?); // 1 µs Takt
messTimer.setOverflow(12000); // bei mehr als ? m
messTimer.attachInterrupt(isr_messTimer_overflow); // wenn Messung zu lange dauert
attachInterrupt (digitalPinToInterrupt (ECHO), isr_echo_change, CHANGE); // wenn sich am ECHO-Pin was tut
}
void isr_startMessung(){ // alle 300 ms eine neue Messung durch ISR ausgelöst
?
}
void isr_messTimer_overflow(){ // Wenn Pulsmessung zu lange dauert
zustand = MESSEN_ENTRY;
}
void isr_echo_change(){ // Wenn sich am ECHO-Pin was tut
?
}
void starteMessung(){ // Impuls senden und messTimer klar machen
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(20); // 10 µs minimal, 20 zur Sicherheit
digitalWrite(TRIGGER, LOW); // Jetzt fängt die Messung an
messTimer.setCount(0);
messTimer.resume();
}
void auswertenMessung(){ // Messung erfolgreich auswerten
entfernung = (entfernung + messTimer.getCount()*0.3434/2)/2; // Berechnung in mm
Serial.printf("Entfernung:%d\n",entfernung); // Ausgabe serieller Plotter
}
void loop(){
switch(zustand){
?
}
}
Mögliche Lösung
#define TRIGGER PA10 // Pin zum Auslösen der Messung
#define ECHO PC9 // Rückgabepin für Impulslänge
#define ECHO_RISE digitalRead(ECHO) // wenn nach CHANGE High ist
static HardwareTimer startMessungTimer = HardwareTimer(TIM3); // Timerinstanz für Messungswiederholung
static HardwareTimer messTimer = HardwareTimer(TIM4); // Timerinstanz für Pulsdauermessung
enum zustandstyp {MESSEN_ENTRY,MESSEN,MESSUNG};
zustandstyp zustand=MESSEN_ENTRY;
unsigned 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
startMessungTimer.setPrescaleFactor(32000); // 1ms Takt
startMessungTimer.setOverflow(300); // 300 ms
startMessungTimer.attachInterrupt(isr_startMessung);
startMessungTimer.resume(); // Timer starten
messTimer.setPrescaleFactor(32); // 1 µs Takt
messTimer.setOverflow(12000); // bei mehr als ? m
messTimer.attachInterrupt(isr_messTimer_overflow); // wenn Messung zu lange dauert
attachInterrupt (digitalPinToInterrupt (ECHO), isr_echo_change, CHANGE); // wenn sich am ECHO-Pin was tut
}
void isr_startMessung(){ // alle 300 ms eine neue Messung durch ISR ausgelöst
if(zustand==MESSEN){
starteMessung();
zustand = MESSUNG;
}
}
void isr_messTimer_overflow(){ // Wenn Pulsmessung zu lange dauert
zustand = MESSEN_ENTRY;
}
void isr_echo_change(){ // Wenn sich am ECHO-Pin was tut
if (zustand!=MESSUNG)return; // keine Messung
if(ECHO_RISE){
messTimer.setCount(0); // Timer zurück setzen
} else { // Falling
auswertenMessung();
zustand = MESSEN_ENTRY;
}
}
void starteMessung(){ // Impuls senden und messTimer klar machen
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(20); // 10 µs minimal, 20 zur Sicherheit
digitalWrite(TRIGGER, LOW); // Jetzt fängt die Messung an
messTimer.setCount(0);
messTimer.resume();
}
void auswertenMessung(){ // Messung erfolgreich auswerten
entfernung = (entfernung + messTimer.getCount()*0.3434/2)/2; // Berechnung in mm
Serial.printf("Entfernung:%d\n",entfernung); // Ausgabe serieller Plotter
}
void loop(){
switch(zustand){
case MESSEN_ENTRY:
messTimer.pause();
zustand = MESSEN;
break;
}
}
🅿️ Einparkhilfe mit Leuchtband und Dezimeteranzeige
Mit dem Code aus der letzten Aufgabe soll eine Einparkhilfe entwickelt werden. Die Einparkhilfe verwendet bei der Ausgabe für Entfernungen über 16 cm eine gemultiplexte zweistellige 7-Segmentanzeige und schaltet bei geringerer Entfernung auf eine Leuchtbandanzeige um (isr_ausgeben()).
- Mit dem entprellten lowaktiven UserButton an PC13 wird sie an- und ausgeschaltet. Der Tastendruck löst eine ISR isr_taster() aus(im Zustandsdiagramm ergänzen).
- Die Timer-ISR isr_ausgeben() wird für die Ausgabe mit 100 Hz im Messbetrieb aufgerufen. Dafür ist ein Timer ausgebenTimer reserviert (siehe Codeschnipsel). Dieser Timer wird Zustand RUHE an- und ausgeschaltet. Da die Funktionsweise der Ausgabe-ISR unabhängig vom Zustand ist, wurde die ISR nicht ins Zustandsdiagramm übernommen.
🖥 Ergänzen Sie das Zustandsdiagramm.
🖥 Erweitern Sie den Code.
#define TASTER PC13 // Entprellter UserButton active low
static HardwareTimer startMessungTimer = HardwareTimer(TIM3); // Timerinstanz für Messungswiederholung
static HardwareTimer messTimer = HardwareTimer(TIM4); // Timerinstanz für Pulsdauermessung
static HardwareTimer ausgebenTimer = HardwareTimer(TIM5); // Timerinstanz für Anzeige
enum zustandstyp {RUHE_ENTRY,RUHE,MESSEN_ENTRY,MESSEN,MESSUNG};
zustandstyp zustand=RUHE_ENTRY;
int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40}; // Umrechnung
int leuchtband[]={0b10101010,0b1,0b11,0b111,0b1111,0b11111,0b111111,0b1111111,0b11111111};
Lösung Zustandsdiagramm
Lösungsvorschlag Code
#define TRIGGER PA10 // Pin zum Auslösen der Messung
#define ECHO PC9 // Rückgabepin für Impulslänge
#define ECHO_RISE digitalRead(ECHO) // wenn nach CHANGE High ist
#define TASTER PC13 // Entprellter UserButton active low
static HardwareTimer startMessungTimer = HardwareTimer(TIM3); // Timerinstanz für Messungswiederholung
static HardwareTimer messTimer = HardwareTimer(TIM4); // Timerinstanz für Pulsdauermessung
static HardwareTimer ausgebenTimer = HardwareTimer(TIM5); // Timerinstanz für Anzeige
enum zustandstyp {RUHE_ENTRY,RUHE,MESSEN_ENTRY,MESSEN,MESSUNG};
zustandstyp zustand=RUHE_ENTRY;
int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40}; // Umrechnung
int leuchtband[]={0b10101010,0b1,0b11,0b111,0b1111,0b11111,0b111111,0b1111111,0b11111111};
unsigned 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
pinMode(TASTER, INPUT); // UserButton
GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
pinMode(PC11,OUTPUT); // Einer
pinMode(PC12,OUTPUT); // Zehner
startMessungTimer.setPrescaleFactor(32000); // 1ms Takt
startMessungTimer.setOverflow(300); // 300 ms
startMessungTimer.attachInterrupt(isr_startMessung);
startMessungTimer.resume(); // Timer starten
messTimer.setPrescaleFactor(32); // 1 µs Takt
messTimer.setOverflow(12000); // bei mehr als ? m
messTimer.attachInterrupt(isr_messTimer_overflow); // wenn Messung zu lange dauert
attachInterrupt (digitalPinToInterrupt (ECHO), isr_echo_change, CHANGE); // wenn sich am ECHO-Pin was tut
ausgebenTimer.setPrescaleFactor(32000); // 1 ms Takt
ausgebenTimer.setOverflow(10); // alle 10 ms 100 Hz
ausgebenTimer.attachInterrupt(isr_ausgeben);
attachInterrupt (digitalPinToInterrupt (TASTER), isr_taster, FALLING); // wenn Taster gedrückt wird
}
void isr_startMessung(){ // alle 300 ms eine neue Messung durch ISR ausgelöst
if(zustand==MESSEN){
starteMessung();
zustand = MESSUNG;
}
}
bool fehlmessung=false;
void isr_messTimer_overflow(){ // Wenn Pulsmessung zu lange dauert
fehlmessung=true;
zustand = MESSEN_ENTRY;
}
void isr_echo_change(){ // Wenn sich am ECHO-Pin was tut
if (zustand!=MESSUNG)return; // keine Messung
if(ECHO_RISE){
messTimer.setCount(0); // Timer zurück setzen
} else { // Falling
auswertenMessung();
zustand = MESSEN_ENTRY;
}
}
void starteMessung(){ // Impuls senden und messTimer klar machen
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(20); // 10 µs minimal, 20 zur Sicherheit
digitalWrite(TRIGGER, LOW); // Jetzt fängt die Messung an
messTimer.setCount(0);
messTimer.resume();
}
void auswertenMessung(){ // Messung erfolgreich auswerten
entfernung = (entfernung + messTimer.getCount()*0.3434/2)/2; // Berechnung in mm
Serial.printf("Entfernung:%d\n",entfernung); // Ausgabe serieller Plotter
fehlmessung=false;
}
void isr_ausgeben(){ // 100Hz
static bool einer = true; // Einerstelle ist dran
int ausgabe;
if(fehlmessung){
GPIOC->ODR = bcd_7seg[10] | (einer?(1<<11):(1<<12));
}
else if(entfernung <= 160){
GPIOC->ODR = leuchtband[entfernung/20];
} else{
ausgabe = (entfernung+50)/100; // in Dezimeter runden
if (einer){
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
}
}
einer = !einer;
}
void isr_taster(){
if(zustand==RUHE){
zustand=MESSEN_ENTRY;
ausgebenTimer.resume(); // Ausgabe starten
} else{
zustand=RUHE_ENTRY;
}
}
void anzeigeAus(){
GPIOC->ODR = 0;
}
void loop(){
switch(zustand){
case RUHE_ENTRY:
ausgebenTimer.pause();
anzeigeAus();
zustand=RUHE;
break;
case MESSEN_ENTRY:
messTimer.pause();
zustand = MESSEN;
break;
}
}