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).
Tipp: Vorsätze für Maßeinheiten 🔗
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() {
}
Hinweis: Die Frequenz der ISR-Aufrufe ist doppelt so hoch wie die Frequenz der blinkenden LED, weil bei jedem Interrupt die LED umgeschaltet wird.
$ Frequenz_{ISR} = Frequenz_{TimerCLK} / PreFaktor / Overflow / = 32 * 10^6 Hz / 32000 / 100 / = 10 Hz $
$ Frequenz_{LED} = Frequenz_{TimerCLK} / PreFaktor / Overflow / 2 = 32 * 10^6 Hz / 32000 / 100 / 2 = 5 Hz $
TimerCLK | Prescaler | Overflow | LED-Frequenz berechnet | LED-Frequenz gemessen |
---|---|---|---|---|
32 MHz | 32000 | 100 | 5 Hz | 5 Hz |
32 MHz | 32000 | 10 | 50 Hz | 50 Hz |
32 MHz | 32000 | 2 | 250 Hz | 250 Hz |
32 MHz | 32000 | 1 | 500 Hz | Kein Signal! |
32 MHz | 65535 | 2 | 122,07 Hz | 122,07 Hz |
32 MHz | 65536 | 2 | 122,07 Hz | 122,07 Hz |
32 MHz | 65535 | 3 | 81,38 Hz | 81,38 Hz |
32 MHz | 1 | 65535 | 244,14 Hz | 244,14 Hz |
32 MHz | 1 | 65536 | 244,14 Hz | 244,14 Hz |
32 MHz | 1 | 100 | 160 kHz | 48,8 kHz (ISR kam nicht hinterher) |
🖥 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.
Info: 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.
Info: 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)
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
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
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!
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:
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]:
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Ton | c‘ | cis‘ | d‘ | es‘ | e‘ | f‘ | fis‘ | g‘ | as‘ | a‘ | b‘ | h‘ | c“ |
Frequenz | 262 | 277 | 293 | 311 | 330 | 349 | 370 | 392 | 415 | 440 | 466 | 494 | 523 |
#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.
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 in der 7 Segment Anzeige leuchtet „-„. Mit jedem Tastendruck auf PC13 wird der jeweils nächste Ton ausgewählt, dies geschieht mit einer ISR. Nach e‘ soll die Auswahl wieder mit E beginnen:
(Ruhe->E->A->d->g->h->e‘->E->…).
Töne genau erzeugen, Prescaler und und Overflow ermitteln
Viele Töne (E,d,h,e‘) lassen sich nicht mit dem Herz-Format genau einstellen, da als Eingabe ganze Zahlen vorgesehen sind. Daher wird im Tick-Format Prescaler und Overflow verwendet. Hier eine Beispielrechnung wie die Werte ermittelt werden können:
- $ \textbf{Taktzahl} = TimerCLK / Tonfrequenz = 32MHz / 82,41Hz = 388302,39 $
- $ \textbf{Prescaler: } Taktzahl / MaxOverflow = {388302,39 / 65536} = 5,92 \textbf{ Aufrunden: 6} $
- $ \textbf{Overflow: } Taktzahl / Prescaler = 388302,39 / 6 = \textbf{64717} $
- $ \textbf{Probe: } TimerCLK / Prescaler / Overflow = 32MHz / 6 / 64717 = \textbf{82,41Hz} $
✍️ Ermitteln Sie die zu erwartenden Werte für Prescaler und Overflow für die anderen Frequenzen.
Tipp: 🖥 Verwenden Sie eine Tabellenkalkulation.
Der Prescaler-Wert ändert sich, es müssten zwei Felder verwendet werden um die Einstellungen zu speichern. Wie genau wären die Frequenzen, wenn der Prescaler-Wert von 82,41 Hz beibehalten und nur die Overflow-Werte geändert werden würden?
✍️ 🖥 Ermitteln Sie die Overflow-Werte für die Frequenzen und bestimmen Sie die Ausgabefrequenz.
Lösung
Werte für 7 Segmentanzeige bestimmen
Bestimmen sie die Werte für die Ausgabe auf der 7 Segmentanzeige, füllen Sie die Tabelle aus:
Ausgabe | g PC6 | f PC5 | e PC4 | d PC3 | c PC2 | b PC1 | a PC0 | Hex |
---|---|---|---|---|---|---|---|---|
– | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0x40 |
E | ||||||||
A | ||||||||
d | ||||||||
g | ||||||||
h | ||||||||
e |
Lösung Umwandlung für 7-Segment Anzeige
Ausgabe | g PC6 | f PC5 | e PC4 | d PC3 | c PC2 | b PC1 | a PC0 | Hex |
---|---|---|---|---|---|---|---|---|
– | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0x40 |
E | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0x79 |
A | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0x77 |
d | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0x5e |
g | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0x6f |
h | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0x74 |
e | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 0x7b |
Code Vorgabe vervollständigen
#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
const unsigned char anzeige[] ={...}; // 7Segmentausgaben für E..e
const unsigned int ton_overflow[] ={64717,...}; // Overflow-Werte für E..e
unsigned char ton=0;
void isr_userB(){ // nächsten Ton ausgeben
GPIOC->ODR = anzeige[ton]; // ton auf 7Seg. ausgeben
digitalWrite(PC11,HIGH); // Einer einschalten
mytimer.setOverflow(ton_overflow[ton], TICK_FORMAT); // Frequenz einstellen
mytimer.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen 0..100
mytimer.resume(); // Timer aktivieren
if (ton>=5) ton=0;
else ton++;
}
void setup() {
pinMode(PC0, OUTPUT); // ohne diese Zeile klappts nicht
GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
pinMode(PC11,OUTPUT); // Einer Ausgang
pinMode(USER_BTN, INPUT);
attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
GPIOC->ODR = 0x40; // - ausgeben
digitalWrite(PC11,HIGH); // Einer einschalten
mytimer.setPrescaleFactor(?); // Prescaler einstellen
mytimer.setMode(KANAL, TIMER_OUTPUT_COMPARE_PWM1, PIEZO_PIN); // Den PWM-Ausgang einstellen
}
void loop() {
}
Lösungsvorschlag
#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
const unsigned char anzeige[] ={0x79,0x77,0x5e,0x6f,0x74,0x7b}; // 7Segmentausgaben für E..e
const unsigned int ton_overflow[] ={64717,48485,36323,27211,21598,16180}; // Overflow-Werte für E..e
unsigned char ton=0;
void isr_userB(){ // nächsten Ton ausgeben
GPIOC->ODR = anzeige[ton]; // ton auf 7Seg. ausgeben
digitalWrite(PC11,HIGH); // Einer einschalten
mytimer.setOverflow(ton_overflow[ton], TICK_FORMAT); // Frequenz einstellen
mytimer.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite einstellen 0..100
mytimer.resume(); // Timer aktivieren
if (ton>=5) ton=0;
else ton++;
}
void setup() {
pinMode(PC0, OUTPUT); // ohne diese Zeile klappts nicht
GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
pinMode(PC11,OUTPUT); // Einer Ausgang
pinMode(USER_BTN, INPUT);
attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
GPIOC->ODR = 0x40; // - ausgeben
digitalWrite(PC11,HIGH); // Einer einschalten
mytimer.setPrescaleFactor(6); // Prescaler einstellen
mytimer.setMode(KANAL, TIMER_OUTPUT_COMPARE_PWM1, PIEZO_PIN); // Den PWM-Ausgang einstellen
}
void loop() {
}
⏰ Wecker stellen
Häufig werden für die Programmierung eines zeitlichen Ablaufs „Wecker“ benötigt. Eine Verzögerung mit delay(..) unterbricht den Programmablauf und ist oft nicht geschickt. Praktisch wäre ein Wecker, der nach Ablauf der Zeit die gewünschte Aktion auslöst ohne den Programmfluss zu behindern.
Hier zeige ich drei typische Lösungsansätze.
Aufgabe: Mit dem UserButton an PC13 soll ein 5 Sekunden Wecker gestellt werden, der statt Klingeln 0,2 Sekunden mit 2 kHz piepst.
Lösung mit Timer-ISR
#include <Arduino.h>
static HardwareTimer myWecker = HardwareTimer(TIM2); // Timerinstanz für Zeit
static HardwareTimer myPieper = HardwareTimer(TIM3); // Timerinstanz für Piepen
#define USER_BTN PC13 // Entpreller lowaktiver UserButton auf dem Board
#define PIEZO_PIN PA7 // Lautsprecher an PA7/D11 -> TIM3_CH2
#define KANAL 2 // CH2
void piep(){ // den Pieper kurz piepen lassen
myPieper.resume(); // Pieper starten
delay(200);
myPieper.pause(); // Pieper stoppen
}
void isr_userB(){ // UserButton
myWecker.resume(); // Timer starten
Serial.printf("Wecker starten, Zählerstand: %d\n",myWecker.getCount());
piep();
}
void isr_Wecker(){ // Wecker ist abgelaufen (timeout)
myWecker.pause(); // Timer stoppen
Serial.printf("Wecker abgelaufen, Zählerstand: %d\n",myWecker.getCount());
piep();
}
void setup() {
pinMode(USER_BTN, INPUT);
attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
myPieper.setOverflow(2000, HERTZ_FORMAT); // Frequenz einstellen
myPieper.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite 50%
myPieper.setMode(KANAL, TIMER_OUTPUT_COMPARE_PWM1, PIEZO_PIN); // Den PWM-Ausgang einstellen
myWecker.setOverflow(5000000,MICROSEC_FORMAT); // Wecker auf 5 Sekunden stellen
myWecker.attachInterrupt(isr_Wecker); // ISR einstellen
Serial.begin(9600);
}
void loop() {
}
- Ermitteln Sie die längste Weckzeit, die damit erreicht werden kann, Herleitung!
- Wie können längere Weckzeiten erzielt werden?
- Wird der Wecker neu gestartet, wenn während der Wecker läuft der UserButton erneut gedrückt wird?
Lösungen
- Maximale Zeit: MaxPrescaler * MaxOverflow / TimerCLK = 232 / 32 MHz = 134,22 s
- In der isr_Wecker() einen Zähler einbauen und mehrere Durchläufe zählen
- Nein, er läuft einfach weiter.
Bessere Lösung mit Timer-ISR
Bei dieser Lösung lässt sich der Wecker erneut stellen und die Berechnung erfolgt Abi-gerecht mit selbst eingestellten Prescaler und Overflow
#include <Arduino.h>
static HardwareTimer myWecker = HardwareTimer(TIM2); // Timerinstanz für Zeit
static HardwareTimer myPieper = HardwareTimer(TIM3); // Timerinstanz für Piepen
#define USER_BTN PC13 // Entpreller lowaktiver UserButton auf dem Board
#define PIEZO_PIN PA7 // Lautsprecher an PA7/D11 -> TIM3_CH2
#define KANAL 2 // CH2
void piep(){ // den Pieper kurz piepen lassen
myPieper.resume(); // Pieper starten
delay(200);
myPieper.pause(); // Pieper stoppen
}
void isr_userB(){ // UserButton
stelleWecker(5);
piep();
}
int zeitmarke; // Testen ob die Zeit auch stimmt
void stelleWecker(int n){ // Sekundenwecker stellen
//myWecker.setOverflow(n*1000000,MICROSEC_FORMAT); // Wecker auf 5 Sekunden stellen
myWecker.setPrescaleFactor(32000); // 32MHz/32000 = 1kHz -> 1ms Periodendauer
myWecker.setOverflow(n*1000); // Zeit in ms einstellen
myWecker.attachInterrupt(isr_Wecker); // ISR einstellen
myWecker.setCount(0); // Zählerstand wieder auf 0 stellen
myWecker.resume(); // Timer starten
zeitmarke=millis();
Serial.printf("Wecker auf %d Sekunden gestellt\n",n);
}
void isr_Wecker(){ // Wecker ist abgelaufen (timeout)
myWecker.pause(); // Timer stoppen
Serial.printf("Wecker abgelaufen, Zeit: %d ms\n",millis()-zeitmarke);
piep();
}
void setup() {
pinMode(USER_BTN, INPUT);
attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
myPieper.setOverflow(2000, HERTZ_FORMAT); // Frequenz einstellen
myPieper.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite 50%
myPieper.setMode(KANAL, TIMER_OUTPUT_COMPARE_PWM1, PIEZO_PIN); // Den PWM-Ausgang einstellen
Serial.begin(9600);
}
void loop() {
}
Lösung mit millis()
Bei Arduino ist eine Funktion millis(): uint32_t🔗 eingebaut (intern wird dafür ein Timer abgestellt der die Zeit zählt), sie gibt die Millisekunden seit Systemstart zurück.
static HardwareTimer myPieper = HardwareTimer(TIM3); // Timerinstanz für Piepen
#define USER_BTN PC13 // Entpreller lowaktiver UserButton auf dem Board
#define PIEZO_PIN PA7 // Lautsprecher an PA7/D11 -> TIM3_CH2
#define KANAL 2 // CH2
unsigned long weckzeit; // wann der Wecker "klingeln" soll
enum zustandstyp {CHILLEN,WECKER,PIEPEN};
zustandstyp zustand=CHILLEN;
void stelleWecker(int n){ // n ist Zeit in ms
weckzeit = millis()+n;
}
bool abgelaufenWecker(){
return millis()>=weckzeit;
}
void isr_userB(){ // Wecker stellen
switch(zustand){
case CHILLEN:
stelleWecker(10000); // auf 10 Sekunden stellen
zustand=WECKER;
}
}
void setup() {
pinMode(USER_BTN, INPUT);
attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
myPieper.setOverflow(2000, HERTZ_FORMAT); // Frequenz einstellen
myPieper.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite 50%
myPieper.setMode(KANAL, TIMER_OUTPUT_COMPARE_PWM1, PIEZO_PIN); // Den PWM-Ausgang einstellen
}
void loop() {
switch(zustand){
case CHILLEN:
break;
case WECKER: // Wecker ist gestellt, warten auf Klingeln
if(abgelaufenWecker()){
myPieper.resume(); // Pieper an
stelleWecker(300);
zustand=PIEPEN;
}
break;
case PIEPEN: // Piepen auch mit Wecker gelöst
if(abgelaufenWecker()){
myPieper.pause();
zustand=CHILLEN;
}
break;
}
}
- Erstellen Sie ein Zustandsdiagramm für den Code.
- Was passiert, wenn während der Wecker läuft der UserButton erneut gedrückt wird?
- Was passiert, wenn während des Piepens der UserButton gedrückt wird?
- Nach welcher Zeit springt millis() wieder auf 0? Muss dies bei dem Wecker beachtet werden, Begründung?
- Bonus: Entwickeln Sie eine Totmanneinrichtung🔗
Lösung 1
Lösungen 2..4
- Nichts.
- Keine Auswirkung.
- Datentyp uint32_t ist 32 Bit, nach 232 ms = 4294967,3 s = 1193 h = 49,71 Tagen springt er wieder auf 0. Falls der Wert nahe dem Maximalwert ist kann es passieren, dass beim Stellen des Weckers der Wecker sofort ausgelöst wird, weil durch den Überlauf die Summe kleiner als die aktuelle millis()-Zeit ist. Falls Sie Anwendungen bauen die länger als 49 Tage durchgehend laufen sollten Sie dies beachten!
Lösung mit selbst gebautem Zeitticker
Wenn Software ohne Arduino-Umgebung erstellt wird und deshalb millis() nicht zu Verfügung steht wünsche ich mir für Weckzeiten einen eigenen Zeitticker, ich habe in diesen Fällen oft in einem periodischen Interrupt z.B. für eine Multiplexanzeige einen Zähler eingebaut, der mir eine Zeitbasis (Ticks) gibt. Für die Problemstellung würde als Zeitbasis 0,1 Sekunden reichen.
Erstellen Sie eine Lösung mit einem Timer myTicker, der alle 0,1 s eine Variable myTicks:GZ hochzählt und deren Weckzeiten darauf basiert.
Mögliche Lösung
static HardwareTimer myTicker = HardwareTimer(TIM2); // Timerinstanz für Zeit
static HardwareTimer myPieper = HardwareTimer(TIM3); // Timerinstanz für Piepen
#define USER_BTN PC13 // Entpreller lowaktiver UserButton auf dem Board
#define PIEZO_PIN PA7 // Lautsprecher an PA7/D11 -> TIM3_CH2
#define KANAL 2 // CH2
unsigned long weckzeit; // wann der Wecker "klingeln" soll
unsigned long myTicks=0; // 0,1s Ticks
enum zustandstyp {CHILLEN,WECKER,PIEPEN};
zustandstyp zustand=CHILLEN;
void stelleWecker(int n){ // n ist Zeit in 0,1s
weckzeit = myTicks+n;
}
bool abgelaufenWecker(){
return myTicks>=weckzeit;
}
void isr_userB(){ // Wecker stellen
stelleWecker(100); // auf 10 Sekunden stellen
zustand=WECKER;
}
void isr_Tick(){ // Tickzeit erhöhen
myTicks++;
}
void setup() {
pinMode(USER_BTN, INPUT);
attachInterrupt (digitalPinToInterrupt (USER_BTN), isr_userB, FALLING);
myPieper.setOverflow(2000, HERTZ_FORMAT); // Frequenz einstellen
myPieper.setCaptureCompare(KANAL,50,PERCENT_COMPARE_FORMAT); // Impulsbreite 50%
myPieper.setMode(KANAL, TIMER_OUTPUT_COMPARE_PWM1, PIEZO_PIN); // Den PWM-Ausgang einstellen
myTicker.setOverflow(100000,MICROSEC_FORMAT); // Ticks auf 0,1 Sekunden stellen
myTicker.attachInterrupt(isr_Tick); // ISR einstellen
myTicker.resume(); // Ticker starten
}
void loop() {
switch(zustand){
case CHILLEN:
break;
case WECKER: // Wecker ist gestellt, warten auf Klingeln
if(abgelaufenWecker()){
myPieper.resume(); // Pieper an
stelleWecker(3); // 0,3 Sekunden
zustand=PIEPEN;
}
break;
case PIEPEN: // Piepen auch mit Wecker gelöst
if(abgelaufenWecker()){
myPieper.pause();
zustand=CHILLEN;
}
break;
}
}
☕️ 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 mit ausschaltenAlles(). Es sind Zustände vorgegeben: AUS,HEIZEN_ENTRY,HEIZEN,HEISS,PUMPEN.
Taster | PortPin | PortPin | Ausgang |
---|---|---|---|
EIN_AUS | PC13 (LowAktiv) | PC0 | HEIZUNG |
KL_TA | PA1 (HighAktiv) | PC1 | PUMPE |
GR_TA | PA6 (HighAktiv) | PC2 | LED |
- Nach dem Einschalten wird das Wasser für 15 Sekunden (Wecker) aufgeheizt (HEIZUNG=1) dabei blinkt die LED mit 4 Hz.
- Nach dem Heizen ist das Wasser heiss, die LED leuchtet dauernd und die Heizung ist aus.
- Nun kann mit KL_TA eine kleine Tasse oder mit GR_TA eine große Tasse angefordert werden.
- Bei einer kleinen Tasse läuft die Pumpe 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.
// Codevorgabe
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 ausschaltenAlles(){ // Alles ist aus
}
void stelleWecker(int n){ // Wecker stellen, n sind Sekunden
}
bool abgelaufenWecker(){ // True, wenn Wecker abgelaufen ist
}
void isr_einAus(){ // Externer Interrupt
}
void isr_blinken(){ // Invertiert die LED
}
void blinkenLED(int n){ // Einstellen der Blinkzeit
}
void einschaltenLED(){ // LED leuchtet dauernd
}
void setup() {
pinMode(EIN_AUS, INPUT);
..
}
void loop() {
switch (zustand){
}
}
☕️ 1. Vervollständigen Sie das Zustandsdiagramm 10P
Mögliche Lösung Kaffeeautomat Zustandsdiagramm
☕️ 2. Welche Taster müssen entprellt sein? 3P
Begründen Sie welche Taster entprellt sein müssen.
Lösung
Der Taster EIN_AUS muss entprellt sein, weil sonst bei Betätigung der Automat zufällig an oder aus ist.
Die Taster KL_TA und GR_TA müssen nicht unbedingt entprellt sein, nach dem ersten HIGH wir in den Zustand PUMPEN verzweigt und die Taster werden nicht mehr weiter abgefragt.
☕️ 3. Initialisierung der Pins 4P
Erstellen Sie die Codesequenz um die Pins zu initialisieren.
Lösung
pinMode(EIN_AUS, INPUT);
pinMode(KL_TA, INPUT_PULLDOWN);
pinMode(GR_TA, INPUT_PULLDOWN);
pinMode(HEIZUNG, OUTPUT);
pinMode(PUMPE, OUTPUT);
pinMode(LED, OUTPUT);
☕️ 4. isr_einAus() 5P
Erstellen Sie die Codesequenz zur Initialisierung der ISR und den Code für isr_einAus().
Lösung
attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
void isr_einAus(){ // Externer Interrupt
if(zustand==AUS){
zustand=HEIZEN_ENTRY;
}
else{
zustand=AUS;
auschaltenAlles();
}
}
☕️ 5. ausschaltenAlles() 3P
Erstellen Sie den Code für ausschaltenAlles(). Alle Ausgänge werden auf 0 geschaltet, es blinkt auch nichts mehr.
Lösung
void ausschaltenAlles(){ // Alles ist aus
mytimer.pause(); // keine ISRs mehr ausloesen
digitalWrite(LED,LOW);
digitalWrite(HEIZUNG,LOW);
digitalWrite(PUMPE,LOW);
}
☕️ 6. Weckerfunktionen 4P
Erstellen Sie den Code für stelleWecker(int n) und abgelaufenWecker():bool.
Für die 15,10,5 Sekunden wird ein Wecker mit stelleWecker(int n) gestellt. Der Parameter n gibt die Weckzeit in Sekunden vor. Mit abgelaufenWecker():bool wird der Wecker abgefragt -> true wenn Wecker abgelaufen ist. Der Wecker verwendet die millis()-Funktion
Lösung
void stelleWecker(int n){ // Wecker stellen n sind Sekunden
weckzeit = millis()+n*1000;
}
bool abgelaufenWecker(){ // True, wenn Wecker abgelaufen ist
return millis()>=weckzeit;
}
☕️ 7. LED blinken und dauernd leuchten lassen 10P
Die LED soll blinken, dauerleuchten und aus sein. Das LED-Blinken wird mit einer isr_blinken() und Timer3 erzeugt.
Für das Einstellen des LED-Blinkens wird ein Unterprogramm blinkenLED(int n) verwendet. Der Parameter n ist die Blinkfrequenz.
Erstellen Sie den Code für die Initialisierung von isr_blinken(), isr_blinken(), blinkenLED(int n) und einschaltenLED().
Lösung
mytimer.attachInterrupt(isr_blinken); // Timer ISR einstellen
void isr_blinken(){ // Invertiert die LED
digitalWrite(LED,!digitalRead(LED));
}
void blinkenLED(int n){ // Einstellen der Blinkzeit
mytimer.setOverflow(n*2,HERTZ_FORMAT);
mytimer.resume();
}
void einschaltenLED(){ // LED leuchtet dauernd
mytimer.pause();
digitalWrite(LED,HIGH);
}
☕️ 8. loop() 15P
Erstellen Sie den Code für die loop().
Lösung
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 komplette 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 PC1
enum zustandstyp {AUS,HEIZEN_ENTRY,HEIZEN,HEISS,PUMPEN};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
void ausschaltenAlles(){ // Alles ist aus
mytimer.pause(); // keine ISRs mehr ausloesen
digitalWrite(LED,LOW);
digitalWrite(HEIZUNG,LOW);
digitalWrite(PUMPE,LOW);
}
void stelleWecker(int n){ // Wecker stellen n sind Sekunden
weckzeit = millis()+n*1000;
}
bool abgelaufenWecker(){ // True, wenn Wecker abgelaufen ist
return millis()>=weckzeit;
}
void isr_einAus(){ // Externer Interrupt
if(zustand==AUS){
zustand=HEIZEN_ENTRY;
}
else{
zustand=AUS;
ausschaltenAlles();
}
}
void isr_blinken(){ // Invertiert die LED
digitalWrite(LED,!digitalRead(LED));
}
void blinkenLED(int n){ // Einstellen der Blinkzeit
mytimer.setOverflow(n*2,HERTZ_FORMAT);
mytimer.resume();
}
void einschaltenLED(){ // LED leuchtet dauernd
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;
}
}
☕️ 9. Wasser wieder erwärmen 10P
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
Mögliche komplette 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
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. Ein Taster EIN_AUS (LowAktiv, entprellt) schaltet die Zahnbürste ein und aus jedem Zustand wieder aus. Der Putzfortschritt wird durch ein ein LED-Leuchtband (LEDA..LEDD) angezeigt, nach 2 Minuten leuchten alle 4 LEDs.
- Nach dem Einschalten mit Taster EIN_AUS leuchtet zunächst LEDA für den ersten Quadranten und der Motor wird eingeschaltet. Zustand AN.
- Nach 29,5 Sekunden stoppt der Motor kurz für 0,5 Sekunden im Zustand STOPP.
- Für jeden weiteren Quadranten wird eine weitere LED an und der Motor wieder eingeschaltet.
- Nach 2 Minuten geht der Motor 3 mal aus und wieder an. Zustand ZEIT_UM.
- Danach wird wieder in den Zustand AN verzweigt.
- Immer wenn alle Quadranten geputzt wurden (%4==0) wird in den Zustand ZEIT_UM verzweigt, sonst in den Zustand AN.
Taster | PortPin | PortPin | Ausgang |
---|---|---|---|
EIN_AUS | PC13 (LowAktiv, entprellt) | PC0 | LEDA |
MODUS | PA1 (HighAktiv, prellt) | PC1 | LEDB |
PC2 | LEDC | ||
PC3 | LEDD | ||
PC6 | MOTOR |
// Vorgabe Code
static HardwareTimer mytimer = HardwareTimer(TIM3); // Timerinstanz sowie Timerauswahl
# define EIN_AUS PC13 // Entpreller lowaktiver UserButton auf dem Board
# define MODUS PA1 // Taster highaktiv prellend
# define MOTOR PC6 // PWM mit Timer 3 Kanal 1 möglich
enum zustandstyp {AUS,AN_ENTRY,AN,STOPP,ZEIT_UM,AUFHELLEN,SENSITIV};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
int quadrant = 0; // Mundquadrant, der gerade geputzt werden soll
void anschaltenMotor(){
}
void ausschaltenMotor(){
}
void ausschaltenLED(){ // schaltet nur die LED aus
}
void stelleWecker(int n){ // n in Zehntelsekunden
}
bool abgelaufenWecker(){ // true, wenn Weckzeit vorbei
}
void isr_einAus(){ // wird mit EIN_AUS aufgerufen
}
void ausgebenLED(int n){ // gibt Leuchtband aus
}
void setup() {
}
void loop() {
}
🪥 1. Vervollständigen Sie das Zustandsdiagramm
Mögliche Lösung Zustandsdiagramm
🪥 2. Initialisierung der Pins
Erstellen Sie die Codesequenz um die Pins zu initialisieren.
Lösung
pinMode(PC0, OUTPUT); // LEDA Ausgang
pinMode(PC1, OUTPUT); // LEDB Ausgang
pinMode(PC2, OUTPUT); // LEDC Ausgang
pinMode(PC3, OUTPUT); // LEDD Ausgang
pinMode(MOTOR, OUTPUT); // Motor Ausgang
pinMode(EIN_AUS, INPUT);
pinMode(MODUS, INPUT_PULLDOWN);
🪥 3. isr_einAus()
Erstellen Sie die Codesequenz zur Initialisierung der ISR und den Code für isr_einAus().
Lösung
attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
void isr_einAus(){ // wird mit EIN_AUS aufgerufen
if(zustand==AUS){
zustand=AN_ENTRY;
}
else{
zustand=AUS;
ausschaltenLED();
ausschaltenMotor();
quadrant=0;
}
}
🪥 4. Motor und LEDs schalten
Erstellen Sie den Programmcode für anschaltenMotor(), ausschaltenMotor() und ausschaltenLED().
Lösung
void anschaltenMotor(){
digitalWrite(MOTOR,HIGH);
}
void ausschaltenMotor(){
digitalWrite(MOTOR,LOW);
}
void ausschaltenLED(){// schaltet nur die LED aus
GPIOC->ODR &= ~0b1111;
}
🪥 5. Weckerfunktion
Erstellen Sie den Code für stelleWecker(int n) und abgelaufenWecker():bool.
Mit stelleWecker(int n) wird die Weckzeit in Zehntelsekunden gestellt. Mit abgelaufenWecker():bool wird der Wecker abgefragt -> true wenn Wecker abgelaufen ist. Der Wecker verwendet die millis()-Funktion
Lösung
void stelleWecker(int n){ // n in Zehntelsekunden
weckzeit = millis()+n*100;
}
bool abgelaufenWecker(){ // true, wenn Weckzeit vorbei
return millis()>=weckzeit;
}
🪥 6. ausgebenLED(int n)
Erstellen Sie den Code für ausgebenLED(int n), das ein Leuchtband mit LEDA..LEDD ausgibt. Für n=0 leuchtet LEDA, für n>2 leuchten alle 4 LED.
Lösung
void ausgebenLED(int n){ // gibt Leuchtband aus
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: // für quadrant > 2
GPIOC->ODR = GPIOC->ODR | 0b1111;
break;
}
}
🪥 7. loop()
Erstellen Sie den Code für die loop().
Lösung
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;
}
}
Mögliche komplette Lösung
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3); // Timerinstanz sowie Timerauswahl
# define EIN_AUS PC13 // Entpreller lowaktiver UserButton auf dem Board
# define MODUS PA1 // Taster highaktiv prellend
# define MOTOR PC6 // PWM mit Timer 3 Kanal 1 möglich
enum zustandstyp {AUS,AN_ENTRY,AN,STOPP,ZEIT_UM,AUFHELLEN,SENSITIV};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
int quadrant = 0; // Mundquadrant, der gerade geputzt werden soll
void anschaltenMotor(){
digitalWrite(MOTOR,HIGH);
}
void ausschaltenMotor(){
digitalWrite(MOTOR,LOW);
}
void ausschaltenLED(){// schaltet nur die LED aus
GPIOC->ODR &= ~0b1111;
}
void stelleWecker(int n){ // n in Zehntelsekunden
weckzeit = millis()+n*100;
}
bool abgelaufenWecker(){ // true, wenn Weckzeit vorbei
return millis()>=weckzeit;
}
void isr_einAus(){ // wird mit EIN_AUS aufgerufen
if(zustand==AUS){
zustand=AN_ENTRY;
}
else{
zustand=AUS;
ausschaltenLED();
ausschaltenMotor();
quadrant=0;
}
}
void ausgebenLED(int n){ // gibt Leuchtband aus
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: // für quadrant > 2
GPIOC->ODR = GPIOC->ODR | 0b1111;
break;
}
}
void setup() {
pinMode(PC0, OUTPUT); // LEDA Ausgang
pinMode(PC1, OUTPUT); // LEDB Ausgang
pinMode(PC2, OUTPUT); // LEDC Ausgang
pinMode(PC3, OUTPUT); // LEDD Ausgang
pinMode(MOTOR, OUTPUT); // Motor Ausgang
pinMode(EIN_AUS, INPUT);
pinMode(MODUS, INPUT_PULLDOWN);
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;
}
}
🪥 8. Weitere Betriebsmodi: Aufhellen und Sensitiv
Mit dem Taster MODUS (PA1, HighAktiv, prellt) kann aus dem Zustand AN in zwei weitere Modi (AUFHELLEN und SENSITIV) geschaltet werden.
AN -[MODUS]-> AUFHELLEN -[MODUS]-> SENSITIV. Diese Modi kennen keine Quadranten und können nur durch Ausschalten beendet werden.
- AUFHELLEN: Aufruf der Funktionen aufhellen() (Der Bürstenmotor wird nun mit 1 Hz Rechtecksignal angesteuert) und ausschaltenLED()
- SENSITIV: Aufruf der Funktion sensitiv() (Der Bürstenmotor wird nun mit 50 Hz Rechtecksignal angesteuert).
🪥 8.1 MODUS Taste entprellen
Um die MODUS-Taste zu entprellen erstellen Sie eine Funktion boolean checkModus() die beim Drücken des high-aktiven prellenden (10 ms) Tasters an PA1 ein true zurückgibt (Flankendedektion, Entprellen). Diese Funktion wird im Zustandsdiagramm und Programm verwendet, Codevorgabe:
boolean checkModus(){ // prellfreies Ueberpruefen Tastendruck
static boolean oldT=false; // static bewirkt persistente lokale Variable
...
return false;
}
Lösung
boolean checkModus(){ // prellfreies Ueberpruefen Tastendruck
static boolean oldT=false; // static bewirkt persistente lokale Variable
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;
}
🪥 8.2 Zustandsdiagramm erweitern
Erweitern Sie das Zustandsdiagramm.
Mögliche Lösung Zustandsdiagramm
🪥 8.3 Motoransteuerung mit Timer-PWM (meine bevorzugte Lösung)
Die Motoransteuerung geschieht über [PWM-Prozent] mit Timer3-PWM Kanal 1. Ergänzen Sie den Code für aufhellen(), sensitiv() und loop(). Codeschnipsel:
static HardwareTimer mytimer = HardwareTimer(TIM3); // Timerinstanz sowie Timerauswahl
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 aufhellen(){ // Motor läuft mit 1 Hz
...
}
void sensitiv(){ // Motor läuft mit 50 Hz
...
}
void setup() {
...
mytimer.setPWM(1, PC6, 100, 0); // Initialisierung mit 100 Hz
}
void loop() {
switch (zustand){
}
}
Meine Lösung Kompletter 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;
}
}
🪥 8.4 Motoransteuerung mit Timer und ABI-Bezug (kein PWM mit Timer-Kanälen)
Leider ist die Verwendung von echtem PWM mit Timerkanälen (setCaptureCompare usw.) derzeit nicht bei ABI-Aufgaben vorgesehen. Daher sollte diese Aufgabe im ABI mit Timer-ISRs gelöst werden. Somit müsste aufhellen() und sensitiv() mit Timer-ISR so erzeugt werden:
- Eine Timer ISR isr_Motor() invertiert den Motorausgang. Verwenden Sie z.B. Timer3.
- In den Operationen sensitiv() und aufhellen() wird u.a. die Interruptzeit passend eingestellt..
Erstellen Sie den modifizierten Code für die Initialisierung der Timer ISR, der Operationen isr_Motor(), ausschaltenMotor(), aufhellen() und sensitiv().
Mögliche komplette Lösung
#include <Arduino.h>
static HardwareTimer mytimer = HardwareTimer(TIM3); // Timerinstanz sowie Timerauswahl
# define EIN_AUS PC13 // Entpreller lowaktiver UserButton auf dem Board
# define MODUS PA1 // Taster highaktiv prellend
# define MOTOR PC6 // PWM mit Timer 3 Kanal 1 möglich
enum zustandstyp {AUS,AN_ENTRY,AN,STOPP,ZEIT_UM,AUFHELLEN,SENSITIV};
zustandstyp zustand=AUS;
uint32_t weckzeit; // Zeit zum Wecken
int quadrant = 0; // Mundquadrant, der gerade geputzt werden soll
void anschaltenMotor(){
digitalWrite(MOTOR,HIGH);
}
void ausschaltenMotor(){
mytimer.pause();
digitalWrite(MOTOR,LOW);
}
void ausschaltenLED(){// schaltet nur die LED aus
GPIOC->ODR &= ~0b1111;
}
void stelleWecker(int n){ // n in Zehntelsekunden
weckzeit = millis()+n*100;
}
bool abgelaufenWecker(){ // true, wenn Weckzeit vorbei
return millis()>=weckzeit;
}
void isr_einAus(){ // wird mit EIN_AUS aufgerufen
if(zustand==AUS){
zustand=AN_ENTRY;
}
else{
zustand=AUS;
ausschaltenLED();
ausschaltenMotor();
quadrant=0;
}
}
void ausgebenLED(int n){ // gibt Leuchtband aus
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: // für quadrant > 2
GPIOC->ODR = GPIOC->ODR | 0b1111;
break;
}
}
void isr_Motor(){ // MotorAusgang invertieren
digitalWrite(MOTOR,!digitalRead(MOTOR));
}
void aufhellen(){ // Motor läuft mit 1 Hz
mytimer.setOverflow(2,HERTZ_FORMAT);
mytimer.resume();
}
void sensitiv(){ // Motor läuft mit 50 Hz
mytimer.setOverflow(100,HERTZ_FORMAT);
mytimer.resume();
}
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); // LEDA Ausgang
pinMode(PC1, OUTPUT); // LEDB Ausgang
pinMode(PC2, OUTPUT); // LEDC Ausgang
pinMode(PC3, OUTPUT); // LEDD Ausgang
pinMode(MOTOR, OUTPUT); // Motor Ausgang
pinMode(EIN_AUS, INPUT);
pinMode(MODUS, INPUT_PULLDOWN);
attachInterrupt (digitalPinToInterrupt (EIN_AUS), isr_einAus, FALLING);
mytimer.attachInterrupt(isr_Motor); // Timer ISR einstellen
}
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;
}
}
📦 🚧 Paketerfassung mit Sortierstraße (HP09)
Synopsis: https://github.com/EinarArnason/ArduinoQueue🔗
Aufgabe wurde im Fachnetz-BS von Kollege Carsten N. gepostet, ich habe versucht sie für meinen Unterricht mit Arduino an zu passen.
Auf einem Förderband werden zwei unterschiedlich lange Paketsorten (P_lang, P_kurz) mit zwei Lichtschranken erfasst und anschließend in Transportbehälter geschoben (Unterprogramme pKurz(), pLang()). Eine zweistellige gemultiplexte 7-Segment-LED-Anzeige zeigt die Anzahl der verarbeiteten Pakete an.
Es gelten folgende Randbedingungen:
- Mit 2 Lichtschranken-Sensoren S1 und S2 werden die Pakete erfasst. Das Signal Sx ist ‚0‘,
wenn ein Paket die Lichtschranke unterbricht, sonst ‚1‘. - Die Paketsorte P_lang kann gleichzeitig von beiden Sensoren erfasst werden.
- Die Paketsorte P_kurz ist zu kurz, um gleichzeitig von beiden Sensoren erfasst zu werden.
- Die Pakete liegen mit einem Mindestabstand auseinander, so dass das nächste Paket den Sensor
S1 erst erreichen kann, wenn das vorherige Paket bereits verpackt wurde. - Die Zeit zwischen dem Verlassen von S2 und dem Verpackungsauslösen pKurz() für ein P_kurz-
Paket muss 3 Sekunden betragen, zwischen S2 und pLang() 8 Sekunden. - Das Programm besitzt folgende globale Variablen: timeout:Timeout
- Das Programm besitzt folgende Routinen:
isr_S1_rise(), isr_S1_fall (), isr_S2_rise(), isr_S2_fall(), isr_Wecker(), pKurz(), pLang()
Zeichnen Sie ein UML-Zustandsdiagramm, das für eine Erkennung der Paketsorten P1 und P2 und deren
korrekte Verpackung sorgt.
Probleme bei der Umsetzung mit STM32-Arduino
Auf einen Pin z.B. S1 zwei verschiedene ISRs (rise, fall) zu setzen ist mit Arduino nicht einfach möglich, es kann nur eine ISR pro Pin eingestellt werden, somit muss statt dessen eine change-ISR verwendet werden und in dieser wird bestimmt ob rise bzw. fall vorliegt.
Zustandsdiagramm
Testcode
Statt der Lichtschranken habe ich die Taster an PA1 und PA6 verwendet und entprellt. Die Logik ist umgekehrt.
Ausserdem habe ich versucht, die ISRs in Events um zu wandeln mit einer EventQueue.
#include <ArduinoQueue.h>
static HardwareTimer myWecker = HardwareTimer(TIM2); // Timerinstanz für Zeitintervall
static HardwareTimer anzeigeTimer = HardwareTimer(TIM4); // Timerinstanz für gemultiplexte Anzeige
typedef std::function<void(void)> my_event_t;
ArduinoQueue <my_event_t> eventQueue(10);
#define S1 PA1 // Prellend High aktiv
#define S2 PA6 // Prellend High aktiv
#define ENTPRELL_ZEIT 8 // Entprellzeit in ms
enum zustandstyp {RUHE,S1_UNTERBROCHEN,P_LANG_ERKANNT,P_KURZ_ERKANNT,P_LANG_AUSWERFEN,P_KURZ_AUSWERFEN};
zustandstyp zustand=RUHE;
void isr_S1_fall(){ // Lichtschranke fall = Taster rise
Serial.println("isr_S1_fall");
delay(ENTPRELL_ZEIT); // 5ms warten Entprellen
if(digitalRead(S1)){ // welchen stabilen Zustand hat nun S1 Bei true Action!
eventQueue.enqueue(event_S1_fall);
}
}
void event_S1_fall(){
Serial.println("event_S1_fall");
switch(zustand){
case RUHE:
zustand=S1_UNTERBROCHEN;
break;
}
}
void isr_S1_rise(){ // Lichtschranke rise = Taster fall
delay(ENTPRELL_ZEIT); // 5ms warten Entprellen
if(!digitalRead(S1)){ // welchen stabilen Zustand hat nun S1 Bei true Action!
eventQueue.enqueue(event_S1_rise);
}
}
void event_S1_rise(){
Serial.println("event_S1_rise");
switch(zustand){
case S1_UNTERBROCHEN:
zustand=P_KURZ_ERKANNT;
break;
}
}
void isr_S2_fall(){ // Lichtschranke fall = Taster rise
delay(ENTPRELL_ZEIT); // 5ms warten Entprellen
if(digitalRead(S2)){ // welchen stabilen Zustand hat nun S2 Bei true Action!
eventQueue.enqueue(event_S2_fall);
}
}
void event_S2_fall(){
Serial.println("event_S2_fall");
switch(zustand){
case S1_UNTERBROCHEN:
zustand=P_LANG_ERKANNT;
break;
}
}
void isr_S2_rise(){ // Lichtschranke rise = Taster fall
delay(ENTPRELL_ZEIT); // 5ms warten Entprellen
if(!digitalRead(S2)){ // welchen stabilen Zustand hat nun S2 Bei false Action!
eventQueue.enqueue(event_S2_rise);
}
}
void event_S2_rise(){
Serial.println("event_S2_rise");
switch(zustand){
case P_LANG_ERKANNT:
zustand=P_LANG_AUSWERFEN;
stelleWecker(8);
break;
case P_KURZ_ERKANNT:
zustand=P_KURZ_AUSWERFEN;
stelleWecker(3);
break;
}
}
void stelleWecker(int n){ // Sekundenwecker stellen
myWecker.setPrescaleFactor(32000); // 32MHz/32000 = 1kHz -> 1ms Periodendauer
myWecker.setOverflow(n*1000); // Zeit in ms einstellen
myWecker.attachInterrupt(isr_Wecker); // ISR einstellen
myWecker.setCount(0); // Zählerstand wieder auf 0 stellen
myWecker.resume(); // Timer starten
}
void isr_Wecker(){ // Wecker ist abgelaufen
eventQueue.enqueue(event_Wecker);
}
void event_Wecker(){
Serial.println("event_Wecker");
switch(zustand){
case P_LANG_AUSWERFEN:
zustand=RUHE;
pLang();
break;
case P_KURZ_AUSWERFEN:
zustand=RUHE;
pKurz();
break;
}
}
void pLang(){
Serial.println("pLang()");
}
void pKurz(){
Serial.println("pKurz()");
}
void setup() {
pinMode(S1, INPUT_PULLDOWN);
pinMode(S2, INPUT_PULLDOWN);
attachInterrupt (digitalPinToInterrupt (S1), isr_S1_fall, RISING); // Taster umgekehrte Logik
//attachInterrupt (digitalPinToInterrupt (S1), isr_S1_rise, FALLING); // Taster umgekehrte Logik
attachInterrupt (digitalPinToInterrupt (S2), isr_S2_fall, RISING);
attachInterrupt (digitalPinToInterrupt (S2), isr_S2_rise, FALLING); // Taster umgekehrte Logik
Serial.begin(9600);
}
void loop() {
if (!eventQueue.isEmpty()) { // alle Events aufarbeiten
my_event_t e = eventQueue.dequeue();
e();
}
switch(zustand){ // für do und Transitionen
case RUHE:
break;
}
}
Zwei ISRs mit Rising und Falling auf dem selben Pin klappt nicht bei Arduino?!