1.5 Timer

Dank der STM32-Arduino HardwareTimer library wird man von den nicht ganz einfachen Details der STM32 Timer zunächst verschont.
Info: In der MBED-Umgebung könnte das so aussehen: Ticker Dort wird mit Threads gearbeitet, die schlafen gelegt werden, erinnert mich an Java..
Hier nun der Weg mit STM32-Arduino. (Unterschied zu Atmel-AVR bei Register Compare, hier kein +1 zu Compareregister bei Zeitberechnung, Rücksetzen und ISR-Aufruf erfolgt offensichtlich sofort bei Erreichen des Vergleichswerts, nicht erst mit dem nächsten Systemtakt).

Timer vereinfachte Darstellung
Timer, eine vereinfachte Darstellung

Ein Timer ist ein feines Stück eingebauter Hardware in einem Mikrocontroller, nach einer einstellbaren Zeit kann er z.B. einen Interrupt auslösen. Als Zeitbasis braucht er einen Timer-Takt (CK_PSC), hier sind es 32 MHz (andere Taktquellen sind einstellbar). Um z.B. jede Sekunde einen Interrupt auslösen zu können bräuchte man einen Zähler der bis 32 Millionen zählt (wie viele Bit müsste der haben?). Statt dessen werden ein Vorteiler (Prescaler) mit 16 Bit und ein 16 Bit Zähler (CTR DIV 65536) kombiniert, die einstellbaren Werte der Hardware-Register für beide können daher 65535 nicht überschreiten. Der Prescaler (Vorteiler) teilt den Timertakt mit einem einstellbaren Faktor (1..65536) (PSC-Registerwert 0..65535) und mit diesem langsameren Takt zählt ein Zähler bis zu einem vorgegebenen Wert im Auto-Reload Register ARR. Beim Erreichen des ARR-Wertes wird der Zähler wieder auf 0 gesetzt und ein Timer-Interrupt kann auslöst werden. In der STM32-API wird dafür die Bezeichnung Overflow eingeführt:
$ ARR = Overflow-1 $
Beispiel: Overflow hat den Wert 2, es wird ARR auf 1 gesetzt. Das bewirkt beim Erreichen des Zählerwertes 1 ein Zurückstellen auf 0 und das Setzen eines Interrupt-Flags. Zwei Zählertakte sind dabei vergangen. Overflow gibt also die Anzahl der Zählertakte bis zum Zurücksetzen an. Ein Overflowwert von 1 (ARR=0) scheint allerdings nicht zu funktionieren.
AVR-Bemerkung: Funktioniert wie CTC.
Es gibt 3 Varianten um den Timer mit setOverflow() ein zu stellen:

  • Ticks: z.B. setOverflow(10000, TICK_FORMAT); nach 10000 Zählertakten wird ISR ausgelöst, dazu muss auch der Prescaler gesetzt werden..
  • Hertz: z.B. setOverflow(10000, HERTZ_FORMAT); die ISR wird mit 10kHz also alle 100µs ausgelöst, der Prescaler wird automatisch eingestellt.
  • µs: z.B. setOverflow(10000, MICROSEC_FORMAT); nach 10000µs=10ms wird ISR auslöst, der Prescaler wird automatisch eingestellt.

TICK_FORMAT ausprobieren und berechnen

Beim TICK_FORMAT müssen Prescaler und Overflow-Wert des Zählers selber eingestellt werden. Ich habe die zu erwartende Frequenz an der LED berechnet und mit diesem Beispielcode einige Messungen durchgeführt.

#define LED_B PA5 // D13 Led auf dem Board
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
void ISR_blinken(){
  digitalWrite(LED_B,!digitalRead(LED_B)); // LED invertieren
}
void setup() {
  pinMode(LED_B,OUTPUT); // BoardLED
  mytimer.setOverflow(100, TICK_FORMAT); // 2..65535
  mytimer.setPrescaleFactor(32000);  // 1..65535
  mytimer.attachInterrupt(ISR_blinken); //Timer IR aktivieren und Sprung zur ISR// put your setup code here, to run once:
  mytimer.resume();
}
void loop() {
}

$ Frequenz_{LED} = Frequenz_{TimerCLK} / PreFaktor / Overflow / 2 = 32 * 10^6 Hz / 32000 / 100 / 2 = 5 Hz $

TimerCLKPrescalerOverflowFrequenz berechnetFrequenz gemessen
32 MHz320001005 Hz5 Hz
32 MHz320001050 Hz50 Hz
32 MHz320002250 Hz250 Hz
32 MHz320001500 HzKein Signal!
32 MHz655352122,07 Hz122,07 Hz
32 MHz655362122,07 Hz122,07 Hz
32 MHz65535381,38 Hz81,38 Hz
32 MHz165535244,14 Hz244,14 Hz
32 MHz165536244,14 Hz244,14 Hz
32 MHz1100160 kHz48,8 kHz (ISR kam nicht hinterher)
LED-Frequenz Berechnungen und Messungen

Erstellen Sie eine Tabellenkalkulation für die Berechnung der Frequenzen.

Die LED soll nun mit 0,5 Hz blinken, 1 s an und 1 s aus. Ermitteln Sie passende Werte für Prescaler und Overflow.

Die Erfindung der Taktzahl

Die Timer-ISR soll alle 10 ms aufgerufen werden, ermittle passende Werte für Prescaler und Overflow.

$ Zeit_{ISR} = Prescaler * Overflow / TimerCLK $
$ \Leftrightarrow Zeit_{ISR} * TimerCLK = Prescaler * Overflow = \textbf{Taktzahl} $

Letztlich werden Takte gezählt und die Anzahl der Takte auf Prescaler und Overflow geschickt verteilt.

$ \textbf{Taktzahl} = Zeit_{ISR} * TimerCLK $
$ \textbf{Taktzahl} = Prescaler * Overflow $

Berechnen wir die Zahl der zu zählenden Takte:

$ \textbf{Taktzahl} = 10 ms * 32 MHz = 10 *10^{-3} s * 32 * 10^6 Hz = 320 * 10^3 = 320.000 $

Nun die Qual der Wahl: Welche Kombination von Prescaler und Overflow die 320.000 als Produkt ergibt sollen wir nun wählen? Zeit um sich die beiden anderen Timer-Formate an zu sehen!

Einfach mit Hertz

Hier ein Testprogramm um das Hertz-Format zu erkunden, dabei wird Prescaler und Overflow passend eingestellt. ISR_blinken() wird am Anfang jede Sekunde aufgerufen. Durch Druck auf den UserButton verdoppelt sich die Frequenz und die eingestellten Werte werden angezeigt.

#define LED_B PA5 // D13 Led auf dem Board
#define T_B PC13  // Entpreller UserButton auf dem Board
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timerauswahl
int isrFrequenz = 1;
void ISR_blinken(){
  digitalWrite(LED_B,!digitalRead(LED_B));
}
void ISR_gedruecktT_B(){ // Interrupt Service Routine
  isrFrequenz*=2;
  stelleTimer();
}
void stelleTimer(){ // Timer einstellen und Werte anzeigen
  mytimer.setOverflow(isrFrequenz, HERTZ_FORMAT);
  Serial.printf("ISR-Freq.: %3d Sytemtakt %d Prescaler %5d Overflow %5d \n", isrFrequenz,mytimer.getTimerClkFreq(),mytimer.getPrescaleFactor(),mytimer.getOverflow());
}
void setup() {
  Serial.begin (115200); //Serielle kommunikation starten
  pinMode(LED_B,OUTPUT); // BoardLED
  stelleTimer();
  mytimer.attachInterrupt(ISR_blinken); // Timer ISR einstellen
  mytimer.resume();  // Timer aktivieren
  pinMode(T_B,INPUT);
  attachInterrupt (digitalPinToInterrupt (T_B), ISR_gedruecktT_B, FALLING);
}
void loop() {
}
ISR-Freq.:   1 Hz TimerCLK: 32 MHz Prescaler:   489 Overflow: 65439
ISR-Freq.:   2 Hz TimerCLK: 32 MHz Prescaler:   245 Overflow: 65306
ISR-Freq.:   4 Hz TimerCLK: 32 MHz Prescaler:   123 Overflow: 65040
ISR-Freq.:   8 Hz TimerCLK: 32 MHz Prescaler:    62 Overflow: 64516

Es fällt auf, dass der Prescaler möglichst klein und der Overflow möglichst groß gewählt wurde. Diese Strategie ist geschickt, wenn damit Pulsweitenmodulation (PWM) oder Zeitmessung mit dem Zähler gemacht werden sollen. Je größer der Prescaler, desto größer sind die Zeithäppchen, die der Zähler bekommt und desto ungenauer lässt sich mit Overflow die Zeit damit einstellen. Also möglichst kleine Zeithäppchen -> Prescaler minimal.

$ \textbf{Prescaler} = Taktzahl / 65536 = 32 * 10^6 / 65536 = 488,28 $

Einfach Runden ist keine gute Idee, denn dann wären die Zeithäppchen hier zu klein, es müssten zu viele davon gezählt werden:

$ \textbf{Overflow} = Taktzahl / Prescaler = 32 * 10^6 / 488 = 65573 > 65535 ! $

Hier also Aufrunden auf 489.

$ \textbf{Overflow} = Taktzahl / Prescaler = 32 * 10^6 / 489 = 65439,67 < 65535 ! $

Auf welchen Wert Overflow einstellen? Ich würde runden, allerdings scheint die API an dieser Stelle bei Hertz- und Mikrosekunden-Format abzurunden, es wird mit Integer gerechnet. Probe machen:

$ \textbf{FreqISR} = TimerCLK / Prescaler / Overflow = \frac{32 MHz} {489 * 65439} = \frac{32 MHz} {31999671} = 1,00001 Hz $

$ \textbf{ZeitISR} = Prescaler * Overflow / TimerCLK = \frac{489 * 65439} {32 MHz} = 0,99998 s $

Erstellen Sie eine Tabellenkalkulation zur Berechnung der Werte.

Zustandsdiagramm
Zustandsdiagramm

MICROSEC_FORMAT

#define LED_B PA5 // D13 Led auf dem Board
#define T_B PC13  // Entpreller UserButton auf dem Board
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timerauswahl
int isrMicrosec = 10000;
void ISR_blinken(){
  digitalWrite(LED_B,!digitalRead(LED_B));
}
void ISR_gedruecktT_B(){ // Interrupt Service Routine
  isrMicrosec*=2;
  stelleTimer();
}
void stelleTimer(){ // Timer einstellen und Werte anzeigen
  mytimer.setOverflow(isrMicrosec, MICROSEC_FORMAT);
  Serial.printf("Zeit-ISR: %8d us TimerCLK: %d MHz Prescaler: %5d Overflow: %5d\n",isrMicrosec,mytimer.getTimerClkFreq()/1000000,mytimer.getPrescaleFactor(),mytimer.getOverflow());
}
void setup() {
  Serial.begin (115200); //Serielle kommunikation starten
  pinMode(LED_B,OUTPUT); // BoardLED
  stelleTimer();
  mytimer.attachInterrupt(ISR_blinken); // Timer ISR einstellen
  mytimer.resume();  // Timer aktivieren
  pinMode(T_B,INPUT);
  attachInterrupt (digitalPinToInterrupt (T_B), ISR_gedruecktT_B, FALLING);
}
void loop() {
}
Zeit-ISR:    10000 us TimerCLK: 32 MHz Prescaler:     5 Overflow: 64000
Zeit-ISR:    20000 us TimerCLK: 32 MHz Prescaler:    10 Overflow: 64000
Zeit-ISR:    40000 us TimerCLK: 32 MHz Prescaler:    20 Overflow: 64000
Zeit-ISR:    80000 us TimerCLK: 32 MHz Prescaler:    40 Overflow: 64000
Zeit-ISR:   160000 us TimerCLK: 32 MHz Prescaler:    79 Overflow: 64810
Zeit-ISR:   320000 us TimerCLK: 32 MHz Prescaler:   157 Overflow: 65222
Zeit-ISR:   640000 us TimerCLK: 32 MHz Prescaler:   313 Overflow: 65431
Zeit-ISR:  1280000 us TimerCLK: 32 MHz Prescaler:   626 Overflow: 65431
Zeit-ISR:  2560000 us TimerCLK: 32 MHz Prescaler:  1251 Overflow: 65483
Zeit-ISR:  5120000 us TimerCLK: 32 MHz Prescaler:  2501 Overflow: 65509
Zeit-ISR: 10240000 us TimerCLK: 32 MHz Prescaler:  5001 Overflow: 65522

Wie wird jetzt Prescaler und Overflow berechnet? Erstellen Sie eine Tabellenkalkulation. Werden die Werte von setOverflow() optimal berechnet?

Gleisanlage (HP16-1)

Gleisanlage [Bild von Matthias Kohler] 
Gleisanlage [Bild von Matthias Kohler] 

Für den Bahnhof einer Modellbahnanlage soll eine Steuerung entworfen werden. Der Bahnhof besitzt 6 Gleise. Diese Gleise werden über die Weichen W0 (PC0) bis W4 (PC4) angefahren. Eine Lichtschranke LS (PC13) erfasst vor der ersten Weiche die einfahrenden Züge. Die Züge werden zyklisch aufsteigend (nach Gleis 5 wieder auf Gleis 0) auf die 6 Gleise des Bahnhofs verteilt. (Auffahrunfälle werden durch die hier nicht betrachtete Signalanlage verhindert). Die gelben Zahlen geben die Stellung der Weichen an. Bei einer 0 fährt der Zug nach “oben”, bei einer 1 nach “unten”. Nach dem Start befindet sich das Programm im “Automatikmodus” bei dem alle 5 Sekunden ISR_5sekunden() ein Gleis durch wechsleGleis() zyklisch weitergeschaltet wird. In einem zweiten Modus “Interruptbetrieb” werden die Gleise bei Unterbrechung der Lichtschranke durch ISR_lichtschranke() weiter geschaltet. Der Wechsel zwischen den beiden Modi geschieht mit ISR_moduswechsel() die bei Druck auf Taste PA1 ausgelöst wird. Folgender Quellcode ist gegeben:

enum {AUTOMATIKMODUS,INTERRUPTBETRIEB} zustand=AUTOMATIKMODUS; 
volatile int gleis = 0; // wird in ISR verändert daher volatile
const int fahrstrasse[] = {0b00000, 0b10000, 0b00001, 0b10001, 0b00011, 0b00111};

static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl

# define LICHTSCHRANKE PC13  // Entpreller lowaktiver UserButton auf dem Board
# define MODUSWECHSEL PA1    // Nicht entprellter highaktiver Taster

void wechsleGleis(){ // gleis eins weiter schalten: 0,1,2,3,4,5->0,1...
 
}
void ISR_lichtschranke(){ // im INTERRUPTBETRIEB Gleis weiterschalten
  
}
void ISR_moduswechsel(){ // zwischen AUTOMATIKMODUS und INTERRUPTBETRIEB wechseln
  
}
void ISR_5sekunden(){ // im AUTOMATIKMODUS Gleis weiter schalten
  
}
void setup() {
  pinMode(PC0, OUTPUT);           // ohne diese Zeile klappts nicht mit MODER
  GPIOC->MODER = 0x5555;          // PC0..PC7 als Ausgang
  pinMode(LICHTSCHRANKE, INPUT);  // nicht notwendig?
  pinMode(MODUSWECHSEL, INPUT_PULLDOWN);   // notwendig wegen PullDown einschalten
  
}
void loop() {
}

Erstellen Sie ein Zustandsdiagramm.

Lösungsvorschlag Zustandsdiagramm
Gleisanlage Zustandsdiagramm
Gleisanlage Zustandsdiagramm

Vervollständigen Sie den Quellcode für:

  • wechsleGleis() die Variable gleis wird zum nächsten Gleis weitergeschaltet und die Weichen gestellt.
  • setup() Timer-ISR für 5 Sekunden einstellen, ISRs für Lichtschranke und Moduswechsel (Entprellen nicht notwendig).
  • ISR_lichtschranke(), ISR_moduswechsel(), ISR_5sekunden()
Lösungsvorschlag Quellcode
enum {AUTOMATIKMODUS,INTERRUPTBETRIEB} zustand=AUTOMATIKMODUS; 
volatile int gleis = 0; // wird in ISR verändert daher volatile
const int fahrstrasse[] = {0b00000, 0b10000, 0b00001, 0b10001, 0b00011, 0b00111};

static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl

# define LICHTSCHRANKE PC13  // Entpreller lowaktiver UserButton auf dem Board
# define MODUSWECHSEL PA1    // Nicht entprellter highaktiver Taster

void wechsleGleis(){ // gleis eins weiter schalten: 0,1,2,3,4,5->0,1...
  gleis = (gleis + 1) % 6;
  GPIOC->ODR = fahrstrasse[gleis];
}
void ISR_lichtschranke(){ // im INTERRUPTBETRIEB Gleis weiterschalten
  switch(zustand){
    case INTERRUPTBETRIEB:
      wechsleGleis();
      break;
  }
}
void ISR_moduswechsel(){ // zwischen AUTOMATIKMODUS und INTERRUPTBETRIEB wechseln
  switch(zustand){
    case AUTOMATIKMODUS:
      zustand = INTERRUPTBETRIEB;
      break;
    case INTERRUPTBETRIEB:
      zustand = AUTOMATIKMODUS;
      break;
  }
}
void ISR_5sekunden(){ // im AUTOMATIKMODUS Gleis weiter schalten
  switch(zustand){
    case AUTOMATIKMODUS:
      wechsleGleis();
      break;
  }
}
void setup() {
  pinMode(PC0, OUTPUT);           // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555;          // PC0..PC7 als Ausgang
  pinMode(LICHTSCHRANKE, INPUT);  
  pinMode(MODUSWECHSEL, INPUT_PULLDOWN);   // Start
  attachInterrupt (digitalPinToInterrupt (LICHTSCHRANKE), ISR_lichtschranke, FALLING);
  attachInterrupt (digitalPinToInterrupt (MODUSWECHSEL), ISR_moduswechsel, RISING);
  mytimer.setOverflow(5000000, MICROSEC_FORMAT); // 5 sec = 5 000 000 µs
  mytimer.attachInterrupt(ISR_5sekunden); //Timer IR aktivieren und Sprung zur ISR
  mytimer.resume();   //Timer starten
}
void loop() {
}

Zusatzaufgabe: Im Automatikbetrieb soll nur noch 3 Mal jedes Gleis geschaltet und dann in den Interruptbetrieb gewechselt werden. Beim Moduswechsel in den Automatikbetrieb wird der Gleiswechselzähler wieder auf 0 gesetzt. Entwerfen Sie Zustandsdiagramm und Quellcode.

Lösungsvorschlag Zustandsdiagramm
Gleisanlage Zustandsdiagramm
Gleisanlage Zustandsdiagramm
Lösungsvorschlag Quellcode kompatibel mit PlatformIO
#include <Arduino.h>
enum zustandstyp {AUTOMATIKMODUS,INTERRUPTBETRIEB};
zustandstyp zustand=AUTOMATIKMODUS; 
volatile int gleis = 0; // wird in ISR verändert daher volatile
int gleiswechsel=0; // scheint ohne volatile zu funktionieren 
const int fahrstrasse[] = {0b00000, 0b10000, 0b00001, 0b10001, 0b00011, 0b00111};

static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl

# define LICHTSCHRANKE PC13  // Entpreller lowaktiver UserButton auf dem Board
# define MODUSWECHSEL PA1    // Nicht entprellter highaktiver Taster

void wechsleGleis(){ // gleis eins weiter schalten: 0,1,2,3,4,5->0,1...
  gleis = (gleis + 1) % 6;
  GPIOC->ODR = fahrstrasse[gleis];
}
void ISR_lichtschranke(){ // im INTERRUPTBETRIEB Gleis weiterschalten
  switch(zustand){
    case INTERRUPTBETRIEB:
      wechsleGleis();
      break;
  }
}
void ISR_moduswechsel(){ // zwischen AUTOMATIKMODUS und INTERRUPTBETRIEB wechseln
  switch(zustand){
    case AUTOMATIKMODUS:
      zustand = INTERRUPTBETRIEB;
      break;
    case INTERRUPTBETRIEB:
      gleiswechsel=0;
      zustand = AUTOMATIKMODUS;
      break;
  }
}
void ISR_5sekunden(){ // im AUTOMATIKMODUS Gleis weiter schalten
  switch(zustand){
    case AUTOMATIKMODUS:
      if(gleiswechsel>=18){
        zustand=INTERRUPTBETRIEB;
      }
      else{
        wechsleGleis();
        gleiswechsel++;
      }
      break;
  }
}
void setup() {
  pinMode(PC0, OUTPUT);           // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555;          // PC0..PC7 als Ausgang
  pinMode(LICHTSCHRANKE, INPUT);  
  pinMode(MODUSWECHSEL, INPUT_PULLDOWN);   // Start
  attachInterrupt (digitalPinToInterrupt (LICHTSCHRANKE), ISR_lichtschranke, FALLING);
  attachInterrupt (digitalPinToInterrupt (MODUSWECHSEL), ISR_moduswechsel, RISING);
  mytimer.setOverflow(5000000, MICROSEC_FORMAT); // 5 sec = 5 000 000 µs
  mytimer.attachInterrupt(ISR_5sekunden); //Timer IR aktivieren und Sprung zur ISR
  mytimer.resume();   //Timer starten
}
void loop() {
}

Bei Dir piepts wohl? Tonausgabe mit PWM!

Piezo-Lautsprecher an PA7/D11
Piezo-Lautsprecher an PA7/D11

Ein Piezo-Lautsprecher soll einen Ton mit Frequenz f = 440 Hz ausgeben. Er ist zwischen GND und PA7/D11 angeschlossen. Am Pin wird dazu ein Rechtecksignal erzeugt. Wie lange ist die Periodendauer T des Signals? Wie lange ist der Pin 1 bzw. 0?
$ T = \frac{1}{f}= \frac{1}{440Hz} = 2.27 ms $
So könnte das realisiert werden:

  • Mit delay()? Zu grob
  • delayMicroseconds()!
    Erstellen Sie zur Übung ein Programm..
  • Mit Timer-ISR
    Erstellen Sie zur Übung ein Programm..
  • micros()
    Erstellen Sie zur Übung ein Programm..
  • Mit Timer-PWM Hardware

PulsWeitenModulation (PWM) haben Sie schon bei Analog-Ausgabe kennengelernt. An bestimmten Pins des µC können von der Timer-Hardware Signale ausgegeben werden. Dadurch muss sich nicht die CPU darum kümmern. So funktioniert es prinzipiell:

PWM mit Timer-Hardware
PWM mit Timer-Hardware

Den Timer bis zu einem Wert Overflow zählen lassen und dann wieder auf 0 springen, wie beim Interrupt. Der Overflow-Wert gibt die Periodendauer vor. Beim Sprung auf 0 den Ausgabe-Pin auf 1 schalten. Ein Vergleichsregister das CaptionCompareRegister (CRR) gibt den Wert vor bei dem der Ausgabe-Pin wieder auf 0 geschaltet wird, es gibt die Einschaltdauer, Impulsdauer vor. Das Verhältnis der Impulsdauer zur Periodendauer ist der Tastgrad (engl. dutycycle). 50% dutycycle bedeutet 50% der Zeit ist der Ausgabepin 1 50% 0.

Der Lautsprecher sei an PA7/D11 angeschlossen. Welcher Timer und welcher Kanal kommt dafür in Frage? Gewählt: TIM3_CH2. In der HardwareTimer library nachschauen wie das geht und ordentliches Testprogramm schreiben:

#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

# define USER_BTN PC13  // Entpreller lowaktiver UserButton auf dem Board
# define PIEZO_PIN PA7  // Lautsprecher an PA7/D11 -> TIM3_CH2
# define KANAL 2        // CH2
int einschaltdauer = 50;// 50% dutycycle halbe Zeit an halbe aus
int frequenz = 440;     // 440 Hz
void ausgebenTimerWerte(){
  Serial.printf("Prescaler %5d Overflow %5d Compare %5d\n",mytimer.getPrescaleFactor(),mytimer.getOverflow(),mytimer.getCaptureCompare(KANAL));
}
void isr_userB(){  // Werte verändern und testen
  frequenz+=10;
  mytimer.setPWM(KANAL, PIEZO_PIN, frequenz, einschaltdauer);
  ausgebenTimerWerte();
}
void setup() {
  Serial.begin (9600); //Serielle Kommunikation starten
  pinMode(USER_BTN, INPUT);
  attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
  mytimer.setPWM(KANAL, PIEZO_PIN, frequenz, einschaltdauer);
  ausgebenTimerWerte();
}
void loop() {
}

Nach Programmstart piepst es und am seriellen Monitor wird das ausgegeben:
Prescaler 2 Overflow 35555 Compare 17777
Prima, die API nimmt uns die Rechenarbeit ab.
Leider verstummt bei Druck auf den User-Button der Ton, ein zweiter Aufruf von setPWM() funktioniert nicht zum Verändern der Parameter. Hier der Quellcode der API-Methode:

void HardwareTimer::setPWM(uint32_t channel, PinName pin, uint32_t frequency, uint32_t dutycycle, callback_function_t PeriodCallback, callback_function_t CompareCallback)
{
  setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin);
  setOverflow(frequency, HERTZ_FORMAT);
  setCaptureCompare(channel, dutycycle, PERCENT_COMPARE_FORMAT);
  if (PeriodCallback) {
    attachInterrupt(PeriodCallback);
  }
  if (CompareCallback) {
    attachInterrupt(channel, CompareCallback);
  }
  resume();
}

Verantwortlich ist ein zweiter Aufruf der setMode()-Methode, also mit setOverflow() bzw. setCaptureCompare() die Werte in isr_userB() einzeln verändern:

void isr_userB(){  // Werte verändern und testen
  frequenz+=10;
  mytimer.setOverflow(frequenz,HERTZ_FORMAT); // Frequenz einstellen
  mytimer.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen 0..100
  ausgebenTimerWerte();
}

PWM mit 0,1,99 und 100% ?

#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

# define USER_BTN PC13  // Entpreller lowaktiver UserButton auf dem Board
# define PIEZO_PIN PA7  // Lautsprecher an PA7/D11 -> TIM3_CH2
# define KANAL 2        // CH2
int prozent = 50;// 50% dutycycle halbe Zeit an halbe aus
int frequenz = 440;     // 440 Hz
void ausgebenTimerWerte(){
  Serial.printf("Prescaler %5d Overflow %5d Compare %5d\n",mytimer.getPrescaleFactor(),mytimer.getOverflow(),mytimer.getCaptureCompare(KANAL));
}
void isr_userB(){  // Werte verändern und testen
  switch (prozent){
    case 0: prozent=1; break;
    case 1: prozent=99; break;
    case 99: prozent=100; break;
    default: prozent=0;
  }
  mytimer.setCaptureCompare(KANAL,prozent,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen
  ausgebenTimerWerte();
}
void setup() {
  Serial.begin (9600); //Serielle Kommunikation starten
  pinMode(USER_BTN, INPUT);
  attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
  mytimer.setPWM(KANAL, PIEZO_PIN, frequenz, prozent);
  ausgebenTimerWerte();
}
void loop() {
}

Bei Impulsbreite 0% ist der PWM-Ausgang dauerhaft low, bei Impulsbreite 100% ist der PWM-Ausgang dauerhaft high. Betrachten Sie auch die Ausgaben auf dem Seriellen Monitor.

Alle meine Entchen spielen

Die Noten in eine Tonhöhe also Frequenz umsetzen. Hier eine mögliche Umsetzung [Gleichstufige Stimmung]:

index0123456789101213
Tonc’cis’d’es’e’f’fis’g’as’a’b’h’c”
Frequenz262277293311330349370392415440466494523
Frequenzen der Töne
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

#define USER_BTN PC13  // Entpreller lowaktiver UserButton auf dem Board
#define PIEZO_PIN PA7  // Lautsprecher an PA7/D11 -> TIM3_CH2
#define KANAL 2        // CH2
#define HUELLKURVE 0   // besserer Sound mit Hüllkurve
//const unsigned int ton[] ={264,275,297,317,330,352,367,396,422,440,475,495}; // Frequenzen
const unsigned int ton[] ={262,277,293,311,330,349,370,392,415,440,466,494}; // Frequenzen
const unsigned char kurve[]  = {25,13,8,4,2,1,0}; // Huellkurve
const unsigned char melodie[]  ={0,2,4,5,7,7,9,9,9,9,7,9,9,9,9,7,5,5,5,5,4,4,7,7,7,7,0}; // alle meine Entchen
const unsigned char lange[]    ={2,2,2,2,4,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,4,4,2,2,2,2,4}; // Tonlaengen

void isr_userB(){  // Abschalten können!
  static bool spielt = true;
  if (spielt){
    mytimer.pause();
    spielt=false;
  } else{
    mytimer.resume();
    spielt=true;
  }
}
void setup() {
  pinMode(USER_BTN, INPUT);
  attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
  mytimer.setPWM(KANAL, PIEZO_PIN, 440, 50); // Initialisierung mit 440 Hz
}
void loop() {
  unsigned char d,i,j,l;
  unsigned int k;
  for (i=0;i<sizeof(melodie);i++){ // spiele die Melodie
    k = ton[melodie[i]]; // hole die Frequenz
    mytimer.setOverflow(k,HERTZ_FORMAT);  // Frequenz einstellen
    mytimer.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Signalbreite einstellen
    delay(20);
#if HUELLKURVE == 1    // mit Hüllkurve
    for(j=0;j<sizeof(kurve);j++){ // Huellkurve anwenden: Signalbreite schmäler
       mytimer.setCaptureCompare(KANAL,kurve[j],PERCENT_COMPARE_FORMAT);
      for (d=0;d<lange[i];d++){ // Tonlaenge
        delay(20);
      }
    }
 #else                 // ohne Hüllkurve
    for (d=0;d<lange[i];d++){ // Tonlaenge
      delay(100);
    }
    mytimer.setCaptureCompare(KANAL,0,PERCENT_COMPARE_FORMAT);
    delay(80);          // Pause bis naechster Ton
  #endif  
  }
  delay(300);  // Pause bis Melodie wieder startet
}

Mit Hüllkurve hört es sich besser an: #define HUELLKURVE 1 setzen.
Durch eine geringere Pulsbreite kann die Lautsprecherausgabe leiser werden..
Wie ich Sie kenne werden Sie nun begeistert mit neuen Melodien den Unterrichtsraum beglücken 😜.

Gitarrenstimmhilfe (HP15-2)

Zum Stimmen von Gitarren soll ein µC Referenzfrequenzen an PA7/D11 ausgeben. Das Signal entspricht den Tonfrequenzen der sechs Gitarrensaiten. In der Tabelle sind die Frequenzen und die Ausgaben auf der 7-Segment-LED-Anzeige aufgeführt.

Gitarrenstimmhilfe
Gitarrenstimmhilfe

Die Tonauswahl soll über den prellfreien Low-aktiven Taster an PC13 erfolgen. Der Reset-Taster setzt die Stimmhilfe wieder zurück. Nach Einschalten/Reset wird kein Ton ausgegeben und die Anzeige leuchtet nicht. Mit jedem Tastendruck auf PC13 wird der jeweils nächste Ton ausgewählt. Nach e’ soll die Auswahl wieder mit E beginnen:
(Ruhe->E->A->d->g->h->e’->E->…).
Entwickeln Sie ein Programm:

  • Die Töne sollen mit Timer-PWM erzeugt werden.
  • Der Tastendruck wird per ISR verarbeitet.
  • Die Main-Loop soll leer sein.

Ermitteln Sie die zu erwartenden Werte für Prescaler und Overflow für die E-Saite bei einem Timer-Takt von 32MHz.
Herleitung gefordert, überprüfen Sie Ihr Ergebnis mit Ausgabe auf seriellem Monitor.

Kaffeeautomat (HP12-2)

Ein einfacher Kaffeeautomat soll entwickelt werden. Mit dem Taster EIN_AUS isr_einAus() wird der Automat eingeschaltet und kann jederzeit wieder ausgeschaltet werden auch mitten in einer Aktion. Beim Ausschalten werden alle Ausgänge auf 0 gesetzt. Es sind Zustände vorgegeben: AUS,HEIZEN_ENTRY,HEIZEN,HEISS,PUMPEN. Nach dem Einschalten wird das Wasser für 15 Sekunden aufgeheizt (HEIZUNG=1) dabei blinkt die LED mit 4 Hz. Erst nach dem Heizen leuchtet die LED dauernd, die Heizung ist aus und es kann eine kleine Tasse mit KL_TA oder eine große Tasse mit GR_TA angefordert werden. Bei einer kleinen Tasse läuft die Pumpe über TA_FUELLEN 5 Sekunden, bei einer großen Tasse 10 Sekunden. Während die Pumpe läuft blinkt die LED mit 1 Hz. Nach Ausgabe einer Tasse wird das Wasser wieder aufgeheizt.
Hinweis: Die LED soll blinken und Dauerleuchten. Das LED-Blinken wird mit einer isr_blinken() und Timer3 erzeugt.

TasterPortPinPortPinAusgang
EIN_AUSPC13 (LowAktiv)PC0HEIZUNG
KL_TAPA1 (HighAktiv)PC1PUMPE
GR_TAPA6 (HighAktiv)PC2LED
µC Pinbelegung
  1. Für das Einstellen des LED-Blinkens wird ein Unterprogramm blinkenLED(int Frequenz) verwendet, sie stellt den Timer3 entsprechend ein.
  2. Begründen Sie welche Taster entprellt sein müssen.
  3. Für die 15,10,5 Sekunden wird ein Wecker mit stelleWecker(int n) gestellt und mit abgelaufenWecker():bool abgefragt. Der Wecker verwendet die millis()-Funktion
  4. Entwickeln Sie ein Zustandsdiagramm und daraus die Software.
  5. Bonus: Nach 20 Sekunden im Zustand HEISS wird das Wasser kalt und muss wieder erhitzt werden. Nach 2 mal Nachheizen ohne Benutzung schaltet sich der Automat aus. Erweitern Sie ihr Zustandsdiagramm und Programm.
Mögliche Lösung Kaffeeautomat Zustandsdiagramm
Kaffeeautomat Zustandsdiagramm
Kaffeeautomat Zustandsdiagramm
Mögliche Lösung Kaffeeautomat
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

# define EIN_AUS PC13  // Entpreller lowaktiver UserButton auf dem Board
# define KL_TA PA1 
# define GR_TA PA6
# define HEIZUNG PC0
# define PUMPE PC1
# define LED PC2
enum zustandstyp {AUS,HEIZEN_ENTRY,HEIZEN,HEISS,PUMPEN};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken

void auschaltenAlles(){
  mytimer.pause(); // keine ISRs mehr ausloesen
  digitalWrite(LED,LOW);
  digitalWrite(HEIZUNG,LOW);
  digitalWrite(PUMPE,LOW);
}
void stelleWecker(int n){
  weckzeit = millis()+n*1000;
}
bool abgelaufenWecker(){
  return millis()>=weckzeit;
}
void isr_einAus(){
  if(zustand==AUS){
    zustand=HEIZEN_ENTRY;
  }
  else{
    zustand=AUS;
    auschaltenAlles();
  }
}
void isr_blinken(){
  digitalWrite(LED,!digitalRead(LED));
}

void blinkenLED(int n){  // Werte verändern und testen
  mytimer.setOverflow(n*2,HERTZ_FORMAT);
  mytimer.resume();
}
void einschaltenLED(){
  mytimer.pause();
  digitalWrite(LED,HIGH);
}
void setup() {
  pinMode(EIN_AUS, INPUT);
  pinMode(KL_TA, INPUT_PULLDOWN);
  pinMode(GR_TA, INPUT_PULLDOWN);
  pinMode(HEIZUNG, OUTPUT);
  pinMode(PUMPE, OUTPUT);
  pinMode(LED, OUTPUT);
  attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
  mytimer.attachInterrupt(isr_blinken); // Timer ISR einstellen
}
void loop() {
  switch (zustand){
    case HEIZEN_ENTRY:
      digitalWrite(HEIZUNG,HIGH);
      stelleWecker(15);
      blinkenLED(4);
      zustand=HEIZEN;
      break;
    case HEIZEN:
      if(abgelaufenWecker()){
        zustand=HEISS;
        digitalWrite(HEIZUNG,LOW);
        einschaltenLED();
      }
      break;
    case HEISS:
      if(digitalRead(KL_TA)){
        zustand=PUMPEN;
        stelleWecker(5);
        blinkenLED(1);
        digitalWrite(PUMPE,HIGH);
      }
      else if (digitalRead(GR_TA)){
        zustand=PUMPEN;
        stelleWecker(10);
        blinkenLED(1);
        digitalWrite(PUMPE,HIGH);
      }
      break;
    case PUMPEN:
      if(abgelaufenWecker()){
        zustand=HEIZEN_ENTRY;
        digitalWrite(PUMPE,LOW);
      }
      break;  
  }
}
Mögliche Lösung Kaffeeautomat Bonus Zustandsdiagramm
Kaffeeautomat Bonus Zustandsdiagramm
Kaffeeautomat Bonus Zustandsdiagramm
Mögliche Lösung Kaffeeautomat Bonus
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

# define EIN_AUS PC13  // Entpreller lowaktiver UserButton auf dem Board
# define KL_TA PA1 
# define GR_TA PA6
# define HEIZUNG PC0
# define PUMPE PC1
# define LED PC2
enum zustandstyp {AUS,HEIZEN_ENTRY,HEIZEN,HEISS,PUMPEN};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
int autoAus=0;

void auschaltenAlles(){
  mytimer.pause(); // keine ISRs mehr ausloesen
  digitalWrite(LED,LOW);
  digitalWrite(HEIZUNG,LOW);
  digitalWrite(PUMPE,LOW);
}
void stelleWecker(int n){
  weckzeit = millis()+n*1000;
}
bool abgelaufenWecker(){
  return millis()>=weckzeit;
}
void isr_einAus(){
  if(zustand==AUS){
    zustand=HEIZEN_ENTRY;
  }
  else{
    zustand=AUS;
    auschaltenAlles();
    autoAus=0;
  }
}
void isr_blinken(){
  digitalWrite(LED,!digitalRead(LED));
}

void blinkenLED(int n){  // Werte verändern und testen
  mytimer.setOverflow(n*2,HERTZ_FORMAT);
  mytimer.resume();
}
void einschaltenLED(){
  mytimer.pause();
  digitalWrite(LED,HIGH);
}
void setup() {
  pinMode(EIN_AUS, INPUT);
  pinMode(KL_TA, INPUT_PULLDOWN);
  pinMode(GR_TA, INPUT_PULLDOWN);
  pinMode(HEIZUNG, OUTPUT);
  pinMode(PUMPE, OUTPUT);
  pinMode(LED, OUTPUT);
  attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
  mytimer.attachInterrupt(isr_blinken); // Timer ISR einstellen
}
void loop() {
  switch (zustand){
    case HEIZEN_ENTRY:
      digitalWrite(HEIZUNG,HIGH);
      stelleWecker(15);
      blinkenLED(4);
      zustand=HEIZEN;
      break;
    case HEIZEN:
      if(autoAus>2){
        zustand=AUS;
        auschaltenAlles();
        autoAus=0;
      }
      if(abgelaufenWecker()){
        zustand=HEISS;
        digitalWrite(HEIZUNG,LOW);
        einschaltenLED();
        stelleWecker(20);
      }
      break;
    case HEISS:
      if(digitalRead(KL_TA)){
        zustand=PUMPEN;
        stelleWecker(5);
        blinkenLED(1);
        digitalWrite(PUMPE,HIGH);
      }
      else if(digitalRead(GR_TA)){
        zustand=PUMPEN;
        stelleWecker(10);
        blinkenLED(1);
        digitalWrite(PUMPE,HIGH);
      }
      else if(abgelaufenWecker()){
        zustand=HEIZEN_ENTRY;
        autoAus++;
      }
      break;
    case PUMPEN:
      if(abgelaufenWecker()){
        zustand=HEIZEN_ENTRY;
        digitalWrite(PUMPE,LOW);
        autoAus=0;
      }
      break;  
  }
}

Elektrische Zahnbürste (HP19-2)

Zur Unterstützung einer optimalen Zahnhygiene soll die Nutzerin einer elektrischen Zahnbürste motiviert werden die 4 Quadranten des Mundes jeweils 30 Sekunden zu putzen. Nach dem Einschalten mit Taster EIN_AUS leuchtet zunächst LEDA für den ersten Quadranten und der Motor wird eingeschaltet. Nach 29,5 Sekunden stoppt der Motor kurz für 0,5 Sekunden und danach wird für jeden weiteren Quadranten eine weitere LED eingeschaltet. Nach 2 Minuten geht der Motor 3 mal aus und wieder an. Vorgaben:
enum zustandstyp {AUS,AN_ENTRY,AN,STOPP,ZEIT_UM};
void ausgebenLED(int n)

Entwickeln Sie ein Zustandsdiagramm und eine Lösung.

TasterPortPinPortPinAusgang
EIN_AUSPC13 (LowAktiv, entprellt)PC0LEDA
MODUSPA1 (HighAktiv, prellt)PC1LEDB
PC2LEDC
PC3LEDD
PC6MOTOR
µC Pinbelegung
Mögliche Lösung Zustandsdiagramm
Zahnbürste Zustandsdiagramm
Zahnbürste Zustandsdiagramm
Mögliche Lösung Code
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

# define EIN_AUS PC13  // Entpreller lowaktiver UserButton auf dem Board
# define MODUS PA1 
# define MOTOR PC6

enum zustandstyp {AUS,AN_ENTRY,AN,STOPP,ZEIT_UM};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
int quadrant = 0;

void auschaltenAlles(){
  mytimer.pause(); // keine ISRs mehr ausloesen
  GPIOC->ODR = 0;
}
void stelleWecker(int n){ // Zehntelsekunden
  weckzeit = millis()+n*100;
}
bool abgelaufenWecker(){
  return millis()>=weckzeit;
}
void isr_einAus(){
  if(zustand==AUS){
    zustand=AN_ENTRY;
  }
  else{
    zustand=AUS;
    auschaltenAlles();
    quadrant=0;
  }
}
void ausgebenLED(int n){
  switch (n){
  case 0:
    GPIOC->ODR = (GPIOC->ODR & ~0b1111) | 0b1; 
    break;
  case 1:
    GPIOC->ODR = (GPIOC->ODR & ~0b1111) | 0b11;
    break;
  case 2:
    GPIOC->ODR = (GPIOC->ODR & ~0b1111) | 0b111;  
    break;
  default:
    GPIOC->ODR = GPIOC->ODR | 0b1111;  
    break;
  }
}
void anschaltenMotor(){
  digitalWrite(MOTOR,HIGH);
}
void ausschaltenMotor(){
  digitalWrite(MOTOR,LOW);
}
void setup() {
  pinMode(PC0, OUTPUT);           // ohne diese Zeile klappts nicht mit MODER
  GPIOC->MODER = 0x5555;          // PC0..PC7 als Ausgang
  pinMode(EIN_AUS, INPUT);
  pinMode(MODUS, INPUT_PULLDOWN);
  pinMode(MOTOR, OUTPUT);
  attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
}
void loop() {
  switch (zustand){
    case AN_ENTRY:
      stelleWecker(55); // 295, mit 55 testen
      anschaltenMotor();
      ausgebenLED(quadrant);
      zustand=AN;
      break;
    case AN:
      if(abgelaufenWecker()){
        zustand=STOPP;
        quadrant++;
        stelleWecker(5);
        ausschaltenMotor();
      }
      break;
    case STOPP:
      if(abgelaufenWecker()){
        if(quadrant%4==0) zustand=ZEIT_UM;
        else zustand=AN_ENTRY;
      }
      break;
    case ZEIT_UM:
      anschaltenMotor();
      delay(200);
      ausschaltenMotor();
      delay(200);
      anschaltenMotor();
      delay(200);
      ausschaltenMotor();
      delay(200);
      zustand=AN_ENTRY;
      break;  
  }
}

Bonus: Durch den Taster MODUS können zwischen diesen Modi umgeschaltet werden (Aufhellen und Sensitiv können nur durch Ausschalten beendet werden):

  1. Normal: Nach 29,5 Sekunden wird der Dauerbetrieb immer wieder für 0,5 Sekunden unterbrochen.
  2. Aufhellen: Ansteuerung des Bürstenmotors mit 1 Hz Rechtecksignal.
  3. Sensitiv: Ansteuern des Bürstenmotors mit 50 Hz Rechtecksignal.

Erstellen Sie eine Funktion boolean checkModus() die bei Drücken des high-aktiven prellenden (10 ms) Tasters an PA1 ein true zurückgibt (Flankendedektion, Entprellen).
Erweitern Sie Ihr Zustandsdiagramm und versuchen Sie die Motoransteuerung mit PWM zu realisieren [PWM-Prozent]. Den Timer und Timerkanal für PC6 finden Sie hier.

Mögliche Lösung Zustandsdiagramm
Zahnbürste Bonus Zustandsdiagramm
Zahnbürste Bonus Zustandsdiagramm
Mögliche Lösung Code
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3);  // Timerinstanz sowie Timerauswahl

# define EIN_AUS PC13  // Entpreller lowaktiver UserButton auf dem Board
# define MODUS PA1 
# define MOTOR PC6  // Timer 3 Kanal 1

enum zustandstyp {AUS,AN_ENTRY,AN,STOPP,ZEIT_UM,AUFHELLEN,SENSITIV};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
int quadrant = 0;

void anschaltenMotor(){
   mytimer.setOverflow(100,HERTZ_FORMAT);  // Frequenz einstellen
   mytimer.setCaptureCompare(1,100,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen
}
void ausschaltenMotor(){
   mytimer.setOverflow(100,HERTZ_FORMAT);  // Frequenz einstellen
   mytimer.setCaptureCompare(1,0,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen
}
void ausschaltenLED(){
  GPIOC->ODR = 0;
}
void stelleWecker(int n){ // Zehntelsekunden
  weckzeit = millis()+n*100;
}
bool abgelaufenWecker(){
  return millis()>=weckzeit;
}
void isr_einAus(){
  if(zustand==AUS){
    zustand=AN_ENTRY;
  }
  else{
    zustand=AUS;
    ausschaltenLED();
    ausschaltenMotor();
    quadrant=0;
  }
}
void ausgebenLED(int n){
  switch (n){
  case 0:
    GPIOC->ODR = (GPIOC->ODR & ~0b1111) | 0b1; 
    break;
  case 1:
    GPIOC->ODR = (GPIOC->ODR & ~0b1111) | 0b11;
    break;
  case 2:
    GPIOC->ODR = (GPIOC->ODR & ~0b1111) | 0b111;  
    break;
  default:
    GPIOC->ODR = GPIOC->ODR | 0b1111;  
    break;
  }
}

void aufhellen(){
  mytimer.setOverflow(1,HERTZ_FORMAT);  // Frequenz einstellen
  mytimer.setCaptureCompare(1,50,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen
}
void sensitiv(){
  mytimer.setOverflow(10,HERTZ_FORMAT);  // Frequenz einstellen
  mytimer.setCaptureCompare(1,50,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen
}
boolean checkModus(){ // prellfreies Ueberpruefen Tastendruck
  static boolean oldT=false;
  if (oldT!=digitalRead(MODUS)){
    delay(20); // Prellen abwarten
    if (oldT!=digitalRead(MODUS)){ // immer noch?
      oldT=!oldT;  // neuer Wert ist anders
      return oldT; // steigende Flanke wenn neuer Wert = true
    }
  }
  return false;
}
void setup() {
  pinMode(PC0, OUTPUT);           // ohne diese Zeile klappts nicht mit MODER
  GPIOC->MODER = 0x5555;          // PC0..PC7 als Ausgang
  pinMode(EIN_AUS, INPUT);
  pinMode(MODUS, INPUT_PULLDOWN);
  pinMode(MOTOR, OUTPUT);
  attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
  mytimer.setPWM(1, PC6, 100, 0); // Initialisierung mit 100 Hz
}
void loop() {
  switch (zustand){
    case AN_ENTRY:
      stelleWecker(55); // 295, mit 55 testen
      anschaltenMotor();
      ausgebenLED(quadrant);
      zustand=AN;
      break;
    case AN:
      if(abgelaufenWecker()){
        zustand=STOPP;
        quadrant++;
        stelleWecker(5);
        ausschaltenMotor();
      }
      if(checkModus()){
        ausschaltenLED();
        aufhellen();
        zustand=AUFHELLEN;
      }
      break;
    case STOPP:
      if(abgelaufenWecker()){
        if(quadrant%4==0) zustand=ZEIT_UM;
        else zustand=AN_ENTRY;
      }
      break;
    case ZEIT_UM:
      anschaltenMotor();
      delay(200);
      ausschaltenMotor();
      delay(200);
      anschaltenMotor();
      delay(200);
      ausschaltenMotor();
      delay(200);
      zustand=AN_ENTRY;
      break;
    case AUFHELLEN:
      if (checkModus()){
        sensitiv();
        zustand=SENSITIV;
      }
      break;
    case SENSITIV:

      break;
  }
}

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 wollte ich für unser Board mit dem STM32 anpassen. 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.
Auch Lehrer machen Fehler: Der angepasste Funduino-Sketch wollte zunächst nicht funktionieren weil ich statt PC9 für das Echosignal PA9 eingetragen hatte. Hab ich aber erst gemerkt, als ich mit dem Oszilloskop Messungen durchführte:

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..

Musste ein wenig suchen um den Schaltplan des Moduls zu finden und entdeckte zwei hochkarätige Seiten zum Thema: Circuit Diagram Ultrasonic Distance Sensor HC-SR04 und HC-SR04
Leider steht im Funduino-Sketch der übliche abgeschriebene Unsinn von anderen (Triggersignal ist 10ms lang statt 10µs wie in der Doku des Sensors verlangt). Doku zu pulseIn(), noch eine Anleitung. Hier mein modifizierter Sketch:

#define TRIGGER PA10
#define ECHO PC9
long dauer=0; // Das Wort dauer ist jetzt eine Variable, unter der die Zeit gespeichert wird, die eine Schallwelle bis zur Reflektion und zurück benötigt. Startwert ist hier 0.
long entfernung=0; // Das Wort „entfernung“ ist jetzt die variable, unter der die berechnete Entfernung gespeichert wird. Info: Anstelle von „int“ steht hier vor den beiden Variablen „long“. Das hat den Vorteil, dass eine größere Zahl gespeichert werden kann. Nachteil: Die Variable benötigt mehr Platz im Speicher.
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(10);
  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; //Nun berechnet man die Entfernung in Zentimetern. Man teilt zunächst die Zeit durch zwei (Weil man ja nur eine Strecke berechnen möchte und nicht die Strecke hin- und zurück). Den Wert multipliziert man mit der Schallgeschwindigkeit in der Einheit Zentimeter/Mikrosekunde und erhält dann den Wert in Zentimetern.
  if (entfernung >= 500 || entfernung <= 0){ //Wenn die gemessene Entfernung über 500cm oder unter 0cm liegt,…
    Serial.println("Kein Messwert"); //dann soll der serial monitor ausgeben „Kein Messwert“, weil Messwerte in diesen Bereichen falsch oder ungenau sind.
  }
  else{ //  Ansonsten…
    Serial.print(entfernung); //…soll der Wert der Entfernung an den serial monitor hier ausgegeben werden.
    Serial.println(" cm"); // Hinter dem Wert der Entfernung soll auch am Serial Monitor die Einheit "cm" angegeben werden.
  }
  delay(1000); //Das delay von einer Sekunde sorgt in ca. jeder neuen Sekunde für einen neuen Messwert.
}

Einschub: Datentypen bei 8 Bit AVM und STM32 Controller

Der klassische Arduino-Controller war ein Atmel AVM 8 Bit Ding. Bei diesem Controller wird für int 16 Bit verwendet, Zahlenbereich -32 Ki .. + 32 Ki -1. Will man grössere Zahlen verarbeiten musste auf long 32 Bit ausgewichen werden. Beim STM32 wird logischerweise gerne mit 32 Bit gearbeitet, daher wird int in 32 Bit übersetzt, Zahlenbereich: -2 Gi .. + 2 Gi-1. Viele “Verrenkungen” aus der 8 Bit Ära können wir uns dort sparen… Messen wir die Anzahl der Bytes für verschiedene Controller und Datentypen mit sizeof()!

char c = 0;
int i=0;
long l=0;
float f=0;
double d=0;
bool b=false;

void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(9600); // Serielle Schnittstelle starten und Baudrate festlegen
    delay(1000); // Nötig für Arduino, bei PlatformIO ging es ohne
    Serial.printf("char: %d int: %d long: %d float: %d double: %d bool: %d\n",sizeof(c),sizeof(i),sizeof(l),sizeof(f),sizeof(d),sizeof(b));
}
void loop(){
}

Ausgabe bei STM32: char: 1 int: 4 long: 4 float: 4 double: 8 bool: 1
ESP32-WROOM-DA: char: 1 int: 4 long: 4 float: 4 double: 8 bool: 1
Für den Arduino-Uno musste ich improvisieren, da printf nicht einfach geht.

char mybuffer[80]; // notwendig für snprintf
char c = 0;
int i=0;
long l=0;
float f=0;
double d=0;
bool b=false;

void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(9600); // Serielle Schnittstelle starten und Baudrate festlegen
    delay(1000);
    snprintf(mybuffer,80,"char: %d int: %d long: %d float: %d double: %d bool: %d\n",sizeof(c),sizeof(i),sizeof(l),sizeof(f),sizeof(d),sizeof(b));
    Serial.println(mybuffer);
}
void loop(){
}

Ausgabe: char: 1 int: 2 long: 4 float: 4 double: 4 bool: 1

Controllerbool(unsigned) char(unsigned) int(unsigned) longfloatdouble
AVM 8Bit1 Byte, 8Bit1 Byte, 8Bit2 Byte, 16Bit4 Byte, 32Bit4 Byte, 32Bit4 Byte, 32Bit
STM321 Byte, 8Bit1 Byte, 8Bit4 Byte, 32Bit4 Byte, 32Bit4 Byte, 32Bit8 Byte, 64Bit
ESP321 Byte, 8Bit1 Byte, 8Bit4 Byte, 32Bit4 Byte, 32Bit4 Byte, 32Bit8 Byte, 64Bit
Speicherbedarf verschiedener Datentypen

Datentypen mit festgelegtem Speicherplatz

Wie Sie oben gesehen haben, kann der Speicherplatz und damit der mögliche Wertebereich bei manchen Datentypen vom verwendeten Controller abhängen. Deshalb wurden zusätzlich Datentypen festgelegt bei denen die Speicherbreite festgelegt ist:

DatentypBitsWertebereich
int_16t16$ -2^{15} .. 2^{15}-1 = -32 Ki .. 32 Ki -1$
uint_16t16$ 0 .. 2^{16}-1 = 0..65535 = 0..64Ki-1$
int_32t32$ -2^{31} .. 2^{31}-1 = -2Gi..2Gi-1$
uint_32t32$ 0 .. 2^{32}-1 = 0..4Gi-1$
Datentypen mit festgelegtem Speicherplatz

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, 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 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(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);
}

ToDo: pulseIn() mit Timer nachbauen

Wie kann die Puls-Dauer mit einem Timer gemessen werden?
In der Doku findet sich Frequency_Dutycycle_measurement.ino wie funktioniert das?

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
Einparkhilfe Zustandsdiagramm
Einparkhilfe Zustandsdiagramm

Bonus/Experiment: Ein Entwurf ohne “Events”

ISRs sind keine echten Events, daher können lustige Probleme auftreten, wenn z.B. im Hauptprogramm und in der ISR auf die selben Ressourcen verändert werden. Wollte das von den SuS fern halten, in dem ich alle Zustandsänderungen nur in Hauptprogramm vornehme und künstlich Zustände NAH und FERN verwende. Hier ein Experiment wie das funktionieren könnte (Würg! Nie wieder!):

#define trigger PA10
#define echo PC9
int entfernung=0; // entfernung in cm
int csZaehler=0; // Centi-Sekunden
bool taster_F=false;
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 Timerauswahl
enum{RUHE,FERN,NAH} 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_F=true;
}
bool taster() { // Abfrage Taster-Flag und bei true wieder auf false setzen
  if(taster_F){
    taster_F=false;
    return true;
  }
  return false;
}
void ISR_ausgeben(){
  int ausgabe; // Dezimeter
  switch(zustand){
    case RUHE:
      GPIOC->ODR = 0;
      break;
    case FERN:
      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
      break;
    case NAH:    
      GPIOC->ODR = leuchtband[entfernung/2];
  }
  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); 
  mess = (dauer/2) * 0.03432;
  if (mess>300){
    entfernung = 300;
  }
  else{
    entfernung = (entfernung+mess)/2; // Messungen glätten
  }
}
void loop(){
  switch(zustand){
    case RUHE:
      if(taster()){
        zustand = FERN;
      }
      break;
    case FERN:
      if(csZaehler%30==0){ // alle 300 ms Messen
        messung();
      }
      if(taster()){    // Tastendruck hat Priorität, 
        zustand = RUHE; // sonst könnte er übersehen werden
      }
      else if(entfernung <= 16){ // falls diese Bedingung
        zustand = NAH;           // auch zutrifft
      }
      break;
    case NAH:
      if(csZaehler%30==0){ // alle 300 ms Messen
        messung();
      }
      if(taster()){ // Tastenereignis hat Priorität
        zustand = RUHE;
      }
      else if(entfernung > 16){
        zustand=FERN;
      }
  }
}

Versuch den Code so präzise wie möglich in einem Zustandsdiagramm ab zu bilden:

Einparkhilfe Zustandsdiagramm
Einparkhilfe Zustandsdiagramm

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert