Frage zur Abhandlung PCINT_ISR für gesamten Port

Zur Vorgeschichte:
Ich muss in den nächsten Tagen eine seit einigen Jahren funktionierende sowie mit wenigen Kenntnissen gebaute Steuerung überarbeiten und dann als nicht mehr zu änderndes Objekt hinterlassen. Was sehr Hardwarenah ist, ist irgendwo zusammengeklaubt...
Aufgebaut wurde das mit einem MEGA.
Unter anderem wurde der gesamte Port K - also die AnalogPins 8 bis 15 - dafür vorgesehen, als ZählerPins zu fungieren. Das geht auch. Nach bisherigen Erkenntnissen auch fehlerfrei.
Die Zustände bleiben lange genug - ich werde die ISR PCINT2_vect nicht mehr ändern.
Aus dem letzten Backup von vor 5 Jahren stelle ich

  • das komplett runtergekürzte Stück Code mal vor und
  • die Frage, wie das zählen auf den Pins sonst gelöst werden könnte
    (Flanke von HIGH nach LOW mit Ausschluss eines prellen)
/*
  PinChangeInterrupt, ISR und Ausgabe
  zusätzlich 8 Interruptkanäle
  an den Analogports A8 bis A15 auf einem MEGA 2650
  runtergekürzt aus Heizungssteuerung begrenzt auf die Volumenstromgeber
*/
volatile uint32_t pinscore[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // Vorbelegung Zähler mit 0
volatile bool pinlaststate[8] = {1, 1, 1, 1, 1, 1, 1, 1}; // Vorbelegung für PinStatusAbfrage

long lastmillis = 0;

void setup(void)
{
  Serial.begin(115200);          // serieller Monitor
  Serial.println(F("Start...."));//
  pinMode(A8,  INPUT_PULLUP);    // PIN A8 bis A15 auf dem MEGA
  pinMode(A9,  INPUT_PULLUP);    // als INPUT deklarieren und
  pinMode(A10, INPUT_PULLUP);    // den internen PULLUP aktivieren
  pinMode(A11, INPUT_PULLUP);    // PINs stehen auf HIGH
  pinMode(A12, INPUT_PULLUP);    // ausgewertet wird, wenn PIN auf
  pinMode(A13, INPUT_PULLUP);    // GND (LOW) geht
  pinMode(A14, INPUT_PULLUP);    // A8 = PCINT16 / A15 = PCINT23
  pinMode(A15, INPUT_PULLUP);    // AVR-Pin: 89-82
  cli();                         // schaltet Interrups ab
  PCMSK2 = B11111111;            // aktiviert PIN A8 bis A15 / entspricht PORT K
  //PCMSK2 |= (1 << PCINT16);    // alternativ: aktiviert nur PIN A8
  //PCMSK2 |= (1 << PCINT17);    // alternativ: aktiviert nur PIN A9
  PCICR |= (1 << PCIE1);         // aktiviert PinChangeInterrupt (im) ControlRegister
  sei();                         // schaltet Interrupts wieder ein
}

ISR(PCINT2_vect)                                              // Name ist zwingend festgelegt
{
  static uint32_t pinLastMillis[8] = {0, 0, 0, 0, 0, 0, 0, 0};// wird nur hier gebraucht
  const uint8_t pindebounce[8] = {0, 0, 0, 0, 0, 0, 0, 0};    // festlegen der Bouncezeit je PIN in ms
  //  const int pindebounce = 40;                             // Wenn nur eine Zeit ausreicht
  unsigned char portstate = PINK;                             // PINK ist nicht die Farbe sondern der PINportK
  //                                                          // kopiert den Zustand in eine Variable
  for (uint8_t i = 0; i <= 7; i++)                            // Auswertung: jeden einzelnen PIN
  {
    if (pinlaststate[i] != ((portstate << i) & 0x80 ))        // Hat sich der PINZustand verändert?
    {
      if (millis() - pinLastMillis[i] > pindebounce[i])       // Wenn ja: Ist die Bouncezeit abgelaufen?
      {
        /* if (millis() - pinlastmillis[i] > pindebounce) { */// (Alternative wenn nur eine Bouncetime)
        pinlaststate[i] = ((portstate << i) & 0x80 );         // Dann speichere aktuellen Zustand und ...
        pinLastMillis[i] = millis();                          // ... speichere aktuelle millis und ...
        if (0 == pinlaststate[i])                             // ... prüfe ob der Zustand jetzt 0 ist
        {
          pinscore[i]++;                                      // wenn ja, dann zähle für den PIN +1
        }
      }
    }
  }
}

void loop(void)
{
  static uint32_t lastmillis = 0;
  //delay(2000);                          // delay um zu zeigen, das Interrupt unabhängig von delay()
  if (millis() - lastmillis >= 2000)      // gibt nur alle xxx ms eine Ausgabe - gezählt wird trotzdem
  {
    Serial.print(F("Score in loop: "));   // Ausgabe des Inhalts des Zählers, um zu zeigen, das und wie man an die
    for (uint8_t i = 5; i <= 7; i++)      // Zählerinhalte innerhalb von loop kommt.
    {
      Serial.print(pinscore[i]);
      //      pinscore[i] = 0;
      Serial.print(";");
    }
    Serial.println();
    lastmillis = millis();
  }
}

Man könnte ein Maskenbit durchschieben und (portstate & i) prüfen.

Bitte mach das mal für einen Unwissenden.
Ich bin da wirklich vollkommen unbeleckt. :cry:
Ich kann Logik - Mach wenn irgendwas - aber diese ganze bit-Schieberei ist wirklich Tobak, den ich ganz vorsichtig aufnehmen muss...

Z.B.:

for (byte i = 0x80; i; i >>= 1) {...]

War das ausführlich genug? Falls nicht bitte nachfragen.

1 Like

Mach ich hiermit. Ich versteh nichts davon. Ok, das for noch.
Was passiert da?
Ich fange an mit

i = 0x80

macht BIN10000000

Wenn ich jetzt interpretiere, wird das ein schieben

i >>= 1

BIN 01000000 - usw.
Richtig?

Kannst Du mal zeigen, wie Du die ISR damit runterkürzen würdest?
Ich bin dafür zu blind - im wahrsten Sinne des Wortes.

Richtig erkannt :slight_smile:

Man kann dann z.B. lastPortState und newState als Variablen benutzen, ohne Arrays.

byte newState = PINK;
byte changed = lastState ^ newState; //port states
for (byte i = 0x80; i; i >>= 1) {
  if (changed & i) {  //hat sich geändert
    if (millis() - pinLastMillis[i] > pindebounce) {
      ...
    }
  }
}
lastState = newState;

Das entspricht der Logik Deines Codes, der dürfte IMO aber nicht funktionieren. Wenn die Zeit nur abgefragt wird, wenn sich etwas geändert hat, wie soll dann bemerkt werden, daß sich eine Zeit lang nichts mehr geändert hat, der Button also sicher gedrückt bzw. losgelassen wurde?

1 Like

Da habe ich jetzt bissl gebraucht, warum als zweites Element nur i; steht. Aber jetzt ist es mir klar, da i nach achtmal schieben 0 ist und somit FALSE und die for-Schleife abbricht. Die Schleife finde ich interessant und kannte ich bisher nicht. Danke.

Ich würde folgendes in Erwägung ziehen:
In der ISR passieren insgesamt 16 Aufrufe millis(), die m.E. unnötig Zeit kosten. Das würde ich vor die Schleife ziehen und innen dann nur noch den Wert verwenden.

Die auskommentierte Variante mit der gleichen Debounce-Zeit für alle Pins ist mir auch sympathischer, falls die Hardware außen sich für alle Pins gleichartig verhält ("Speichersparsamkeit" kann auch auf einem Mega nicht schaden).

Das blinde Huhn hat ein Korn gefunden :slight_smile:

Muss das denn?
Reicht es nicht, nur feststellen ob die aktuelle Änderung nach der vorher ausgelösten debouncetime eintritt und mit dem newState verknüpft, festgestellt wird, das der aktuelle Zustand des Pins 0 ist.
Damit löst wieder die debouncetime aus und das spätere ändern in den HIGH-Zustand wird nicht erfasst.

Ich hab das die Nacht aufgemalt und hab dann meine Interpretation draus gemacht. Ja, das hat wirklich was. Aber das denken in bits ist bei mir vollkommen Neuland.

Das ist ein guter Einwand...

Das je Pin gesonderte abhandeln resultiert aus den unterschiedlichen Gebern. Es gab welche mit reed und welche mit openCollector - letztere wurden dann mit 0 gesetzt.
Für den Anwendungsfall, könnte m.E. jetzt sogar ganz auf ein debounce verzichtet werden, da alle mit OC ausgerüstet sind.

Danke Euch.

Wie soll dann festgestellt werden, daß während des Debounce-Intervalls keine Änderung stattgefunden hat und der Zustand daher stabil ist?

Hallo,

ich muss einfach fragen. Warum unbedingt bei Interrupt bleiben wenn es nur Taster sind?

Reine Prinzipfrage. :wink:

Ich glaub ich hab das nicht richtig rüber gebracht.
Es soll nur gezählt werden, wenn der Pin vorher HIGH war und jetzt LOW ist.
Ich hab das mal in Code geschmissen. Der machts anders rum - ich muss das noch mit den Bits lernen :wink:
Da ist jetzt kein debounce drin. Annahme an einem Pin:
Er war HIGH und ist LOW.
Dann wird gezählt.
Jetzt kommt das debounce-timing. In der Zeit ist es egal, ob der Pin HIGH oder LOW geht.
Variante 1: Nach der debounceTime ist der Pin (immernoch) LOW
Wenn er HIGH geht, ist das egal.
Geht er DANN wieder LOW wird wieder gezählt

Variante2: Nach der debounceTime ist der Pin (wieder) HIGH
Geht er jetzt LOW wird gezählt.
Und das Spiel beginnt von vorn.

char *b2s(byte myByte)
//https://forum.arduino.cc/t/serial-print-n-bin-displaying-all-bits/46461
{
  static char Str[8];
  byte mask = B10000000;
  for (byte i = 0; i < 8; i++)
  {
    Str[i] = '0';
    if (((mask >> i) & myByte) == (mask >> i))
    {
      Str[i] = '1';
    }
  }
  // terminate the string with the null character
  Str[8] = '\0';
  return Str;
}

void tik(byte newState)
{
  static byte lastState = 0;
  Serial.print(F("Vergleiche:   ")); Serial.print(b2s(lastState)); Serial.println(F(" alt"));
  Serial.print(F("              ")); Serial.print(b2s(newState)); Serial.println(F(" neu"));
  byte changed = lastState ^ newState; //port states
  Serial.print(F("geändert     :")); Serial.println(b2s(changed));
  Serial.print(F("ändere Zähler:"));
  for (byte i = 0x80; i; i >>= 1)
  {
    if ((changed & i) && (newState & i))   //hat sich geändert
    {
      Serial.print(1);
    }
    else
    {
      Serial.print(0);
    }
  }
  lastState = newState;
  Serial.println(); Serial.println();
}
void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
}

void loop()
{
  if (millis() % 5000 == 0) {tik(analogRead(A0));}
}

Ergebnis:

14:43:50.188 -> Vergleiche:   00101101 alt
14:43:50.188 ->               00111101 neu
14:43:50.188 -> geändert     :00010000
14:43:50.188 -> ändere Zähler:00010000
14:43:50.188 -> 
14:43:55.189 -> Vergleiche:   00111101 alt
14:43:55.189 ->               00110110 neu
14:43:55.189 -> geändert     :00001011
14:43:55.189 -> ändere Zähler:00000010

Im ersten hat sich ein Bit geändert und wird gezählt - im zweiten 3 geändert aber auch nur 1 gezählt.

Eine Zeile ändern:

    if ((changed & i) && !(newState & i))   //hat sich geändert

Neue Ausgabe:

15:45:01.829 -> Vergleiche:   01000100 alt
15:45:01.829 ->               00111001 neu
15:45:01.862 -> geändert     :01111101
15:45:01.862 -> ändere Zähler:01000100

Immer nur dann wenn der alte Zustand HIGH war und der neue LOW ist, wird für diesen Zähler das aufaddieren aktiviert....

Das kann man noch vereinfachen:
if ((changed & !newState & i) //hat sich geändert
Muß aber nicht sein, möglicherweise optimiert das der Compiler von sich aus.

Das ist richtig, aber wenn das debounce dazu kommt eben nicht mehr, das gehört dazwischen.

Ich hab nen ganz anders Verständnisproblem.
Wie komme ich denn drauf, welches bit gesetzt ist?
Geht das nur mit einer UmlaufVariablen?
Der Schnipsel macht beides deutlich.

  uint8_t u = 0;
  for (byte i = 0x80; i; i >>= 1)
  {
    if ((zeit - pinLastMillis[u] > pinDebounce[u]) && (changed & i))
    {
      pinLastMillis[u] = zeit;
      if  (!(newState & i))
      {
        pinscore[u]++;
      }
    }
    u++;
  }
  lastState = newState;
  }

Wenn das bouncen nicht benötigt wird, ist es schon schick kurz. u bräuchte ich aber trotzdem.

Man kann das mit 2 Variablen machen, vielleicht als
for (byte u=7, i = 0x80; i; i >>= 1, u--)
oder mit
i = 1 << (7-u);
oder
i = table[u]; //tabelle const, einmal vorbesetzen

Da sollte doch jeder etwas finden, das ihm gefällt :slight_smile:

1 Like

Das gefällt.

Dann bau ich mal.

So das ist ja richtig schick geworden.

  uint32_t zeit = millis();
  byte u = 0;
  byte changed = lastState ^ newState; //port states
  for (byte i = 0x80; i; i >>= 1)
  {
    if ((zeit - pinLastMillis[u] > pinDebounce[u]) && (changed & i))
    {
      pinLastMillis[u] = zeit;
      if  (!(newState & i))
      {
        pinscore[u]++;
      }
    }
    u++;
  }
  lastState = newState;
}

Gibt aus:

Der Sketch verwendet 2410 Bytes (0%) des Programmspeicherplatzes.
Globale Variablen verwenden 255 Bytes (3%) des dynamischen Speichers,
  uint32_t zeit = millis();
  byte changed = lastState ^ newState; //port states
  for (byte u = 7, i = 0x80; i; i >>= 1, u--)
  {
    if ((zeit - pinLastMillis[u] > pinDebounce[u]) && (changed & i))
    {
      pinLastMillis[u] = zeit;
      if  (!(newState & i))
      {
        pinscore[u]++;
      }
    }
  }
  lastState = newState;
}
Der Sketch verwendet 2384 Bytes (0%) des Programmspeicherplatzes. Das Maximum sind 253952 Bytes.
Globale Variablen verwenden 255 Bytes (3%)

26 bytes... Nicht schlecht.

So und hier die Variante als reiner Zähler. Dank für die Geduld! :smiley:

volatile uint32_t pinscore[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // Vorbelegung Zähler mit 0

void tik(byte pink)
{
  byte newState = pink;
  static uint8_t lastState = 0;
  byte changed = lastState ^ newState; //port states
  for (byte u = 7, i = 0x80; i; i >>= 1, u--)
  {
    if ((changed & i) && !(newState & i)) //hat sich geändert
    {
      pinscore[u]++;
    }
  }
  lastState = newState;
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
}
void loop()
{
  if (millis() % 5000 == 0)
  {
    tik(analogRead(A0));
    Serial.print(F("Zähler : "));
    for (byte b = 0; b < 8; b++)
    {Serial.print(pinscore[b]); Serial.print(" ");}   Serial.println(); Serial.println();
  }
}
1 Like

Wenn Du mehr mit BitArrays (beliebiger Länge) machen willst, kannst Du Dir ja mal meine BitArrays anschauen.

Gruß Tommy