Akkorduhr

Kein Problem mittels editieren.

Ich denke er meint warum meine Antwort vor seiner Frage positioniert ist.

Guten Morgen :slight_smile:
Jaja, das hat MicroBahner auch so festgestellt...
:wink: :wink: :wink:

Dann ist ja alles geklärt, ausser dass ich noch immer nicht weiss, was broken by design in diesem Zusammenhang bedeutet. :wink:

Ich habe den Minutenteil jetzt mal so angelegt (ohne Referenzierungsteil)

case Schritt::WARTEN_MIN:

      if ( stellenAktiv ) {
        // im Einstellmodus wird das vorrücken über den Stelltaster ausgelöst
        posMinuten = altMinuten + Taster.clicked(stellMin); // Bei einfach Klick wird eins, bei Doppelklick 2 addiert
        if ( posMinuten > 11 ) posMinuten = 0;
      } else {
        // im Normalbetrieb die Uhr abfragen
        readDS3231time(minute, hour);
        posMinuten =  ((minute+1) / 5) ;
        MposMinuten = posMinuten -1;
       
      }

      if (altMinuten != posMinuten) {
        // Ereignis: Zeitpunkt für den Bewegungsablauf ist gekommen
      
        debug( "Min: "); displayTime(0, minute, hour);

        debug(F("altMinuten: ")); debug(altMinuten); debug('\t'); debug("posMinuten: "); debug(posMinuten); debug('\n');
        altMinuten = posMinuten;
        
       
        schritt = Schritt::M_ZEIT_FAHRT;
      }
      break;


      case Schritt::M_ZEIT_FAHRT:
      
      if (MposMinuten  == 0) {     
        
          StepMinuten.write(0); 
           
        }
        else {
          StepMinuten.write(Min_Schritte[posMinuten]);        //  Minutenschlitten fährt an die Zeitposition
          debug("Abfahrt: "); debugln((posMinuten * 5) -1);
        }
        
          schritt = Schritt::MIN_KLANG;
       

      break;

    case Schritt::MIN_KLANG:

      if ((posMinuten + MposMinuten) == 0)
           
         {
          digitalWrite(MinBeater, HIGH);           // Minutenschlag
           debug("Minuten: "); debugln(5 * posMinuten);
         }
          
     else {
          schritt = Schritt::MIN_KLANG;
          }
          
          schritt = Schritt::WARTEN_MIN;
      
        break;
       

  }

  if ( schritt == Schritt::WARTEN_MIN ) return altMinuten;
  else return -1;
}

Dieser Teil ist noch nicht richtig. MIN_KLANG sollte 1 Minute später ablaufen. Das habe ich bis jetzt noch nicht hinbekommen.

case Schritt::MIN_KLANG:

      if ((posMinuten + MposMinuten) == 0)
           
         {
          digitalWrite(MinBeater, HIGH);           // Minutenschlag
           debug("Minuten: "); debugln(5 * posMinuten);
         }
          
     else {
          schritt = Schritt::MIN_KLANG;
          }
          
          schritt = Schritt::WARTEN_MIN;
      
        break;
       

Ich habe jetzt noch den Schritt WARTEN_MIN in die if Klammer genommen, so dass dieser Schritt erst ausgeführt wird, wenn diese Verzögerungs-Minute um ist.

  case Schritt::MIN_KLANG:

      if (??)
           
         {
          digitalWrite(MinBeater, HIGH);           // Minutenschlag
           debug("Minuten: "); debugln(5 * posMinuten);
           schritt = Schritt::WARTEN_MIN;
         }
          
      
        break;

Jetzt hänge ich aber fest und bekomme das nicht zum Laufen .
Also: in MIN_KLANG sollte eine Minute verstreichen und dann kann es weiter zum Schritt WARTEN_MIN gehen.
Wie könnte man das formulieren?

Da ich nach ewigem herumprobieren nicht weitergekommen bin, habe ich die Stelle jetzt mal mit

delay(60000):

versehen. Ist sicher nicht die eleganteste Lösung, sollte aber meiner Meinung nach den Ablauf nicht stören da die nächste Zeitabfrage erst 4 Minuten später geschieht.
So sieht demnach der Minutenteil im Moment aus:

Der Schlitten fährt 1 Minute vor dem 5-Minuten-Schritt an die Position, wartet dort bis die Minute um ist und führt dann den Schlag aus.



void printMinSchritt( byte schritt ) {
  static const char *schrittTexte[] = {"REF_CHK_M_SCHLITTEN", "REF_FAHRT_M", "REF_FREI_M", "MIN_GEFUNDEN", "MIN_REFERENZ", "WARTEN_MIN", "M_ZEIT_FAHRT", "MIN_KLANG" };
  debug("Min: "); debugln( schrittTexte[schritt] );
}

// Rückgabe wert ist die aktuelle Position ( wenn im Zustand WARTEN )
// oder -1 wenn in Bewegung
int minutenRing() {
  byte minute, hour;
  static byte altMinuten = 12, posMinuten = 0, MposMinuten = 0;




  enum class Schritt : byte {REF_CHK_M_SCHLITTEN, REF_FAHRT_M, REF_FREI_M, MIN_GEFUNDEN, MIN_REFERENZ, WARTEN_MIN, M_ZEIT_FAHRT, MIN_KLANG};
  static Schritt schritt = Schritt::REF_CHK_M_SCHLITTEN, altSchritt = Schritt::MIN_REFERENZ;
#ifdef DEBUG
  if ( schritt != altSchritt ) {
    // Debugging; Ausgabe wenn Statuswechsel
    printMinSchritt((byte) schritt);
    altSchritt = schritt;
  }
#endif

  // Referenzierung der Minuten unabhängig von der FSM

  if (Taster.pressed(refMinPin)) {                  // Wenn Referenzpunkt erreicht, Nullpunkt setzen.
    StepMinuten.setZero(NULLPOSMINUTEN);          // Nullpunkt etwas hinter den referenzpunkt setzen, um ihn auch bei Schrittverlusten sicher zu erreichen
    StepMinuten.writeSteps(-1300);
    debugln("Referenz Minuten gefunden");

  }
  switch (schritt) {

    case Schritt:: REF_CHK_M_SCHLITTEN:

      if (Taster.state(refMinPin)) {         // Prüfen ob Schlitten in der Lichtschranke steht
        debugln(F("Min LS freifahren"));
        StepMinuten.writeSteps(-5000 );      //aus der LS fahren

        schritt = Schritt::REF_FREI_M;

      } else {

        schritt = Schritt::REF_FAHRT_M;      // Referenzfahrt M-Schlitten
      }
      break;


    case Schritt::REF_FREI_M:

      if (!Taster.released(refMinPin)) {
        delay(500);
        StepMinuten.writeSteps(-3000 );              // etwas über LS Grenze hinausfahren
        debugln(F("Minuten über LS hinausfahren"));
      }
      if (!StepMinuten.moving()) {
        debugln(F("Std frei gefahren"));
        schritt = Schritt::REF_FAHRT_M;
      }
      break;


    case Schritt::REF_FAHRT_M:

      StepMinuten.writeSteps(90000);         // Referenz-Fahrt des M-Schlitten starten.


      schritt = Schritt::MIN_GEFUNDEN;

      break;

    case Schritt::MIN_GEFUNDEN:

      if (Taster.state(refMinPin)) {               // Schlitten hat Referenzposition gefunden

        debugln(F("Min Referenzpunkt  gefunden"));

        schritt = Schritt::MIN_REFERENZ;

        break;

      case    Schritt::MIN_REFERENZ:

        if (Taster.pressed(refMinPin)) {
          StepMinuten.setZero(NULLPOSMINUTEN);
          StepMinuten.writeSteps(-1300);         //  Schlitten Fährt zur Grundposition
          debugln(F("Minuten Referenzierung"));

        }

        schritt = Schritt::WARTEN_MIN;;
      }
      break;


    // Auf UhrZeit warten:

    case Schritt::WARTEN_MIN:

      if ( stellenAktiv ) {
        // im Einstellmodus wird das vorrücken über den Stelltaster ausgelöst
        posMinuten = altMinuten + Taster.clicked(stellMin); // Bei einfach Klick wird eins, bei Doppelklick 2 addiert
        if ( posMinuten > 11 ) posMinuten = 0;
      } else {
        // im Normalbetrieb die Uhr abfragen
        readDS3231time(minute, hour);
        posMinuten =  ((minute + 1) / 5) ;
      

      }

      if (altMinuten != posMinuten) {
        // Ereignis: Zeitpunkt für den Bewegungsablauf ist gekommen

        debug( "Min: "); displayTime(0, minute, hour);

        debug(F("altMinuten: ")); debug(altMinuten); debug('\t'); debug("posMinuten: "); debug(posMinuten); debug('\n');
        altMinuten = posMinuten;


        schritt = Schritt::M_ZEIT_FAHRT;
      }
      break;


    case Schritt::M_ZEIT_FAHRT:

      if (posMinuten  == 0) {

        StepMinuten.write(0);

      }
      else {
        StepMinuten.write(Min_Schritte[posMinuten]);        //  Minutenschlitten fährt an die Zeitposition
        debug("Abfahrt: "); debugln((posMinuten * 5) - 1);
      }

      schritt = Schritt::MIN_KLANG;


      break;

    case Schritt::MIN_KLANG:


      delay(60000);

      {
        digitalWrite(MinBeater, HIGH);           // Minutenschlag
        debug("Minuten: "); debugln(5 * posMinuten);
        schritt = Schritt::WARTEN_MIN;
      }


      break;


  }

  if ( schritt == Schritt::WARTEN_MIN ) return altMinuten;
  else return -1;
}

und wenn ich schon am rumhandieren bin mit delay, kann ich auch noch die Verzögerung der beiden Schläge zueinander, ebenso mit delay regeln.

case Schritt::MIN_KLANG:


      delay(10000);

      {
        digitalWrite(GndBeater, HIGH);           // Minutenschlag
        debug("Minuten: "); debugln(5 * posMinuten);
        debugln("SCHLAG1  ");
        delay(500);
        digitalWrite(MinBeater, HIGH); 
        debugln("SCHLAG2");
        schritt = Schritt::WARTEN_MIN;
      }


      break;

Ich glaube, dass Du schon viel zu sehr in die Details der Programmierung vertiefst, ohne dir den Ablauf deiner Schrittkette im Detail klarzumachen. Du solltest erstmal wieder einen Schritt zurückgehen, und dir das klarmachen - unabhängig von den Programierdetails. Das unterscheidet sich doch schon sehr wesentlich von deiner Farbuhr.
Wenn ich mich recht erinnere, willst Du das Schlagen des Akkords ja auch manuell auslösen können. Deshalb solltest Du das 'Schlagwerk' in einer eigenen 3. Schrittkette realisieren, die Du dann zum gegebenen Zeitpunkten automatisch oder manuell auslösen kannst. Der Ablauf der Schegel ist doch immer gleich, egal wo die jeweiligen Minuten bzw. Stundenschlitten gerade stehen.

Für die Minuten würde ich da folgende Zustände sehen:

  • Der Schlitten steht in Position fürs Schlagen.
    = Auslösung ist manuell möglich.
    = Die Uhrzeit wird gelesen. Geprüft wird auf 4..9..14 ... ... 59 Minuten. Ist eine der Minuten erreicht, startet die Bewegung zu nächsten Position und es geht zum nächsten Schritt. Bei '59 Minuten' könnte gegebenenfalls auch die Stundenkette weitergeschaltet werden.

  • Schlitten ist in Bewegung
    = geprüft wird auf das Erreichen der Zielposition, dann zum nächsten Schritt

  • Schlitten steht auf neuer Position, die entsprechende Uhrzeit ist aber noch nicht erreicht
    = Die Uhrzeit wird gelesen. geprüft wird auf 5..10..15 .. 00 Minuten
    Wenn die Minuten erreicht sind wird das Schlagwerk ausgelöst und es geht wieder zum Zustand 'Position für Schlagen'

Mehr sollte da gar nicht notwendig sein. Wie gesagt, das Schlagwerk in einer ganz eigenen Schrittkette. Die Verzögerungen zwischen den Schritten da dann wie gehabt mit einem Timer.

Spiel das alles mal theoretisch durch, ob da evtl. noch was fehlt. Erst wenn das 'im Kopf' oder auf einem Blatt Papier sauber läuft, machst Du dich ans Programmieren.

Und schreib beim Programmieren dann bei den jeweilgen Zuständen auch im Kommentar hin, was Du dir im theoretischen Ablauf gedacht hast.

P.S. Ich frag mich auch ob es bei der Akkorduhr überhaupt Sinn macht, für die Stunden eine eigene Schrittkette zu haben. Im Gegensatz zur Farbuhr gibt's da doch keine speziellen Abläufe ( abgesehen von der Referenzierung). Wenn die Minuten auf '59' springen muss sich der Stundenschlitten auch zur nächsten Position bewegen. Das war's dann aber auch schon. Das sollte mit im Minutenablauf integrierbar sein.

Ja, das ist richtig. Es könnte auch alles durch den Minutenteil gesteuert werden.

Ich habe jetzt mal den Schlagwerk-Part skizziert. Ist das etwa so wie du es vorschlägst?

Schlagwerk Zeitpunkt des Schlagens (Schlagabfolge alle 5 Minuten oder durch manuelles auslösen)


Grundschlag  (GndBeater 0,5 Sekunden high)  
Pause 0,5 Sekunden
Stundenschlag  (StdBeater 0,5 Sekunden high) 

Pause 2 Sekunden 

Grundschlag  (GndBeater 0,5 Sekunden high) 
Pause 0,5 Sekunden
MinutenSchlag (MinBeater 0,5 Sekunden high)

Genau so! :grinning:

Ja, sollte dann eine recht einfache Schrittkette sein, die einfach nur zeitgesteuert durchläuft und dann wieder im Grundschritt stehen bleibt. Da könnte man ein Flag abfragen, und wenn das gesetzt ist, läuft die Kette los ( und setzt das Flag wieder zurück ). Das Flag kann man dann in der Minutenschrittkette uhrzeitgesteuert oder eben auch manuell durch einen Button setzen.
Oder man definiert das ganze als Klasse, und der Start wird durch eine entsprechende Methode ausgelöst :wink:

Welche Position fährt er eigentlich bei der Referenzierung an? Ist das dann bereits eine der 'Schlagpositionen'? Dann würde ich auch die Referenzierung mit einer getrennten FSM realisieren, die im Setup ausgelöst wird und immer dann, wenn diese Schlagposition angefahren werden soll. Damit hast Du dann auch die Referenzierung im Betrieb.

ja, doch das wäre dann eine der Schlagpositionen.

Und wie könnte ich die Schrittkette zeitsteuern, mit den Sekunden von meinem Zeitmodul?
Geht das überhaupt für z.B 0,5 Sekunden?

Schrittkette mit Flag etwas in der Art?




bool flag = false;


while (!flag)

ZEITSCHLAG        Schrittkette für den Zeitschlag

case Schritt::MIN_SCHLAG:

Grundschlag  (GndBeater 0,5 Sekunden high)   // Grundschlag wird ausgeführt
Pause 0,5 Sekunden
MinutenSchlag (MinBeater 0,5 Sekunden high)  // Minutenschlag wird ausgeführt
Pause 2 Sekunden                             // 2 Sekunden Pause
schritt = Schritt::STD_SCHLAG;
      
case Schritt::STD_SCHLAG:

Grundschlag  (GndBeater 0,5 Sekunden high)   // Grundschlag wird ausgeführt
Pause 0,5 Sekunden
Stundenschlag  (StdBeater 0,5 Sekunden high) // Stundenschlag wird ausgeführt

flag = true;

break;

Da kannst Du den MoToTImer verwenden. Und das Ganze ist dann wieder eine normale Schrittkette, mit switch, die im loop aufgerufen wird, wie die Minutenkette auch. Der 'Ruhezustand' ist dann der erste case der Schrittkette, und wenn das Flag gesetzt ist wird zum nächsten Zustand geschaltet. Für jede Wartezeit brauchst Du dann einen Schritt. Etwa so:

MoToTimer schlagTimer;
bool schlagwerkStarten = false;


switch ( schlagwerkZustand ) {
  case WAIT:
    // Ruhezustand, warten bis Flag gesetzt
    if ( schlagwerkStarten ) {
      digitalWrite( GndBeater, HIGH );
      schlagTimer.setTime( 500 );
      schlagWerkZustand = GRUND_SCHLAG_MIN
    }
    break;
  case SchlagWerk::GRUND_SCHLAG_MIN:
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, LOW );
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::WAIT_MIN;
    }
    break;
  case SchlagWerk::WAIT_MIN:
    if schlagTimer.expired() ) {
      digitalWrite( MinBeater, HIGH );
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::MIN_SCHLAG;
    }
    break;
  case SchlagWerk::MIN_SCHLAG:
    if schlagTimer.expired() ) {
      digitalWrite( MinBeater, LOW );
      schlagTimer.setTime( 2000 );
      schlagWerkZustand = SchlagWerk::PAUSE_MIN_STD;
    }
    break;
  case WAIT:
    // Ruhezustand, warten bis Flag gesetzt
    if ( schlagTimer.expired() ) {
      digitalWrite( GndBeater, HIGH );
      schlagTimer.setTime( 500 );
      schlagWerkZustand = GRUND_SCHLAG_Std
    }
    break;
  case SchlagWerk::GRUND_SCHLAG_STD:
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, LOW );
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::WAIT_STD;
    }
    break;
    
  // usw ... bis zum letzten Schlag und dann das Flag rücksetzen und nach WAIT

}

( jetzt mal einfach so runtergeschrieben, Tippfehler sind möglich :wink: )

Super, danke! werde mich mal damit etwas beschäftigen. :smiley:

Ich habs jetzt mal noch ergänzt. Die Flag zwischen Minuten und Stunden hab ich rausgenommen, da das ganze Schlagwerk immer vollständig abläuft. Es kommen immer alle 5 Minuten sowohl der Minutenteil, als auch der Stundenteil zum klingen, also nicht wie bei den Kirchenglocken nur zur vollen Stunde.

MoToTimer schlagTimer;
bool schlagwerkStarten = false;


switch ( schlagwerkZustand ) {
  case WAIT:
    // Ruhezustand, warten bis Flag gesetzt
    if ( schlagwerkStarten ) {
      digitalWrite( GndBeater, HIGH );      // Grundschlag-Minuten Anfang
      schlagTimer.setTime( 500 );
      schlagWerkZustand = GRUND_SCHLAG_MIN
    }
    break;
  case SchlagWerk::GRUND_SCHLAG_MIN:
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, LOW );        // Grundschlag-Minuten Ende
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::WAIT_MIN;
    }
    break;
  case SchlagWerk::WAIT_MIN:                
    if schlagTimer.expired() ) {
      digitalWrite( MinBeater, HIGH );         // Minutenschlag Anfang
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::MIN_SCHLAG;
    }
    break;
  case SchlagWerk::MIN_SCHLAG:
    if schlagTimer.expired() ) {
      digitalWrite( MinBeater, LOW );                // Minutenschlag Ende
      schlagTimer.setTime( 2000 );                   // 2 Sekunden Pause
      schlagWerkZustand = SchlagWerk::PAUSE_MIN_STD;
    }
    break;
  case PAUSE_MIN_STD:
    
    if ( schlagTimer.expired() ) {
      digitalWrite( GndBeater, HIGH );       // Grundschlag-Stunden Anfang
      schlagTimer.setTime( 500 );
      schlagWerkZustand = GRUND_SCHLAG_Std
    }
    break;
  case SchlagWerk::GRUND_SCHLAG_STD:
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, LOW );        // Grundschlag-Stunden Ende
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::WAIT_STD;
    }
    break;

    case SchlagWerk::WAIT_STD:                
    if schlagTimer.expired() ) {
      digitalWrite( MinBeater, HIGH );         // Stundenschlag Anfang
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::STD_SCHLAG;
    }
    break;
    
    case SchlagWerk::STD_SCHLAG:
    if schlagTimer.expired() ) {
      digitalWrite( MinBeater, LOW );                // Stundenschlag Ende
       ( !schlagwerkZustand )             
      schlagWerkZustand = SchlagWerk::WAIT;
    }
    break;
    
    
 

}

Da waren noch ein paar Fehler drinnen in dem letzten Sketch.
Ebenso hab ich den Sketch jetzt mal so umgewandelt, dass ich später die Zeiten noch frei anpassen kann, da die 0,5 respektive die 2 Sekunden nur mal eine Schätzung sind und ich die dann evtl. noch anpassen muss.
Die Grund/Minutenschläge und die Grund/Stundenschläge laufen ja aber parallel.

MoToTimer schlagTimer;
bool schlagwerkStarten = false;


switch ( schlagwerkZustand ) {
  case WAIT:
    // Ruhezustand, warten bis Flag gesetzt
    if ( schlagwerkStarten ) {
      digitalWrite( GndBeater, HIGH );         // Grundschlag-Minuten Anfang
      digitalWrite( MinBeater, HIGH );         // Minutenschlag Anfang
      schlagTimer.setTime( 500 );
      
      schlagWerkZustand = GRUND_SCHLAG_MIN
    }
    break;
  case SchlagWerk::GRUND_SCHLAG_MIN:
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, LOW );                // Grundschlag-Minuten Ende
      digitalWrite( MinBeater, LOW );                // Minutenschlag Ende
      schlagTimer.setTime( 2000 );                   // 2 Sekunden Pause
      schlagWerkZustand = SchlagWerk::WAIT_MIN;
    }
    break;
  case SchlagWerk::WAIT_MIN:                
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, HIGH );             // Grundschlag-Stunden Anfang
      digitalWrite( StdBeater, HIGH );             // Stundenschlag Anfang
      schlagTimer.setTime( 500 );
      schlagWerkZustand = SchlagWerk::STD_SCHLAG;
    }
    break;
  case SchlagWerk::STD_SCHLAG:
    if schlagTimer.expired() ) {
      digitalWrite( GndBeater, LOW );                // Grundschlag-Stunden Ende
      digitalWrite( StdBeater, LOW );                // Minutenschlag Ende
     
      schlagWerkZustand = SchlagWerk::WAIT;
    }
    break;
  
}