Forum: Mikrocontroller und Digitale Elektronik [AVR] Zwischen einzelnen PCINT-Quellen unterscheiden


von Note (Gast)


Lesenswert?

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:
1
ISR(PCINT1_vect)
2
{
3
    ///////////////////////////////////////////////////////////
4
    // Hier Pin2 abfragen
5
  if (!(PINB&(1<<PIN_1)))
6
  {
7
    STATUS_BYTE |= (1<<PIN_1_ACTIVE);
8
    if (ActCDPinToggle1==0)
9
    {  // Zähler Flankenwechsel inkrementieren
10
      PinToggle1 = 1;
11
12
    }
13
    else if (PinToggle1==1)
14
    {
15
      // Wert zurücksetzen
16
      PinToggle1 = 0;
17
18
      if (WICHTIGE_AKTION)
19
      {// Das entsprechende Flag setzen
20
            }
21
    }
22
  }
23
    
24
  else if ((PINB & (1<<PIN_1)))  
25
  {
26
    STATUS_BYTE |= (1<<PIN_1_ACTIVE);
27
    if (ActCDPinToggle1==0)
28
    {  // Zähler Flankenwechsel inkrementieren
29
      PinToggle1 = 1;
30
    }
31
    else if (PinToggle1==1)
32
    {
33
      // Wert zurücksetzen
34
      PinToggle1 = 0;
35
      
36
      if (WICHTIGE_AKTION)
37
      {// Das entsprechende Flag setzen
38
            }
39
    }  
40
  }
41
42
    ///////////////////////////////////////////////////////////
43
    // Hier Pin2 abfragen
44
  if (!(PINB&(1<<PIN_2)))
45
  {
46
    STATUS_BYTE |= (1<<PIN_2_ACTIVE);
47
    if (ActCDPinToggle2==0)
48
    {  // Zähler Flankenwechsel inkrementieren
49
      PinToggle2 = 1;
50
    }
51
    else if (ActCDPinToggle2==1)
52
    {
53
      // Wert zurücksetzen
54
      PinToggle2 = 0;
55
      
56
      if (WICHTIGE_AKTION)
57
      {// Das entsprechende Flag setzen
58
            }
59
    }
60
  }
61
  
62
  else if ((PINB & (1<<PIN2)))
63
  {
64
    // Hier ein Flag setzen
65
    if (PinToggle2==0)
66
    {  // Zähler Flankenwechsel inkrementieren
67
      PinToggle2 = 1;
68
    }
69
    else if (PinToggle2==1)
70
    {
71
      if (WICHTIGE_AKTION)
72
      {// Das entsprechende Flag setzen
73
            }
74
    }
75
  }
76
77
}

von Note (Gast)


Lesenswert?

Ähm, jetzt bin ich mit den Namen durcheinander gekommen:
ActCDPinToggle1 = PinToggle1;
ActCDPinToggle2 = PinToggle2;

von Tr (Gast)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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
    case Pin_0:
5
     ...
6
    break;
7
...
8
...
9
    case Pin_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.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

: Bearbeitet durch User
von Hosenmatz (Gast)


Lesenswert?

@ 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?

von c-hater (Gast)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

: Bearbeitet durch User
von Stefan K. (stefan64)


Lesenswert?

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

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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 ?

von c-hater (Gast)


Lesenswert?

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

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

von Stefan K. (stefan64)


Lesenswert?

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:
1
  if (!(PINB&(1<<PIN_1)))
2
  {
3
    ...
4
  }    
5
  else if ((PINB & (1<<PIN_2)))  
6
  {
7
   ...
8
  }

Gruß, Stefan

von Note (Gast)


Lesenswert?

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 :)

von Note (Gast)


Lesenswert?

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?

von c-hater (Gast)


Lesenswert?

Note schrieb:
1
> ISR(PCINT1_vect)
2
> {
3
>   // Vergleich alter/neuer Portzustand
4
>   uint8_t changedBits;
5
> 
6
>   changedBits = PINB^portOldState;
7
>   portOldState = PINB;

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

: Bearbeitet durch User
von Note (Gast)


Lesenswert?

@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
}

von Carl D. (jcw2)


Lesenswert?

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_t changedBits;
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.

: Bearbeitet durch User
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.