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

Moijn, ach schau, Du hast die richtige Stelle schon gefunden :slight_smile:
Doch doch, der macht schon das richtige.
Der Merker spiel.serveStartmerkt sich den Spieler, der den Aufschlag hat.
Darum wandert der Aufschlag auch mit dem Seitenwechsel mit.
Änder mal die letzte Zeile:

setAufschlag(getSite(spiel.serveStart));              // Aufschlag setzen

Sollte der Aufschlag wieder mitwandern, dann:

setAufschlag(getSite(!spiel.serveStart));              // Aufschlag setzen

ansonsten muss ich das anders lösen.

Schönen guten Tag an alle,

Leider funktioniert das nicht.

...
Nehm ich zur Kenntnis.

Ich habe auch mal rumprobiert mit ! oder auch ohne !
kein Unterschied, außer wenn Ich ein reset der Sätze mache dann setzt er den Aufschlag richtig.

Würdest du dich dieses Problem noch annehmen?

Entferne: noch.
Dann könnte das was werden.

Mein Sarkasmus ist heute grenzenlos....

Würdest du dich dieses Problem annehmen?

:joy: :innocent:

1 Like

Ich hab mir mein Archiv geholt.
So, jetzt Du.
Erster Satz - erstes Spiel
A:B
Ersten Aufschlag bekommt B
Satz ist zu Ende.
Seiten werden gewechselt:
B:A
Aufschlag hat jetzt A?

Aufschlag hat jetzt A?

Genau so ist das richtig!

Dann denk ich den Grund zu haben.
Es wird nach dem setzen aus dem Speicher ein Seitenwechsel gemacht.
Bekommen wir gelöst.

  bigDispRegOut[24] = spiel.spieler[getSite(B)].serve ? segActiv : !segActiv;  // spielerBAufschlag

Mach doch mal bitte:

  bigDispRegOut[24] = spiel.spieler[getSite(B)].serve ? !segActiv : segActiv;  // spielerBAufschlag

Und

  bigDispRegOut[8] = spiel.spieler[getSite(A)].serve ? segActiv : !segActiv;  // spielerAAufschlag

wird

  bigDispRegOut[8] = spiel.spieler[getSite(A)].serve ? !segActiv : segActiv;  // spielerAAufschlag

Nabend,
leider führt dieser weg nicht zum Ziel!

Das weiss ich.
Ich wüsste nur gerne, ob jetzt der erste Aufschlag seitenverkehrt und dann alle passend sind.
Wenn Du nicht mit dem rausrückst was passiert, wird das nix.

Okay Okay Okay.....

Ja ist seitenverkehrt,

Es leuchten direkt beide Angaben beim einschalten,

beim drücken einer Angaben Taste erlischt das Licht der andere leuchtet weiter,

Hmm witzig, die ersten beiden Seitenwechsel macht er richtig danach nicht mehr!?

Ok, dann kann ich mich mit dem Flow beschäftigen.
Denke dran: Ich habe keinerlei Hardware. Ich muss alles irgendwie aus Logik und Wissen und Raten zusammenstammeln. Alles as ich mache, hat einen Grund und ich brauch dann aber dazu passend eine Aussage. Und das wenn möglich komplett ereignisoffen.

Es macht keinen Sinn, wenn ich frage, ob der Punkt leuchtet und dann als Antwort kommt JA.
Du musst das jedes Mal durchspielen, was alles an Varianten möglich sind und mir wiedergeben. Ich kann nicht auf Deinen Tisch schauen.

Ich gebe mein bestes.....

Fehlen dir jetzt noch info´s?

1 Like

Ich kann das erstmal nehmen und sehen, ob mir das reicht. Die Idee mit dem Seitentausch war schon ok, jetzt muss ich nur zusehen, wo es dann passt, damit das ab Anfang richtig geht.

Hallo

hättest du noch Lust dieses Projekt zu beenden?

Grüße

Darf Ich dich bitten mit mir das Projekt zu beenden?

Wenn du keine Lust mehr hast..... Okay, aber sage mir dann bitte wenigstens bescheid.

Vielen Dank für deine Arbeit(kein Sarkasmus! meine es ernst)

Holi shit.
Den hab ich irgendwie übersehen.
Ich dachte wir wären durch...

Schau ich drauf - und nee, Mein Ehrgeiz ist leider, dass ich etwas fertig machen möchte....

1 Like

Ich hab den nicht vergessen, nur beim aufräumen der Fenster geschlossen...

Es gibt ein paar zusätzliche Serielle Ausgaben.
Sollte was nicht passen, brauche ich die vollständige Ausgabe ab Controllerstart.

Was mir fehlt und unbedingt noch rein muss:
Du kannst Punkte dazurechnen, ohne dass ein Aufschlag ausgewält ist.
Das darf natürlich nicht passieren.
Mache ich noch, wenn der Aufschlag jetzt auf der richtigen Seite angezeigt wird.

// 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
*/
#include <Streaming.h>
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

constexpr 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);

  for (byte b = 0; b < spielers; b++)
  {
    kt.timeOutStart = !gedrueckt;
    kt.timeOutStop = !gedrueckt;
    kt.timeEinStart = !gedrueckt;
    kt.timeEinStop = !gedrueckt;
    kt.sp[b].spielPlus = !gedrueckt;
    kt.sp[b].aufschlag = !gedrueckt;
    kt.sp[b].spielPlus = !gedrueckt;
    kt.sp[b].spielMinus = !gedrueckt;
    kt.sp[b].spielReset = !gedrueckt;
    kt.sp[b].satzPlus = !gedrueckt;
    kt.sp[b].satzMinus = !gedrueckt;
    kt.sp[b].satzReset = !gedrueckt;
    oldtable.timeOutStart = !gedrueckt;
    oldtable.timeOutStop = !gedrueckt;
    oldtable.timeEinStart = !gedrueckt;
    oldtable.timeEinStop = !gedrueckt;
    oldtable.sp[b].spielPlus = !gedrueckt;
    oldtable.sp[b].aufschlag = !gedrueckt;
    oldtable.sp[b].spielPlus = !gedrueckt;
    oldtable.sp[b].spielMinus = !gedrueckt;
    oldtable.sp[b].spielReset = !gedrueckt;
    oldtable.sp[b].satzPlus = !gedrueckt;
    oldtable.sp[b].satzMinus = !gedrueckt;
    oldtable.sp[b].satzReset = !gedrueckt;
  }
}

void loop()
{
  getKeys();        // Holt Tastenzustände
  checkKeys();      // wertet Tasten aus
  myTimer();        // CountDownTimer
  displaySpieler(); // kleine Anzeige
  bigDisplay();     // große Anzeige
  displayTime();    // countDownDisplay
}

//
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;
}
//
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 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

  Serial << "setAufschlag site " << seite << endl;
  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; }
    }
  }

  // Serial << "myAufschlag " << myAufschlag << endl;
  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

  Serial << "newSet " << spiel.serveStart << endl;
  setAufschlag(spiel.serveStart);              // Aufschlag setzen *****
}
//
void automatikAufschlag()                      // wird aufgerufen, wenn points(+/-)-Taste gedrückt wurde
{
  uint8_t allPoints = 0;

  for (uint8_t b = 0; b < spielers; b++)
  { allPoints += spiel.spieler[b].points; }

  // Serial << "automatikAufschlag" << endl;

  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
  }

  Serial << "getSite site: " << site << endl;
  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
      }
    }
  }

  Serial << "checkSetEnd " << isEnd << endl;
  return isEnd; //
}

//
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 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];

  //
  if (zeit.minute + zeit.sekunde)
  {
    memcpy(timerHunderter, dec[zeit.minute % 10], 8);
    memcpy(timerZehner, dec[zeit.sekunde / 10], 8);
    memcpy(timerEiner, dec[zeit.sekunde % 10], 8);
  }
  else
  {
    memcpy(timerHunderter, dec[10], 8);
    memcpy(timerZehner, dec[10], 8);
    memcpy(timerEiner, dec[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();
  }
};

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

      //Serial<<"READIN: "<<readIn<<endl;
      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 leerZeile()
{
  Serial << "\r\n" << endl;
}
void checkSpielerKeys()                                   // ausgelesene Werte aus den Schieberegistern verarbeiten
{
  for (byte b = 0; b < spielers; b++)                     // alle Spieler
  {
    //Serial<<"x"<<endl;

    // aufschlag
    if (kt.sp[b].aufschlag != oldtable.sp[b].aufschlag)   // aktuellen Zustand und Merker vergleichen
    {
      if (kt.sp[b].aufschlag == gedrueckt)               // Taste wurde losgelassen?
      {
        leerZeile();
        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)
      {
        leerZeile();
        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)
      {
        leerZeile();

        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)
      {
        leerZeile();
        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)
      {
        leerZeile();
        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)
      {
        leerZeile();

        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)
    {
      //Serial << b << " AA " << endl;
      if (kt.sp[b].spielPlus == gedrueckt)
      {
        leerZeile();

        // TODO!
        // Den Teil evtl. auslagern
        if (spiel.spieler[0].points + spiel.spieler[1].points == 0) // Noch kein Punkt gespielt?
        {
          Serial << "noch kein Punkt" << endl;

          for (byte c = 0; c < spielers; c++)                       // Frage alle Aufschläge ab
            if (spiel.spieler[c].serve)                             // Spieler hat ersten Aufschlag
            {
              Serial << "erster Aufschlag gemerkt: " << c << endl;
              spiel.serveStart = c;
            }                               // Merke für den Satz den ersten Aufschlag
        }

        // Ende auslagern
        spiel.spieler[b].points++; //
        Serial << "Spieler " << b << " Point " << spiel.spieler[b].points << endl;
        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;
  }
}