Fragen zum Code für Sprechende Uhr für Blinde

Hallo ihr Lieben,
ich bin blind und zudem neu in der Welt der Arduinos.
Ich bastel aktuell zu Lernzwecken an einer sprechenden Uhr für Blinde mit einem Nano, Display, RTC, DFPlayer und mini Lautsprecher.
Der aktuelle Sketch läuft auch soweit. Die Zeitansage läuft noch mit vielen Pausen, aber werde ich ändern, sobald die delays raus sind. Auch wenn einige Modis noch nicht fertig sind, möchte ich gerne wissen, was man am aktuellen Code noch optimieren müsste. Hat jemand eine Idee, wie das eleganter aussehen könnte? Einige Funktionen sehen ja gleich aus. Vielleicht gibts ja Ideen.
Mir fehlt die Metode für einmaliges Abspielen der Ansagen in den entsprechenden Modis und beim Alarm.
Hat jemand vielleicht auch eine Idee, wie man die delays für diesen Sketch ersetzen könnte und wie die Alarmfunktion noch vervollständigt werden kann? z.B. der Alarm soll 5 min. lang klingeln und bei Tastendruck auf Button 2 oder 3 beendet werden können.
Über Ideen, Ratschläge und lesbaren Code freue ich mich sehr.

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include "TM1637Display.h"
#include "DS3231.h"
#include "Wire.h"

//Display PINs
#define Disp_CLK 2
#define Disp_DIO 3

//DFPlayer PINs
#define MF_RX 10
#define MF_TX 11

//Buttons PINs
int bGetMode=4; //Button für Modis
int bSetMin=5; Button 2
int bSetHour=6; Button 3

//Variablen für Programmlogik
byte hour, minute;
int mode = 0; //Verschiedene Modis
byte alarmhour=18, alarmminute=30; //Variablen für Alarm/Wecker

//Serielle Schnittstelle Konfigurieren 
SoftwareSerial mySoftwareSerial(MF_RX, MF_TX); // RX, TX für dfplayer

//Anlegen des Objekts myDFPlayer über DFRobotDFPlayerMini Library
DFRobotDFPlayerMini myDFPlayer;

//Anlegen des Objekts display über TM1637 Library
TM1637Display display(Disp_CLK, Disp_DIO);

//Anlegen des Objekts Clock über DS3231 Library
DS3231 Clock;


void setup() {
  //------------------------------ init Taster
  pinMode(bGetMode, INPUT);
  pinMode(bSetMin, INPUT);
  pinMode(bSetHour, INPUT);

  // Starte die kommunikation mit dem seriellen Monitor mit der Baudrate
  Serial.begin(115200);
  Serial.println("MyTalkingClock v1.0 is starting..."); //Der Arduino schreibt in den seriellen Monitor
  Serial.println("");

  //------------------------------ init DFPlayer
  mySoftwareSerial.begin(9600);
if (!myDFPlayer.begin(mySoftwareSerial, true, false))
{ //Geräusch beim Starten des DFPlayers verhindern
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Recheck the connection."));
Serial.println(F("2.Insert the SD card."));
while(true)
{
delay(0); // the ESP8266 watchdog likes this.
}
}

  myDFPlayer.setTimeOut(500); //Set serial communication time out 500ms

  //myDFPlayer.volume(30); //0-30 möglich

  myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); //----Set device we use SD as default

  //----------------------------- init Display
  display.setBrightness(0x00); //0-15 möglich

  //----------------------------- init DS3231
  Wire.begin();
}

void loop() {

if(bPressed(bGetMode)){
delay(700);
mode++;
if(mode > 3) mode = 0;
}

switch(mode) {
case 1:
//Modus 1 = Uhrzeit (stunden und minuten) einstellen
//Einmalige Ansage des Modus
//if (millis() % 1000 > 300)
//    playSound(31);
setClock();
break;

case 2:
//Modus 2 = Datum (Tag und monat) einstellen
//Einmalige Ansage des Modus
//if (millis() % 1000 > 300)
//    playSound(32);
//Funktion für Datum einstellen kommt hier noch...
break;

case 3:
//Modus 3 = Alarm/Wecker (Stunde und Minute) einstellen
//Einmalige Ansage des Modus
//if (millis() % 1000 > 300)
//    playSound(33);
getAlarm();
setAlarm();
break;

default:
//Modus 0 = Uhrzeit und Datum ansagen
//Einmalige Ansage des Modus
//if (millis() % 1000 > 998)
//    playSound(34);

  getTime();
ringAlarm();

//Uhrzeit ansagen auf Tastendruck
  if(bPressed(bSetMin)) sayTime();
//Datum ansagen auf Tastendruck
  if(bPressed(bSetHour)) sayTime(); //Datumfunktion fehlt, spricht ersatzweise aktuelle Zeit
break;
}//end switch
}

// Zeige Dezimalzahl mit führenden Nullen und Doppelpunkt
void printNum(int num){
  display.showNumberDecEx(num, 0b11100000, true, 4, 0);  
}

// Hole die Zeit vom DS3231 RTC
void getTime(){  
  int time;
  bool A12h, Apm;
  minute=Clock.getMinute();
  hour=Clock.getHour(A12h, Apm);
  time=hour*100;
  time=time+minute;
  printNum(time);
}


/*
 * ------------------Definition der Dateien, die in dem Ordner liegen
 * 1-21 = durchgängig
 * 22 = thirty
 * 23 = fourty
 * 24 = fifty
 * 25 = and
 * 26 = hours
 * 27 = minutes
 * 28 = beep
 * 29 = beep beep
 * 30 = ringtone / alarm
 * 31 - 34 = modis
 */

// Spielt die Datei in Ordner 01 ab
void playSound(int track){
myDFPlayer.volume(30);
delay(700);
  myDFPlayer.playLargeFolder(1, track);
/*
int playerState = 0;
while(playerState != 512) {
delay(pause); // prevents clicks and static during playback - increase if necessary.
playerState = myDFPlayer.readState();
}
*/
//delay(500);
//myDFPlayer.volume(0);
}

// Angepasste Zahlenansage bis 59, noch in Englisch
// Muss verändert werden wenn die Ansage auf Deutsch erfolgen soll
void sayDecNum(int num){
  if(num<21){
    playSound(num+1); 
    if(num>11)delay(700);
  }else {
    playSound(20+(num/10)-1);
    delay(600);
    if(num%10!=0) {
    delay(500);
      playSound((num%10)+1);
    delay(600);
    }
  }  
}

// Ansage der Momentanen Zeit
void sayTime(){
  int dely=700;
  playSound(29);
  delay(dely);
  playSound(26);
  delay(dely);
  sayDecNum(hour);
  delay(dely);
  sayDecNum(minute);
  delay(dely);
  playSound(27);
  delay(dely);
  playSound(28);
}


// Reagieren auf Taster für Uhrzeit (Stunde und Minute) einstellen
void setClock(){

//Minuten einstellen
  if(bPressed(bSetMin)){
    minute++;
    if(minute>=60){
      hour++;
      minute=0;
    }
    Clock.setMinute(minute);
    delay(500);
    sayDecNum(minute);   
    delay(650);
    playSound(27);  
  }

//Stunde einstellen
  if(bPressed(bSetHour)){
    hour++;
    if(hour>=24){
      hour=0;
    }
    Clock.setHour(hour);  
    delay(500);
    sayDecNum(hour);  
    delay(650);
    playSound(26); 
  }
}

// Abfrage ob eine bestimmte Taste gedrückt UND wieder los gelassen wurde
// Solange die Taste gedrückt wird passiert nichts
bool bPressed(int button){
  if(digitalRead(button)==HIGH){
    while(digitalRead(button)==HIGH){}
    return true;
  }
  return false;
}


// Reagieren auf Taster für Alarm/Wecker (Stunde und Minute) einstellen
void setAlarm(){

//Minuten einstellen
  if(bPressed(bSetMin)){
    alarmminute++;
    if(alarmminute>=60){
      alarmhour++;
      alarmminute=0;
    }
    delay(500);
    sayDecNum(alarmminute);   
delay(700);
    playSound(27);
  }

//Stunden einstellen
  if(bPressed(bSetHour)){
    alarmhour++;
    if(alarmhour>=24){
      alarmhour=0;
    }
    delay(500);
    sayDecNum(alarmhour);
    delay(650);
    playSound(26);
  }
}

// Hole die Zeit für Alarm
void getAlarm(){
  int time;
  alarmminute = alarmminute;
  alarmhour = alarmhour;
  time = alarmhour*100;
  time=time+alarmminute;
  printNum(time);
}

//Alarmfunktion
void ringAlarm() {
if(hour == alarmhour && minute == alarmminute && mode == 0) {
//einmalige Ansage der aktuellen Uhrzeit
if (millis() % 1000 > 100) {
sayTime();
delay(50);
}
    playSound(30);
delay(7000);
}
}

liebe Grüße

Niemand eine Idee?

Herzlich Willkommen im Forum.
Ich kann dir in der Sache nicht konkret helfen, da ich keine DFPlayer im Einsatz habe.

Für Dinge die nur eine bestimmte Zeit lang laufen sollen, wie du in deiner Frage nach

"der Alarm soll 5 min. lang klingeln und bei Tastendruck auf Button 2 oder 3 beendet werden können."

wissen wolltest folgendes:

Dazu eignet sich millis().
Du merkst dir in einer Variable vom Typ unsigned long die millis() wann du die Aktion (das Klingen) gestartet hast und prüfst dann im loop() laufend, ob die aktuellen millis() minus den gespeicherten Millis größer als 5x60x1000 also 300000 ist.
Im Prinzip so wie das Beispiel "Blink Without Delay" in der Arduino IDE aufgebaut ist.

Off topic und wenig passend: Ich lieben meinen Google Echo. Der hört auf Sprachkommados wie zum Beispiel: "hey google, wie spät ist es".

vbprofi0:
Niemand eine Idee?

Doch doch....

Allgemeine Hinweise, kann ich dir geben.

// Hole die Zeit für Alarm
void getAlarm(){
  int time;
  alarmminute = alarmminute; // wirkungslos
  alarmhour = alarmhour; // wirkungslos
  time = alarmhour*100;
  time=time+alarmminute;
  printNum(time);
}

Dort sind ein paar Zeilen wirkungslos

Habe sie mal markiert.

Weder die Verwendung von define, als auch die Definition als int, ist optimal für Werte, welche konstant bleiben sollen..
Habe das mal an 2 Stellen so abgeändert,..
Nicht, dass das sonderlich wichtig wäre.

Aber es ist für mich und den Compiler, die bessere Variante.
Da herrscht auch eine große Übereinstimmung hier im Forum.

//Display PINs
#define Disp_CLK 2  // suboptimal
// #define Disp_DIO 3  // suboptimal
const byte  Disp_DIO = 3; // besser

//DFPlayer PINs
#define MF_RX 10  // suboptimal
#define MF_TX 11  // suboptimal

//Buttons PINs
int bGetMode=4; //Button für Modis  // suboptimal
int bSetMin=5; Button 2  // suboptimal
// int bSetHour=6; Button 3  // suboptimal
const byte bSetHour=6; Button 3 // besser
while(true)
{
delay(0); // the ESP8266 watchdog likes this.
}

Ist das Ironie?
Aus meiner Sicht führt das zum absoluten Stillstand des Programms.

if (millis() % 1000 > 100)

Die Berechnung geht nach 49,x Tagen schief
Was jetzt genau die Wirkung in deinem Code ist, habe ich nicht geprüft.

switch(mode)

mode ist bei dir ein int.
ok, kein Drama.
Aber, es darf auch ruhig ein enum werden.
So verschwinden dann ein paar magische Zahlen aus deinem Programm.

Mir fehlt die Metode für einmaliges Abspielen der Ansagen in den entsprechenden Modis und beim Alarm.
Hat jemand vielleicht auch eine Idee, wie man die delays für diesen Sketch ersetzen könnte und wie die Alarmfunktion noch vervollständigt werden kann? z.B. der Alarm soll 5 min. lang klingeln und bei Tastendruck auf Button 2 oder 3 beendet werden können.

Konkret.... nicht leicht.

Allgemein:
Du möchtest "endliche Automaten" bauen.
Schrittketten, Ablaufsteuerungen.

Suche mal hier im Forum nach der "Nachtwächter Erklärung"

Du merkst dir in einer Variable vom Typ unsigned long die millis() wann du die Aktion (das Klingen) gestartet hast und prüfst dann im loop() laufend, ob die aktuellen millis() minus den gespeicherten Millis größer als 5x60x1000 also 300000 ist.

Da gibt es einen Stolperstein:

if (millis() - oldmillis >= 300000L)

Berechnungen werden normalerweise mit int Genauigkeit (16 bit) berechnet. 300.000 ist dafür zu groß.
Dazu braucht es das L.

Grüße Uwe

300.000 ist dafür zu groß. Dazu braucht es das L.

Der C++ Standard ist hier leider nicht deiner Meinung.
int ist der default Type für integrale Literale.
Richtig.
Wenn int nicht reicht, wird auf long erweitert.
Automatisch.

Aber stimmt schon, das L ist kein Schaden und macht klar, was man will.
Wobei in dieser konkreten Situation 300000UL sicherlich angemessener wäre, als 300000L

Ein Test auf dem UNO

Serial.println(sizeof(decltype(30000))); // sagt 2
Serial.println(sizeof(decltype(300000)));// sagt 4

bestätigt die automatische Erweiterung.

Nachtrag:
Berechnungen wie diese, führen allerdings ins sofortige Versagen:

long value = 30000 * 10;

Da hast du vollkommen Wahr.
Eine gewisse Vorsicht ist also geboten.

Hallo ihr Lieben,
erst einmal einen großen Dank für eure Anmerkungen. Ich habe den Code mit den Vorschlägen und Anmerkungen zu berücksichtigen aktuallisiert.
Alarm und die Nachtwächter-Erklärung schau ich mir noch genauer an. Gibt es denn noch allgemein am aktuellen Code Kritiken?
Freue mich gerne über Rückmeldungen.

Eine Anmerkung zur While-Schleife im Setup() habe ich noch. Ich habe den Tipp hier im Forum gefunden, wonach bei einem Problem mit dem DFPlayer beim Starten verhindert wird, dass dieser den loop() erreicht. Wenn das nicht ratsam ist, kann ich es ja wieder rausnehmen.

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include "TM1637Display.h"
#include "DS3231.h"
#include "Wire.h"

//Display PINs
const byte Disp_CLK = 2;
const byte Disp_DIO = 3;

//DFPlayer PINs
const byte MF_RX = 10;
const byte MF_TX = 11;

//Buttons PINs
const byte bGetMode=4; //Button 1: für Modis
const byte bSetMin=5; //Button 2
const byte bSetHour=6; //Button 3

//Variablen für Programmlogik
//byte year, month, date, DoW, hour, minute, second; //Wird noch in der zukunft benötigt
byte hour, minute; //aktuelle Uhrzeit
enum modeStates {Msay, MsetTime, MsetDate, MsetAlarm}; //Verschiedene Modis
enum modeStates mode = Msay; //aktueller Modus
byte alarmhour=18, alarmminute=30; //Variablen für Alarm/Wecker
bool alt=false, neu=false; //Wird für einmalige Tonausgabe beim Modiwechsel genutz


//Serielle Schnittstelle Konfigurieren 
SoftwareSerial mySoftwareSerial(MF_RX, MF_TX); // RX, TX für dfplayer

//Anlegen des Objekts myDFPlayer über DFRobotDFPlayerMini Library
DFRobotDFPlayerMini myDFPlayer;

//Anlegen des Objekts display über TM1637 Library
TM1637Display display(Disp_CLK, Disp_DIO);

//Anlegen des Objekts Clock über DS3231 Library
DS3231 Clock;


void setup() {
  //------------------------------ init Taster
  pinMode(bGetMode, INPUT);
  pinMode(bSetMin, INPUT);
  pinMode(bSetHour, INPUT);

  // Starte die kommunikation mit dem seriellen Monitor mit der Baudrate
  Serial.begin(115200);
  Serial.println("MyTalkingClock v1.0 is starting..."); //Der Arduino schreibt in den seriellen Monitor
  Serial.println("");

  //------------------------------ init DFPlayer
  mySoftwareSerial.begin(9600);
if (!myDFPlayer.begin(mySoftwareSerial, true, false))
{ //Geräusch beim Starten des DFPlayers verhindern
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Recheck the connection."));
Serial.println(F("2.Insert the SD card."));
while(true)
{
delay(0); // the ESP8266 watchdog likes this.
}
}

  myDFPlayer.setTimeOut(500); //Set serial communication time out 500ms

  //myDFPlayer.volume(30); //0-30 möglich

  myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);

  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); //----Set device we use SD as default

  //----------------------------- init Display
  display.setBrightness(0x00); //0-15 möglich

  //----------------------------- init DS3231
  Wire.begin();
}

void loop() {
switch(mode) {
case Msay:
//Modus 0 = Uhrzeit und Datum ansagen
if(alt==false) {
    playSound(34);
alt=true;
neu=false;
delay(100);
}

  getTime();
ringAlarm();

//Uhrzeit ansagen
  if(bPressed(bSetMin)) sayTime();

//Datum ansagen
  if(bPressed(bSetHour)) sayTime(); //Datumfunktion fehlt, spricht ersatzweise aktuelle Zeit

//Modus wechseln
if(bPressed(bGetMode)){
mode = MsetTime;
}
break;

case MsetTime:
//Modus 1 = Uhrzeit (stunden und minuten) einstellen
if(neu==false) {
    playSound(31);
neu = true;
alt=false;
delay(100);
}

setClock();

//Modus wechseln
if(bPressed(bGetMode)){
mode = MsetDate;
}
break;

case MsetDate:
//Modus 2 = Datum (Tag und monat) einstellen
if(alt==false) {
    playSound(32);
alt=true;
neu=false;
delay(100);
}

//Funktion für Datum einstellen kommt hier noch

//Modus wechseln
if(bPressed(bGetMode)){
mode = MsetAlarm;
}
break;

case MsetAlarm:
//Modus 3 = Alarm/Wecker (Stunde und Minute) einstellen
if(neu==false) {
    playSound(33);
neu=true;
alt=false;
delay(100);
}

getAlarm();
setAlarm();

//Modus wechseln
if(bPressed(bGetMode)){
mode = Msay;
}
break;
}//end switch
}

// Zeige Dezimalzahl mit führenden Nullen und Doppelpunkt
void printNum(int num){
  display.showNumberDecEx(num, 0b11100000, true, 4, 0);  
}

// Hole die Zeit vom DS3231
void getTime(){  
  int time;
  bool A12h, Apm;
  minute=Clock.getMinute();
  hour=Clock.getHour(A12h, Apm);
  time=hour*100;
  time=time+minute;
  printNum(time);
}


/*
 * ------------------Definition der Datein die in dem Ordner liegen
 * 1-21 = durchgängig
 * 22 = thirty
 * 23 = fourty
 * 24 = fifty
 * 25 = and
 * 26 = hours
 * 27 = minutes
 * 28 = beep
 * 29 = beep beep
 * 30 = ringtone / alarm
 * 31 - 34 = modis
 * 35 = ringtone / alarm
 */

// Spielt die Datei in Ordner 01 ab.
void playSound(int track){
myDFPlayer.volume(30);
delay(700);
  myDFPlayer.playLargeFolder(1, track);
/*
*funktioniert noch nicht: soll Abfragen ob das Abspielen beendet wurde
int playerState = 0;
while(playerState != 512) {
delay(pause); // prevents clicks and static during playback - increase if necessary.
playerState = myDFPlayer.readState();
}
delay(500);
myDFPlayer.volume(0);
*/
}

// Angepasste Zahlenansage bis 59, noch in Englisch
// Muss verändert werden wenn die Ansage auf Deutsch erfolgen soll
void sayDecNum(int num){
  if(num<21){
    playSound(num+1); 
    if(num>11)delay(300);
  }else {
    playSound(20+(num/10)-1);

    if(num%10!=0) {
    delay(400);
      playSound((num%10)+1);
    delay(100);
    }
  }  
}

// Ansage der Momentanen Zeit
void sayTime(){
  //int dely=700;
  playSound(29);
  delay(200);
  playSound(26);
  delay(500);
  sayDecNum(hour);
  delay(100);
  //playSound(26);
  //delay(dely);
  sayDecNum(minute);
  delay(400);
  playSound(27);
  delay(400);
  playSound(28);
}


// Reagieren auf Taster für Uhrzeit (Stunde und Minute) einstellen
void setClock(){

//Minuten einstellen
  if(bPressed(bSetMin)){
    minute++;
    if(minute>=60){
      hour++;
      minute=0;
    }
    Clock.setMinute(minute);
    delay(200);
    sayDecNum(minute);   
    delay(500);
    playSound(27);  
  }

//Stunde einstellen
  if(bPressed(bSetHour)){
    hour++;
    if(hour>=24){
      hour=0;
    }
    Clock.setHour(hour);  
    delay(500);
    sayDecNum(hour);  
    delay(650);
    playSound(26); 
  }
}

// Abfrage ob eine bestimmte Taste gedrückt UND wieder los gelassen wurde
// Solange die Taste gedrückt wird passiert nichts
bool bPressed(int button){
  if(digitalRead(button)==HIGH){
    while(digitalRead(button)==HIGH){}
    return true;
  }
  return false;
}


// Reagieren auf Taster für Alarm/Wecker (Stunde und Minute) einstellen
void setAlarm(){

//Minuten einstellen
  if(bPressed(bSetMin)){
    alarmminute++;
    if(alarmminute>=60){
      alarmhour++;
      alarmminute=0;
    }
    delay(500);
    sayDecNum(alarmminute);   
delay(700);
    playSound(27);
  }

//Stunden einstellen
  if(bPressed(bSetHour)){
    alarmhour++;
    if(alarmhour>=24){
      alarmhour=0;
    }
    delay(500);
    sayDecNum(alarmhour);
    delay(650);
    playSound(26);
  }
}

// Hole die Zeit für Alarm
void getAlarm(){
  int time = alarmhour*100;
  time=time+alarmminute;
  printNum(time);
}

//Alarmfunktion
void ringAlarm() {
if(hour == alarmhour && minute == alarmminute && mode == Msay) {
sayTime();
delay(50);
    playSound(30);
delay(7000);
}
}

liebe Grüße

Eine Anmerkung zur While-Schleife im Setup() habe ich noch. Ich habe den Tipp hier im Forum gefunden, wonach bei einem Problem mit dem DFPlayer beim Starten verhindert wird, dass dieser den loop() erreicht. Wenn das nicht ratsam ist, kann ich es ja wieder rausnehmen.

Das entzieht sich meiner Logik!
Vollständig.
z.B. Warum befindet sich Programmcode in loop(), wenn doch loop() gar nicht erreicht werden soll.

Auch kenne ich die Quelle nicht, auf die du dich beziehst.
So auch nicht den Sinn der Maßnahme.

combie:
So auch nicht den Sinn der Maßnahme.

Es ist durchaus üblich, eine fehlgeschlagene Initialisierung mit einer Meldung und einer Endlosschleife zu beantworten. Der Kommentar zum ESP8266 paßt nicht zum verwendeten Nano, ist aber auch kein Beinbruch.

@vbprofi0: Woher hast Du die Sounddateien?

Es ist durchaus üblich, eine fehlgeschlagene Initialisierung mit einer Meldung und einer Endlosschleife zu beantworten.

Ach....

Ja...
Jetzt ja..

Habe das nicht erkannt, wohl wegen den fehlenden Einrückungen...

Danke für den Hinweis.

Ab jetzt behaupte ich das Gegenteil.

combie:
Danke für den Hinweis.

Bitte gerne!

vbprofi0:
Eine Anmerkung zur While-Schleife im Setup() habe ich noch. Ich habe den Tipp hier im Forum gefunden, wonach bei einem Problem mit dem DFPlayer beim Starten verhindert wird, dass dieser den loop() erreicht. Wenn das nicht ratsam ist, kann ich es ja wieder rausnehmen.

Kannst Du uns den Link zu diesem Tip geben?
Grüße Uwe

Hallo ihr Lieben,

@agmue
danke für den Hinweis auf das Kommentar. Copy & Paste überbleibsel :wink:
Zur Frage mit den Sounddateien. Aktuell habe ich eigene deutsche Sprachdateien erzeugt. Die englischen Audiodateien hatte ich aus diesem Projekt:

@uwefed
den Beitrag mit der Empfehlung bei Hardware-Inizialisierungsproblemen eine endlos schleife in die setup() zu packen hatte ich mir blöderweise nicht gespeichert. Wenn ich mich aber richtig erinnere, hatte das jemand aus dem Forum hier in einem Beitrag empfohlen. Dort wurde auf Posting von ChuckM Nr #32 des Beitrages unten verwiesen. Ich glaube, als Beispiel für Probleme mit dem DFPlayer.

@all
die nachtwächter erklärung ist für einfache bzw. einzelne sich wiederholende abläufe nach bestimmter pausezeit doch recht verständlich. aber ich stehe auf dem schlauch gerade, um das auf mein vorhaben zu übertragen, da bei meinem vorhaben dinge nacheinander nach bestimmter pausezeit gesprochen werden müssen.
wie geht man denn vor, wenn man mehrere aufeinander folgende abläufe hat, die alle zeitlich voneinander abhängig sind?
erst wird datei abgespielt: gesprochen:
"es ist", als nächstes nach einer bestimmten wartezeit die Stunden, nach einer weiteren bestimmten Zeit die minuten. (vereinfacht mal dargestellt)

speichere ich die millis() in eine globale variable old_millis.
Und schreibe in der funktion sayTime():
unsigned long aktuelleMillis = millis()
spreche "es ist"
if(aktuelleMillis - old_millis >= 400){
spreche stunde
old_millis = aktuelleMillis

if(aktuelleMillis - old_millis >= 800){
spreche "uhr und"
old_millis = aktuelleMillis

if(aktuelleMillis - old_millis >= 400){
spreche minuten
old_millis = aktuelleMillis
}
}
}

Irgend wie bin ich mir nicht ganz sicher bei dem Gedankengang. Kann das hinhauen oder ist es noch komplizierter?

liebe Grüße

vbprofi0:
Zur Frage mit den Sounddateien. Aktuell habe ich eigene deutsche Sprachdateien erzeugt. Die englischen Audiodateien hatte ich aus diesem Projekt:
GitHub - rydepier/Arduino-talking-clock-with-WT588D---U-32M: Using a WT588D - U 32m audio module and a DS1307 to build a talking clock, the WT588D is used in 3 line mode. For more information on this project see my blog at

Danke!

vbprofi0:
wie geht man denn vor, wenn man mehrere aufeinander folgende abläufe hat, die alle zeitlich voneinander abhängig sind?

Du möchtest eine Schrittkette (= endlicher Automat, = finite state machine) programmieren. (Diesen Satz schreibe ich gefühlt einmal pro Tag im Forum.)

  1. Schritt “Es ist”
  2. Schritt Stunde
  3. Schritt “Uhr”
  4. Schritt Minute

vbprofi0:
… nach einer bestimmten wartezeit …

Hat die Bibliothek keine Methode “Sounddatei fertig abgespielt”?

... zumindest wird Es einfacher, wenn vermehrt große Buchstaben Verwendung finden - Das sollte schon alleine deshalb nötig werden, da sonst der Kompiler nicht will - also können MUSS man Das eh - warum also nicht auch hier?

Du machst Es Dir, glaube ich, unnötig schwer.
Auch werden die Sprach-Files direkt nacheinander ausgegeben - zumindest ist davon auszugehen, daß die Aufnahmen jeweils länger, als 400ms sind.
Somit sind die folgenden IFs direkt true und werden auch ausgeführt.
Bein Problem ist jetzt aber - wenn Du die Startzeit nach jeder Sprachausgabe neu setzt, daß die IFs zwar nicht gültig werden (also die Pause anfängt), Du aber den kompletten Block direkt verlässt.
Beim nächsten Aufruf wird dann schon wieder 'es ist' als Sprache ausgegeben.
Mache auch hier eine eigene FSM, Die sich nur um die Aneinanderreihung der Sprach-Files und Pausen kümmert.
State 1: 'Es ist' sprechen + Uhrzeit merken
State 2 (und 4 und 6 und 8 und ...): Weiterschalten, wenn die Wartezeit vorbei ist
State 3: Stunden sprechen
State 5: 'Uhr' sprechen (wenn keine Minuten und Sekunden, hier fertig)
State 7: Minuten sprechen (wenn keine Sekunden, hier fertig)
State 9: 'und' sprechen
State 11: Sekunden sprechen
State 13: 'Sekunden' sprechen - fertig

Wenn hier Nichts zu tun ist, tun wir Nichts und verlassen die Funktion direkt wieder.
Das 'Uhrzeit merken' klappt so nur, wenn die Sprachausgabe blockierend ist - sonst muß vor dem Uhrzeit merken noch die Abfrage, ob wir mit Sprechen fertig sind! - sonst werden die Files in einander gestartet (kA, was der Player dann macht, ignorieren oder Aktuell abschalten, Neu einschalten wären aber wahrscheinlich).

MfG

@agmue
Die Idee mit einer metode nach "fertig abgespielt" zu suchen, war goldwert. Ich konnte zwar keine direkte Metode in der lib finden, aber Hinweise darauf, wie solch eine Funktion aussehen könnte.
ich habe mal meine playSound Funktion aktuallisiert. Siehe, es geht. Konnte jetzt alle delays() rausnehmen. Nun gibts ein kleines Problem beim Abspielen:
Der klingt beim Abspielen etwas rau. Kennt jemand eine elegantere Möglichkeit als das, was ich da jetzt gemacht habe? Wenn das eleganter gelöst werden kann, kann ich mir nämlich die ganzen Verkettungen etc sparen.

void playSound(int track){
  myDFPlayer.playLargeFolder(1, track);
while(true)
{
if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) break;
}
}

Danke für den Tipp!

@postmaster
verstehe das mit der großschreibung nicht genau. was meinst du denn genau? meinst du die Namen der Variablen, Methoden?

@all,
danke für die detailierte Auflistung der jeweiligen Schritte. Vielleihct wird das noch notwendig, wenn wir diese Funktion nicht fixen können.

liebe Grüße

vbprofi0:
Danke für den Tipp!

Bitte gerne!

vbprofi0:
Konnte jetzt alle delays() rausnehmen.

Das ist schön, aber mit while(true) hast Du eine blockierende Schleife gebaut, die den µC nichts anderes machen läßt. Tastendrücke können so nicht erkannt werden.

Besser wäre, wenn Du loop als Schleife verwendest und in einer Schrittkette von einem Schritt zum nächsten wechselst:

if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) 
{
  schritt++;  // zum nächsten Schritt
}

vbprofi0:
Der klingt beim Abspielen etwas rau. Kennt jemand eine elegantere Möglichkeit als das, was ich da jetzt gemacht habe?

Die Kommunikation des µCs mit dem Player scheint die Ausgabe zu stören. Die Methoden myDFPlayer.readType() und myDFPlayer.readState() führen daher zum "Knistern".

Ich hoffe, da kennt jemand eine Lösung.

im Datenblatt des DFPlayers konnte ich nichts hilfreiches dazu finden. In der Library auch nichts. Bei den Beispielen jedoch fällt auf, dass nach senden eines Befehls an den DFPlayers ein delay() gemacht wird. Ich habe das mal ausprobiert. Mit delay(250) ist die Ausgabe besser geworden bzw. das Problem ist damit beseitigt:

void playSound(int track){
  myDFPlayer.playLargeFolder(1, track);
while(true)
{
if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) break;
delay(250);
}
}

Ich habe mal den Vorschlag aufgegriffen und die Funktion umgeschrieben. Anbei der gesamte Code. Kann man das eleganter machen mit der Ansage der aktuellen Uhrzeit oder ist das so in ordnung? Ich finde den Code langsam schon unübersichtlich. Wie kann ich nun die anderen Funktionen, bei den playSound() aufgerufen wird anpassen? muss dafür auch jeweils eine endliche Maschine her?

...
...
//Variablen für Programmlogik
byte hour, minute; //aktuelle Uhrzeit
enum modeStates {Msay, MsetTime, MsetDate, MsetAlarm}; //Verschiedene Modis
enum modeStates mode = Msay; //aktueller Modus
byte alarmhour=15, alarmminute=1; //Variablen für Alarm/Wecker
bool alt=false, neu=false; //Wird für einmalige Tonausgabe beim Modiwechsel genutz
bool AActive=true, AState; //Wird für die Alarmzeit gebraucht, während Alarm läuft

//Wird zum Abspielen der aktuellen Zeit genutzt
enum ST {beepStart, sWordHr, SHr, SMin, SWordMin, beepStop}; //Reihenfolge der gesprochenen Inhalte
enum ST sMode = beepStart; //Ansage beginnen mit beep
bool UhrZeitsprechen; //ob schon gesprochen wurde


//Serielle Schnittstelle Konfigurieren
SoftwareSerial mySoftwareSerial(MF_RX, MF_TX); // RX, TX für dfplayer

//Anlegen des Objekts myDFPlayer über DFRobotDFPlayerMini Library
DFRobotDFPlayerMini myDFPlayer;

//Anlegen des Objekts display über TM1637 Library
TM1637Display display(Disp_CLK, Disp_DIO);

//Anlegen des Objekts Clock über DS3231 Library
DS3231 Clock;


void setup() {
	//------------------------------ init Taster
	pinMode(bGetMode, INPUT);
	pinMode(bSetMin, INPUT);
	pinMode(bSetHour, INPUT);

	// Starte die kommunikation mit dem seriellen Monitor mit der Baudrate
	Serial.begin(115200);
	Serial.println("MyTalkingClock v1.03 is starting..."); //Der Arduino schreibt in den seriellen Monitor
	Serial.println("");

	//------------------------------ init DFPlayer
	mySoftwareSerial.begin(9600);
	if (!myDFPlayer.begin(mySoftwareSerial, true, false))
	{ //Geräusch beim Starten des DFPlayers verhindern
		Serial.println(F("Unable to begin:"));
		Serial.println(F("1.Recheck the connection."));
		Serial.println(F("2.Insert the SD card."));
		while(true)
		{
			delay(0);
		}
	}

	myDFPlayer.setTimeOut(500); //Set serial communication time out 500ms

	myDFPlayer.volume(30); //0-30 möglich

	myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);

	myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); //----Set device we use SD as default

	//----------------------------- init Display
	display.setBrightness(0x00); //0-15 möglich

	//----------------------------- init DS3231
	Wire.begin();
}

void loop() {

	if(UhrZeitsprechen == true){
		sayTime();
	} else {
		sMode = (ST)0;
	}

	switch(mode) {
	case Msay:
		//Modus 0 = Uhrzeit und Datum ansagen
		if(alt==false) {
			playSound(34);
			alt=true;
			neu=false;
		}

		getTime();
		ringAlarm();

		//Uhrzeit ansagen
		if(bPressed(bSetMin)) UhrZeitsprechen = true;

		//Datum ansagen
		if(bPressed(bSetHour)) UhrZeitsprechen = true; //Datumfunktion fehlt, spricht ersatzweise aktuelle Zeit

		//Modus wechseln
		if(bPressed(bGetMode)){
			mode = MsetTime;
		}
		break;

	case MsetTime:
		//Modus 1 = Uhrzeit (stunden und minuten) einstellen
		if(neu==false) {
			playSound(31);
			neu = true;
			alt=false;
		}

		setClock();

		//Modus wechseln
		if(bPressed(bGetMode)){
			mode = MsetDate;
		}
		break;

	case MsetDate:
		//Modus 2 = Datum (Tag und monat) einstellen
		if(alt==false) {
			playSound(32);
			alt=true;
			neu=false;
		}

		getDate();

		//Modus wechseln
		if(bPressed(bGetMode)){
			mode = MsetAlarm;
		}
		break;

	case MsetAlarm:
		//Modus 3 = Alarm/Wecker (Stunde und Minute) einstellen
		if(neu==false) {
			playSound(33);
			neu=true;
			alt=false;
		}

		getAlarm();
		setAlarm();

		//Modus wechseln
		if(bPressed(bGetMode)){
			mode = Msay;
		}
		break;
	}//end switch
}

// Zeige Dezimalzahl mit führenden Nullen und Doppelpunkt
void printNum(int num){
	display.showNumberDecEx(num, 0b11100000, true, 4, 0);
}

// Hole die Zeit vom DS3231
void getTime(){
	int time;
	bool A12h, Apm;
	minute=Clock.getMinute();
	hour=Clock.getHour(A12h, Apm);
	time=hour*100;
	time=time+minute;
	printNum(time);
}


/*
 * ------------------Definition der Datein die in dem Ordner liegen
 * 1-21 = durchgängig
 * 22 = thirty
 * 23 = fourty
 * 24 = fifty
 * 25 = and
 * 26 = hours
 * 27 = minutes
 * 28 = beep
 * 29 = beep beep
 * 30 = ringtone / alarm
 * 31 - 34 = modis
 * 35 = ringtone / alarm
 */

// Spielt die Datei in Ordner 01 ab
void playSound(int track){
	myDFPlayer.playLargeFolder(1, track);
}

// Angepasste Zahlenansage bis 59
void sayDecNum(int num){
	if(num<21){
		playSound(num+1);
	}else {
		playSound(20+(num/10)-1);
		if(num%10!=0) {
			playSound((num%10)+1);
		}
	}
}


// Reagieren auf Taster für Uhrzeit (Stunde und Minute) einstellen
void setClock(){

	//Minuten einstellen
	if(bPressed(bSetMin)){
		minute++;
		if(minute>=60){
			hour++;
			minute=0;
		}
		Clock.setMinute(minute);
		Clock.setSecond(0);
		sayDecNum(minute);
		playSound(27);
	}

	//Stunde einstellen
	if(bPressed(bSetHour)){
		hour++;
		if(hour>=24){
			hour=0;
		}
		Clock.setHour(hour);
		sayDecNum(hour);
		playSound(26);
	}
}

// Abfrage ob eine bestimmte Taste gedrückt UND wieder los gelassen wurde
// Solange die Taste gedrückt wird passiert nichts
bool bPressed(int button){
	if(digitalRead(button)==HIGH){
		while(digitalRead(button)==HIGH){}
		return true;
	}
	return false;
}


// Reagieren auf Taster für Alarm/Wecker (Stunde und Minute) einstellen
void setAlarm(){

	//Minuten einstellen
	if(bPressed(bSetMin)){
		alarmminute++;
		if(alarmminute>=60){
			alarmhour++;
			alarmminute=0;
		}
		sayDecNum(alarmminute);
		playSound(27);
	}

	//Stunden einstellen
	if(bPressed(bSetHour)){
		alarmhour++;
		if(alarmhour>=24){
			alarmhour=0;
		}
		sayDecNum(alarmhour);
		playSound(26);
	}
}

// Hole die Zeit für Alarm
void getAlarm(){
	int time = alarmhour*100;
	time=time+alarmminute;
	printNum(time);
}

//Alarmfunktion, prüfen ob Alarmzeit ist
void AlarmCheck() {
	if(hour == alarmhour && minute == alarmminute && mode == Msay && AActive == true) {
		AState = true;
	}
}

//Klingeln, wenn Alarmzeit ist und auf Tastendruck aufhören
void ringAlarm() {
	AlarmCheck();
	if(AActive == true) {
		if(AState == true) {
			playSound(30);
			if(bPressed(bSetMin)) {
				playSound(29);
				AState = false;
			}
		}
	}
}


//Funktion zeigt aktuelles Datum (Tag, Monat, Jahr, Wochentag) im seriellen Monitor
void getDate() {
	bool A12h, Apm, jh;
	byte sekunde = Clock.getSecond();
	byte min = Clock.getMinute();
	byte stunde = Clock.getHour(A12h, Apm);
	byte tag_der_woche = Clock.getDoW();
	byte tag = Clock.getDate();
	byte monat = Clock.getMonth(jh);
	byte jahr = Clock.getYear();
	float temp = Clock.getTemperature();
	Serial.println("");
	Serial.print(stunde);
	Serial.print(":");
	Serial.print(min);
	Serial.print(":");
	Serial.print(sekunde);

	Serial.println("");
	Serial.println("");

	Serial.print("DayOfWeek: ");
	Serial.print(tag_der_woche);

	Serial.println("");
	Serial.println("");

	Serial.print(tag);
	Serial.print(".");
	Serial.print(monat);
	Serial.print(".");
	Serial.print(jahr);

	Serial.println("");
	Serial.println("");

	Serial.print("temperatur: ");
	Serial.print(temp);
}


// Ansage der Momentanen Zeit
void sayTime(){

	switch(sMode) {
	case beepStart:
		if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) {
			playSound(29);
			sMode = (ST)(sMode + 1);
		}
		break;

	case sWordHr:
		if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) {
			playSound(26);
			sMode = (ST)(sMode + 1);
		}
		break;

	case SHr:
		if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) {
			sayDecNum(hour);
			sMode = (ST)(sMode + 1);
		}
		break;

	case SMin:
		if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) {
			sayDecNum(minute);
			sMode = (ST)(sMode + 1);
		}
		break;

	case SWordMin:
		if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) {
			playSound(27);
			sMode = (ST)(sMode + 1);
		}
		break;

	case beepStop:
		if(myDFPlayer.readType() == DFPlayerPlayFinished || myDFPlayer.readState() == DFPlayerPlayFinished) {
			playSound(28);
			UhrZeitsprechen = false;
		}
		break;
	}
	delay(250); //Fixen der DFPlayer Befehlsverarbeitung
}

vbprofi0:
Ich finde den Code langsam schon unübersichtlich.

Wenn Du den Code mal ordentlich formatierst (+T in der IDE hilft Dir dabei) wird das besser.

Gruß Tommy