Akkorduhr

Hier eine Skizze der Anlage. Darauf ist der Grundtonschläger und der Stundenschläger zu sehen. Der Minutenschläger mit seiner eigenen Spindel würde sich auf dem Bild hinter den Klangstäben befinden.

agmue:
"Beim Gongschlag ist es ..." tönte es früher aus dem Radio, man ist also eine gewisse Genauigkeit bei akustischen Zeitsignalen gewohnt.

Meune Uhr ist ja höchstens 5 Minuten genau :slight_smile:

agmue:
Du könntest daher den Tonerzeuger (Klöppel?) schon etwas früher positionieren, um den Schlag dann präzise auszuführen. Die Tabelle (siehe #15) könnte also um die notwendige Verfahrzeit ergänzt werden

ja, das wäre möglich

Draht:
Alles zusammen ergäbe ein C-Dur Akkord.

So macht das Sinn.

Die Interpretation der Uhrzeit dürfte dann allerdings nur musikalisch Bewanderten vorbehalten sein, wohl eher eine Minderheit.

Draht:
Der Minutenschläger mit seiner eigenen Spindel würde sich auf dem Bild hinter den Klangstäben befinden.

Es wären also 12 Klangstäbe? Nutzt der Minutenschläger dann weitere 12 Klangstäbe? Oder nutzen Minuten und Stunden die selben Klangstäbe?

agmue:
Ich frage mich gerade, ob ich so eine Uhr, die alle 5 Minuten Lärm macht, haben möchte. Standuhren und Regulatoren waren wegen ihres Stundenschlages teilweise gefürchtet. Zumindest sollte eine Ruhezeit aktivierbar sein. Schöne Aufgaben für eine Applikation1) (nicht Komplikation2) wie bei anderen Uhren).


Anm.:

  1. Gemeint ist eine App auf einem Smartphone.

  2. Zusatzfunktionen bei mechanischen Uhren wie z. B. Stoppfunktion.

Ja, dies wird evtl. nicht eine Uhr für die ruihige Stube, oder das Schlafzimmer. :slight_smile: Allerdings ist das eher ein sehr leiser und feiner Klang. Ich habe mir aber auch überlegt , ob ich eine Funktion einbaue 1. wenn ich den Klang nochmal hören muss zB. weil ich ihn nicht bestimmen konnte. und/oder 2. Das die Uhr nur tönt, wenn ich zB. meine Hand vor einem Sensor bewege.

agmue:
So macht das Sinn.

Die Interpretation der Uhrzeit dürfte dann allerdings nur musikalisch Bewanderten vorbehalten sein, wohl eher eine Minderheit.
Es wären also 12 Klangstäbe? Nutzt der Minutenschläger dann weitere 12 Klangstäbe? Oder nutzen Minuten und Stunden die selben Klangstäbe?

agmue:
So macht das Sinn.

Die Interpretation der Uhrzeit dürfte dann allerdings nur musikalisch Bewanderten vorbehalten sein, wohl eher eine Minderheit.
Es wären also 12 Klangstäbe? Nutzt der Minutenschläger dann weitere 12 Klangstäbe? Oder nutzen Minuten und Stunden die selben Klangstäbe?

Das wird wohl so sein, oder für solche die interessiert wären ihr Gehör etwas zu trainieren. Ja genau 12 Klangstäbe. Das ist eine Oktav und eine Quinte. Stunden und Minuten nutzen die gleichen Stäbe. Einer von hinten und einer von vorne.

Draht:
Einer von hinten und einer von vorne.

Dann darf ein Klangstab wohl nicht gleichzeitig von Minuten und Stunden angeschlagen werden. Also Grundton - Zeit - Stundenton - Zeit - Minutenton. Dann müssen die Schläger aber doch schon in Position sein, sonst gibt es ein zeitliches Durcheinander.

MicroBahner:
Vielleicht zeigt Draht ja auch mal wieder ein Bild, wie das Ganze aussehen soll. Ist nur der Klang das Zeitsignal, oder gibt es auch eine mechanische Anzeige - d.h. die Klöppelposition zeigt die Uhrzeit? Das hatte ich bisher nicht so gesehen, da der Klöppel ja hin-und herfährt. Deshalb würde ich nämlich den Klöppel nach dem Abspielen des Akkords gleich in die nächste Position fahren, um dort pünktlich starten zu können.

Ob ich so ein Uhr, die alle 5 Min. Musik macht zu Hause haben möchte ... :roll_eyes:

Nein, es gibt keine mechanische Anzeige. Ja, das wäre gut, wenn der Klöppel schon früher an seine Position fährt. Falls ich aber die Variante weiter verfolge, wo man den Klang selbst auslösen kann, dann wäre das verfrühte anfahren der Position zwar doch nicht so ideal.

agmue:
Dann darf ein Klangstab wohl nicht gleichzeitig von Minuten und Stunden angeschlagen werden. Also Grundton - Zeit - Stundenton - Zeit - Minutenton. Dann müssen die Schläger aber doch schon in Position sein, sonst gibt es ein zeitliches Durcheinander.

agmue:
Dann darf ein Klangstab wohl nicht gleichzeitig von Minuten und Stunden angeschlagen werden. Also Grundton - Zeit - Stundenton - Zeit - Minutenton. Dann müssen die Schläger aber doch schon in Position sein, sonst gibt es ein zeitliches Durcheinander.

Bin nicht sicher, ob ich dich richtig verstehe. Du meinst, weil Die Schlitten unterschiedlich lange brauchen um an ihre Position zu kommen?

Ich denke ich werde es so mache, dass im normal Zustand der Klang nur über einen Sensor ausgelöst werdenn soll. in einem 2. Zustand gibt es einen klang alle 5 Minuten. ZB. für eine Ausstellung oder wenn die Uhr etwa in einem Gang steht.

Wenn Stunden und Minuten auf den gleichen Klangstäben gespielt werden und die Position der Klöppel nicht für die Uhrzeit genutzt wird, warum brauchst Du dann überhaupt 2 Klöppel?
Wenn ich dich richtig verstehe, unterscheiden sich Minuten und Stunden nur durch unterschiedliche Klänge. Das kann dann doch auch ein Klöppel erledigen.

MicroBahner:
Wenn Stunden und Minuten auf den gleichen Klangstäben gespielt werden und die Position der Klöppel nicht für die Uhrzeit genutzt wird, warum brauchst Du dann überhaupt 2 Klöppel?
Wenn ich dich richtig verstehe, unterscheiden sich Minuten und Stunden nur durch unterschiedliche Klänge. Das kann dann doch auch ein Klöppel erledigen.

Die Uhrzeit wird durch 2 Klänge (Klang = 2 Töne Gleichzeitig) dargestellt. Einer der 2 Klangtöne ist immer der Grundton und hat einen eigenen Klöppel. der 2. Stunden angebenden Ton ist irgendwo bei einem Klangstab sagen wir zB. beim 1. von links. . Diese 2 zusammen geben den ersten Stundenklang. nach einer kurzen Pause, sagen wir 1/2 Sekunde, ertönt dann der Minutenklang. Dieser besteht wieder aus dem Grundton und dem Minutenton. Der Minutenton befindet sich zB. beim 5. Klangstab von links. Das würde zu lange dauern. bis der Stundenklöppel da angefahren käme und diesen Job auch übernehmen könnte. Ich muss noch dazu sagen, dass die Distanz von dem Klangstab ganz links bis zum Klangstab ganz rechts 50 cm beträgt.

Draht:
Meune Uhr ist ja höchstens 5 Minuten genau :slight_smile:

Bei dieser Version wolltest Du doch die genaue Zeit aus dem Internet holen ::slight_smile:

Draht:
Du meinst, weil Die Schlitten unterschiedlich lange brauchen um an ihre Position zu kommen?

Nein, aber es gibt eine Uhrzeit, da stehen Stunden- und Minutenklöppel beim selben Klangstab. Würden sie gleichzeitig von hinten und vorne zuschlagen, würde sich kein schöner Ton ergeben.

Draht:
nach einer kurzen Pause, sagen wir 1/2 Sekunde, ertönt dann der Minutenklang.

Das ist es, worauf ich hinweisen wollte. Der Ablauf wird dadurch schon etwas komplizierter.

agmue:
Bei dieser Version wolltest Du doch die genaue Zeit aus dem Internet holen ::slight_smile:

Das habe ich eigentlich bei keiner Version gewollt. :confused:

agmue:
Nein, aber es gibt eine Uhrzeit, da stehen Stunden- und Minutenklöppel beim selben Klangstab. Würden sie gleichzeitig von hinten und vorne zuschlagen, würde sich kein schöner Ton ergeben.

Da hast du natürlich recht. Da aber Stundenklang und Minutenklang nicht genau gleichzeitig erklingen ergibt sich das Problem ja eigentlich nicht. (Mit einer einzigen Ausnahme, wenn der Grundton mit dem Stunden und Minuten Ton zusammenfallen. Das wäre (angenommen der Grundton stellt die Ziffer 1 dar, nur um 5 nach 1.
Diese Version müsste dann arpeggiert (Töne erklingen nacheinander) sein. Dies könnte aber der Grundton-Klöppel erledigen 3 Grundtöne hintereinander. Oder, 2. Möglichkeit es es gibt nur einen einzelnen Schlag auf den Grundton.

agmue:
Das ist es, worauf ich hinweisen wollte. Der Ablauf wird dadurch schon etwas komplizierter.

Der Schläger vom 2. Klang (Minutenklang) wird einfach immer und um die gleichen (z.B. 0.5) Sekunden leicht verzögert ausgelöst. Das sollte doch nicht zu kompliziert sein.

Draht:
Das sollte doch nicht zu kompliziert sein.

Nein, aber man muß halt solche Kleinigkeiten rechtzeitig in der Planung berücksichtigen. Ein gutes Konzept ist schon das halbe Programm ;D

agmue:
Nein, aber man muß halt solche Kleinigkeiten rechtzeitig in der Planung berücksichtigen. Ein gutes Konzept ist schon das halbe Programm ;D

Da hast du sicher wieder einmal recht :wink:

Ich hab mal den Farbuhr-Sketch etwas bearbeitet, so wie ich mir das in etwa vorstellen könnte, für die Klanguhr.

ino.

// Debugging
#define DEBUG Serial // Diese Zeile als Kommentar, wenn keine Ausgabe auf dem seriellen Monitor gewünscht ist.
#ifdef DEBUG
#define debug(...) DEBUG.print(__VA_ARGS__)
#define debugln(...) DEBUG.println(__VA_ARGS__)
#define debugbegin(...) DEBUG.begin(__VA_ARGS__)
#define SECPERMIN 4  // Sekunden pro Minute - zur Beschleunignung beim Testen

#else
#define debug(...)
#define debugln(...)
#define debugbegin(...)
#endif

#include "Wire.h"
constexpr int DS3231_I2C_ADDRESS = 0x68; // Die Bibliothek möchte int!
#include <MobaTools.h>
MoToTimer Zeitlesen;

constexpr uint32_t PAUSE = 1000;
constexpr uint16_t FULLROT = 4080; 
constexpr uint16_t FULLROT2 = 4080;

constexpr uint16_t NULLPOSMINUTEN = 171;  // Abstand Referenzpunkt ( LS ) zum Nullpunkt
constexpr uint16_t NULLPOSSTUNDEN = 100;
constexpr uint16_t NULLPOSSTDKELL = 10;
constexpr uint16_t NULLPOSMINKELL = 10;


MoToStepper StepMinuten(FULLROT);
MoToStepper StepMinKelle(FULLROT);
MoToStepper StepStunden(FULLROT2);
MoToStepper StepStdKelle(FULLROT);


const byte stdDisPin = 9;      // disable Stunden-Schiebregister

// Pindefinitions - change to your needs
const byte dirPin       = 5;
const byte stepPin      = 6;
const byte enaPin       = 7;

// Input-Pins ( Lichtschranken und Zeitsetz-Taster ) über MoToButtons verwalten
const byte inPins[] = { 2, 3, 4, 5, A1, A2, A3 };
const byte pinAnzahl = sizeof( inPins );
enum : byte { refMinPin, refStdPin, refMinKellPin, refStdKellPin, stellMin, stellStd, stellMode };
#define MAX8BUTTONS     // optional, spart Speicherplatz ( Standard ist maximal 16 Taster )
MoToButtons Taster ( inPins, pinAnzahl, 20, 2000 );  // Einrichten der Tasterverwaltung

//Variable für das Uhr stellen
boolean stellenAktiv;
const byte stellenLED = A0;

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val / 16 * 10) + (val % 16) );
}

//Convertiert Dezimalzeichen in BCD Zeichen.
byte decToBcd(byte val){
  return ( (val/10*16) + (val%10) );
}

// Zeit von DS3231 holen
void readDS3231time( byte &minute, byte &hour)
{ // Die DS3231 wird nur jede 500ms abgefragt, sonst werden die
  // gespeicherten Zeiten zurückgegeben
  static byte DShour, DSmin, DSsec;
  if (!Zeitlesen.running()) {
    Zeitlesen.setTime( PAUSE );
    Wire.beginTransmission(DS3231_I2C_ADDRESS);
    Wire.write(0); // set DS3231 register pointer to 00h
    Wire.endTransmission();
    Wire.requestFrom(DS3231_I2C_ADDRESS, 3);
    // request three bytes of data from DS3231 starting from register 00h
    DSsec = bcdToDec(Wire.read() & 0x7f);
    DSmin = bcdToDec(Wire.read());
    DShour = bcdToDec(Wire.read() & 0x3f);

  }
   

 
  hour = DShour;
  minute = DSmin;
}
// Anzeige im seriellen Monitor
void displayTime(byte second, byte minute, byte hour)
{
  char buf [10];
  snprintf ( buf, 10, "%02u:%02u:%02u", hour, minute, second );
  debugln(buf);  // send it to the serial monitor
}

void setup() {
  Wire.begin();
  debugbegin(115200);
  debugln("\nStart");
  // pinMode für die Tater/LS-Eingänge wird in MoToButtons gemacht

  pinMode(stellenLED, OUTPUT);

  StepMinuten.attach( stepPin, dirPin );
  StepMinuten.attachEnable( enaPin, 7, LOW );        // Enable Pin aktivieren ( LOW=aktiv )
  StepMinuten.setSpeed( 100 );
  StepMinuten.setRampLen( 100 );
  StepMinuten.setZero(0); 

  StepStunden.attach( stepPin, dirPin );
  StepStunden.attachEnable( enaPin, 7, LOW );        // Enable Pin aktivieren ( LOW=aktiv )
  StepStunden.setSpeed( 100 );
  StepStunden.setRampLen( 100 );
  StepStunden.setZero(0); 
 
  delay(50);
}

void loop () {
  Taster.processButtons();
  int minutenPosition = minutenRing();
  int stundenPosition = stundenRing();

  // Verwaltung Einstellmode
  if (! stellenAktiv ) {
    // Wenn Taster lange gedrückt, in Stellmode schalten
    if ( Taster.longPress(stellMode) ) {
      stellenAktiv = true;
      debugln("Stellmode aktiv");
      digitalWrite( stellenLED, HIGH );
    }
  } else {
     // Das Beenden des Stellmode ist nur im Stillstand der Ringe möglich
    if ( Taster.shortPress(stellMode) ) {
      debug (F("minutenPosition=")); debugln(minutenPosition);
      debug(F("stundenPosition=")); debugln(stundenPosition);
      if ( minutenPosition >= 0 && stundenPosition >= 0 ) {
        debugln(F("Stellmode beenden"));
        debug("Std: "); debug(stundenPosition); debug(" - Min: "); debugln(minutenPosition * 5);

        // aktuelle Stunden und Minuten an RTC übertragen
        Wire.beginTransmission(DS3231_I2C_ADDRESS);
        Wire.write(0);              // Start ab Register 0 (Sekunden )
        Wire.write(decToBcd(0));    // Sekunden
        Wire.write(decToBcd(minutenPosition * 5)); // Minuten
        Wire.write(decToBcd(stundenPosition));    // Stunde
        Wire.endTransmission();
        digitalWrite( stellenLED, LOW );
        stellenAktiv = false;
      }
    }

}
}

Minuten:

void printMinSchritt( byte schritt ) {
  static const char *schrittTexte[] = { "REF_M", "REF_SUCHEN", "WARTEN", "MIN_START", "STD_SCHLAG" };
  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;
  enum class Schritt : byte { REF_M, REF_SUCHEN, WARTEN, MIN_START, M_SCHLAG};
  static Schritt schritt = Schritt::REF_M, altSchritt = Schritt::REF_M;
#ifdef DEBUG
  if ( schritt != altSchritt ) {
    // Debugging; Ausgabe wenn Statuswechsel
    printMinSchritt((byte) schritt);
    altSchritt = schritt;
  }
#endif

  // Ring-Referenz
  if (Taster.pressed(refMinPin)) {
    StepMinuten.setZero(NULLPOSMINUTEN);
    StepMinuten.write(0);
    debugln("Ref-Minuten erreicht");
  }

  switch (schritt) {
    
   
    case Schritt::REF_M:

      debugln(F("Referenzpunkt Minuten suche"));
      StepMinuten.write(390);
      schritt = Schritt::REF_SUCHEN;
      break;

    case Schritt::REF_SUCHEN:

      if (Taster.state(refMinPin) ) {
        debugln(F("Referenzpunkt Minuten gefunden"));
        schritt = Schritt::WARTEN;

      }
      break;
    case Schritt::WARTEN:

      // Das Abschalten der Stepper wurde bereits in MIN_KELLE_RAUF gemacht

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

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

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

    case Schritt::MIN_START:

      if (!StepMinKelle.moving()) {
        
        if (posMinuten == 0) {
          StepMinuten.write(360);
          schritt = Schritt::M_SCHLAG;
        }
        else {
          StepMinuten.write(30 * posMinuten);
          debug("Minuten-Winkel: "); debugln(30 * posMinuten);

          schritt = Schritt::M_SCHLAG;
        }
      }

      break;
    

    case Schritt::M_SCHLAG:
      if (!StepMinuten.moving() )   // HIER KOMMT DANN DIE BEWEGUNG DES MINUTEN-KLÖPPELS
      { 
    
        schritt = Schritt::WARTEN;
      }
      break;
  }

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

Stunden:

void printStdSchritt( byte schritt ) {
  static const char *schrittTexte[] = { "REF_STD", "REF_STD_SUCHEN", "WARTEN_STD", "STD_START", "STD_SCHLAG" };
  debug( "Std: "); debugln( schrittTexte[schritt] );
}

// Rückgabe wert ist die aktuelle Position ( wenn im Zustand WARTEN )
// oder -1 wenn in Bewegung
int stundenRing() {
  byte minute, hour;
  static byte altStunden = 12, posStunden = 0;
  enum class Schritt : byte { REF_STD, REF_STD_SUCHEN, WARTEN_STD, STD_START, STD_SCHLAG};
  static Schritt schritt = Schritt::REF_STD, altSchritt = Schritt::REF_STD;
#ifdef DEBUG
  if ( schritt != altSchritt ) {
    // Debugging: Ausgabe wenn Statuswechsel
    printStdSchritt((byte)schritt);
    altSchritt = schritt;
  }
#endif

  // Ring-Referenz
  if (Taster.pressed(refStdPin)) {
    StepStunden.setZero(NULLPOSMINUTEN);
    StepStunden.write(0);
    debugln("Ref-Stunden erreicht");
  }


  switch (schritt) {

    case Schritt::REF_STD:

      debugln("Referenzpunkt Stunden suche");
      StepStunden.write(390);
      schritt = Schritt::REF_STD_SUCHEN;
      break;

    case Schritt::REF_STD_SUCHEN:

      if (Taster.state(refStdPin)) {
        debugln("Referenzpunkt Stunden gefunden");
        schritt = Schritt::WARTEN_STD;
      }
      break;
    case Schritt::WARTEN_STD:

      // Das Abschalten wurde bereits in STD_KELLE_RAUF gemacht

      if ( stellenAktiv ) {
        // im Einstellmodus wird das vorrücken über den Stelltaster ausgelöst
          posStunden = altStunden+Taster.clicked(stellStd);  // Bei einfach Klick wird eins, bei Doppelklick 2 addiert
          if ( posStunden > 11 ) posStunden = 0;
      } else {
       
        // im Normalbetrieb die Uhr abfragen
        readDS3231time(minute, hour);
        posStunden = hour > 11 ? hour - 12 : hour;     //posStunden = minute > 11 ? minute - 12 : minute; // nur zum Testen
      }
      if (altStunden != posStunden) {
        debug( "Std: "); displayTime(0, minute, hour);
        debug("altStunden: "); debug(altStunden); debug('\t'); debug("posStunden: "); debug(posStunden); debug('\n');
        altStunden = posStunden;
        digitalWrite(stdDisPin, LOW);                 // Stepperspulen anschalten
        StepStdKelle.write(0);                // Stundenkelle runter
        schritt = Schritt::STD_START;
      }
      break;

    case Schritt::STD_START:

      /*if (Taster.pressed(refStdKellPin)) {    // Referenzieren der Stundenkelle/Hochdrücker im Betrieb
        StepStdKelle.setZero();
        }*/

      if (!StepStdKelle.moving()) {
        
        // Aktion: Beweegung des Stundenrings starten

        if (posStunden == 0) {
          StepStunden.write(360);
          schritt = Schritt::STD_SCHLAG;
        }
        else {
          StepStunden.writeSteps(30 * posStunden);
          debug("Stunden-Winkel: "); debugln(30 * posStunden);
          // if (posStunden == 0) StepStunden.setZero();
          schritt = Schritt::STD_SCHLAG;
        }
      }

      break;
    

    case Schritt::STD_SCHLAG:
      if (!StepStdKelle.moving())  // HIER KOMMT DANN DIE BEWEGUNG FÜR DEN STUNDENKLÖPPEL
      { 
        schritt = Schritt::WARTEN_STD;
      }
      break;
  }
  if ( schritt == Schritt::WARTEN_STD ) return altStunden;
  else return -1;
}