Wie programmiert man einen Zähler?

Hallo an alle Profis,

von vielen Jahren habe ich (aus der Not), einen Zähler für Drehgeber gebaut, der bis heute funktioniert. :wink:

  • Ich kann leider nicht programmieren, bin 80 Jahre “jung” und als Test hätte ich gern die Schaltung mit Arduino Nano oder Uno aufgebaut.
    Leider habe ich keinen Ahnung, wie ich beginnen sollte. Habe schon viele Anleitungen gelesen, aber noch nie gefunden, wie man digitale Logik in eine Sprache umsetzt.

  • Zähler.pdf (21,5 KB)

  • Es werden zwei JK Flipflops, drei UND und ein ODER verwendet. Habe versucht die Funktion zu beschreiben, leider kenne ich NUR die Folgematrix.

    • Vorsorglich sende ich eine Anleitung, wie man sie auswertet.

    Folgematrix.pdf (294,3 KB)

  • Ich Bitte um Hilfe

    bm-magic

Die Frage ist, was Du haben willst:
Möchtest Du den Drehgeber als Eingabegerät und auch die LED H1 - H3 als Ausgabe ebenso wie die das 5x7 Segmentdisplay?

Wenn Du nur den Drehgeber als Eingang benutzt -> In deinem Plan sind da 15V, geht der auch mit 5V? dann ist das vermutlich relativ simpel zu lösen.

Die jungen Leute können leider nicht mit Flipflops und UND-Gliedern umgehen, weil programmieren tausendmal einfacher ist. Die sind zum Teil übrigens auch schon über siebzig.
Einen Zähler definiert man beim Arduino-Programmieren so:
unsigned long zaehler;

Und zählen tut er so:
zaehler++;

Diese Antwort wird dir nicht viel helfen. Zeigt aber, dass beim Programmieren die elektronische Variante nicht sehr hilfreich ist, sondern man komplett anders anfängt.
(Vergiss deine "Folgematrix")

Dem Rat würde ich mich nicht anschließen wollen, denn da drin stecken alle Zutaten für einen Zustandsautomaten:
Wie ist der Systemzustand, was kommt rein, wie ist der neue Zustand und was geht raus.

Das ist richtig, bei FlipFlops hört die Umsetzung auf. Ich selbst hatte in den 60er Jahren ganz ähnliche Probleme, wie man nämllich 2 Zähler zusammenzählt. Daß ein Dezimalzähler aus 4 irgendwie rückgekoppelten Flipflops besteht, das hatte ich damals schon, aber wie ich dann z.B. eine 4 im einen Zähler zun Inhalt eines anderen Zählers addieren sollte, dazu fielen mir nur 4 Impulse ein, die ich aus dem ersten Zähler herauskitzeln mußte. Auf die Schaltungen von Halb- oder Voll-Addierern bin ich erst gekommen, als es die als TTL IC zu kaufen und auch mit Schaltbild gab. Und wenn man die auch noch verstanden hat, dann muß man sich gleich wieder davon verabschieden, weil es das alles und noch viel mehr schon in Mikroprozessoren und in den "richtigen" Computern fertig eingebaut gibt.

Im Informatik-Studium war ich zunächst der große Crack, weil ich kombinatorische (nur Gatter) und sequentielle (mit FlipFlops) Netzwerke schon längst kannte, und nur die Theorie dazu neu war. Ählich hast Du mit der Folgematrix so etwas wie einen endlichen Automaten (state machine) erfunden. Damit gehören wir wohl zu den verkannten Genies, die vom Fortschritt gnadenlos überrollt wurden :frowning:

Bei Deinem jetzugen Problem hilft Dir Dein elektronisches Wissen immerhin, die Verarbeitung der beiden Eingangssignale (z.B. als Takt und Richtung) in einem Programm nachzuverfolgen. Ein möglicher Ansatz besteht ja aus einer (Folge)Matrix, die 0, +1 oder -1 ausgibt, die dann zum Positiionszähler addiert werden nüssen. Den Zähler und die Addition gibt es dann schon, im Code, etwa als
Position += Matrix[Takt, Richtung];

Hab ich mal gemacht.
Die Logik aus Deinem Zählerblatt verrät, dass Du hardwareseitig

  • eine Taste hast, die den gesamten Vorgang startet (reset)
  • einen Geber hast, der den Zähler freigibt (1)
  • einen Geber hast, der Impulse abgibt (A)
  • der Geber (1) erneut ausgelöst stoppt den Zählvorgang

Zusätzlich gibt es noch einen Geberausgang (B) - möglicherweise invertiert - für eine weitere visuelle Darstellung (H3)
Letztere Kombination läuft extern ohne Controller, ich habe mir erlaubt die mit aufzunehmen :slight_smile:

In der Variable zaehler ist die Anzahl der gemessenen Flanken LOW->HIGH vom Geber (A) versteckt.

Für eine weitere Darstellung, z.B. auf einem Display, kann der Inhalt jederzeit verwendet werden. Aktuell erfolgt die Ausgabe auf dem Bildschirm nach Abschluß des Zählvorgang.
Mit jeder Folge reset -> start (1) wird der Zähler gelöscht und neu beschrieben.

Da ich nicht weiss, wie schnell Deine Flankenfolge ist, habe ich das erstmal als Polling gemacht. An der Stelle kann aber auch jederzeit mit einer Interruptroutine gearbeitet werden, die hardwareseitig eingestellt werden kann.

// Forensketch
// https://forum.arduino.cc/

// Eingänge
constexpr uint8_t taktPin {2};   // ORIGINAL: A
constexpr uint8_t ctlPin {3};    // ORIGINAL: B

struct PIN
{
  const uint8_t pin;             // Pin auf dem gelesen wird
  const bool choice;             // Zustand bei dem ausgelöst werden soll
  bool lastState;                // Merker für letzten gelesenen Zustand
};

PIN resetPin {4, HIGH, HIGH};    // ORIGINAL ?
PIN startPin {5, HIGH, HIGH};    // ORIGINAL: 1

// Ausgänge Signalisierung - Bezeichnungen entsprechen Original
constexpr uint8_t H1 {6};
constexpr uint8_t H2 {7};
constexpr uint8_t H3 {8};
constexpr uint8_t H4 {9};

constexpr bool _LEDOFF {LOW};
constexpr bool _LEDON {!_LEDOFF};

uint32_t zaehler;                // Zum Impulse zählen

// Zustandsliste für Schrittkette
enum class state : uint8_t {waitFree, waitStart, zaehlen, end};

// Startzustand
state myState = state::waitFree;

void setup()
{
  Serial.begin(115200);
  delay(300);
  Serial.println(F("\r\nStart...\r\n"));
  pinInit();
}

void loop()
{
  schrittKette();
  bControl();
}

void bControl()
{
  digitalWrite(H3, digitalRead(ctlPin));
}

void schrittKette()
{
  switch (myState)
  {
    case state::waitFree:
      if (chkFlanke(resetPin) == true)
      {
        digitalWrite(H1, _LEDON);                           // anzeigen
        myState = state::waitStart;                         // nächster Schritt
      }

      break;

    case state::waitStart:
      if (chkFlanke(startPin) == true)
      {
        zaehler = 0;                                        // init
        digitalWrite(H1, _LEDOFF);                          // anzeigen
        digitalWrite(H2, _LEDON);
        myState = state::zaehlen;                           // nächster Schritt
      }

      break;

    case state::zaehlen:
      if (digitalRead(taktPin) != digitalRead(H4))          // Eingang hat sich geändert
      {
        digitalWrite(H4, !digitalRead(H4));                 // Ausgang umschalten

        if (digitalRead(H4) == _LEDON)                      // steigende Flanke?
        { zaehler++; }                                      // erklärt sich von selbst :-)
      }

      if (chkFlanke(startPin) == true)                      // Neuer Auslöser beendet zaehlen
      {
        myState = state::end;
      }

      break;

    case state::end:
      // ausgabe:
      Serial.println(zaehler);
      digitalWrite(H4, _LEDOFF);
      digitalWrite(H2, _LEDOFF);
      myState = state::waitFree;
      break;
  }
}

bool chkFlanke (PIN &chk)                       // gibt true zurück, wenn Flanke erkannt wird
{
  bool myFlanke = false;                        // default: keine Flanke erkannt

  if (digitalRead(chk.pin) != chk.choice)       // Pin hat nicht geforderten Zustand
  {
    chk.lastState = !chk.choice;                // merken
  }
  else if (chk.lastState != chk.choice)         // Pin hat geforderten Zustand und vorher nicht -> Flanke erkannt
  {
    chk.lastState = chk.choice;                 // merken
    myFlanke = true;                            // Flanke erkannt
  }

  return myFlanke;                              // Erkennung zurückgeben
}

void pinInit()
{
  pinMode(resetPin.pin, INPUT);
  pinMode(startPin.pin, INPUT);
  pinMode(taktPin, INPUT);
  pinMode(ctlPin, INPUT);
  pinMode(H1, OUTPUT);
  pinMode(H2, OUTPUT);
  pinMode(H3, OUTPUT);
  pinMode(H4, OUTPUT);
  digitalWrite(H1, _LEDOFF);
  digitalWrite(H2, _LEDOFF);
  digitalWrite(H3, _LEDOFF);
  digitalWrite(H4, _LEDOFF);
}

@my_xy_projekt,

Einen RIESEN Dank für Deine Lösung. Ich versuche den ganzen Nachmittag, mich im Forum anzumelden, ohne Erfolg :-((

  • Bekomme immer die Meldung und nun .
    Dabei habe ich das Passwort erst Gestern erneuert und bin sicher, das es stimmt.
  • Wenn ich Heute auf "Passwort vergessen" drücke, bekomme ich die Meldung <es wurde eine Mail ...versendet...>
    aber es kommt keine Mail. Habe es im "Spam und Junk Ordner" versucht von Arduino kommt nichts!
  • Postfach ist IO. Habe in der Zwischenzeit mehrere Mail erhalten.
    ---war nur zum Aufklären, warum ich nicht früher geantwortet habe---

Du hast die Aufgabe PERFEKT verstanden. Die Schaltung habe ich gemacht um zu Testen, ob die Wellen Übersetzung korrekt ist.
Beispiel: Ein Drehgeber gibt auf auf der Spur 1 einen (Phasen) Impuls und den Spuren A (und invertiert auf B) 100 Impulse je Umdrehung.

  • Es kann passieren, das z.B. durch Verschmutzung einige Schlitze in der Scheibe einige Takte nicht durchlassen oder der Zahnriemen
    defekt ist und der Drehgeber durchrutscht.
  • Die Spannung: die CMOS Logik arbeitet mit 15 V, der Drehgeber mit 24 V. Ich habe die Schaltung "nur Symbolisch" direkt verbunden.
    Im Wirklichkeit sind die Spannungen galvanisch über Optokoppler Getrennt.

Nun zu Deinem schönem "und überraschend kurzem" und gut dokumentierten Code.
Der ich leider noch nicht verstehe...aber...ich verspreche, das ich mich bemühen werde die Bedeutung "irgendwo" nachzulesen um es zu Verstehen.

  • Du hasst Fragezeichen beim "Reset" gemacht. Ich bin nicht sicher, ob man ihn bei Deinem Lösung braucht.
    In CMOS Logik ist es nötig, nach dem Einschalten einen Reset zu machen um den Flipflop in den Grundzustand zu bringen.
  • Was bedeuten die Zahlen {x}? Sind es die Nummern von Arduino UNO Leisten, oder die Pin von dem ATMega328p?

Wenn ich Zugang zum Forum bekomme, werde ich mich noch einmal bedanken.

Sehr Nette Grüße

Bohu

Hat auch nen Moment gedauert :laughing:
Ich musste mich erstmal in die Logik reinfinden. Das letzte Mal, das ich ein GATTER in der Hand hatte, war das ein DIP14 D100 aus Franfurt Oder - oder so...

Der Text hat das ganz gut ergänzt.

Jetzt zu den Fragen:
Das ? hinter Reset ist nur die Frage, wie das ausgelöst wird. Ich hab das als Flanke im Code von LOW nach HIGH.
Da kein Eingabegerät, also Taster oder zusätzlicher Pin von einer Bedieneinheit oder ggfls. aus dem Geber benannt war, hab ich da einfach nur ein ? gesetzt und mich auf meine Intention verlassen.

Für (1) und (A), (B) sind das die Abgänge des Gebers in der Zeichnung.

Das mit den OK ist gut, wenn das welche sind die auch 5V Logik können, ist das perfekt.

Das mit dem Taster fürs Resetten würde ich sogar beibehalten.
Muss man nur sehen, dass das elektrisch passend gemacht wird.

Zum Code: Wenn Du Fragen hast, frag. Wir haben alle mal bei 0 angefangen. Ich auch :slight_smile:
Da meine Rangehensweise auch etwas anders ist, komm ich mal schneller ans Ziel (so wie hier) und manchmal stehe ich mir selbst im Weg...

Ich hab eine Downloadempfehlung für Dich: leicht verständliches Grundlagen-pdf. Da steht nicht alles drin was ich verwendet habe, aber ich empfehle bis zur Hälfte zu lesen, damit Du die grundlegenden Funktionen einmal gesehen hast. Nicht auswendig lernen! Das kommt automatisch, wenn Du weißt wo Du nachschlagen kannst.

Na dann - viel Spaß beim lesen.

Edit:

Ah - Das sind die Pin-Nummern basierend auf einem UNO/NANO.
Achte drauf, solltest Du noch in der Beschaffungsphase sein: Es gibt mittlerweile einen UNO R4, der hat keine 5V Logik! Die Bezeichnung ist etwas unglücklich gewählt.

@my_xy_projekt, nun habe ich wieder Zugang ins Forum ;-)
-Ich wollte Deine Lösung testen, aber kein von meinen Nano (Klonen) funktionierte.
Nach langem Suchen stellte ich fest, das es mit der Option (Old_Bootloader) funktioniert.

  • Leider sehe ich kein Ergebnis :frowning:
    ( In dem Monitor sehe ich nur Start…) Mehr nicht. Egal, welche Tasten ich drücke,…
    … keine Wirkung!)

Aus Verzweiflung, habe ich nun mein Zustand mit Fritzing (genau so mein erstes Projekt)
dokumentiert. Habe ich etwas falsch umgesetzt?

Kannst Du mal ein Foto von Deinem Aufbau machen? (Wenn möglich von oben, so dass man alle Kabel sieht)
In der Zwischenzeit schau ich mal, ob ich mich irgendwo vertan habe...

// EDIT: AAAHHH - In Deinen Bildern ist der Switch mit einem R nach GND und schaltet nach +5V.
Tausche mal bitte die Schalter und die Widerstände (S1-S4 / R1-R4)

@my_xy_projekt , vielen Dank für die schnelle Antwort. Ich habe etwas Zeit für das Foto gebraucht. In der Wirklichkeit habe ich einen Rechteck Generator (1,000 Hz) am D3 und zur Zeit den Taster (+ 5V) mit Pulldown Widerstand (10 KHz) am D5. Die übrigen Eingänge habe ich mit Drahtbrücken simuliert.

  • Ich habe Deinen Ratschlag nicht verstanden. Soll ich mit (GND) schalten? Und brauche ich überhaupt die externe Widerstände?

  • Danke im Voraus

Moijn,
ich seh zwar an Pos 24 den roten Draht zum Taster, aber zeig mir mal, wie das gesamte Breadboard aussieht.

// EDIT: Ich hab auf der Bahnfahrt mal schnell in den Ursprünglichen Code geschaut. Evtl. ist mir da ein Fehler in der Schrittkette unterlaufen.

Muss ich mir später ansehen, wird zu morgen.

@my_xy_projekt , DANKE für Deine Hilfe. Ich habe versucht eine “Lehrbuch Schaltung” zu erstellen. Habe aber nichts geändert. Meine Frage, braucht man bei den Eingängen externe Widerstände?

Ein klares Jein.
Wenn Du den internen PulUp aktivierst (INPUT_PULLUP) und den Taster nach GND schaltest (LOW == gedrückt) brauchst Du keinen.
Wenn Du Den Taster gegen + schaltest (INPUT) (HIGH==gedrückt) brauchst Du Widerstände gegen GND.
Damit ist sicher gestellt, dass immer ein definiertes Potential am Eingang anliegt.

Gruß Tommy

@Tommy56 , vielen Dank. Ich möchte mit der positive Flanke schalten, also sind die (pulldown) Widerstände richtig. “Habe wieder etwas gelernt”. ;-)

Wenn Du das möchtest, dann musst Du sie nutzen.
Mir leuchtet nur nicht ein, warum man diesen Zusatzaufwand möchten möchte.
Aber Deine Entscheidung.

Gruß Tommy

Besten Dank!
Die Schienen für die Stromzufuhr ist durchgehend. Damit schau ich mir meinen Code nochmal an.

Der Ausgangszustand ist eine Schaltung, die etwas älter ist und kompatibel bleiben soll. Von daher sehe ich die Notwendigkeit nicht infrage gestellt.

Ok, jeder wie er mag, besonders bei so unkritischen Sachen, wie Taster.
Manchmal kann "Kompatibilität" auch hemmend sein. Aber (mir) egal.

Gruß Tommy

Eben nicht. #6 & #7

Und #10 war schon eine Idee, aber davon bin ich gerade abgerückt, als mir auffiel evtl. die Schrittkette falsch eingesetzt zu haben.

Ok, ich war von Bordaufbau ausgegangen. Aber auch in #6/7 wird nicht konkret auf die Herkunft der Eingangssignale eingegangen. Seis drum, ich bestehe natürlich nicht auf INPUT_PULLUP :grin:

Gruß Tommy