Schieberegistersteuerung

Hallo zusammen,

ich versuche mich gerade an dem Setzen von Schieberegistern, gesteuert entweder über den COM-Port oder über ein Parallelinterface.

Im Grundzustand sollen die Schieberegister über den COM-Port kontrolliert werden.

Wenn der Latchpin des parallelen Interface auf high wechselt, soll entprellt das Bitmuster an den latchBitPins ausgelesen und der entsprechende Kanal gesetzt werden. Während der Latchpin high ist, soll das Bitmuster nicht weiter eingelesen werden und über den COM-Port sollen nur Hilfsbefehle ausgeführt werden können, aber kein setzen von Kanälen.

Diesen Code habe ich mir hierfür aus als mehr oder weniger gut funktionierend bekannten Codeschnipseln zusammengestückelt. Bevor ich jetzt an das große Debugging gehe, kann/sollte man das überhaupt so machen? Habe ich groben Unfug fabriziert? Geht es besser/eleganter/stabiler?

#define SCLK  PIN_PC1
#define LATCH PIN_PB2
#define MOSI  PIN_PE3
#define MISO  PIN_PC0

const uint8_t shiftregistercount = 4;
const uint8_t channelcount = 18;
const uint8_t channelassignment[channelcount] = {
  18, 19, 20, 21, 22, 23, 24, 25, 26,
  13, 12, 11, 10, 9, 8, 7, 6, 5
};

uint8_t outputdata[shiftregistercount];
uint8_t routputdata[shiftregistercount];
uint8_t allon[shiftregistercount];

// Parallelinterface
const uint8_t latchBitPins[5] = {2, 3, 4, 5, 6}; // 5 Bitmuster-Pins
const uint8_t LATCHPIN = 7;                      // Latch-Signalpin
unsigned long lastLatchCheck = 0;
const uint16_t latchSampleInterval = 50;
uint8_t stableLatchCounter = 0;
uint8_t lastStablePattern = 255;
bool latchActive = false;
bool latchPending = false;
bool latchWasHigh = false;

void setup() {
  pinMode(SCLK, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(MOSI, OUTPUT);
  pinMode(MISO, INPUT);
  digitalWrite(SCLK, LOW);
  digitalWrite(LATCH, LOW);
  digitalWrite(MOSI, LOW);

  for (uint8_t i = 0; i < channelcount; i++) {
    uint8_t pointer = channelassignment[i];
    allon[pointer >> 3] |= (1 << (pointer & 7));
  }

  for (int i = 0; i < 5; i++) pinMode(latchBitPins[i], INPUT);
  pinMode(LATCHPIN, INPUT);

  Serial.begin(115200);
}

void loop() {
  // Parallelinterface mit Latch-Flankenerkennung und Bitmusterstabilität
  bool latchNow = digitalRead(LATCHPIN) == HIGH;

  if (millis() - lastLatchCheck >= latchSampleInterval) {
    lastLatchCheck = millis();

    if (latchNow && !latchWasHigh) {
      latchPending = true;
      stableLatchCounter = 0;
      lastStablePattern = 255;
    }

    latchWasHigh = latchNow;

    if (latchNow && latchPending) {
      uint8_t currentPattern = 0;
      for (int i = 0; i < 5; i++) {
        if (digitalRead(latchBitPins[i])) {
          currentPattern |= (1 << i);
        }
      }

      if (currentPattern == lastStablePattern) {
        stableLatchCounter++;
      } else {
        stableLatchCounter = 1;
        lastStablePattern = currentPattern;
      }

      if (stableLatchCounter >= 4 && currentPattern < channelcount) {
        latchActive = true;
        latchPending = false;
        resetalloutputs();
        setoutput(currentPattern);
        shiftoutdata();
      }
    }

    if (!latchNow) {
      latchActive = false;
      latchPending = false;
      stableLatchCounter = 0;
      lastStablePattern = 255;
    }
  }

  // USB-Kommandos
  if (Serial.available()) {
    String cmdStr = Serial.readStringUntil('\n');
    cmdStr.trim();
    processCommand(cmdStr.c_str());
  }
}

void processCommand(const char* cmd) {
  if (strlen(cmd) < 2) {
    Serial.println(F("ERR: Command too short"));
    return;
  }

  char cmd1 = cmd[0];
  char cmd2 = cmd[1];

  int ch = 0;
  if (strlen(cmd) > 2) {
    ch = atoi(cmd + 2); // Kanalnummer (optional)
  }

  uint16_t code = (cmd1 << 8) | cmd2;

  // Sperre nur für schreibende Kommandos während latchActive
  bool isWriteCommand = (code == ('S' << 8 | 'H')) || (code == ('S' << 8 | 'L')) ||
                        (code == ('A' << 8 | 'H')) || (code == ('A' << 8 | 'L')) ||
                        (code == ('S' << 8 | 'T'));

  if (latchActive && isWriteCommand) {
    Serial.println(F("ERR: Channel modification disabled during latch active"));
    return;
  }

  switch (code) {
    case ('S' << 8 | 'H'): // SH - Set HIGH (ein Kanal auf 1)
      if (ch < 1 || ch > channelcount) {
        Serial.println(F("ERR: Invalid channel"));
      } else {
        setoutput(ch - 1);
        Serial.println(F("OK"));
      }
      break;

    case ('S' << 8 | 'L'): // SL - Set LOW (ein Kanal auf 0)
      if (ch < 1 || ch > channelcount) {
        Serial.println(F("ERR: Invalid channel"));
      } else {
        resetoutput(ch - 1);
        Serial.println(F("OK"));
      }
      break;

    case ('A' << 8 | 'H'): // AH - Alle HIGH
      setalloutputs();
      Serial.println(F("ALL HIGH"));
      break;

    case ('A' << 8 | 'L'): // AL - Alle LOW
      resetalloutputs();
      Serial.println(F("ALL LOW"));
      break;

    case ('S' << 8 | 'T'): // ST - Shift & Test
      shiftoutdata();
      {
        bool all_match = true;
        for (uint8_t i = 0; i < shiftregistercount; i++) {
          if (outputdata[i] != routputdata[i]) all_match = false;
        }
        Serial.println(all_match ? F("UPDATED OK") : F("ERROR: Mismatch"));
      }
      break;

    case ('P' << 8 | 'H'): // PH - Print HIGH
      Serial.print(F("HIGH: "));
      for (uint8_t i = 0; i < channelcount; i++) {
        if (readoutput(i)) {
          Serial.print(i + 1);
          Serial.print(' ');
        }
      }
      Serial.println();
      break;

    case ('P' << 8 | 'L'): // PL - Print LOW
      Serial.print(F("LOW: "));
      for (uint8_t i = 0; i < channelcount; i++) {
        if (!readoutput(i)) {
          Serial.print(i + 1);
          Serial.print(' ');
        }
      }
      Serial.println();
      break;

    case ('H' << 8 | 'P'): // HP - Hilfe
      Serial.println(F("Verfuegbare Kommandos:"));
      Serial.println(F("SH[n] - Set channel HIGH (1-basiert)"));
      Serial.println(F("SL[n] - Set channel LOW"));
      Serial.println(F("AH    - All HIGH"));
      Serial.println(F("AL    - All LOW"));
      Serial.println(F("ST    - Shift and verify"));
      Serial.println(F("PH    - Print HIGH channels"));
      Serial.println(F("PL    - Print LOW channels"));
      Serial.println(F("HP    - Help"));
      break;

    default:
      Serial.println(F("ERR: Unknown command"));
      break;
  }
}

void shiftoutdata() {
  digitalWrite(SCLK, LOW);
  digitalWrite(LATCH, LOW);
  digitalWrite(MOSI, LOW);

  for (int j = shiftregistercount - 1; j >= 0; j--) {
    for (int i = 7; i >= 0; i--) {
      digitalWrite(SCLK, LOW);
      digitalWrite(MOSI, (outputdata[j] >> i) & 1);
      digitalWrite(SCLK, HIGH);
    }
  }
  digitalWrite(SCLK, LOW);
  digitalWrite(LATCH, HIGH);
  digitalWrite(LATCH, LOW);

  for (int j = shiftregistercount - 1; j >= 0; j--) {
    for (int i = 7; i >= 0; i--) {
      routputdata[j] = (routputdata[j] & ~(1 << i)) | (digitalRead(MISO) << i);
      digitalWrite(SCLK, HIGH);
      digitalWrite(SCLK, LOW);
    }
  }
}

void setoutput(uint8_t pointer) {
  pointer = channelassignment[pointer];
  outputdata[pointer >> 3] |= (1 << (pointer & 7));
}

void resetoutput(uint8_t pointer) {
  pointer = channelassignment[pointer];
  outputdata[pointer >> 3] &= ~(1 << (pointer & 7));
}

bool readoutput(uint8_t pointer) {
  pointer = channelassignment[pointer];
  return (outputdata[pointer >> 3] >> (pointer & 7)) & 1;
}

void setalloutputs() {
  for (int i = 0; i < shiftregistercount; i++) {
    outputdata[i] = allon[i];
  }
}

void resetalloutputs() {
  for (int i = 0; i < shiftregistercount; i++) {
    outputdata[i] = 0;
  }
}

Vielen Dank im voraus für eure Antworten :slight_smile:

Beste Grüße

Dominic

Bitte nochmal erklähren. Ich verstehe nicht was Du machen willst.
Willst Du mit einem Arduino Board ein Schieberegister simulieren?

Ich möchte mit einem Arduino Micro mehreren 74HC595 mitteilen, welche Ausgänge high oder low sein sollen. Die Kommunikation nach außen soll über den COM-Port per USB laufen.

Zusätzlich soll eine parallele Eingangsschnittstelle vorhanden sein, um auch ohne USB-Verbindung Ausgänge einzeln auf high setzen zu können. Also z.B. bei 00101 an der Schnittstelle soll nur der fünfte Ausgang high sein, alle anderen low.

aha.
Schieberegister kann man in Reihe schalten, es braucht dann nur 1 Latch Pin für alle. und man schickt dann einfach bei 5 Schieberegistern 5 mal 8 Bit raus. Wenn sich ein Pin nicht ändern soll dann schreibt man einfach den gleichen wert wie vorher hinein.

Ich verstehe nicht wie Du mit 5 Eingängen 40 Ausgangspins ( 5 Schieberegister a 8 Ausgängen ) ansteuern willst.
Wenn Du SPI verwenden willst dann nimm die Bibliothek. Ansonsten die Funktion shiftout()
Grüße Uwe

Zeichne doch mal ein Schaltbild, damit man die Pins im Code besser interpretieren kann.

Ich habe im Beispielcode nur vier Schieberegister, von denen nur 18 Ausgänge in den ersten Zeilen als adressierbar definiert werden. Das parallele Interface soll ausschließlich dazu dienen, den einen Ausgang, dessen Zuordnung dem Bitmuster am Parallelport entspricht (current pattern), auf high zu setzen, während alle andern Ausgänge low sind. Siehe diese Zeilen:

Die Verbindungen zu den Schieberegistern sind:
SCLK --> shift register clock
LATCH --> storage register clock
MOSI --> serial data in vom ersten Schieberegister
MISO --> serial data out vom letzten Schieberegister

LATCHPIN ist der Eingang, bei dessen Low-High Flanke das Bitmuster an den latchBitPins ausgelesen und der entsprechende Ausgang gesetzt werden soll

channelassigment enthält pointer:

Der nach außen erste Kanal spricht den 18. Ausgang der Schieberegister an. Der nach außen zweite Kanal geht auf den 19. usw.

Zu was brauchst Du das?
Grüße Uwe

In outputdata stehen die an die Schieberegister zu übertragenden Daten. In routputdata werden die Daten nach dem Setzen der Schieberegister über MISO zurückgelesen. Die beiden Arrays können dann verglichen werden, um zu testen ob die Übertragung erfolgreich war. Das Übertragen und Zurücklesen passiert in void shiftoutdata.

Beste Grüße

Dominic

Soweit verstanden, ist nix problematisches.

Das wird auch nicht so problematisch. Mit 6 Pin am Controller könnten alle Pins der SH gesetzt werden. Mit einem zusätzlichen Pin, regelst Du die Übernahme des anliegenden Bitmusters in die Register.

Habe ich das jetzt so richtig verstanden?

Sorry, das funktioniert nicht. Was Du da über MISO hereinbekommst ist der alte Zustand des Schieberegisters, nicht das was Du rausgeschoben hast.

Das funktioniert so nicht!

Wenn du neue Daten in das Schieberegister sendest, werden die vorherigen an MISO ausgegeben.

Du musst die Daten also nochmal ausgeben, damit du die frischen Daten an MISO bekommst. Aber dann weißt du immer noch nicht ob die letzte Übertragung geklappt hat.

um zu testen ob die Übertragung erfolgreich war.

Das wird also keiner.

Edit: Doppelt hält besser :ogre:

Nicht ganz. Ein Flankenwechsel von low nach high an Pin 7 löst ein auf low setzen aller Ausgänge aus. Wenn an Pin 2-6 ein Bitmuster anliegt, wird dieser eine Ausgang auf high gesetzt.

Hier ein Beispiel: 00010 am Parallelport, steigende Flanke an Pin 7, dann wird der zweite Ausgang high setzt, alles andere low. Über die Pointer in channelassignment wird aus dem zweiten Ausgang der 19. Schieberegisterausgang.

Ich schiebe erst 4 Byte raus, aktiviere dann das storage register per LATCH pin und schiebe dann noch 32x weiter, um die Daten über MISO zurück zu lesen und zu vergleichen. Der Teil des Codes ist auf jeden Fall lauffähig, nach shiftoutdata stimmen outputdata und routputdata überein.

Du bekommst die gesendeten Daten zurück, aber nicht die wahren Zustände der Ausgänge. Ich sehe diese Arbeit als sinnlos, weil sie keine Sicherheit bringt, ob die Ausgänge korrekt gesetzt sind. Ok Du könntest übertragungsfehler haben, aber die könnten gleichgut beim zurücklesen entstehen.

Wenn Du kontrolieren willst, ob die Ausgänge der Schieberegister die richtigen Zustände haben, müßtest Du mit weiteren Schieberegistern PISO (paralell in serial out zB HC165) die Zustände der SIPO Schieberegister HC595 einlesen und übertragen. Du kannst auch HC194 nehmen, das sind 4 bit PISO und SIPO.

Grüße Uwe

Mit der steigenden Flanke werden also 3 Schritte durchgeführt:

  1. Löschen des biserigen Inhaltes
  2. Auslesen des Bitmuster
  3. Setzen des Inhaltes aus dem Bitmuster.

Wie gehst Du mit der Konkurrenz des Seriellen Signals um?
Gibt es da einen Vorrang oder soll wer als letzter kommt ist Gewinner sein?

Und ich glaub das jetzt auch mit Deinem Verify verstanden zu haben.

  1. Byte schieben ins Shiftregister
  2. Shiftregister übernehmen ins Storageregister
  3. Storageregister sperren
  4. Byte nochmal ins Shiftregister schieben um am Ende den bisherigen Inhalt abzufragen

Dass die Schieberegister bei korrekten Eingangsdaten die Ausgänge falsch setzen, habe ich bis dato noch nie erlebt. Dass die Daten sich auf dem Weg zu den Schieberegistern ändern, ist deutlich wahrscheinlicher, vor allem bei langen Leitungslängen. Durch den Abgleich wird letzteres mit hoher Sicherheit ausgeschlossen, denn dass auf dem Weg von MOSI zu MISO das gleiche Bit zweimal flippt und somit beide Arrays gleich sind, ist höchst unwahrscheinlich.

Solange Pin 7 High ist, soll die serielle Kommunikation auf Hilfskommandos beschränkt sein, die keine Ausgänge verändern:

Genau das ist die Idee dahinter :slight_smile:

Beste Grüße

Dominic

Wenn ich das richtig verstehe, soll von den 32 ( eff. 18 ) Bits der Schieberegister genau eins gesetzt werden. ( Oder eventuell auch gar keins, was aber über die Parallel-Eingabe nicht möglich ist ? )

Ist das eine Fingerübung fürs Programmieren und Schieberegister-Ansteuern, oder wozu brauchst du das?

Irgendein Bitmuster zwischen 00000 und 11111 liegt doch immer an, oder ist Ausgang #0 ein Sonderfall und du hast nur max 31 Ausgänge?

Wenn an Pin 7 eine steigende Flanke erkannt wird, soll das Bitmuster an Pin 2-6 eingelesen werden und der entsprechende Ausgang high, die restlichen low werden. Solange Pin 7 high ist, sind schreibende Kommandos per serieller Schnittstelle disabled.

Ich brauche das ganze, um entweder über USB möglichst flexibel die Schieberegisterausgänge setzen zu können, oder mit nur ein paar parallelen Datenleitungen immer nur einen Ausgang auf high zu setzen.

Da ich nur 18 Ausgänge nutze, kann ich 00000 als kein Ausgang an definieren. Wenn das Projekt größer wird, muss ich mehr parallele Pins nehmen.

Von welchen Leitungslängen reden wir?
Brauchst Du eine schnelle Datenübertragung von über 1 Mbite pro Sekunde oder kann eine gemächliche und darum stöhrungsresistentere Übertragung auch deinen Bedürfnissen entsprechen?

Grüße Uwe

Das kommt drauf an, mit welcher Flanke die Daten reingeschoben werden. Steht im jeweiligen Datenblatt.