Guten Abend Leute,
ich befinde mich aktuell auf der (geistigen) Suche nach eine
intelligenten Vorgehensweise zur Unterscheidung von
PinChangeInterrupt-Quellen. Bis heute mit irgendwelchen Workarounds und
Krücken ausgekommen.
Nun wende ich mich an euch, in der Hoffnung, dass daraus ein Ansatz
entsteht, den man auch wiederverwenden kann.
Folgendes Szenario:
Ein Pin Change Interrupt Vektor beinhaltet mehrere Pins (Sowie ich das
bei den aktuell hier rumfliegenden ATmega88 und 1284P sehe, wohl immer
einen Port, also 8 Stück).
Wenn ich also 4 Pins mit einem PCINT-Vektor habe, muss ich sie alle der
Reihe nach in der ISR abfragen. Gibt es vielleicht eine Möglichkeit, wie
ich bei einem aufgetretenen Interrupt nicht alle vier Pins durchgehen
muss?
Ein praktisches Beispiel:
Bei 4 als Eingänge definierten Pins ist auf jede zweite Flanke zu
reagieren. Sobald aber die zweite Flanke eines der Pins erreicht wird
(Flag wird gesetzt) , wird auch das Flag beim anderen Pin gesetzt. (Weil
es eben nur eine einzige ISR ist, welche aufgerufen wird, auch wenn nur
ein Pin den Zustand geändert hat. Logischerweise)
Könnt ihr mir verraten, wie ihr dieses Problem löst? Oder versucht ihr
die PCINT zu umschiffen?
Ein Beispielscode mit 2 unterschiedlichen Pins:
So wie gezeigt mache ich das bisher auch, bis auf eine kleinen
Unterschied: Direkt am Anfang der ISR speichere ich das PINB Register
zwischen und mache die Auswertung dann mit der Variable.
Wenn du nur Status-Bits bei steigender Flanke setzt, könntest direkt das
PINB Register mit einem Flag-Byte ver-oder-n und ein Flag setzen was im
Hauptprogramm eine Änderung ankündigt.
Note schrieb:> Könnt ihr mir verraten, wie ihr dieses Problem löst? Oder versucht ihr> die PCINT zu umschiffen?
Das Problem hast du ja nur wenn mehr als 1 Pin am selben Port ist.
In deinem Beispiel werden alle Pins unnötigerweise nacheinander
abgefragt.
Mache das mit z.B. OldState und ActState.
Einzelne Pins muss man auf jeden Fall abarbeiten, mit dem Unterschied,
dass ich es mit switch oder select..case mache, da nur ein Pin seinen
Zustand gewechselt hat. Es stehen zwar immer noch 4 Abfragen für
4 Pins aber es wird nur jeweils eine davon ausgeführt.
Hier als Beispiel
(nicht durch den Compiler gejagt, sollte aber OK sein):
1
ActState=PINB&0x0F;
2
mask=ActState^OldState;
3
switch(mask){
4
casePin_0:
5
...
6
break;
7
...
8
...
9
casePin_3:
10
...
11
break;
12
default:
13
}
14
OldState=ActState;
OldState wird als volatile deklariert und nur einmal im main()
ganz am Anfang eingelesen.
P.S.
Compiler übersetzt switch..case sowieso in if..elseif..else
Abfrage, so dass es vielleicht besser ist, von Anfang an mit
if..elseif..else zu arbeiten aber für mich ist switch..case
irgendwie übersichtlicher, deswegen.
Note schrieb:> ich befinde mich aktuell auf der (geistigen) Suche nach eine> intelligenten Vorgehensweise zur Unterscheidung von> PinChangeInterrupt-Quellen.
Tja, das Problem ist: man kann die nicht mit letzter Sicherheit
unterscheiden, da die Hardware halt keine getrennten Flags für jedes
einzelne Bit bereitstellt, sondern nur ein gemeinsames für den gesamten
Port.
Man ist also, sobald man an einem PCINT mehrere mögliche Quellen hat,
darauf angewiesen, den Status der Portbits in Software zu verfolgen. Da
deren Reaktionszeit aber endlich ist, kann die Sitation auftreten, dass
zwar das Interrupflag anzeigt, dass mindestens ein Eingang getoggelt
wurde, aber mittels Statusabfrage nicht mehr feststellbar ist, welche(r)
das war(en), weil ein oder mehrere der Eingänge schon wieder auf den
ursprünglichen Pegel zurückgekehrt sind, bevor die Software dazu kam,
den Status abzufragen.
Das ist ein prinzipielles Problem, was auch durch noch so gute Software
alleine nicht lösbar ist. Das schränkt leider die Verwendbarkeit der
PCINT-Interrupts in der Praxis erheblich ein.
c-hater schrieb:> Das ist ein prinzipielles Problem, was auch durch noch so gute Software> alleine nicht lösbar ist. Das schränkt leider die Verwendbarkeit der> PCINT-Interrupts in der Praxis erheblich ein.
Nein, das ist nur selten eine Einschränkung.
Die Frage ist, ob die Nutz-Pulse immer lang genug sind, damit der
Interrupt abgearbeitet werden kann. Ist das der Fall, dann kann man auch
immer die Flanke erkennen.
Natürlich kann der Interrupt auch durch kurze Störungen (ESD) ausgelöst
werden. Dann sieht man im Interrupt, daß keine Nutz-Änderung erfolgte
und kann ihn sofort wieder verlassen.
Wichtig ist natürlich auch, daß man das PINx-Register nur an einer
einzigen Stelle im Interrupthandler einliest, damit die nachfolgenden
Auswertungen immer konsistent sind.
Für schnarchlahme Tasteneingabe nehme ich eh keine externen Interrupts,
bzw. nur zum Aufwachen aus dem Power-Down. Das geht mit dem
Timerinterrupt viel besser und bequemer.
@ Note
Da könnte man doch prima den Vertikalzähler, ähnlich wie er in Peter
Danneggers Tastenentprellung verwendet wird, benutzen. Zum
Funktionsprinzip siehe den Artikel "Tastenentprellung".
Dann noch ein Bitmaske dazu um die Wirkung der ActCDPinToggle1-Flags zu
erreichen, - entweder beim Zählen oder bei der Flag-Abfrage -, und
fertig.
Nützt Dir das was?
Peter D. schrieb:> c-hater schrieb:>> Das ist ein prinzipielles Problem, was auch durch noch so gute Software>> alleine nicht lösbar ist. Das schränkt leider die Verwendbarkeit der>> PCINT-Interrupts in der Praxis erheblich ein.> Die Frage ist, ob die Nutz-Pulse immer lang genug sind
Ganz genau. Und das eben kann die abfragende Software alleine halt
nicht sicherstellen. Und genau das schrieb ich auch.
> Nein, das ist nur selten eine Einschränkung.
Das ist oft eine Einschränkung. Eben immer dann, wenn o.g. Bedingung
nicht garantiert erfüllt ist.
c-hater schrieb:> Das ist oft eine Einschränkung. Eben immer dann, wenn o.g. Bedingung> nicht garantiert erfüllt ist.
Wenn sich der uC nicht gerade in einer anderen ISR befindet, dauert
es höchstens 5 Cy bis zum Einsprung. PUSH, MOV und IN noch 4 Cy -
wobei man IN (falls es ein ASM ist) zuerst machen kann, aber auch
so sind es nicht mehr als 9Cy - max. 562,5ns bei 16MHz.
Alles, was länger als 600ns dauert, wird somit zuverlässig erfasst und
ohne Probleme ausgewertet.
c-hater schrieb:> Ganz genau. Und das eben kann die abfragende Software alleine halt> nicht sicherstellen. Und genau das schrieb ich auch.
Alles, was kürzer als 600ns dauert, sind ganz einfach irgendwelche
Störspitzen und diese sollen auch nicht ausgewertet werden.
Kein normaler Mensch wird mit so kurzen Impulsen arbeiten und dann
auch noch erwarten, dass diese mit Pin Change Interrupt zuverlässig
detektiert und ausgewertet werden.
Wenn schon, dann mit INT0 oder INT1.
Ich finde Deinen Code korrekt.
Je nach Anwendung kann es angebracht sein, 4 if() Bedingungen ohne else
zu benutzen, da es u.U. auch sein kann, dass mehrere Pins (fast)
gleichzeitig den Zustand wechseln und daher mehrere State-Changes in
einem Interrupt gemeldet werden.
gruß, Stefan
Stefan K. schrieb:> Ich finde Deinen Code korrekt.
Der Code ist alles andere als korrekt, der TO hat es wahrscheinlich
selber gemerkt und deswegen hier nachgefragt.
Stefan K. schrieb:> Je nach Anwendung kann es angebracht sein, 4 if() Bedingungen ohne else> zu benutzen, da es u.U. auch sein kann, dass mehrere Pins (fast)> gleichzeitig den Zustand wechseln und daher mehrere State-Changes in> einem Interrupt gemeldet werden.
Was hat die else Abfrage damit zu tun ?
Marc V. schrieb:> c-hater schrieb:>> Das ist oft eine Einschränkung. Eben immer dann, wenn o.g. Bedingung>> nicht garantiert erfüllt ist.> Wenn sich der uC nicht gerade in einer anderen ISR befindet
Die garantierte Abwesenheit jeder konkurrierenden ISR würde ich schon
als Einschränkung bezeichnen, sogar als recht herbe Einschränkung.
Du nicht? Tsss...
c-hater schrieb:> Du nicht? Tsss...
Ich auch, natürlich.
Deswegen nehme ich für PCINT entweder nur einen einzigen Pin am
entsprechendem Port oder halt INT0 oder INT1.
Marc V. schrieb:>> Je nach Anwendung kann es angebracht sein, 4 if() Bedingungen ohne else>> zu benutzen, da es u.U. auch sein kann, dass mehrere Pins (fast)>> gleichzeitig den Zustand wechseln und daher mehrere State-Changes in>> einem Interrupt gemeldet werden.>> Was hat die else Abfrage damit zu tun ?
Ok, ich nehme alles zurück, beim zu oberflächlichen Durchlesen des Codes
ging ich von so etwas aus:
Hallo, da bin ich wieder. Danke für die zahlreichen und ausführlichen
Antworten, das freut mich.
Bei so vielen Antworten kann ich nicht jedem Einzelnen antworten,
entschuldigt.
Jedoch:
Gleich die ersten Antworten beinhalteten die Funktion, nach der ich
gesucht habe. Nämlich den alten Zustand sichern. Habe vorher immer jeden
einzelnen Pinstatus gesichert und bin nicht auf die Idee gekommen, den
ganzen Portzustand zu speichern. Asche auf mein Haupt
Also im Grunde bin ich mit dem Ansatz echt zufrieden:
volatile uint8_t portOldState = 0;
ISR(PCINT1_vect)
{
// Vergleich alter/neuer Portzustand
uint8_t changedBits;
changedBits = PINB^portOldState;
portOldState = PINB;
// Falls sich seit dem letzten PCINT PIN_1 geändert hat
if (changedBits&(1<<PIN_1))
{ // Hier die Pin1-Routine, falls sich der Zustand vom alten
unterscheidet}
if (changedBits&(1<<PIN_2))
{ // Hier die Pin2-Routine, falls sich der Zustand vom alten
unterscheidet}
}
Dafür vielen Dank an Tr, Marc Vesely. An sich wurde der bereits
vorhandene Code lediglich durch vier weitere Schleifen und zwei
Variablen erweitert.
Das Verfahren nach Peter Dannegger habe ich mir angeschaut, das ist
wirklich eine schöne Arbeit. Da werde ich mich drum kümmern, wenn das
o.g. Verfahren an seine Grenzen stößt bzw. noch mehr Pins abgearbeitet
werden müssen.
Danke an euch Leute und einen schönen Tag :)
P.S.: Welchen Hintergrund hat es eigentlich, dass man die Funktionen der
ext.Interrupts wie INT0/1 nicht an jedem Pin in dieser Form
implementiert? Deutlich größerer Aufwand?
Du hast definitiv das PCINT-Problem nicht annähernd begriffen. Du führst
mit deinem Code noch mehr Probleme ein, als es ohnehin schon birgt.
Vermeide es, mehrfach aus dem PIN-Register zu lesen. Denn der Status des
Ports könnte sich zwischen den einzelnen Lesezugriffen bereits wieder
ändern...
Note schrieb:> Deutlich größerer Aufwand?
Ja, schätzungsweise schon. Der pin change interrupt ist ja historisch
erst viel später dazu gekommen, damit wollte man vermutlich
die Interruptmöglichkeiten ohne großen Hardwareaufwand erweitern.
Insbesondere kann man einen AVR ja mit dem PCINT aus dem Schlaf
aufwecken, da gibt es viele Fälle, bei denen nur die Tatsache des
Aufweckens selbst wichtig ist, nicht die Kenntnis der genauen Quelle.
Note schrieb:> implementiert? Deutlich größerer Aufwand?
Ungefähr 8x (anstatt 24 für 3 Ports, nur 3).
(PCMSK(B0) AND PINB0) => \
... > PCIF_PortB
(PCMSK(B7) AND PINB7) => /
(PCMSK(C0) AND PINC0) => \
... > PCIF_PortC
(PCMSK(C6) AND PINC6) => /
(PCMSK(D0) AND PIND0) => \
... > PCIF_PortD
(PCMSK(D7) AND PIND7) => /
c-hater schrieb:> Vermeide es, mehrfach aus dem PIN-Register zu lesen. Denn der Status des> Ports könnte sich zwischen den einzelnen Lesezugriffen bereits wieder> ändern...
Ja, und vor allem sollte man als erstes den Pinzustand lesen.
@c-hater: Keinen Grund ausfallend zu werden. Ich habe einen Fehler
gemacht, verstanden. Aber gleich Worte wie "*definitiv* das
PCINT-Problem *nicht annähernd begriffen*" müssen doch nicht sein. Bin
auch nur ein Mensch, der sich aufgrund seines Problems an andere
Menschen gewandt hat.
So besser?
ISR(PCINT1_vect)
{
// Vergleich alter/neuer Portzustand
uint8_t changedBits;
uint8_t changedBitsBuffer;
changedBitsBuffer = PINB;
changedBits = changedBitsBuffer^portHistory;
portHistory = changedBitsBuffer;
// die restlichen Abfragen
}
c-hater schrieb:>> Du hast definitiv das PCINT-Problem nicht annähernd begriffen. Du führst> mit deinem Code noch mehr Probleme ein, als es ohnehin schon birgt.>> Vermeide es, mehrfach aus dem PIN-Register zu lesen. Denn der Status des> Ports könnte sich zwischen den einzelnen Lesezugriffen bereits wieder> ändern...
was er (c-hater) eigentlich sagen wollte:
Wenn man in einer ISR von volatilen Variablen eine lokale Kopie anlegt,
dann ist das sowohl für den Compiler besser zu optimieren, solange die
INT's gesperrt sind (AVR-Standard-ISR) korrekt und wenn wie hier ein
HW-Zustand zu einem Zeitpunkt ausgewertet werden soll, sogar Pflicht:
1
ISR(PCINT1_vect)
2
{
3
// erst mal HW-Zustand sichern
4
uint8_t_PINB=PINB;
5
6
// Vergleich alter/neuer Portzustand
7
uint8_tchangedBits;
8
9
10
changedBits=_PINB^portOldState;
11
portOldState=_PINB;
So macht es ja InputCapture, nur eben in HW, um die Interrupt-Latenz
auszuschalten.
Bei AVR ist diese lokale Kopie praktisch immer in einem Register, was
für erheblich kürzeren Code sorgt.
Wobei das auch auch außerhalb von ISR's eine gute Wahl sein kann, wenn
man z.B. in einem ATOMIC_BLOCK volatile Variablen verwurschtelt.
(Block-)lokale Kopie machen und vor Ende des Blocks wieder
zurückkopieren, falls nötig.
Edit: alle waren schneller ;-) Blöde Touchscreens.