Forum: Mikrocontroller und Digitale Elektronik 3 Interrupts am Arduino Nano


von Tobias (Gast)


Lesenswert?

Hallo Zusammen,

ich habe eine kleine Schaltung für meinen Warmwasserspeicher um 3x 
Durchflussmesser (mit kleinen Turbinen die pulse ausgeben) und 6x 
Temperatur (mit 18b20) gebaut und hänge jetzt an der Herrausforderung 
fest, 3 Interrupteingänge zu verwenden.
Ich habe mir einige Beispiele rausgesucht um mehr als die zwei externen 
Interrupts zu nutzen, aber die Beispiele spiegeln nicht das Szenario 
wieder das mir vorliegt und bedauerlicherweise verstehe ich sie nicht so 
umfänglich das ich es adaptieren kann :(

Hier mein aktueller Stand...

Meine drei Turbinen sind an den Pins (Beschriftung auf der Platine) D2, 
D3, D4 angeschlossen. Die Dinger wechseln immer zwischen High und Low. 
Anfänglich habe ich nur PIN D2+D3 genutz und mit 
"attachInterrupt(digitalPinToInterrupt(pulse_A_pin), ISR_A, CHANGE);" 
wie man es ja leicht beim googlen findet hat auch alles geklappt, die 
Turbinen scheinen schon zu funktionieren und korrekt angeschlossen zu 
sein.

in der setup()-Routine habe ich folgendes:
1
    cli();
2
    //Warum PCMSK2 und nicht PCMSK3 für Port D?
3
    PCMSK2 |= 0b00001110;  // 2,3,4 
4
5
    PCIFR |= bit(PCIF2);    // clear any outstanding interrupts
6
    PCICR |= bit(PCIE2);    // enable pin change interrupts for D0 to D7
7
    sei();
8
    //
9
    pinMode(pulse_A_pin, INPUT_PULLUP);
10
    pinMode(pulse_B_pin, INPUT_PULLUP);
11
    pinMode(pulse_C_pin, INPUT_PULLUP);

Die Variablen pulse_#_pin sind wie folgt zugewiesen:
1
uint8_t pulse_A_pin = 2;
2
uint8_t pulse_B_pin = 3;
3
uint8_t pulse_C_pin = 4;

Die Service-Routine sieht so aus:
Nach meinem Verständnis der Beispiele springt bei einem Zustandswechsel 
einer der drei Pins der µ in diese Methode. Nur tut er das nicht :(
Die Zerlegenung welcher Pin sich geändert hat, erscheint mir noch 
unvollständig, aber das bekomme ich schon hin wenn die Methode mal 
aufgerufen werden würde.
1
ISR(PCINT2_vect)
2
{
3
    // handle pin change interrupt for D0 to D7 here
4
    if (PIND & bit(2))  // if it was high
5
        ISR_A;
6
7
    if (PIND & bit(3))  // if it was high
8
        ISR_B;
9
    
10
    if (PIND & bit(4))  // if it was high
11
        ISR_C;
12
13
    Serial.println("ISR");    //Kommt NICHT :(
14
}
ISR_A,B,C sind die separaten Methoden für die einzelnen Pins

von Stefan F. (Gast)


Lesenswert?

Zeige mal den ganzen Quelltext.

Was hast du dir bei der ISR(PCINT2_vect) gedacht? Damit programmierst du 
nicht nur am Arduino Framework vorbei, sondern auch noch falsch. Denn du 
verzweigst dort nach dem Zustand der drei Pins, nicht nach deren 
Wechsel. Alle drei Pins können gleichzeitig HIGH sein!

Siehe 
https://www.arduino.cc/reference/de/language/functions/external-interrupts/attachinterrupt/

Außerdem darf man Serial.println() nicht innerhalb von ISR verwenden, 
weil es selbst wiederum von Interrupts abhängt.

Siehe 
https://forum.arduino.cc/t/serial-println-inside-isr-bad-idea/298010

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Tobias schrieb:
> Meine drei Turbinen sind an den Pins (Beschriftung auf der Platine) D2,
> D3, D4 angeschlossen. Die Dinger wechseln immer zwischen High und Low.
An welcher Stelle ist da jetzt ein Interrupt nötig?

> Die Dinger wechseln immer zwischen High und Low.
Wie schnell wechseln die zwischen h und l? Und welche Information ist 
darin versteckt?

> PCMSK2 |= 0b00001110;  // 2,3,4
Das scheint mir 1 Bit versetzt, denn wenn ich es mit 
https://arduino-projekte.webnode.at/registerprogrammierung/pinchangeinterrupt/ 
vergleiche, dann müssten da rechts 2 Nullen sein.

> bei einem Zustandswechsel einer der drei Pins der µ in diese Methode.
> Nur tut er das nicht
Hast du da tatsächlich eine Signaländerung und brauchbare Pegel? Wie 
wäre es, wenn du da statt einen ewig langen Text auszugeben einfach mal 
eine LED toggelst?

: Bearbeitet durch Moderator
von Lukas K. (kugelblitz)


Lesenswert?

1
const byte interruptPin = 2;
2
3
void setup() {
4
  pinMode(4, INPUT_PULLUP);
5
  pinMode(5, INPUT_PULLUP);
6
  pinMode(6, INPUT_PULLUP);
7
  pinMode(interruptPin, INPUT_PULLUP);
8
  attachInterrupt(0, inter, CHANGE);   //pin 2
9
}
10
11
void loop() {
12
}
13
14
void inter() {
15
 if(!digitalRead(4)){  
16
      //wenn pin 2 und 4
17
 }
18
 if(!digitalRead(5)){
19
    //wenn pin 2 und 5
20
 }
21
 if(!digitalRead(6)){
22
    //wenn pin 2 und 6
23
 } 
24
25
}
Muss nur noch 4,5,6 mit 2 über eine Diode verbinden.(habs nicht geteste)

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Nochmal, lies die Anleitung!

https://www.arduino.cc/reference/de/language/functions/external-interrupts/attachinterrupt/

Du hast die attachInterrupt() ohne digitalPinToInterrupt() verwendet! 
Meinst du die schreiben das aus Spaß sowohl in den Text als auch ins 
Code-Beispiel?

Nun fragst du in deiner ISR ab, ob die drei Pins LOW sind. Was willst du 
damit bezwecken? Sie können alle gleichzeitig LOW sein!

Was hast du mit dem EINEN Interrupt-Pin vor? Du hast doch oben 
geschrieben, dass du bei drei Eingängen auf Flankenwechsel reagieren 
willst. Also musst du drei Interrupthandler schreiben die du mit drei 
mal attachInterrupt() zuweist.

von Lukas K. (kugelblitz)


Lesenswert?

> Was hast du mit dem EINEN Interrupt-Pin vor? Du hast doch oben
> geschrieben, dass du bei drei Eingängen auf Flankenwechsel reagieren
> willst. Also musst du drei Interrupthandler schreiben die du mit drei
> mal attachInterrupt() zuweist.

Wenn sein µC das her gibt

: Bearbeitet durch User
von Lukas K. (kugelblitz)


Lesenswert?

Stefan ⛄ F. schrieb:

> Du hast die attachInterrupt() ohne digitalPinToInterrupt() verwendet!
> Meinst du die schreiben das aus Spaß sowohl in den Text als auch ins
> Code-Beispiel?

#define digitalPinToInterrupt(p)  ((p) == 2 ? 0 : ((p) == 3 ? 1 : 
NOT_AN_INTERRUPT))

digitalPinToInterrupt(2)=0
digitalPinToInterrupt(3)=1
digitalPinToInterrupt(4)=NOT_AN_INTERRUPT

von Wolfgang (Gast)


Lesenswert?

Tobias schrieb:
> ich habe eine kleine Schaltung für meinen Warmwasserspeicher um 3x
> Durchflussmesser (mit kleinen Turbinen die pulse ausgeben) ...

Mit welcher Frequenz kommen die Pulse bei maximalem Durchfluss?

von Tobias (Gast)


Lesenswert?

Vielen Dank für die Zahlreichen Antworten.

Also das Serial.print in der ISR ist warhaftig eine blöde Idee gewesen 
und ohne das wird die methode auch aufgerufen (bzw. ich kann nicht sagen 
ob sie vorher auch aufgerufen wurde, jedenfalls kam nichts über Seriel).

@ Lothar M.
Die kleinen Turbinen wechseln zwischen High und Low in Abhängigkeit zum 
Volumenstrom. Je mehr durchfließt desto schneller erfolgt der Wechsel. 
Um den Durchfluss zu ermitteln (darum geht es mir) muss ich messen wie 
viel Zeit zwischen den Wechseln erfolgt. Das sind in Betrieb bis zu 150 
Hz daher die Notwendigkeit der Interupts, weil der Nano ja auch noch mit 
den 18b20 messungen beschäftigt ist wäre das zu ungenau im loop() zu 
prüfen ob sie sich ändern.
Und völlig richtig ich war um ein Bit versetzt. Hab ich echt nicht 
gesehen obwohl ich die Stelle immer wieder geprüft hab.

Stefan ⛄ F. schrieb:
> Was hast du dir bei der ISR(PCINT2_vect) gedacht? Damit programmierst du
> nicht nur am Arduino Framework vorbei, sondern auch noch falsch. Denn du
> verzweigst dort nach dem Zustand der drei Pins, nicht nach deren
> Wechsel. Alle drei Pins können gleichzeitig HIGH sein!

Ich hab bewusst am Framework vorbeigebastelt weil AttachInterrupt(..) 
nur für Pin 2,3 zu funktionieren schien. Liege ich da falsch? 
AttachInterrupt(..) ist viel einfacher :) ISR(PCINT2_vect) wird 
aufgerufen wenn sich einer der Pins, sofern man beim maskieren nicht um 
ein Bit verrutscht :), ändert. Das der Code in der ISR murkst ist hab 
ich ja erwähnt.

Inzwischen habe ich aus Verzweifelung die 3. Turbine an D6 gehängt. 
Folgender Code scheint jetzt zu funktionieren:
1
uint8_t pulse_A_pin = 2;
2
uint8_t pulse_B_pin = 3;
3
uint8_t pulse_C_pin = 6;
4
5
void setup()
6
{
7
...
8
    cli();
9
    //PCMSK2 |= 0b00100110;  // D 2,3,6 geht, müsste aber falsch sein
10
    PCMSK2 |= 0b01001100;  // D 2,3,6 geht, müsste aber falsch sein
11
    PCIFR |= bit(PCIF2);    // clear any outstanding interrupts
12
    PCICR |= bit(PCIE2);    // enable pin change interrupts for D0 to D7
13
    sei();
14
    //
15
    pinMode(pulse_A_pin, INPUT_PULLUP);
16
    pinMode(pulse_B_pin, INPUT_PULLUP);
17
    pinMode(pulse_C_pin, INPUT_PULLUP);
18
}
19
20
int last_A = 0;
21
int last_B = 0;
22
int last_C = 0;
23
24
ISR(PCINT2_vect)
25
{ 
26
    int cur_A = digitalRead(pulse_A_pin);
27
    int cur_B = digitalRead(pulse_B_pin);
28
    int cur_C = digitalRead(pulse_C_pin);
29
30
    if (last_A != cur_A)
31
    {
32
        last_A = cur_A;
33
        total_A++;
34
    }
35
36
    if (last_B != cur_B)
37
    {
38
        last_B = cur_B;
39
        total_B++;
40
    }    
41
42
    if (last_C != cur_C)
43
    {
44
        last_C = cur_C;
45
        total_C++;
46
    }
47
}
48
49
void loop()
50
{
51
   // Ident();
52
    sensors.requestTemperatures();
53
54
    tv_A = sensors.getTempC(adr_vl_a);
55
    tv_B = sensors.getTempC(adr_vl_b);
56
    tv_C = sensors.getTempC(adr_vl_c);
57
58
    tr_A = sensors.getTempC(adr_rl_a);
59
    tr_B = sensors.getTempC(adr_rl_b);
60
    tr_C = sensors.getTempC(adr_rl_c);
61
62
    UpdateScreen(); //Stellt die Werte auf einem OLED Display dar
63
64
    Serial.println("A: " + String(total_A) + "  B: " + String(total_B) + "  C: " + String(total_C));
65
}

Erstaunlicherweise, haben die Zähler "total_A,B,C" auch plausibel 
gezählt obwohl ich mit den Bits um eins verschoben war, es scheint mir 
als ob die Maskierei gar nicht wirkt. Ich schau mir das nochmal genauer 
an, weil ja auch die Serielle auf D liegt und vielleicht auch die ISR 
unnötig aufruft.

von Lukas K. (kugelblitz)


Lesenswert?

Tobias schrieb:

> Ich hab bewusst am Framework vorbeigebastelt weil AttachInterrupt(..)
> nur für Pin 2,3 zu funktionieren schien. Liege ich da falsch?

Der µC hat nur 2 Interrupt pins (Arduino 2,3)

von c-hater (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:

> Was hast du mit dem EINEN Interrupt-Pin vor? Du hast doch oben
> geschrieben, dass du bei drei Eingängen auf Flankenwechsel reagieren
> willst. Also musst du drei Interrupthandler schreiben die du mit drei
> mal attachInterrupt() zuweist.

Ähem, ich kenne mich mit dem Arduino-Gedöhns nicht aus.

Aber dafür mit der Hardware, sprich: ATmega328P. Und die kann nunmal für 
die Pinchange-Geschichte nur einen Interrupt pro Port (also für bis zu 
acht Pins) und für diesen Interrupt gibt es auch nur einen 
Interruptvektor und dementsprechend einen Interrupthandler.

Was aber am Schlimmsten ist: sie stellt keinen zuverlässigen Mechanismus 
bereit, um in der ISR erkennen zu können, welcher Pin genau nun den 
Interrupt ausgelöst hat, nur dann, wenn nur ein Pin an einem Port für 
die Interupterzeugung freigeschaltet ist, kann man sicher sein, dass es 
auch dieser Pin war.
Sind mehrere freigeschaltet, muss man Pegel verwalten und 
Pegeländerungen auswerten, um das erkennen zu können, was aber leider 
nur eine Behelfslösung ist und ganz prinzipiell nicht immer zuverlässig 
funktionieren kann.

Bei den neueren AVRs hatte Atmel übrigens genau an diesen Teil der 
Hardware deutlich nachgebessert. Da gibt es dann endlich ein 
Interrupt-Flagbit pro Pin und nicht nur einen pro Port wie bei den alten 
(allerdings nach wie vor nur einen Vektor und einen Handler pro Port). 
Darin ist aber zumindest zuverlässig erkennbar, welche(r) Pin(s) den 
Interrupt ausgelöst hat/haben.

von Tobias (Gast)


Lesenswert?

Lukas K. schrieb:
> Tobias schrieb:
>
>> Ich hab bewusst am Framework vorbeigebastelt weil AttachInterrupt(..)
>> nur für Pin 2,3 zu funktionieren schien. Liege ich da falsch?
>
> Der µC hat nur 2 Interrupt pins (Arduino 2,3)

Er hat zwei externe Interrupts, man kann wohl aber eigentlich alle 
digitalen Eingänge als Intterupt nutzen, hab ich hier 
https://wikimho.com/de/q/arduino/1784 gelernt.

Mein letztgenanntes Beispiel funktioniert nun auch mit drei Interrupts 
(reicht mir auch für die Anwendung).

von Lukas K. (kugelblitz)


Lesenswert?

Tobias schrieb:
> Lukas K. schrieb:
>> Tobias schrieb:
>>
>>> Ich hab bewusst am Framework vorbeigebastelt weil AttachInterrupt(..)
>>> nur für Pin 2,3 zu funktionieren schien. Liege ich da falsch?
>>
>> Der µC hat nur 2 Interrupt pins (Arduino 2,3)
>
> Er hat zwei externe Interrupts, man kann wohl aber eigentlich alle
> digitalen Eingänge als Intterupt nutzen, hab ich hier
> https://wikimho.com/de/q/arduino/1784 gelernt.

Da lag ich wohl falsch, wieder was gelernt:)

von Achim H. (pluto25)


Lesenswert?

Nicht vergessen das es auch ein Int gibt wenn der Pin zu high zurück 
kehrt. Ist dann noch einer Low wird der falsch gezählt. Neben Pin D2 und 
3 hat auch Pin B.0 einen alleinigen Int der nur auf pegelwechsel in 
einer Richtung reagiert. (In Deinem Fall von High auf low)

von Tobias (Gast)


Lesenswert?

A. H. schrieb:
> Nicht vergessen das es auch ein Int gibt wenn der Pin zu high
> zurück
> kehrt. Ist dann noch einer Low wird der falsch gezählt. Neben Pin D2 und
> 3 hat auch Pin B.0 einen alleinigen Int der nur auf pegelwechsel in
> einer Richtung reagiert. (In Deinem Fall von High auf low)

Danke für den Tip, aber habe ich das nicht in meiner ISR gelöst? So wie 
ich es verstanden habe löst dir ISR aus wenn irgend einer der maskierten 
Pins von low auf high oder umgekehrt wechselt. Dann prüfe ich ob sich 
Zustände der drei Eingänge geändert haben und zähle dann für diesen 
Eingang einen Zähler eins hoch. Einen Fehler von 1 kann es zum 
Programmstart geben, wenn eine der Turbinen gerade auf HIGH ist, weil 
die last_x ja auf LOW initialisiert sind, das macht aber nichts.
1
//Merker der Zustände wie sie vor dem letzten ISR-Aufruf waren
2
int last_A = 0;
3
int last_B = 0;
4
int last_C = 0;
5
6
ISR(PCINT2_vect)
7
{
8
    int cur_A = digitalRead(pulse_A_pin);
9
    int cur_B = digitalRead(pulse_B_pin);
10
    int cur_C = digitalRead(pulse_C_pin);
11
12
    if (last_A != cur_A)
13
    {
14
        last_A = cur_A;
15
        total_A++;
16
    }
17
18
    if (last_B != cur_B)
19
    {
20
        last_B = cur_B;
21
        total_B++;
22
    }    
23
24
    if (last_C != cur_C)
25
    {
26
        last_C = cur_C;
27
        total_C++;
28
    }
29
    //Wir überwachen ob noch irgend etwas die Methode aufruft
30
    //Die Summe der total_x muss = ISR_Visits sein
31
    ISR_Visits++;
32
}

von c-hater (Gast)


Lesenswert?

Tobias schrieb:

> Danke für den Tip, aber habe ich das nicht in meiner ISR gelöst? So wie
> ich es verstanden habe löst dir ISR aus wenn irgend einer der maskierten
> Pins von low auf high oder umgekehrt wechselt.

Genau.

> Dann prüfe ich ob sich
> Zustände der drei Eingänge geändert haben

Wenn der Pegel sich nur sehr kurz ändert, hast du zwar einen Interrupt, 
siehst in der ISR aber keine Pegeländerung mehr. Das ist das ganz 
grundlegende Problem.

Was mit der gegebenen Hardware möglich ist, hast du getan. Aber es ist 
eben grundlegend potentiell fehlerbehaftet.

Wenn die Hardware nicht änderbar ist, dann ist der einzige Ausweg eine 
weise Pinwahl. Nur bei einem Pin eines Ports einen Pinchange-Interrupt 
benutzen. Dazu noch die zwei "echten" Int0 und 1. Damit hast du dann 
insgesamt 5 recht brauchbare Pin-Interrupts. Um die Pegelverfolgung 
kommst du aber für die drei Pinchange-Interrupts trotzdem nicht herum.

von Sebastian (Gast)


Lesenswert?

Macht es nicht viel mehr Sinn einen Timerinterrupt mit dagen wir 1000Hz 
aufzurufen und in diesem Interrupt die Pegeländerungen der drei 
Turbinenpins zu überwachen? Dann kann man auch gleich entprellen (falls 
Reedkontakte verwendet werden) ...

LG, Sebastian

von E.Gärtner (Gast)


Lesenswert?

Ich würde den Capture Modus zur Periodenmessung verwenden und die 
Turbinen nacheinander mittels MUX messen. Ist viel sauberer. Abgesehen 
davon sind die Flügelräder mechanisch mit Inertia behaftet und die 
Pulsrate ändert sich ohnehin nur relativ langsam.

Die Flügelräder haben eine relativ niedrige Pulsfrequenz, meist unter 
100 Hz bei den üblichen Anwendungen.

Eine sequentielle Periodenmessung würde ausreichende Genauigkeit über 
ein paar gemittelten Periodenmessungen ergeben.

Es ist m.M. ungünstig ein uC unnötig in eine zeitliche Zwangsjacke zu 
stecken. die Signale von den Turbinen stören so andauernd den Ablauf des 
Programms was ab einen bestimmten Punkt störend sein kann. Am besten 
fährt man mit einer State Machine die die zeitlichen Abläufe ordentlich 
nicht-synchron abfährt. Da ist dann das ganze Programm viel gemütlicher. 
Auch serielle Communication und andere Aufgaben stören sich nicht 
gegenseitig.

von Tobias (Gast)


Lesenswert?

c-hater schrieb:
> Wenn der Pegel sich nur sehr kurz ändert, hast du zwar einen Interrupt,
> siehst in der ISR aber keine Pegeländerung mehr. Das ist das ganz
> grundlegende Problem.
>
> Was mit der gegebenen Hardware möglich ist, hast du getan. Aber es ist
> eben grundlegend potentiell fehlerbehaftet.
>
> Wenn die Hardware nicht änderbar ist, dann ist der einzige Ausweg eine
> weise Pinwahl. Nur bei einem Pin eines Ports einen Pinchange-Interrupt
> benutzen. Dazu noch die zwei "echten" Int0 und 1. Damit hast du dann
> insgesamt 5 recht brauchbare Pin-Interrupts. Um die Pegelverfolgung
> kommst du aber für die drei Pinchange-Interrupts trotzdem nicht herum.

Also ich bin mir nicht sicher ob ich dich richtig verstehe. Du meinst 
wenn sich der Pegel nochmal ändert bevor ich ihn in der ISR mit dem 
alten vergleichen konnte?
Oder wenn die ISR ganz kurz hintereinander durch unterschiedliche Pins 
aufgerufen wird?
Oder beides :)
Was passiert eigentlich wenn die ISR länger läuft als es bis zum 
nächsten Interrupt dauert?

Also nochmal kurz zum Einsatzzweck. Die Turbinen liefern ein 
Rechtecksignal dessen Frequenz ich messen muss/will. Die Frequenz 
erwarte ich in dem Bereich von bis zu 150 hZ. Meinst du das kommt zu 
Problemen, ist ja wirklich nicht schnell.
Es ist auch nicht dramatisch wenn nicht 100% akurat gemessen wird. Das 
ganze misst im Prinzip wieviel Wasser pro Zeit um welche Differenz 
erwärmt wird um die Leistung von Solarkollektor, Kamin und Ölheizung zu 
ermitteln. Alle drei beheizen einen Pufferspeicher mit dem ich dann 
wieder das Haus heize und Warmwasser bereitstelle.

Sebastian schrieb:
> Macht es nicht viel mehr Sinn einen Timerinterrupt mit dagen wir
> 1000Hz
> aufzurufen und in diesem Interrupt die Pegeländerungen der drei
> Turbinenpins zu überwachen? Dann kann man auch gleich entprellen (falls
> Reedkontakte verwendet werden) ...
>
> LG, Sebastian

Mit solchen Timern hab ich noch garnicht gearbeitet. Ob in den Dingern 
Reedkontakte verbaut sind kann ich nicht sagen. Prellen konnte ich 
bisher nicht feststellen. Aber wie würdest du in so einem Timer das 
entprellen lösen?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Tobias schrieb:
> Das sind in Betrieb bis zu 150 Hz daher die Notwendigkeit der Interupts
150 Hz sind im Grunde schnarchlangsam. Da kann ein µC mit 16MHz mehr als 
hunderttausend Maschinenbefehle zwischendurch ausführen. Und wenn das 
nicht reicht, dann ist er ungünstig programmiert.

Diese Abfrage gehört in einen 1ms-Timerinterrupt. Und dort wird zur 
Abkürzung der Interruptdauer natürlich nur ein Zähler hochgezählt 
und/oder ein Zeitstempel genommen, aber eben noch nichts ausgewertet.
Die Auswertung findet dann zeitlich völlig entspannt in der 
Hauptschleife statt.

Tobias schrieb:
> Du meinst wenn sich der Pegel nochmal ändert bevor ich ihn in der ISR
> mit dem alten vergleichen konnte?
> Oder wenn die ISR ganz kurz hintereinander durch unterschiedliche Pins
> aufgerufen wird?
> Oder beides :)
> Was passiert eigentlich wenn die ISR länger läuft als es bis zum
> nächsten Interrupt dauert?
Du bist auf der richtigen Spur. Diese Gedanken musst du bis zum Ende 
durchdenken. Und dann noch: was passiert, wenn ein kleiner Störspike 
durch einen Schaltfunken auftritt? Oder der Sensor einen Wackelkontakt 
hat und sacht vor sich hinbrutzelt und ganze Scharen von Interrupts 
erzeugt?

: Bearbeitet durch Moderator
von Tobias (Gast)


Lesenswert?

E.Gärtner schrieb:
> Ich würde den Capture Modus zur Periodenmessung verwenden und die
> Turbinen nacheinander mittels MUX messen. Ist viel sauberer. Abgesehen
> davon sind die Flügelräder mechanisch mit Inertia behaftet und die
> Pulsrate ändert sich ohnehin nur relativ langsam.
>
> Die Flügelräder haben eine relativ niedrige Pulsfrequenz, meist unter
> 100 Hz bei den üblichen Anwendungen.
>
> Eine sequentielle Periodenmessung würde ausreichende Genauigkeit über
> ein paar gemittelten Periodenmessungen ergeben.
>
> Es ist m.M. ungünstig ein uC unnötig in eine zeitliche Zwangsjacke zu
> stecken. die Signale von den Turbinen stören so andauernd den Ablauf des
> Programms was ab einen bestimmten Punkt störend sein kann. Am besten
> fährt man mit einer State Machine die die zeitlichen Abläufe ordentlich
> nicht-synchron abfährt. Da ist dann das ganze Programm viel gemütlicher.
> Auch serielle Communication und andere Aufgaben stören sich nicht
> gegenseitig.

OK ein Bauwerk zum Be- und Entladen von Zügen :) Geile Idee. (Ich 
versteh nur Bahnhof)

Kann es sein das du mir sagen willst, das es eine Möglichkeit gibt 
Eingänge  einfach Register hochzählen zulassen, in die man dann ab und 
an mal reinschaut? Das ist natürlich wirklich viel besser als die Kiste 
dauernd anzuhalten. Was muss ich googlen?

von c-hater (Gast)


Lesenswert?

Tobias schrieb:

> Oder beides :)

Beides natürlich. Mit der "intelligenten Pinwahl" kannst du eins der 
Probleme vollständig und das andere "zur Hälfte" eliminieren.

> Was passiert eigentlich wenn die ISR länger läuft als es bis zum
> nächsten Interrupt dauert?

Dann passiert Scheiße, immer und überall. Nicht zwingend tödlich, aber 
i.d.R. wird zumindest die Funktion der Anwendung dramatisch 
beeinträchtigt.

von Joachim B. (jar)


Lesenswert?

c-hater schrieb:
> Tobias schrieb:
>> Was passiert eigentlich wenn die ISR länger läuft als es bis zum
>> nächsten Interrupt dauert?
>
> Dann passiert Scheiße, immer und überall.

eben drum, darum ist es gut zu wissen wie lange man in der ISR Zeit hat 
bevor der nächste IRQ kommt!

man setzt einen Port, wenn es in den IRQ hereingeht und setzt den 
zurück, wenn es wieder herausgeht.
Dann hängt man das Oszilloskop an und schaut wie viel Zeit da zwischen 
ist, packt zur Not seine Berechnungen rein, die sofort erledigt werden 
müssen, setzt einen weiteren Port und rücksetzt wenn die NÖTIGE 
Berechnung erledigt ist, und schaut wie lange das dauert.
Man sieht sofort wann eines nicht mehr zwischen 2 IRQ oder in einen IRQ 
passt!

wenn natürlich im 10ms Timer IRQ eine I2C Tasten Abfrage nur 1,5µs 
dauert ist noch massig Luft!

Mit dem Oszi gucken ist für mich einfacher als CLK zählen, c-hater würde 
natürlich wissen wieviel CLK er benötigt und rechnen.

: Bearbeitet durch User
von Lukas K. (kugelblitz)


Lesenswert?

Joachim B. schrieb:
> c-hater schrieb:
>> Tobias schrieb:
>>> Was passiert eigentlich wenn die ISR länger läuft als es bis zum
>>> nächsten Interrupt dauert?
>>
>> Dann passiert Scheiße, immer und überall.
>

Wenn ISR gestartet wird, wird das Programm unterbrochen und nach ISR 
fortgesetzt. Wenn du in ISR, ISR startest wird die auch unterbrochen. Im 
Datenblatt steht, das er es 5mal kann und das 6. mal geht es schief. 
Also ich versteh das so, dass er sich 5 Unterbrechungen merken kann.

von Veit D. (devil-elec)


Lesenswert?

Tobias schrieb:

> Also ich bin mir nicht sicher ob ich dich richtig verstehe. Du meinst
> wenn sich der Pegel nochmal ändert bevor ich ihn in der ISR mit dem
> alten vergleichen konnte?
> Oder wenn die ISR ganz kurz hintereinander durch unterschiedliche Pins
> aufgerufen wird?
> Oder beides :)
> Was passiert eigentlich wenn die ISR länger läuft als es bis zum
> nächsten Interrupt dauert?

Wenn die Abarbeitung soeben in der ISR erfolgt, dann wird der nächste 
eintreffende Interrupt noch erkannt und gepuffert. Treffen während 
dieser Abarbeitung weitere Interrupts ein gehen diese verloren. Ist die 
erste ISR Abarbeitung fertig erfolgt die nächste ISR Abarbeitung vom 
"damals" nachfolgenden Interrupt, natürlich jetzt zeitlich verzögert. 
Trifft während dieser Abarbeitung der nächste Interrupt ein wird dieser 
wiederum gepuffert und alle anderen gehen verloren während diese 
Abarbeitung noch läuft. Dieses Spiel setzt sich unendlich fort. Das 
heißt, dauert deine ISR länger wie die zeitlichen Abstände der 
eintreffenden Interruptfolge ist, gehen immer mittendrin Interrupts 
verloren. Außerdem hängt dadurch dein Controller permanent im Interrupt. 
Das Hauptprogramm ist gefühlt "eingefroren".

von c-hater (Gast)


Lesenswert?

Lukas K. schrieb:

> Wenn ISR gestartet wird, wird das Programm unterbrochen und nach ISR
> fortgesetzt.

Naja, das ist etwas vereinfacht ausgedrückt (ISRs sind schließlich auch 
"Programm"), aber im Wesentlichen ist das so.

> Wenn du in ISR, ISR startest wird die auch unterbrochen.

Nein. Ganz sicher nicht bei Standard-ISRs auf Standard-AVR8. Die sind 
von Hause aus exclusiv.

> Im
> Datenblatt steht, das er es 5mal kann und das 6. mal geht es schief.
> Also ich versteh das so, dass er sich 5 Unterbrechungen merken kann.

In welchem Datenblatt willst du das gelesen haben, du Troll?

von Veit D. (devil-elec)


Lesenswert?

Lukas K. schrieb:

> Wenn ISR gestartet wird, wird das Programm unterbrochen und nach ISR
> fortgesetzt. Wenn du in ISR, ISR startest wird die auch unterbrochen. Im
> Datenblatt steht, das er es 5mal kann und das 6. mal geht es schief.
> Also ich versteh das so, dass er sich 5 Unterbrechungen merken kann.

Das kann ich die leider nicht glauben. Wir reden immer noch vom 
ATmega328P? Welche Seite im Manual? Am besten Kapitel Nummer nennen. Ich 
gehe von der aktuellsten Version aus.

von Sebastian W. (wangnick)


Lesenswert?

Tobias schrieb:
> Mit solchen Timern hab ich noch garnicht gearbeitet. Ob in den Dingern
> Reedkontakte verbaut sind kann ich nicht sagen. Prellen konnte ich
> bisher nicht feststellen. Aber wie würdest du in so einem Timer das
> entprellen lösen?

Dadurch dass der neue Zustand über mehrere Timerinterrupts stabil sein 
muss.

Wenn du einen Timerinterrupt alle 100us auslöst, dann wäre der 
Messfehler der Frequenz max. 100us, der bei 150Hz (6666us) bis zu +-1.5% 
ausmachen würde. Allerdings wäre der Fehler nicht kumulativ sondern 
würde sich über mehrere Impulse ausgleichen. Hier mal ein Paar 
Codeschnipsel als Anregung:
1
void tick_setup () {
2
  // Set up timer 2 to run every 100us
3
  TCCR2A = (1<<WGM21); // Prescaler 8
4
  TCCR2B = (1<<CS21); // CTC mode (count up until OCR2A)
5
  OCR2A = clockCyclesPerMicrosecond() * 100 / 8;
6
  TCNT2 = 0;
7
  TIFR2 |= (1<<OCF2A);
8
  TIMSK2 = (1<<OCIE2A); // Activate TIMER2_COMPA_vect interrupt
9
}
10
11
ISR(TIMER2_COMPA_vect) { // This runs every 100us.
12
  hl_tick();
13
}
14
15
#define HLSTAT ((PINC&(1<<PINC5))!=0)
16
typedef struct {
17
  uint8_t stat;
18
  uint32_t hl, ms;
19
} Hl;
20
volatile Hl vhl;
21
const uint8_t HLS = 10;
22
Hl hls[HLS];
23
volatile uint8_t hlw, hlr;
24
#define HLI(hli) ((hli+1)%HLS)
25
Hl hl;
26
27
void hl_setup (void) {
28
  PORTC |= (1<<PORTC5); // Activate pullup
29
  hl.stat = HLSTAT;
30
  vhl.stat = hl.stat;
31
}
32
33
static inline void hl_tick () { // This is called in ISR context
34
  static uint8_t nlmod = 0;
35
  uint8_t hlstat = HLSTAT;
36
  if (hlstat==vhl.stat) {
37
    nlmod = 0;
38
  } else {
39
    if (++nlmod>=5) { // 500us stable different from llstat
40
      nlmod = 0;
41
      vhl.stat = hlstat;
42
      vhl.hl++;
43
      vhl.ms = millis();
44
      if (HLI(hlw)!=hlr) {
45
        hls[hlw].stat = vhl.stat;
46
        hls[hlw].hl = vhl.hl;
47
        hls[hlw].ms = vhl.ms;
48
        hlw = HLI(hlw);
49
      }
50
    }
51
  }
52
}
53
54
void hl_loop (uint32_t nowms) {
55
  if (hlr!=hlw) { // Handle one entry per loop.
56
    hl = hls[hlr];
57
    hlr = HLI(hlr);
58
    hl_send_update(nowms);
59
  }
60
}

"hl_loop" muss natürlich mit solcher Häufigkeit und Latenz aufgerufen 
werden dass der Ringspeicher "hls" aller erkannter Flanken nie überläuft 
...

LG, Sebastian

von Stefan F. (Gast)


Lesenswert?

Tobias schrieb:
> Ich hab bewusst am Framework vorbeigebastelt weil AttachInterrupt(..)
> nur für Pin 2,3 zu funktionieren schien. Liege ich da falsch?

Kann gut sein. Steht dogar so in der Doku 
(https://www.arduino.cc/reference/de/language/functions/external-interrupts/attachinterrupt/).

Mich hat sehr verwirrt, das du im Eröffnungsbeitrag geschrieben hast, 
dass du
> attachInterrupt(digitalPinToInterrupt(pulse_A_pin), ISR_A, CHANGE)
erfolgreich verwendet hast, aber dein Code darunter ganz anders war.

Ich sehe, dass dir zwischenzeitlich geholfen wurde. Die weiteren Fragen 
die dazu stellen würde kamen auch schon, zum Teil sogar von dir selbst. 
Gut so, weiter machen.

von EAF (Gast)


Lesenswert?

Tobias schrieb:
> um 3x
> Durchflussmesser (mit kleinen Turbinen die pulse ausgeben)

Tobias schrieb:
> und 6x
> Temperatur (mit 18b20)
Das Timing ist relativ eng.
So dass man während dessen wohl die Interrupts abschalten muss.
Es wird also nicht gezählt, bzw können sich Lücken einschleichen.


Wäre es nicht evtl möglich einen ATMega, oder was anderes zu verwenden, 
welches 3 freie Counter/Timer hat, und die dann den Job im Hintergrund 
erledigen zu lassen?

von Wolfgang (Gast)


Lesenswert?

Tobias schrieb:
> Das sind in Betrieb bis zu 150 Hz daher die Notwendigkeit der Interupts

Wie kommst du zu diesem Schluss?
Wenn die Impulse so langsam kommen, reicht es doch völlig, die 
Pinzustände mit dem sowieso laufenden 1ms Interrupt vom Timer0 
auszuwerten.

von E.Gärtner (Gast)


Lesenswert?

Tobias schrieb:
> Kann es sein das du mir sagen willst, das es eine Möglichkeit gibt
> Eingänge  einfach Register hochzählen zulassen, in die man dann ab und
> an mal reinschaut? Das ist natürlich wirklich viel besser als die Kiste
> dauernd anzuhalten. Was muss ich googlen?

Eigentlich hatte ich etwas sehr Einfaches im Sinn. Die Aufgabe ist, drei 
Durchflussmesser periodisch zu messen um für eine sehr langsam 
ablaufende Temperatur Reglung benutzt zu werden. Diese Sensoren dieser 
Art, die ich kenne, geben Frequenzen bis zu ein paar hundert Hz aus. Mit 
dem Capture Timer kann man nun die Turbinen Ausgangs-Perioden messen. 
Ein Capture Eingang genügt wenn man die Sensoren nach einander 
abarbeitet. Nur ein einziger Zeit unkritischer Interrupt vom Capture 
Timer bei den Eingangstransitionen  ist notwendig. Die Sensoren schaltet 
man z.B. mit einen CMOS Analog Switch wie MC14052 um.

Den Capture timer konfiguriert man so, dass er nach jeder kompletten 
Periodenmessung in eine sehr kurze zeitunkritische ISR springt wo das 
Resultat der Messung gespeichert wird und dann automatisch den Capture 
Eingang auf die nächste Turbine umschaltet. Das lauft dann komplett 
automatisch und die drei Drehzahlen sind dann immer für die 
Hauptanwendung zugänglich. Die State machine kann dann gemütlich die 
eigentliche Logik der Anwendung abarbeiten, LCD Werte anzeigen, Tasten 
scannen und sonstige Däumchen drehen. Ein kleiner NANO kann das ohne zu 
schwitzen bequem erledigen.

Den Capture Timer konfiguriert man so, dass er bei jeder H->L Flanke den 
Messvorgang startet und bei der nächsten H->L in den ISR springt wo man 
dann einfach den TIMER Zählerstand liest und mit dem vorhergehenden 
vergleicht. Natürlich muss man die Überläufe berücksichtigen die 
periodisch entstehen wenn man es nicht vorzieht den Timer 
zurückzustellen. Der Timer1 wird so getaktet, dass genug Auflösung 
erzielt wird. Z.B. `PRESCALER=256. Bei 16MHZ Takt ist der Messbereich 
ohne Überlauf von 0.4768Hz ab mit einer TIMER1 Auflösung von 32us. Man 
kann also 300 Hz mit rund 0.5Hz Auflösung erfassen. Durch andere 
Einstellungen oder Mitteln ist mehr möglich. PSC=64 ist die Auflösung 
0.125 Hz und minimale Frequenz 2Hz. Wenn man Überläufe miteinbezieht ist 
natürlich mehr Auflösung möglich.

So ein Ansatz verursacht überhaupt keine kritischen Engpässe und lässt 
viel Luft nach oben zu und der uC steckt nicht in irgendeiner 
zeitkritischen Zwangsjacke.

Ist aber nur einer der Wege die nach ROM führen. Da ich faul und 
gefräßig bin, ziehe ich Methoden vor die mit geringsten 
Entwicklungsaufwand maximale Ergebnisse abgeben;-)

von Wolfgang (Gast)


Lesenswert?

Tobias schrieb:
> ich habe eine kleine Schaltung für meinen Warmwasserspeicher um 3x
> Durchflussmesser (mit kleinen Turbinen die pulse ausgeben) und 6x
> Temperatur (mit 18b20) gebaut und hänge jetzt an der Herrausforderung
> fest, 3 Interrupteingänge zu verwenden.

Was ist dein eigentliches Problem?
Durchflussmesser mit Temperatursensor in Zusammenhang mit 
Warmwasserspeicher hört sich nach Wärmemengenzähler an. Dazu müssten nur 
die Impulse gezählt werden. Jeder Puls, multipliziert mit der 
Temperaturdifferenz V-L, ergibt eine Energie. Die Frequenz/Periodendauer 
wäre dabei ziemlich egal.

von Sebastian W. (wangnick)


Lesenswert?

EAF schrieb:
> Das Timing ist relativ eng.
> So dass man während dessen wohl die Interrupts abschalten muss.
> Es wird also nicht gezählt, bzw können sich Lücken einschleichen.

Die Stelle im Arduino OneWire-Code an der interrupts am längsten 
gesperrt sind ist diese:
1
    noInterrupts();
2
    DIRECT_WRITE_LOW(reg, mask);
3
    DIRECT_MODE_OUTPUT(reg, mask);  // drive output low
4
    delayMicroseconds(65);
5
    DIRECT_WRITE_HIGH(reg, mask);  // drive output high
6
    interrupts();

Das sollte die Messung auch nicht übermäßig verfälschen. Dass dadurch 
150Hz-Impulse verpasst würden glaube ich eher nicht ...

LG, Sebastian

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.