Ein Schiedsrichtertisch für Tischtennis mit 74HC595 und 74HC165

Von mir aus.
Oder nimmst das Komplettpaket - wir konzentrieren uns jetzt nur noch auf das Display.

tt.h
void bigDisplay();
void checkSpielerKeys();
void displaySpieler();
bool getSite(const bool site);
/*
       0      |     7     |
    5     1   |  2     6  |
       6      |     1     |
    4     2   |  3     5  |
       3      |     4     |
*/

/*
  Digit0 Timer Hunderter  // Minute Einer
  Digit1 Timer zehner     // Sekunde Zehner
  Digit2 Timer einer      // Sekunde Einer
  Digit3 SpielerA Satz + Aufschlag D40
  Digit4 SpielerA Punkte einer + Zehner D32
  Digit5 SpielerB Satz + Aufschlag D24
  Digit6 SpielerB Punkte einer + Zehner D16
*/
constexpr bool segActiv {0};  // Die Segmente leuchten, wenn RegisterPin => 0!!!
constexpr uint8_t bigDisp {1};                        // -> Nummer des Registerzweig aus displays
constexpr uint8_t bigDispReg {7};                     // -> Anzahl der Register auf dem Zweig
bool bigDispRegOut[bigDispReg * 8] = {!segActiv};     // -> Summe aller bits auf dem Zweig
bool oldBigDisplay[bigDispReg * 8] = {!segActiv};     // Merker für Aktuallisierung



constexpr byte displays {3};
struct REGPIN
{
  uint8_t clock;
  uint8_t data;
  uint8_t latch;
};
REGPIN regpin[displays]
{{5, 7, 6}, {9, 11, 10}, {12, 14, 13}};

boolean dec[][8] =
{
  {1, 1, 0, 0, 0, 0, 0, 0}, // 0
  {1, 1, 1, 1, 1, 0, 0, 1}, // 1
  {1, 0, 1, 0, 0, 1, 0, 0}, // 2
  {1, 0, 1, 1, 0, 0, 0, 0}, // 3
  {1, 0, 0, 1, 1, 0, 0, 1}, // 4
  {1, 0, 0, 1, 0, 0, 1, 0}, // 5
  {1, 0, 0, 0, 0, 0, 1, 0}, // 6
  {1, 1, 1, 1, 1, 0, 0, 0}, // 7
  {1, 0, 0, 0, 0, 0, 0, 0}, // 8
  {1, 0, 0, 1, 0, 0, 0, 0}, // 9
  {1, 1, 1, 1, 1, 1, 1, 1}, // ALL OFF
};

constexpr uint8_t spielers {2};
struct SPIELER
{
  uint8_t points;
  uint8_t sets;
  bool serve;
};
struct SPIEL
{
  SPIELER spieler[spielers];
  bool serveIsChange;                       // Merker ob bei Satz 5 Spiel 5 gewechselt wurde
  uint8_t serveStart;                       // Merker welcher Spieler 1. Aufschlag hat
} spiel; //{{0, 0, 0}, {0, 0, 0}, 0, 0};

constexpr byte A = 0;
constexpr byte B = 1;

REGPIN keypad
{16, 17, 15};

struct SCOREPAD
{
  bool aufschlag;   // 21 # 22
  bool spielPlus;   //  6 # 14
  bool spielMinus;  //  5 # 13
  bool spielReset;  //  4 # 12
  bool satzPlus;    //  3 # 11
  bool satzMinus;   //  2 # 10
  bool satzReset;   //  1 #  9
};

struct KEYTABLE
{
  bool timeOutStart;   // 20 Auszeit
  bool timeOutStop;    // 19
  bool timeEinStart;   // 18 Einspielzeit
  bool timeEinStop;    // 17
  SCOREPAD sp[spielers]; // Spielertasten
};

constexpr bool gedrueckt {HIGH};

KEYTABLE kt;
KEYTABLE oldtable; // Hilfstabelle für das merken der Tastendrücke

struct ZEIT
{
  /*
    1+2 Minutenanzeige ( Anzeige 1 oder 2)( aber nur 2, 1 ist nicht in benutzung)
    3 Sekundenanzeige zehner ( Anzeige 0-5)
    4 Sekundenanzeige einer ( Anzeige 0-9)
  */
  uint8_t minute = 0;
  uint8_t sekunde = 0;
  uint32_t lastTik = 0; // Merker, wann ausgelöst
} zeit;


//
void setAufschlag(const byte seite)
{
  for (byte a = 0; a < spielers; a++)
  {spiel.spieler[a].serve = false;}                    // löscht alles
  spiel.spieler[seite].serve = true;                   // setzt aktuellen Aufschlag
}
//
void nextAufschlag()                                       // Nächsten Aufschlag errechnen
{
  byte myAufschlag = 0;
  for (byte b = 0; b < spielers; b++)                      // gehe durch alle Spieler
  {
    if (spiel.spieler[b].serve)                        // Spieler hat aufschlag ?
    {
      // nächsten Spieler setzen
      // Wert ist größer als Spieler, dann erster Spieler
      myAufschlag = b + 1;
      if (myAufschlag >= spielers)
      { myAufschlag = 0; }
    }
  }
  setAufschlag(myAufschlag);                              // Aufschlag setzen
}
//
void newSet(const byte site)
{
  spiel.spieler[site].sets++;  // Satz gewonnen
  for (byte b = 0; b < spielers; b++)
  { spiel.spieler[b].points = 0; }         // Punkte-Reset
  setAufschlag(spiel.serveStart);          // Aufschlag setzen
}
//
void automatikAufschlag()                                             // wird aufgerufen, wenn points-Taste gedrückt wurde
{
  uint8_t allPoints = spiel.spieler[0].points + spiel.spieler[1].points;
  if (allPoints < 20)       // summe points für jedes 2tes Spiel
  {
    Serial.print(spiel.spieler[0].points);
    Serial.println(spiel.spieler[1].points);
    if (allPoints % 2 == 0) // jedes zweite Spiel auswerten
    { nextAufschlag(); }
  }
  else                      // Jedes Spiel
  {
    Serial.print(spiel.spieler[0].points);
    Serial.println(spiel.spieler[1].points);
    nextAufschlag();
  }
}
//
bool getSite(const bool site)
{
  byte allSet = 0;
  for (byte b = 0; b < spielers; b++)
  { allSet += spiel.spieler[b].sets; }
  if (allSet != 4)                         // Solange nicht 4 Säze vollständig gespielt wurden normaler Seitenwechsel
  {
    return  allSet % 2 == 0 ? site : !site;
  }
  // Seitenwechsel bei Satz 5 bis Spiel 5 normal - dann Seitenwechsel!
  for (byte b = 0; b < spielers; b++)    // durchlaufe alle Spielerpoints
  {
    if (spiel.spieler[b].points >= 5)     // Einer hat die Grenze erreicht?
    {
      return !site;
    }
  }
  return site;
}



//
bool checkSetEnd()                              // Prüft ob Satz zu Ende ist
{
  bool isEnd = false;
  constexpr uint8_t setPoint {11};              // Satzende in Punkte
  constexpr uint8_t winPoint {2};               // Unterschied Spielerpunkte zum Gewinn
  //
  uint8_t allPoints = 0;
  for (byte b = 0; b < spielers; b++)           // addiere alle Spielerpunkte als Zwischenvariable
  { allPoints += spiel.spieler[b].points; }
  //
  for (byte b = 0; b < spielers; b++)           // Frage Spieler auf Siegpunkt ab
  {
    if (spiel.spieler[b].points >= setPoint)    // Mindestpunktzahl zum Gewinn erreicht?
    {
      if ((spiel.spieler[b].points - winPoint) >= (allPoints - spiel.spieler[b].points)) // Unterschied passt?
      {
        newSet(b);                 // Satzpunkt setzen und Spielpunkte löschen
        isEnd = true;
      }
    }
  }
  return isEnd;
}
//

//
void getKeys()                                    // Funktion aus dem Ursprungspost übernommen und erweitert
{
  // Das sind Schieberegister n:1
  // Auch wenn nicht alle Pins gebraucht werden, müssen ungenutzte mitgelesen werden!
  constexpr uint8_t zaehler {24};                 // Anzahl Chips * 8 Pin
  constexpr uint32_t interval {50};               // "debounce Time"
  static uint32_t lastRead = 0;                   // Merker
  if (millis() - lastRead > interval)
  {
    digitalWrite(keypad.latch, LOW);              // Chip  aktivieren
    delay(1);
    digitalWrite(keypad.latch, HIGH);
    bool readIn = true;
    for (uint8_t b = 0; b < zaehler; b++)           // Eingänge durchzählen
    {
      readIn = digitalRead(keypad.data);
      switch (b)                                    // Zuweisung Pin => Variable
      {
//*INDENT-OFF*  
      case  1: kt.sp[getSite(A)].satzReset   = readIn; break;  // bit 1 aus Schieberegister 
      case  2: kt.sp[getSite(A)].satzMinus   = readIn; break;  // bit 2
      case  3: kt.sp[getSite(A)].satzPlus    = readIn; break;  // bit 3
      case  4: kt.sp[getSite(A)].spielReset  = readIn; break;
      case  5: kt.sp[getSite(A)].spielMinus  = readIn; break;
      case  6: kt.sp[getSite(A)].spielPlus   = readIn; break;
      case  9: kt.sp[getSite(B)].satzReset   = readIn; break;
      case 10: kt.sp[getSite(B)].satzMinus   = readIn; break;
      case 11: kt.sp[getSite(B)].satzPlus    = readIn; break;
      case 12: kt.sp[getSite(B)].spielReset  = readIn; break;
      case 13: kt.sp[getSite(B)].spielMinus  = readIn; break;
      case 14: kt.sp[getSite(B)].spielPlus   = readIn; break;
      case 17: kt.timeEinStop       = readIn; break; 
      case 18: kt.timeEinStart      = readIn; break;
      case 19: kt.timeOutStop       = readIn; break; 
      case 20: kt.timeOutStart      = readIn; break; 
      case 21: kt.sp[getSite(A)].aufschlag   = readIn; break; 
      case 22: kt.sp[getSite(B)].aufschlag   = readIn; break;
//*INDENT-ON*  
      }
      digitalWrite(keypad.clock, HIGH);
      digitalWrite(keypad.clock, LOW);
    }
    lastRead = millis();
  }
}
//
void checkKeys()
{
  checkSpielerKeys();                                             // Spielertasten ausgelagert
  // EinspielZeit
  if (kt.timeEinStart != oldtable.timeEinStart)                   // Tastenstatus geändert?
  {
    if (kt.timeEinStart == gedrueckt)                             //
    {
      zeit.minute = 2;                                            // Vorbelegung
      zeit.sekunde = 0;
      zeit.lastTik = millis();                                    // aktuelle Zeit für timer
    }
    oldtable.timeEinStart = kt.timeEinStart;                      // Tastenstatus merken
  }
  if (kt.timeEinStop != oldtable.timeEinStop)
  {
    if (kt.timeEinStop == gedrueckt)
    {
      zeit.minute = 0;
      zeit.sekunde = 0;;
    }
    oldtable.timeEinStop = kt.timeEinStop;
  }
  // TimeOutZeit
  if (kt.timeOutStart != oldtable.timeOutStart)
  {
    if (kt.timeOutStart == gedrueckt)
    {
      zeit.minute = 1;
      zeit.sekunde = 0;
      zeit.lastTik = millis();
    }
    oldtable.timeOutStart = kt.timeOutStart;
  }
  if (kt.timeOutStop != oldtable.timeOutStop)
  {
    if (kt.timeOutStop == gedrueckt)
    {
      zeit.minute = 0;
      zeit.sekunde = 0;;
    }
    oldtable.timeOutStop = kt.timeOutStop;
  }
  //
}
//
void checkSpielerKeys()
{
  for (byte b = 0; b < spielers; b++)
  {
    // aufschlag
    if (kt.sp[b].aufschlag != oldtable.sp[b].aufschlag)
    {
      if (kt.sp[b].aufschlag == !gedrueckt)
      { setAufschlag(b); }
      oldtable.sp[b].aufschlag = kt.sp[b].aufschlag;
    }
    // satz Reset
    if (kt.sp[b].satzReset != oldtable.sp[b].satzReset)
    {
      if (kt.sp[b].satzReset == !gedrueckt)
      {
        spiel.spieler[b].sets = 0;
        spiel.serveIsChange = false;                                   // Wenn Zähler gelöscht wird, wird Merker gelöscht;
      }
      oldtable.sp[b].satzReset = kt.sp[b].satzReset;
    }
    // satz Minus
    if (kt.sp[b].satzMinus != oldtable.sp[b].satzMinus)        // Vergleich auf deaktiven Merker
    {
      if (kt.sp[b].satzMinus == !gedrueckt)
      {
        if (spiel.spieler[b].sets > 0)                                  // Nur wenn was auf dem Zähler steht
        { spiel.spieler[b].sets--; }
      }
      oldtable.sp[b].satzMinus = kt.sp[b].satzMinus;            // Merker damit nicht weiter gezählt wird
    }
    // satz Plus
    if (kt.sp[b].satzPlus != oldtable.sp[b].satzPlus)           // mit Merker
    {
      if (kt.sp[b].satzPlus == !gedrueckt)
      {
        spiel.spieler[b].sets++;
      }
      oldtable.sp[b].satzPlus = kt.sp[b].satzPlus;
    }
    // spiel Reset
    if (kt.sp[b].spielReset != oldtable.sp[b].spielReset)
    {
      if (kt.sp[b].spielReset == !gedrueckt)
      {
        spiel.spieler[b].points = 0;
        spiel.serveIsChange = false;                                   // Wenn Zähler gelöscht wird, wird Merker gelöscht;
      }
      oldtable.sp[b].spielReset = kt.sp[b].spielReset;
    }
    // spiel Minus
    if (kt.sp[b].spielMinus != oldtable.sp[b].spielMinus)        // mit Merker für Spielstand
    {
      if (spiel.spieler[b].points > 0 && kt.sp[b].spielMinus == !gedrueckt)
      { spiel.spieler[b].points--; }
      oldtable.sp[b].spielMinus = kt.sp[b].spielMinus;
    }
    // spiel Plus
    if (kt.sp[b].spielPlus != oldtable.sp[b].spielPlus)           // Siehe Satz
    {
      if (kt.sp[b].spielPlus == !gedrueckt)
      {
        if (spiel.spieler[0].points + spiel.spieler[1].points == 0) // Noch kein Punkt gespielt?
        {
          for (byte c = 0; c < spielers; c++)                       // Frage alle Aufschläge ab
            if (spiel.spieler[c].serve)                             // Spieler hat ersten Aufschlag
            { spiel.serveStart = c; }                               // Merke für den Satz den ersten Aufschlag
        }
        spiel.spieler[b].points++;                                  //
        automatikAufschlag();
        checkSetEnd();                                              // Wenn Satz zu Ende wird Zählerstände gesetzt
      }
      oldtable.sp[b].spielPlus = kt.sp[b].spielPlus;
    }
  }
}


void printSegment(const byte &segmentReihe, const bool *array, const byte &laenge)
{
  for (uint8_t b = 0; b < laenge; b++)
  {
    digitalWrite(regpin[segmentReihe].data, array[b]);    // Array auf dem Schieberegister
    digitalWrite(regpin[segmentReihe].clock, HIGH);
    digitalWrite(regpin[segmentReihe].clock, LOW);
    digitalWrite(regpin[segmentReihe].data, LOW);
  }
  digitalWrite(regpin[segmentReihe].latch, HIGH);
  digitalWrite(regpin[segmentReihe].latch, LOW);
}
//
void displayTime()
{
  constexpr byte laenge {32};
  bool time[laenge] = {0};
  bool *timeSekundeEiner = &time[0];
  bool *timeSekundeZehner = &time[8];     // 2.tes Register
  bool *timeMinuteEiner = &time[16];      // 3.tes Register
  bool *timeMinuteZehner = &time[24];     // 4.tes Register
  memset(time, 0, laenge);
  memcpy(timeMinuteZehner, dec[zeit.minute / 10], 8);
  memcpy(timeMinuteEiner, dec[zeit.minute % 10], 8);
  memcpy(timeSekundeZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timeSekundeEiner, dec[zeit.sekunde % 10], 8);
  printSegment(2, time, laenge);                        // 3.ter Kanal
}
//
void displaySpieler()
{
  /*
    1+2 Punktanzeige Spieler A (Anzeige 0-19)
    3+4 Satzanzeige Spieler A (Anzeige 0-9)( aber nur 3, 4 ist nicht in benutzung)
    5+6 Satzanzeige Spieler B (Anzeige 0-9)( aber nur 5, 6 ist nicht in benutzung)
    7+8 Punktanzeige Spieler B ( Anzeige 0-19)
  */
  constexpr byte laenge {64};
  byte allSet = 0;
  for (byte b = 0; b < spielers; b++)
  { allSet += spiel.spieler[b].sets; }
  bool spell[laenge] = {0};
  bool *spielerBeiner = &spell[0];
  bool *spielerBzehner = &spell[8];
  bool *satzBeiner = &spell[24];
  bool *satzAeiner = &spell[40];
  bool *spielerAeiner = &spell[48];
  bool *spielerAzehner = &spell[56];
  memset(spell, 0, laenge);
  // Das ist tricky:
  // die Ausgabe der Spielerdaten ist abhängig von der Anzahl gespielter Sätze
  // Mit jedem gespieltem Satz wechselt der Spieler die Seite
  // Und bei Satz 5 nochmal mitten im Spiel... getSite() soll das ermitteln
  memcpy(spielerAzehner, dec[spiel.spieler[getSite(A)].points / 10], 8);
  memcpy(spielerAeiner,  dec[spiel.spieler[getSite(A)].points % 10], 8);
  memcpy(satzAeiner,     dec[spiel.spieler[getSite(A)].sets % 10], 8);
  memcpy(satzBeiner,     dec[spiel.spieler[getSite(B)].sets % 10], 8);
  memcpy(spielerBzehner, dec[spiel.spieler[getSite(B)].points / 10], 8);
  memcpy(spielerBeiner,  dec[spiel.spieler[getSite(B)].points % 10], 8);
  printSegment(0, spell, laenge);
}
//
//
void myTimer()
{
  constexpr uint32_t _oneSecond {1000};
  if (zeit.minute || zeit.sekunde)   // Solange Zeit aktiv
  {
    if (millis() - zeit.lastTik >= _oneSecond)
    {
      if (zeit.sekunde > 0)
      { zeit.sekunde--; }
      else if (zeit.minute > 0)
      {
        zeit.minute--;
        zeit.sekunde = 59;
      }
      zeit.lastTik += _oneSecond;
    }
  }
}

//
void segmentTest()
{
  for (byte b = 0; b < 10; b++)
  {
    for (byte s = 0; s < spielers; s++)
    {
      spiel.spieler[s].points = spiel.spieler[s].points ? 0 : 88;
      spiel.spieler[s].sets = spiel.spieler[s].sets ? 0 : 88;
      spiel.spieler[s].serve = spiel.spieler[s].serve ? 0 : 1;
    }
    zeit.minute = zeit.minute ? 0 : 88;
    zeit.sekunde = zeit.sekunde ? 0 : 88;
    displaySpieler();
    displayTime();
    bigDisplay();
    delay(500);
  }
}
//
void allNew()
{
  for (byte s = 0; s < spielers; s++)
  {
    spiel.spieler[s].points = 0;
    spiel.spieler[s].sets = 0;
    spiel.spieler[s].serve = false;
  }
  spiel.serveIsChange = false;
  spiel.serveStart = 0;
  zeit.minute = 0;
  zeit.sekunde = 0;
}
#include "tt.h"

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Tischtennis Display"));
  //display init
  for (byte b = 0; b < displays; b++)
  {
    pinMode(regpin[b].clock, OUTPUT);
    pinMode(regpin[b].data, OUTPUT);
    pinMode(regpin[b].latch, OUTPUT);
    digitalWrite(regpin[b].clock, LOW);
    digitalWrite(regpin[b].data, LOW);
    digitalWrite(regpin[b].latch, LOW);
  };
  pinMode(keypad.clock, OUTPUT);
  pinMode(keypad.data, INPUT);
  pinMode(keypad.latch, OUTPUT);
  digitalWrite(keypad.clock, LOW);
  digitalWrite(keypad.latch, LOW);
  segmentTest();
}

void loop()
{
  getKeys();        // Holt Tastenzustände
  checkKeys();      // wertet Tasten aus
  myTimer();        // CountDownTimer
  displaySpieler(); // kleine Anzeige
  // BigDisplay wird noch umgebaut - dann erst neu coden
  bigDisplay();     // große Anzeige
  displayTime();    // countDownDisplay
}
//
void bigDisplay()
{
  constexpr uint32_t printIntervall {500};
  static uint32_t lastPrint = 0;
  memset(bigDispRegOut, !segActiv, bigDispReg * 8);  // alle bits löschen
  bool *timerHunderter = &bigDispRegOut[0 * 8];      // Register 7 | DIGIT 0
  bool *timerZehner = &bigDispRegOut[1 * 8];         // Register 6 | DIGIT 1
  bool *timerEiner = &bigDispRegOut[2 * 8];          // Register 5 | DIGIT 2
  bool *spielerAsatz = &bigDispRegOut[3 * 8];
  bool *spielerAeiner = &bigDispRegOut[4 * 8];
  bool *spielerBsatz = &bigDispRegOut[5 * 8];
  bool *spielerBeiner = &bigDispRegOut[6 * 8];
  //
  memcpy(timerHunderter, dec[zeit.minute % 10], 8);
  memcpy(timerZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timerEiner, dec[zeit.sekunde % 10], 8);
  memcpy(spielerAeiner, dec[spiel.spieler[0].points % 10], 8);
  memcpy(spielerAsatz, dec[spiel.spieler[0].sets % 10], 8);
  memcpy(spielerBeiner, dec[spiel.spieler[1].points % 10], 8);
  memcpy(spielerBsatz, dec[spiel.spieler[1].sets % 10], 8);
  bigDispRegOut[16] = spiel.spieler[1].points / 10 ? segActiv : !segActiv;  // spielerBZehner
  bigDispRegOut[24] = spiel.spieler[1].serve   ? segActiv : !segActiv;  // spielerBAufschlag
  bigDispRegOut[32] = spiel.spieler[0].points / 10 ? segActiv : !segActiv;  // spielerAZehner
  bigDispRegOut[40] = spiel.spieler[0].serve   ? segActiv : !segActiv;  // spielerAAufschlag
  for (byte b = 0; b < 56; b++)
  { bigDispRegOut[b] = !bigDispRegOut[b]; }
  if ((memcmp(bigDispRegOut, oldBigDisplay, bigDispReg * 8) != 0)  || // Neuer Inhalt ODER
      (millis() - printIntervall > lastPrint))                        // Zeit abgelaufen
  {
    /*
      for (byte b = 0; b < 56; b++)
      {
        Serial.print(bigDispRegOut[b]);
        if ((b + 1) % 8 == 0)
        { Serial.print(" | "); }
      }
      Serial.println();
    */
    printSegment(bigDisp, bigDispRegOut, bigDispReg * 8);
    memcpy(oldBigDisplay, bigDispRegOut, bigDispReg * 8);
    lastPrint = millis();
  }
};
//

Eine Änderung von mir!

SpielerAAufschlag muste Ich ändern von 40 auf 8.

 bigDispRegOut[8] = spiel.spieler[0].serve   ? segActiv : !segActiv;  // spielerAAufschlag

Weiß nicht vielleicht kalte Lötstelle?

Jetzt funktioniert eigentlich alles!!!!

Nochmal

Vielen Vielen Dank für deine Perfekte Arbeit

Eine Sache hätte Ich vielleicht noch....

Die angaben Wechsel funktionieren leider nicht so richtig wenn man mal einen Minus machen muss.

Nö.
Falschen Chip angelötet.
Element 40 ist Chip 6 Pin 1
Element 8 ist Chip 2 Pin 1

Das ist richtig - soweit war noch keiner :slight_smile:

Versuch mal in tt.h in der Funktion

void checkSpielerKeys()
    // spiel Minus
    if (kt.sp[b].spielMinus != oldtable.sp[b].spielMinus)        // mit Merker für Spielstand
    {
      if (spiel.spieler[b].points > 0 && kt.sp[b].spielMinus == !gedrueckt)
      { spiel.spieler[b].points--; }
automatikAufschlag(); // mal sehen, ob das geht
      oldtable.sp[b].spielMinus = kt.sp[b].spielMinus;
    }

Fast richtig :innocent: :joy:

Element 40 ist Chip 6 Pin 8
Element 8 ist Chip 2 Pin 8

Die Großen Anzeigen habe Ich parallel kontrolliert.

Es sollte jetzt alles richtig sein!

Ist es den realisierbar, oder einfach ein zu großer Aufwand?

Ja, ich werd mich nie daran gewöhnen, dass wir anders rum zählen.
Aber wie Du siehst, bist auch Du drauf reingefallen :slight_smile:

Mal sehen, ob der Code oben das noch hergibt....
... dann sind wir endlich durch.

Ich verneige mich vor dir!!!!!!!!!!!!

Ich weiß einfach nicht wie Ich dir DANKEN kann.

Vielen Dank

Oh mein Gott werden das riesige fremde Federn sein mit den Ich mich schmücken Kann

:smiling_face_with_halo:

Mal sie bunt an!
:rainbow:

Ach ja

Mein Dank geht natürlich an alle hier im Forum

Dann hab ich mal das gesamte Konstrukt noch aufgeräumt und ein wenig kommentiert.
Es ist noch nicht perfekt.
zum Teil vorbereitet für irgendwas mit mehr Spielern.

// Forensketch - Schiedsrichterplatz Tischtennis
// https://forum.arduino.cc/t/ein-schiedsrichtertisch-fur-tischtennis-mit-74hc595-und-74hc165/1351812/

/*
   Big-Displays haben die Segmente an den Pins der Schieberegister nach der RECHTEN Variante!

       0      |     7     |
    5     1   |  2     6  |
       6      |     1     |
    4     2   |  3     5  |
       3      |     4     |
*/

/*
  Digit0 Timer Hunderter  // Minute Einer
  Digit1 Timer zehner     // Sekunde Zehner
  Digit2 Timer einer      // Sekunde Einer
  Digit3 SpielerA Satz + Aufschlag D8
  Digit4 SpielerA Punkte einer + Zehner D32
  Digit5 SpielerB Satz + Aufschlag D24
  Digit6 SpielerB Punkte einer + Zehner D16
*/
constexpr bool segActiv {0};                          // Die Segmente leuchten, wenn RegisterPin => 0!!!
constexpr uint8_t bigDisp {1};                        // -> Nummer des Registerzweig aus displays
constexpr uint8_t bigDispReg {7};                     // -> Anzahl der Register auf dem Zweig
bool bigDispRegOut[bigDispReg * 8] = {!segActiv};     // -> Summe aller bits auf dem Zweig
bool oldBigDisplay[bigDispReg * 8] = {!segActiv};     // Merker für Aktuallisierung

constexpr byte shiftRegs {3};          // Anzahl der Zweige mit HC-Schieberegister
struct REGPIN                          // Definition eines Zweiges
{
  uint8_t clock;
  uint8_t data;
  uint8_t latch;
};
REGPIN regpin[shiftRegs]
{{5, 7, 6}, {9, 11, 10}, {12, 14, 13}}; // Pinbelegung Schieberegister

boolean dec[][8] =                      // Umsetzungstabelle Ziffer in 7-Segmentanzeige
{
  {1, 1, 0, 0, 0, 0, 0, 0}, // 0
  {1, 1, 1, 1, 1, 0, 0, 1}, // 1
  {1, 0, 1, 0, 0, 1, 0, 0}, // 2
  {1, 0, 1, 1, 0, 0, 0, 0}, // 3
  {1, 0, 0, 1, 1, 0, 0, 1}, // 4
  {1, 0, 0, 1, 0, 0, 1, 0}, // 5
  {1, 0, 0, 0, 0, 0, 1, 0}, // 6
  {1, 1, 1, 1, 1, 0, 0, 0}, // 7
  {1, 0, 0, 0, 0, 0, 0, 0}, // 8
  {1, 0, 0, 1, 0, 0, 0, 0}, // 9
  {1, 1, 1, 1, 1, 1, 1, 1}, // ALL OFF
};

constexpr uint8_t spielers {2};         //
struct SPIELER
{
  uint8_t points;
  uint8_t sets;
  bool serve;
};
struct SPIEL
{
  SPIELER spieler[spielers];              // Alle Spieler anlegen
  uint8_t serveStart;                     // Merker welcher Spieler 1. Aufschlag hat
} spiel; //{{0, 0, 0}, {0, 0, 0}, 0,};

constexpr byte A = 0;    // Default: Spieler A => Element von spieler[spielers]
constexpr byte B = 1;

REGPIN keypad            // Schieberegister Tastenabfrage
{16, 17, 15};

struct SCOREPAD
{
  //                     // "A"  "B"
  bool aufschlag;        // 21 # 22
  bool spielPlus;        //  6 # 14
  bool spielMinus;       //  5 # 13
  bool spielReset;       //  4 # 12
  bool satzPlus;         //  3 # 11
  bool satzMinus;        //  2 # 10
  bool satzReset;        //  1 #  9
};

struct KEYTABLE
{
  bool timeOutStart;     // 20 Auszeit
  bool timeOutStop;      // 19
  bool timeEinStart;     // 18 Einspielzeit
  bool timeEinStop;      // 17
  SCOREPAD sp[spielers]; // Spielertasten
};

constexpr bool gedrueckt {HIGH}; // Vorbelegung für Tastenabfrage

KEYTABLE kt;
KEYTABLE oldtable;       // Hilfstabelle für das merken der Tastendrücke

struct ZEIT
{
  /*
    1+2 Minutenanzeige ( Anzeige 1 oder 2)( aber nur 2, 1 ist nicht in benutzung)
    3 Sekundenanzeige zehner ( Anzeige 0-5)
    4 Sekundenanzeige einer ( Anzeige 0-9)
  */
  uint8_t minute = 0;
  uint8_t sekunde = 0;
  uint32_t lastTik = 0;   // Merker, wann ausgelöst
} zeit;


void setup()
{
  Serial.begin(115200);
  Serial.println(F("Tischtennis Display"));
  //display init
  for (byte b = 0; b < shiftRegs; b++)
  {
    pinMode(regpin[b].clock, OUTPUT);
    pinMode(regpin[b].data, OUTPUT);
    pinMode(regpin[b].latch, OUTPUT);
    digitalWrite(regpin[b].clock, LOW);
    digitalWrite(regpin[b].data, LOW);
    digitalWrite(regpin[b].latch, LOW);
  };
  pinMode(keypad.clock, OUTPUT);
  pinMode(keypad.data, INPUT);
  pinMode(keypad.latch, OUTPUT);
  digitalWrite(keypad.clock, LOW);
  digitalWrite(keypad.latch, LOW);
  segmentTest();
}

void loop()
{
  getKeys();        // Holt Tastenzustände
  checkKeys();      // wertet Tasten aus
  myTimer();        // CountDownTimer
  displaySpieler(); // kleine Anzeige
  // BigDisplay wird noch umgebaut - dann erst neu coden
  bigDisplay();     // große Anzeige
  displayTime();    // countDownDisplay
}

//
void setAufschlag(const byte seite)            // Stellt den Aufschlag für die entsprechende Spielerseite
{
  for (byte a = 0; a < spielers; a++)
  {spiel.spieler[a].serve = false;}            // löscht alles
  spiel.spieler[seite].serve = true;           // setzt aktuellen Aufschlag
}
//
void nextAufschlag()                           // Nächsten Aufschlag errechnen
{
  byte myAufschlag = 0;
  for (byte b = 0; b < spielers; b++)          // gehe durch alle Spieler
  {
    if (spiel.spieler[b].serve)                // Spieler hat Aufschlag ?
    {
      myAufschlag = b + 1;                     // nächsten Spieler setzen
      if (myAufschlag >= spielers)             // Wert ist größer als Spieler, dann erster Spieler
      { myAufschlag = 0; }
    }
  }
  setAufschlag(myAufschlag);                   // Aufschlag setzen
}
//
void newSet(const byte site)                   // Satz aufaddieren und alle Punkte löschen
{
  spiel.spieler[site].sets++;                  // Satz gewonnen
  for (byte b = 0; b < spielers; b++)
  { spiel.spieler[b].points = 0; }             // Punkte-Reset
  setAufschlag(spiel.serveStart);              // Aufschlag setzen
}
//
void automatikAufschlag()                      // wird aufgerufen, wenn points(+/-)-Taste gedrückt wurde
{
  uint8_t allPoints = spiel.spieler[0].points + spiel.spieler[1].points;
  if (allPoints < 20)                          // summe points für jedes 2tes Spiel
  {
    if (allPoints % 2 == 0)                    // aktuelles Spiel ist zweites Spiel
    { nextAufschlag(); }                       // Aufschlag setzen
  }
  else                                         // Jedes Spiel ab 10:10
  { nextAufschlag(); }
}
//
bool getSite(const bool site)                  // ermittelt die Seite, wo sich Spieler A und B befinden
{
  byte allSet = 0;                             // Summenvariable für alle Sätze
  for (byte b = 0; b < spielers; b++)          // Alle Sätze zählen
  { allSet += spiel.spieler[b].sets; }
  if (allSet != 4)                             // Solange nicht 4 Säze vollständig gespielt wurden normaler Seitenwechsel
  { return  allSet % 2 == 0 ? site : !site; }  // Seitenwechsel nach geradem / ungeradem Satzstand
  // Seitenwechsel bei Satz 5 bis Spiel 5 normal - dann Seitenwechsel!
  for (byte b = 0; b < spielers; b++)          // durchlaufe alle Spielerpoints
  {
    if (spiel.spieler[b].points >= 5)          // Einer hat die Grenze erreicht?
    { return !site; }                          // Tausche die Seite
  }
  return site;                                 // Wenn nix passt, gib die bisherige Seite zurück
}
//
bool checkSetEnd()                             // Prüft ob Satz zu Ende ist
{
  bool isEnd = false;                          // merker
  constexpr uint8_t setPoint {11};             // Satzende in Punkte
  constexpr uint8_t winPoint {2};              // Unterschied Spielerpunkte zum Gewinn
  //
  uint8_t allPoints = 0;
  for (byte b = 0; b < spielers; b++)          // addiere alle Spielerpunkte als Zwischenvariable
  { allPoints += spiel.spieler[b].points; }    //
  for (byte b = 0; b < spielers; b++)          // Frage Spieler auf Siegpunkt ab
  {
    if (spiel.spieler[b].points >= setPoint)   // Mindestpunktzahl zum Gewinn erreicht?
    {
      if ((spiel.spieler[b].points - winPoint) >= (allPoints - spiel.spieler[b].points)) // Unterschied passt?
      {
        newSet(b);                             // Satzpunkt setzen und Spielpunkte löschen
        isEnd = true;                          // merker setzen
      }
    }
  }
  return isEnd;                                //
}
//

//
void getKeys()                                 // Funktion aus dem Ursprungspost übernommen und erweitert
{
  // Das sind Schieberegister n:1
  // Auch wenn nicht alle Pins gebraucht werden, müssen ungenutzte mitgelesen werden!
  constexpr uint8_t zaehler {24};              // Anzahl Chips * 8 Pin
  constexpr uint32_t interval {50};            // "debounce Time"
  static uint32_t lastRead = 0;                // Merker
  if (millis() - lastRead > interval)
  {
    digitalWrite(keypad.latch, LOW);           // Chip  aktivieren
    delay(1);
    digitalWrite(keypad.latch, HIGH);
    bool readIn = true;
    for (uint8_t b = 0; b < zaehler; b++)      // Eingänge durchzählen
    {
      readIn = digitalRead(keypad.data);
      switch (b)                               // Zuweisung Pin => Variable
      {
//*INDENT-OFF*  
      case  1: kt.sp[getSite(A)].satzReset   = readIn; break;  // bit 1 aus Schieberegister 
      case  2: kt.sp[getSite(A)].satzMinus   = readIn; break;  // bit 2
      case  3: kt.sp[getSite(A)].satzPlus    = readIn; break;  // bit 3
      case  4: kt.sp[getSite(A)].spielReset  = readIn; break;
      case  5: kt.sp[getSite(A)].spielMinus  = readIn; break;
      case  6: kt.sp[getSite(A)].spielPlus   = readIn; break;
      case  9: kt.sp[getSite(B)].satzReset   = readIn; break;
      case 10: kt.sp[getSite(B)].satzMinus   = readIn; break;
      case 11: kt.sp[getSite(B)].satzPlus    = readIn; break;
      case 12: kt.sp[getSite(B)].spielReset  = readIn; break;
      case 13: kt.sp[getSite(B)].spielMinus  = readIn; break;
      case 14: kt.sp[getSite(B)].spielPlus   = readIn; break;
      case 17: kt.timeEinStop       = readIn; break; 
      case 18: kt.timeEinStart      = readIn; break;
      case 19: kt.timeOutStop       = readIn; break; 
      case 20: kt.timeOutStart      = readIn; break; 
      case 21: kt.sp[getSite(A)].aufschlag   = readIn; break; 
      case 22: kt.sp[getSite(B)].aufschlag   = readIn; break;
//*INDENT-ON*  
      }
      digitalWrite(keypad.clock, HIGH);
      digitalWrite(keypad.clock, LOW);
    }
    lastRead = millis();
  }
}
//
void checkSpielerKeys()                                   // ausgelesene Werte aus den Schieberegistern verarbeiten
{
  for (byte b = 0; b < spielers; b++)                     // alle Spieler
  {
    // aufschlag
    if (kt.sp[b].aufschlag != oldtable.sp[b].aufschlag)   // aktuellen Zustand und Merker vergleichen
    {
      if (kt.sp[b].aufschlag == !gedrueckt)               // Taste wurde losgelassen?
      { setAufschlag(b); }                                // .... ausführen
      oldtable.sp[b].aufschlag = kt.sp[b].aufschlag;      // Merker setzen, für Auswertesperre
    }
    // satz Reset
    if (kt.sp[b].satzReset != oldtable.sp[b].satzReset)
    {
      if (kt.sp[b].satzReset == !gedrueckt)
      { spiel.spieler[b].sets = 0; }
      oldtable.sp[b].satzReset = kt.sp[b].satzReset;
    }
    // satz Minus
    if (kt.sp[b].satzMinus != oldtable.sp[b].satzMinus)
    {
      if (kt.sp[b].satzMinus == !gedrueckt)
      {
        if (spiel.spieler[b].sets > 0)                    // Nur wenn was auf dem Zähler steht
        { spiel.spieler[b].sets--; }
      }
      oldtable.sp[b].satzMinus = kt.sp[b].satzMinus;
    }
    // satz Plus
    if (kt.sp[b].satzPlus != oldtable.sp[b].satzPlus)
    {
      if (kt.sp[b].satzPlus == !gedrueckt)
      { spiel.spieler[b].sets++; }
      oldtable.sp[b].satzPlus = kt.sp[b].satzPlus;
    }
    // spiel Reset
    if (kt.sp[b].spielReset != oldtable.sp[b].spielReset)
    {
      if (kt.sp[b].spielReset == !gedrueckt)
      { spiel.spieler[b].points = 0; }
      oldtable.sp[b].spielReset = kt.sp[b].spielReset;
    }
    // spiel Minus
    if (kt.sp[b].spielMinus != oldtable.sp[b].spielMinus)
    {
      if (kt.sp[b].spielMinus == !gedrueckt)
      {
        if (spiel.spieler[b].points > 0)                  // Muss ja was drauf sein, damit man was abziehen kann :-)
        { spiel.spieler[b].points--; }
      }
      automatikAufschlag();                               // Aufschlag muss neu berechnet werden, wenn Punkte gesetzt
      // TODO!
      // ACHTUNG: Wenn alle Sätze gelöscht werden, müsste auch überprüft werden,
      // wie der Punktestand ist und dann den Aufschlag nochmal setzen
      oldtable.sp[b].spielMinus = kt.sp[b].spielMinus;
    }
    // spiel Plus
    if (kt.sp[b].spielPlus != oldtable.sp[b].spielPlus)
    {
      if (kt.sp[b].spielPlus == !gedrueckt)
      {
        // TODO!
        // Den Teil evtl. auslagern
        if (spiel.spieler[0].points + spiel.spieler[1].points == 0) // Noch kein Punkt gespielt?
        {
          for (byte c = 0; c < spielers; c++)                       // Frage alle Aufschläge ab
            if (spiel.spieler[c].serve)                             // Spieler hat ersten Aufschlag
            { spiel.serveStart = c; }                               // Merke für den Satz den ersten Aufschlag
        }
        // Ende auslagern
        spiel.spieler[b].points++;                                  //
        automatikAufschlag();                                       // Aufschlagwechsel für Anzeige berechnen
        checkSetEnd();                                              // Wenn Satz zu Ende wird Zählerstände gesetzt
      }
      oldtable.sp[b].spielPlus = kt.sp[b].spielPlus;
    }
  }
}
//
void checkKeys()
{
  checkSpielerKeys();                                             // Spielertasten ausgelagert
  // EinspielZeit
  if (kt.timeEinStart != oldtable.timeEinStart)                   // Tastenstatus geändert?
  {
    if (kt.timeEinStart == gedrueckt)                             //
    {
      zeit.minute = 2;                                            // Vorbelegung
      zeit.sekunde = 0;
      zeit.lastTik = millis();                                    // aktuelle Zeit für timer
    }
    oldtable.timeEinStart = kt.timeEinStart;                      // Tastenstatus merken
  }
  if (kt.timeEinStop != oldtable.timeEinStop)
  {
    if (kt.timeEinStop == gedrueckt)
    {
      zeit.minute = 0;
      zeit.sekunde = 0;;
    }
    oldtable.timeEinStop = kt.timeEinStop;
  }
  // TimeOutZeit
  if (kt.timeOutStart != oldtable.timeOutStart)
  {
    if (kt.timeOutStart == gedrueckt)
    {
      zeit.minute = 1;
      zeit.sekunde = 0;
      zeit.lastTik = millis();
    }
    oldtable.timeOutStart = kt.timeOutStart;
  }
  if (kt.timeOutStop != oldtable.timeOutStop)
  {
    if (kt.timeOutStop == gedrueckt)
    {
      zeit.minute = 0;
      zeit.sekunde = 0;;
    }
    oldtable.timeOutStop = kt.timeOutStop;
  }
}
//
void printSegment(const byte &segmentReihe, const bool *array, const byte &laenge)
{
  for (uint8_t b = 0; b < laenge; b++)
  {
    digitalWrite(regpin[segmentReihe].data, array[b]);    // Array auf dem Schieberegister
    digitalWrite(regpin[segmentReihe].clock, HIGH);
    digitalWrite(regpin[segmentReihe].clock, LOW);
    digitalWrite(regpin[segmentReihe].data, LOW);
  }
  digitalWrite(regpin[segmentReihe].latch, HIGH);
  digitalWrite(regpin[segmentReihe].latch, LOW);
}
//
void displayTime()
{
  constexpr byte laenge {32};
  bool time[laenge] = {0};
  bool *timeSekundeEiner = &time[0];
  bool *timeSekundeZehner = &time[8];     // 2.tes Register
  bool *timeMinuteEiner = &time[16];      // 3.tes Register
  bool *timeMinuteZehner = &time[24];     // 4.tes Register
  memset(time, 0, laenge);
  memcpy(timeMinuteZehner, dec[zeit.minute / 10], 8);
  memcpy(timeMinuteEiner, dec[zeit.minute % 10], 8);
  memcpy(timeSekundeZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timeSekundeEiner, dec[zeit.sekunde % 10], 8);
  printSegment(2, time, laenge);                        // 3.ter Kanal
}
//
void displaySpieler()
{
  /*
    1+2 Punktanzeige Spieler A (Anzeige 0-19)
    3+4 Satzanzeige Spieler A (Anzeige 0-9)( aber nur 3, 4 ist nicht in benutzung)
    5+6 Satzanzeige Spieler B (Anzeige 0-9)( aber nur 5, 6 ist nicht in benutzung)
    7+8 Punktanzeige Spieler B ( Anzeige 0-19)
  */
  constexpr byte laenge {64};
  byte allSet = 0;
  for (byte b = 0; b < spielers; b++)
  { allSet += spiel.spieler[b].sets; }
  bool spell[laenge] = {0};
  bool *spielerBeiner = &spell[0];
  bool *spielerBzehner = &spell[8];
  bool *satzBeiner = &spell[24];
  bool *satzAeiner = &spell[40];
  bool *spielerAeiner = &spell[48];
  bool *spielerAzehner = &spell[56];
  memset(spell, 0, laenge);
  // Das ist tricky:
  // die Ausgabe der Spielerdaten ist abhängig von der Anzahl gespielter Sätze
  // Mit jedem gespieltem Satz wechselt der Spieler die Seite
  // Und bei Satz 5 nochmal mitten im Spiel... getSite() soll das ermitteln
  memcpy(spielerAzehner, dec[spiel.spieler[getSite(A)].points / 10], 8);
  memcpy(spielerAeiner,  dec[spiel.spieler[getSite(A)].points % 10], 8);
  memcpy(satzAeiner,     dec[spiel.spieler[getSite(A)].sets % 10], 8);
  memcpy(satzBeiner,     dec[spiel.spieler[getSite(B)].sets % 10], 8);
  memcpy(spielerBzehner, dec[spiel.spieler[getSite(B)].points / 10], 8);
  memcpy(spielerBeiner,  dec[spiel.spieler[getSite(B)].points % 10], 8);
  printSegment(0, spell, laenge);
}
//
//
void myTimer()
{
  constexpr uint32_t _oneSecond {1000};
  if (zeit.minute || zeit.sekunde)   // Solange Zeit aktiv
  {
    if (millis() - zeit.lastTik >= _oneSecond)
    {
      if (zeit.sekunde > 0)
      { zeit.sekunde--; }
      else if (zeit.minute > 0)
      {
        zeit.minute--;
        zeit.sekunde = 59;
      }
      zeit.lastTik += _oneSecond;
    }
  }
}
//
void bigDisplay()
{
  constexpr uint32_t printIntervall {500};
  static uint32_t lastPrint = 0;
  memset(bigDispRegOut, !segActiv, bigDispReg * 8);  // alle bits löschen
  bool *timerHunderter = &bigDispRegOut[0 * 8];      // Register 7 | DIGIT 0
  bool *timerZehner = &bigDispRegOut[1 * 8];         // Register 6 | DIGIT 1
  bool *timerEiner = &bigDispRegOut[2 * 8];          // Register 5 | DIGIT 2
  bool *spielerAsatz = &bigDispRegOut[3 * 8];
  bool *spielerAeiner = &bigDispRegOut[4 * 8];
  bool *spielerBsatz = &bigDispRegOut[5 * 8];
  bool *spielerBeiner = &bigDispRegOut[6 * 8];
  //
  memcpy(timerHunderter, dec[zeit.minute % 10], 8);
  memcpy(timerZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timerEiner, dec[zeit.sekunde % 10], 8);
  memcpy(spielerAeiner, dec[spiel.spieler[0].points % 10], 8);
  memcpy(spielerAsatz, dec[spiel.spieler[0].sets % 10], 8);
  memcpy(spielerBeiner, dec[spiel.spieler[1].points % 10], 8);
  memcpy(spielerBsatz, dec[spiel.spieler[1].sets % 10], 8);
  bigDispRegOut[16] = spiel.spieler[1].points / 10 ? segActiv : !segActiv;  // spielerBZehner
  bigDispRegOut[24] = spiel.spieler[1].serve   ? segActiv : !segActiv;  // spielerBAufschlag
  bigDispRegOut[32] = spiel.spieler[0].points / 10 ? segActiv : !segActiv;  // spielerAZehner
  // Spieler 0 serve war ursprünglich 40 - falscher Chip angelötet
  bigDispRegOut[8] = spiel.spieler[0].serve   ? segActiv : !segActiv;  // spielerAAufschlag
  for (byte b = 0; b < 56; b++)
  { bigDispRegOut[b] = !bigDispRegOut[b]; }
  if ((memcmp(bigDispRegOut, oldBigDisplay, bigDispReg * 8) != 0)  || // Neuer Inhalt ODER
      (millis() - printIntervall > lastPrint))                        // Zeit abgelaufen
  {
    printSegment(bigDisp, bigDispRegOut, bigDispReg * 8);
    memcpy(oldBigDisplay, bigDispRegOut, bigDispReg * 8);
    lastPrint = millis();
  }
};
//
void segmentTest()
{
  for (byte b = 0; b < 10; b++)
  {
    for (byte s = 0; s < spielers; s++)
    {
      spiel.spieler[s].points = spiel.spieler[s].points ? 0 : 88;
      spiel.spieler[s].sets = spiel.spieler[s].sets ? 0 : 88;
      spiel.spieler[s].serve = spiel.spieler[s].serve ? 0 : 1;
    }
    zeit.minute = zeit.minute ? 0 : 88;
    zeit.sekunde = zeit.sekunde ? 0 : 88;
    displaySpieler();
    displayTime();
    bigDisplay();
    delay(500);
  }
}
//
void allNew()
{
  for (byte s = 0; s < spielers; s++)
  {
    spiel.spieler[s].points = 0;
    spiel.spieler[s].sets = 0;
    spiel.spieler[s].serve = false;
  }
  // spiel.serveIsChange = false;
  spiel.serveStart = 0;
  zeit.minute = 0;
  zeit.sekunde = 0;
}

ein wenig kommentiert.

:joy:

Krass gut

Aber eine Frage.....

vorbereitet für irgendwas mit mehr Spielern.

Das verstehe Ich jetzt nicht so ganz

Naja, vielleicht gibt es ja mal irgendwas mit mehr als 2 Mannschaften und dann liesse sich der code (wenigstens in Teilen) weiterverwenden.

Ich lese nur mit und drücke Euch die Daumen. Nicht daß hier doch noch zuviele Köche mitmischen und den Brei verderben :face_savoring_food:
Aber mit my_xy_projekt hast du ja hier einen versierten Vorstellungs-Umsetzer an Deiner Seite :wink::+1:
Wenns dann mal so läuft und aussieht, wie Du Dir das gedacht hast, darfst du gerne mit der fertigen Anzeige mittels Bild und Video angeben :partying_face:

1 Like

Das werde Ich dann auch gerne tun.

Eine Frage zu OTA.....

Ich habe wollte OTA einfügen, bekomme aber die Fehlermeldung...
"Kein Monitor für das Port-Protokoll Network verfügbar"

=> Neuen Thread.

Guten Abend,

Ich bräuchte mal kurz deine Hilfe.....

Ich habe das BigDisplay etwas angepaßt(Digit)

Jetzt paßt es auch mit den kompletten Anzeigen, aber....

der Seitenwechsel paßt nicht, die großen Anzeigen wechseln nicht die Seiten!

Ich finde es leider nicht im Programm wo der wechsel vorgenommen wird und ob Ich da auch die Digits ändern muss?

// Forensketch - Schiedsrichterplatz Tischtennis
// https://forum.arduino.cc/t/ein-schiedsrichtertisch-fur-tischtennis-mit-74hc595-und-74hc165/1351812/

/*
   Big-Displays haben die Segmente an den Pins der Schieberegister nach der RECHTEN Variante!

       0      |     7     |
    5     1   |  2     6  |
       6      |     1     |
    4     2   |  3     5  |
       3      |     4     |
*/

/*
  Digit0 Timer Hunderter  // Minute Einer
  Digit1 Timer zehner     // Sekunde Zehner
  Digit2 Timer einer      // Sekunde Einer
  Digit3 SpielerA Satz + Aufschlag D8
  Digit4 SpielerA Punkte einer + Zehner D32
  Digit5 SpielerB Satz + Aufschlag D24
  Digit6 SpielerB Punkte einer + Zehner D16
*/
constexpr bool segActiv {0};                          // Die Segmente leuchten, wenn RegisterPin => 0!!!
constexpr uint8_t bigDisp {1};                        // -> Nummer des Registerzweig aus displays
constexpr uint8_t bigDispReg {7};                     // -> Anzahl der Register auf dem Zweig
bool bigDispRegOut[bigDispReg * 8] = {!segActiv};     // -> Summe aller bits auf dem Zweig
bool oldBigDisplay[bigDispReg * 8] = {!segActiv};     // Merker für Aktuallisierung

constexpr byte shiftRegs {3};          // Anzahl der Zweige mit HC-Schieberegister
struct REGPIN                          // Definition eines Zweiges
{
  uint8_t clock;
  uint8_t data;
  uint8_t latch;
};
REGPIN regpin[shiftRegs]
{{5, 7, 6}, {9, 11, 10}, {12, 14, 13}}; // Pinbelegung Schieberegister

boolean dec[][8] =                      // Umsetzungstabelle Ziffer in 7-Segmentanzeige
{
  {1, 1, 0, 0, 0, 0, 0, 0}, // 0
  {1, 1, 1, 1, 1, 0, 0, 1}, // 1
  {1, 0, 1, 0, 0, 1, 0, 0}, // 2
  {1, 0, 1, 1, 0, 0, 0, 0}, // 3
  {1, 0, 0, 1, 1, 0, 0, 1}, // 4
  {1, 0, 0, 1, 0, 0, 1, 0}, // 5
  {1, 0, 0, 0, 0, 0, 1, 0}, // 6
  {1, 1, 1, 1, 1, 0, 0, 0}, // 7
  {1, 0, 0, 0, 0, 0, 0, 0}, // 8
  {1, 0, 0, 1, 0, 0, 0, 0}, // 9
  {1, 1, 1, 1, 1, 1, 1, 1}, // ALL OFF
};

constexpr uint8_t spielers {2};         //
struct SPIELER
{
  uint8_t points;
  uint8_t sets;
  bool serve;
};
struct SPIEL
{
  SPIELER spieler[spielers];              // Alle Spieler anlegen
  uint8_t serveStart;                     // Merker welcher Spieler 1. Aufschlag hat
} spiel; //{{0, 0, 0}, {0, 0, 0}, 0,};

constexpr byte A = 0;    // Default: Spieler A => Element von spieler[spielers]
constexpr byte B = 1;

REGPIN keypad            // Schieberegister Tastenabfrage
{16, 17, 15};

struct SCOREPAD
{
  //                     // "A"  "B"
  bool aufschlag;        // 21 # 22
  bool spielPlus;        //  6 # 14
  bool spielMinus;       //  5 # 13
  bool spielReset;       //  4 # 12
  bool satzPlus;         //  3 # 11
  bool satzMinus;        //  2 # 10
  bool satzReset;        //  1 #  9
};

struct KEYTABLE
{
  bool timeOutStart;     // 20 Auszeit
  bool timeOutStop;      // 19
  bool timeEinStart;     // 18 Einspielzeit
  bool timeEinStop;      // 17
  SCOREPAD sp[spielers]; // Spielertasten
};

constexpr bool gedrueckt {HIGH}; // Vorbelegung für Tastenabfrage

KEYTABLE kt;
KEYTABLE oldtable;       // Hilfstabelle für das merken der Tastendrücke

struct ZEIT
{
  /*
    1+2 Minutenanzeige ( Anzeige 1 oder 2)( aber nur 2, 1 ist nicht in benutzung)
    3 Sekundenanzeige zehner ( Anzeige 0-5)
    4 Sekundenanzeige einer ( Anzeige 0-9)
  */
  uint8_t minute = 0;
  uint8_t sekunde = 0;
  uint32_t lastTik = 0;   // Merker, wann ausgelöst
} zeit;


void setup()
{
  Serial.begin(115200);
  Serial.println(F("Tischtennis Display"));
  //display init
  for (byte b = 0; b < shiftRegs; b++)
  {
    pinMode(regpin[b].clock, OUTPUT);
    pinMode(regpin[b].data, OUTPUT);
    pinMode(regpin[b].latch, OUTPUT);
    digitalWrite(regpin[b].clock, LOW);
    digitalWrite(regpin[b].data, LOW);
    digitalWrite(regpin[b].latch, LOW);
  };
  pinMode(keypad.clock, OUTPUT);
  pinMode(keypad.data, INPUT);
  pinMode(keypad.latch, OUTPUT);
  digitalWrite(keypad.clock, LOW);
  digitalWrite(keypad.latch, LOW);
  segmentTest();
}

void loop()
{
  getKeys();        // Holt Tastenzustände
  checkKeys();      // wertet Tasten aus
  myTimer();        // CountDownTimer
  displaySpieler(); // kleine Anzeige
  // BigDisplay wird noch umgebaut - dann erst neu coden
  bigDisplay();     // große Anzeige
  displayTime();    // countDownDisplay
}

//
void setAufschlag(const byte seite)            // Stellt den Aufschlag für die entsprechende Spielerseite
{
  for (byte a = 0; a < spielers; a++)
  {spiel.spieler[a].serve = false;}            // löscht alles
  spiel.spieler[seite].serve = true;           // setzt aktuellen Aufschlag
}
//
void nextAufschlag()                           // Nächsten Aufschlag errechnen
{
  byte myAufschlag = 0;
  for (byte b = 0; b < spielers; b++)          // gehe durch alle Spieler
  {
    if (spiel.spieler[b].serve)                // Spieler hat Aufschlag ?
    {
      myAufschlag = b + 1;                     // nächsten Spieler setzen
      if (myAufschlag >= spielers)             // Wert ist größer als Spieler, dann erster Spieler
      { myAufschlag = 0; }
    }
  }
  setAufschlag(myAufschlag);                   // Aufschlag setzen
}
//
void newSet(const byte site)                   // Satz aufaddieren und alle Punkte löschen
{
  spiel.spieler[site].sets++;                  // Satz gewonnen
  for (byte b = 0; b < spielers; b++)
  { spiel.spieler[b].points = 0; }             // Punkte-Reset
  setAufschlag(spiel.serveStart);              // Aufschlag setzen
}
//
void automatikAufschlag()                      // wird aufgerufen, wenn points(+/-)-Taste gedrückt wurde
{
  uint8_t allPoints = spiel.spieler[0].points + spiel.spieler[1].points;
  if (allPoints < 20)                          // summe points für jedes 2tes Spiel
  {
    if (allPoints % 2 == 0)                    // aktuelles Spiel ist zweites Spiel
    { nextAufschlag(); }                       // Aufschlag setzen
  }
  else                                         // Jedes Spiel ab 10:10
  { nextAufschlag(); }
}
//
bool getSite(const bool site)                  // ermittelt die Seite, wo sich Spieler A und B befinden
{
  byte allSet = 0;                             // Summenvariable für alle Sätze
  for (byte b = 0; b < spielers; b++)          // Alle Sätze zählen
  { allSet += spiel.spieler[b].sets; }
  if (allSet != 4)                             // Solange nicht 4 Säze vollständig gespielt wurden normaler Seitenwechsel
  { return  allSet % 2 == 0 ? site : !site; }  // Seitenwechsel nach geradem / ungeradem Satzstand
  // Seitenwechsel bei Satz 5 bis Spiel 5 normal - dann Seitenwechsel!
  for (byte b = 0; b < spielers; b++)          // durchlaufe alle Spielerpoints
  {
    if (spiel.spieler[b].points >= 5)          // Einer hat die Grenze erreicht?
    { return !site; }                          // Tausche die Seite
  }
  return site;                                 // Wenn nix passt, gib die bisherige Seite zurück
}
//
bool checkSetEnd()                             // Prüft ob Satz zu Ende ist
{
  bool isEnd = false;                          // merker
  constexpr uint8_t setPoint {11};             // Satzende in Punkte
  constexpr uint8_t winPoint {2};              // Unterschied Spielerpunkte zum Gewinn
  //
  uint8_t allPoints = 0;
  for (byte b = 0; b < spielers; b++)          // addiere alle Spielerpunkte als Zwischenvariable
  { allPoints += spiel.spieler[b].points; }    //
  for (byte b = 0; b < spielers; b++)          // Frage Spieler auf Siegpunkt ab
  {
    if (spiel.spieler[b].points >= setPoint)   // Mindestpunktzahl zum Gewinn erreicht?
    {
      if ((spiel.spieler[b].points - winPoint) >= (allPoints - spiel.spieler[b].points)) // Unterschied passt?
      {
        newSet(b);                             // Satzpunkt setzen und Spielpunkte löschen
        isEnd = true;                          // merker setzen
      }
    }
  }
  return isEnd;                                //
}
//

//
void getKeys()                                 // Funktion aus dem Ursprungspost übernommen und erweitert
{
  // Das sind Schieberegister n:1
  // Auch wenn nicht alle Pins gebraucht werden, müssen ungenutzte mitgelesen werden!
  constexpr uint8_t zaehler {24};              // Anzahl Chips * 8 Pin
  constexpr uint32_t interval {50};            // "debounce Time"
  static uint32_t lastRead = 0;                // Merker
  if (millis() - lastRead > interval)
  {
    digitalWrite(keypad.latch, LOW);           // Chip  aktivieren
    delay(1);
    digitalWrite(keypad.latch, HIGH);
    bool readIn = true;
    for (uint8_t b = 0; b < zaehler; b++)      // Eingänge durchzählen
    {
      readIn = digitalRead(keypad.data);
      switch (b)                               // Zuweisung Pin => Variable
      {
//*INDENT-OFF*  
      case  1: kt.sp[getSite(A)].satzReset   = readIn; break;  // bit 1 aus Schieberegister 
      case  2: kt.sp[getSite(A)].satzMinus   = readIn; break;  // bit 2
      case  3: kt.sp[getSite(A)].satzPlus    = readIn; break;  // bit 3
      case  4: kt.sp[getSite(A)].spielReset  = readIn; break;
      case  5: kt.sp[getSite(A)].spielMinus  = readIn; break;
      case  6: kt.sp[getSite(A)].spielPlus   = readIn; break;
      case  9: kt.sp[getSite(B)].satzReset   = readIn; break;
      case 10: kt.sp[getSite(B)].satzMinus   = readIn; break;
      case 11: kt.sp[getSite(B)].satzPlus    = readIn; break;
      case 12: kt.sp[getSite(B)].spielReset  = readIn; break;
      case 13: kt.sp[getSite(B)].spielMinus  = readIn; break;
      case 14: kt.sp[getSite(B)].spielPlus   = readIn; break;
      case 17: kt.timeEinStop       = readIn; break; 
      case 18: kt.timeEinStart      = readIn; break;
      case 19: kt.timeOutStop       = readIn; break; 
      case 20: kt.timeOutStart      = readIn; break; 
      case 21: kt.sp[getSite(A)].aufschlag   = readIn; break; 
      case 22: kt.sp[getSite(B)].aufschlag   = readIn; break;
//*INDENT-ON*  
      }
      digitalWrite(keypad.clock, HIGH);
      digitalWrite(keypad.clock, LOW);
    }
    lastRead = millis();
  }
}
//
void checkSpielerKeys()                                   // ausgelesene Werte aus den Schieberegistern verarbeiten
{
  for (byte b = 0; b < spielers; b++)                     // alle Spieler
  {
    // aufschlag
    if (kt.sp[b].aufschlag != oldtable.sp[b].aufschlag)   // aktuellen Zustand und Merker vergleichen
    {
      if (kt.sp[b].aufschlag == !gedrueckt)               // Taste wurde losgelassen?
      { setAufschlag(b); }                                // .... ausführen
      oldtable.sp[b].aufschlag = kt.sp[b].aufschlag;      // Merker setzen, für Auswertesperre
    }
    // satz Reset
    if (kt.sp[b].satzReset != oldtable.sp[b].satzReset)
    {
      if (kt.sp[b].satzReset == !gedrueckt)
      { spiel.spieler[b].sets = 0; }
      oldtable.sp[b].satzReset = kt.sp[b].satzReset;
    }
    // satz Minus
    if (kt.sp[b].satzMinus != oldtable.sp[b].satzMinus)
    {
      if (kt.sp[b].satzMinus == !gedrueckt)
      {
        if (spiel.spieler[b].sets > 0)                    // Nur wenn was auf dem Zähler steht
        { spiel.spieler[b].sets--; }
      }
      oldtable.sp[b].satzMinus = kt.sp[b].satzMinus;
    }
    // satz Plus
    if (kt.sp[b].satzPlus != oldtable.sp[b].satzPlus)
    {
      if (kt.sp[b].satzPlus == !gedrueckt)
      { spiel.spieler[b].sets++; }
      oldtable.sp[b].satzPlus = kt.sp[b].satzPlus;
    }
    // spiel Reset
    if (kt.sp[b].spielReset != oldtable.sp[b].spielReset)
    {
      if (kt.sp[b].spielReset == !gedrueckt)
      { spiel.spieler[b].points = 0; }
      oldtable.sp[b].spielReset = kt.sp[b].spielReset;
    }
    // spiel Minus
    if (kt.sp[b].spielMinus != oldtable.sp[b].spielMinus)
    {
      if (kt.sp[b].spielMinus == !gedrueckt)
      {
        if (spiel.spieler[b].points > 0)                  // Muss ja was drauf sein, damit man was abziehen kann :-)
        { spiel.spieler[b].points--; }
      }
      automatikAufschlag();                               // Aufschlag muss neu berechnet werden, wenn Punkte gesetzt
      // TODO!
      // ACHTUNG: Wenn alle Sätze gelöscht werden, müsste auch überprüft werden,
      // wie der Punktestand ist und dann den Aufschlag nochmal setzen
      oldtable.sp[b].spielMinus = kt.sp[b].spielMinus;
    }
    // spiel Plus
    if (kt.sp[b].spielPlus != oldtable.sp[b].spielPlus)
    {
      if (kt.sp[b].spielPlus == !gedrueckt)
      {
        // TODO!
        // Den Teil evtl. auslagern
        if (spiel.spieler[0].points + spiel.spieler[1].points == 0) // Noch kein Punkt gespielt?
        {
          for (byte c = 0; c < spielers; c++)                       // Frage alle Aufschläge ab
            if (spiel.spieler[c].serve)                             // Spieler hat ersten Aufschlag
            { spiel.serveStart = c; }                               // Merke für den Satz den ersten Aufschlag
        }
        // Ende auslagern
        spiel.spieler[b].points++;                                  //
        automatikAufschlag();                                       // Aufschlagwechsel für Anzeige berechnen
        checkSetEnd();                                              // Wenn Satz zu Ende wird Zählerstände gesetzt
      }
      oldtable.sp[b].spielPlus = kt.sp[b].spielPlus;
    }
  }
}
//
void checkKeys()
{
  checkSpielerKeys();                                             // Spielertasten ausgelagert
  // EinspielZeit
  if (kt.timeEinStart != oldtable.timeEinStart)                   // Tastenstatus geändert?
  {
    if (kt.timeEinStart == gedrueckt)                             //
    {
      zeit.minute = 2;                                            // Vorbelegung
      zeit.sekunde = 0;
      zeit.lastTik = millis();                                    // aktuelle Zeit für timer
    }
    oldtable.timeEinStart = kt.timeEinStart;                      // Tastenstatus merken
  }
  if (kt.timeEinStop != oldtable.timeEinStop)
  {
    if (kt.timeEinStop == gedrueckt)
    {
      zeit.minute = 0;
      zeit.sekunde = 0;;
    }
    oldtable.timeEinStop = kt.timeEinStop;
  }
  // TimeOutZeit
  if (kt.timeOutStart != oldtable.timeOutStart)
  {
    if (kt.timeOutStart == gedrueckt)
    {
      zeit.minute = 1;
      zeit.sekunde = 0;
      zeit.lastTik = millis();
    }
    oldtable.timeOutStart = kt.timeOutStart;
  }
  if (kt.timeOutStop != oldtable.timeOutStop)
  {
    if (kt.timeOutStop == gedrueckt)
    {
      zeit.minute = 0;
      zeit.sekunde = 0;;
    }
    oldtable.timeOutStop = kt.timeOutStop;
  }
}
//
void printSegment(const byte &segmentReihe, const bool *array, const byte &laenge)
{
  for (uint8_t b = 0; b < laenge; b++)
  {
    digitalWrite(regpin[segmentReihe].data, array[b]);    // Array auf dem Schieberegister
    digitalWrite(regpin[segmentReihe].clock, HIGH);
    digitalWrite(regpin[segmentReihe].clock, LOW);
    digitalWrite(regpin[segmentReihe].data, LOW);
  }
  digitalWrite(regpin[segmentReihe].latch, HIGH);
  digitalWrite(regpin[segmentReihe].latch, LOW);
}
//
void displayTime()
{
  constexpr byte laenge {32};
  bool time[laenge] = {0};
  bool *timeSekundeEiner = &time[0];
  bool *timeSekundeZehner = &time[8];     // 2.tes Register
  bool *timeMinuteEiner = &time[16];      // 3.tes Register
  bool *timeMinuteZehner = &time[24];     // 4.tes Register
  memset(time, 0, laenge);
  memcpy(timeMinuteZehner, dec[zeit.minute / 10], 8);
  memcpy(timeMinuteEiner, dec[zeit.minute % 10], 8);
  memcpy(timeSekundeZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timeSekundeEiner, dec[zeit.sekunde % 10], 8);
  printSegment(2, time, laenge);                        // 3.ter Kanal
}
//
void displaySpieler()
{
  /*
    1+2 Punktanzeige Spieler A (Anzeige 0-19)
    3+4 Satzanzeige Spieler A (Anzeige 0-9)( aber nur 3, 4 ist nicht in benutzung)
    5+6 Satzanzeige Spieler B (Anzeige 0-9)( aber nur 5, 6 ist nicht in benutzung)
    7+8 Punktanzeige Spieler B ( Anzeige 0-19)
  */
  constexpr byte laenge {64};
  byte allSet = 0;
  for (byte b = 0; b < spielers; b++)
  { allSet += spiel.spieler[b].sets; }
  bool spell[laenge] = {0};
  bool *spielerBeiner = &spell[0];
  bool *spielerBzehner = &spell[8];
  bool *satzBeiner = &spell[24];
  bool *satzAeiner = &spell[40];
  bool *spielerAeiner = &spell[48];
  bool *spielerAzehner = &spell[56];
  memset(spell, 0, laenge);
  // Das ist tricky:
  // die Ausgabe der Spielerdaten ist abhängig von der Anzahl gespielter Sätze
  // Mit jedem gespieltem Satz wechselt der Spieler die Seite
  // Und bei Satz 5 nochmal mitten im Spiel... getSite() soll das ermitteln
  memcpy(spielerAzehner, dec[spiel.spieler[getSite(A)].points / 10], 8);
  memcpy(spielerAeiner,  dec[spiel.spieler[getSite(A)].points % 10], 8);
  memcpy(satzAeiner,     dec[spiel.spieler[getSite(A)].sets % 10], 8);
  memcpy(satzBeiner,     dec[spiel.spieler[getSite(B)].sets % 10], 8);
  memcpy(spielerBzehner, dec[spiel.spieler[getSite(B)].points / 10], 8);
  memcpy(spielerBeiner,  dec[spiel.spieler[getSite(B)].points % 10], 8);
  printSegment(0, spell, laenge);
}
//
//
void myTimer()
{
  constexpr uint32_t _oneSecond {1000};
  if (zeit.minute || zeit.sekunde)   // Solange Zeit aktiv
  {
    if (millis() - zeit.lastTik >= _oneSecond)
    {
      if (zeit.sekunde > 0)
      { zeit.sekunde--; }
      else if (zeit.minute > 0)
      {
        zeit.minute--;
        zeit.sekunde = 59;
      }
      zeit.lastTik += _oneSecond;
    }
  }
}
//
void bigDisplay()
{
  constexpr uint32_t printIntervall {500};
  static uint32_t lastPrint = 0;
  memset(bigDispRegOut, !segActiv, bigDispReg * 8);  // alle bits löschen
  bool *timerHunderter = &bigDispRegOut[4 * 8];      // Register 7 | DIGIT 0
  bool *timerZehner = &bigDispRegOut[5 * 8];         // Register 6 | DIGIT 1
  bool *timerEiner = &bigDispRegOut[6 * 8];          // Register 5 | DIGIT 2
  bool *spielerAsatz = &bigDispRegOut[1 * 8];
  bool *spielerAeiner = &bigDispRegOut[2 * 8];
  bool *spielerBsatz = &bigDispRegOut[3 * 8];
  bool *spielerBeiner = &bigDispRegOut[0 * 8];
  //
  memcpy(timerHunderter, dec[zeit.minute % 10], 8);
  memcpy(timerZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timerEiner, dec[zeit.sekunde % 10], 8);
  memcpy(spielerAeiner, dec[spiel.spieler[0].points % 10], 8);
  memcpy(spielerAsatz, dec[spiel.spieler[0].sets % 10], 8);
  memcpy(spielerBeiner, dec[spiel.spieler[1].points % 10], 8);
  memcpy(spielerBsatz, dec[spiel.spieler[1].sets % 10], 8);
  bigDispRegOut[16] = spiel.spieler[1].points / 10 ? segActiv : !segActiv;  // spielerBZehner
  bigDispRegOut[24] = spiel.spieler[1].serve   ? segActiv : !segActiv;  // spielerBAufschlag
  bigDispRegOut[32] = spiel.spieler[0].points / 10 ? segActiv : !segActiv;  // spielerAZehner
  // Spieler 0 serve war ursprünglich 40 - falscher Chip angelötet
  bigDispRegOut[8] = spiel.spieler[0].serve   ? segActiv : !segActiv;  // spielerAAufschlag
  for (byte b = 0; b < 56; b++)
  { bigDispRegOut[b] = !bigDispRegOut[b]; }
  if ((memcmp(bigDispRegOut, oldBigDisplay, bigDispReg * 8) != 0)  || // Neuer Inhalt ODER
      (millis() - printIntervall > lastPrint))                        // Zeit abgelaufen
  {
    printSegment(bigDisp, bigDispRegOut, bigDispReg * 8);
    memcpy(oldBigDisplay, bigDispRegOut, bigDispReg * 8);
    lastPrint = millis();
  }
};
//
void segmentTest()
{
  for (byte b = 0; b < 10; b++)
  {
    for (byte s = 0; s < spielers; s++)
    {
      spiel.spieler[s].points = spiel.spieler[s].points ? 0 : 88;
      spiel.spieler[s].sets = spiel.spieler[s].sets ? 0 : 88;
      spiel.spieler[s].serve = spiel.spieler[s].serve ? 0 : 1;
    }
    zeit.minute = zeit.minute ? 0 : 88;
    zeit.sekunde = zeit.sekunde ? 0 : 88;
    displaySpieler();
    displayTime();
    bigDisplay();
    delay(500);
  }
}
//
void allNew()
{
  for (byte s = 0; s < spielers; s++)
  {
    spiel.spieler[s].points = 0;
    spiel.spieler[s].sets = 0;
    spiel.spieler[s].serve = false;
  }
  // spiel.serveIsChange = false;
  spiel.serveStart = 0;
  zeit.minute = 0;
  zeit.sekunde = 0;
}

Das hier?


void bigDisplay()
{
  constexpr uint32_t printIntervall {500};
  static uint32_t lastPrint = 0;
  memset(bigDispRegOut, !segActiv, bigDispReg * 8);  // alle bits löschen
  bool *timerHunderter = &bigDispRegOut[4 * 8];      // Register 7 | DIGIT 0
  bool *timerZehner = &bigDispRegOut[5 * 8];         // Register 6 | DIGIT 1
  bool *timerEiner = &bigDispRegOut[6 * 8];          // Register 5 | DIGIT 2
  bool *spielerAsatz = &bigDispRegOut[1 * 8];
  bool *spielerAeiner = &bigDispRegOut[2 * 8];
  bool *spielerBsatz = &bigDispRegOut[3 * 8];
  bool *spielerBeiner = &bigDispRegOut[0 * 8];
  //
  memcpy(timerHunderter, dec[zeit.minute % 10], 8);
  memcpy(timerZehner, dec[zeit.sekunde / 10], 8);
  memcpy(timerEiner, dec[zeit.sekunde % 10], 8);
  memcpy(spielerAeiner, dec[spiel.spieler[getSite(A)].points % 10], 8);
  memcpy(spielerAsatz, dec[spiel.spieler[getSite(A)].sets % 10], 8);
  memcpy(spielerBeiner, dec[spiel.spieler[getSite(B)].points % 10], 8);
  memcpy(spielerBsatz, dec[spiel.spieler[getSite(B)].sets % 10], 8);
  bigDispRegOut[16] = spiel.spieler[getSite(B)].points / 10 ? segActiv : !segActiv;  // spielerBZehner
  bigDispRegOut[24] = spiel.spieler[getSite(B)].serve   ? segActiv : !segActiv;  // spielerBAufschlag
  bigDispRegOut[32] = spiel.spieler[getSite(A)].points / 10 ? segActiv : !segActiv;  // spielerAZehner
  // Spieler 0 serve war ursprünglich 40 - falscher Chip angelötet
  bigDispRegOut[8] = spiel.spieler[getSite(A)].serve   ? segActiv : !segActiv;  // spielerAAufschlag
  for (byte b = 0; b < 56; b++)
  { bigDispRegOut[b] = !bigDispRegOut[b]; }
  if ((memcmp(bigDispRegOut, oldBigDisplay, bigDispReg * 8) != 0)  || // Neuer Inhalt ODER
      (millis() - printIntervall > lastPrint))                        // Zeit abgelaufen
  {
    printSegment(bigDisp, bigDispRegOut, bigDispReg * 8);
    memcpy(oldBigDisplay, bigDispRegOut, bigDispReg * 8);
    lastPrint = millis();
  }
};

Hallo

Du hast jetzt ....[getSite(...)].... eingefügt?

Ist das jetzt neu?

Oder hat er deshalb nicht gewechselt?