3.3 Bäromat 🚧

Ein Bär erzählt Geschichten und Kinder hören zu. Audio kommt von DFPlayer mini und steuert Bewegung der Sprache-Servos.
ToDo: Bewegungen der Figuren-Servos in Abhängigkeit des abgespielten Stückes steuern.

Steckplatine
Steckplatine (Ich bin zu ungeduldig für fritzing)

Ablauf

  • Nach Münzeinwurf wird zufällig ein Stück vorgeschlagen.
  • Das Stück kann mit Taste Ja angenommen oder mit Taste Nein abgelehnt werden, dabei wird ein neues Stück vorgeschlagen.
  • Wird nach 8 Sekunden keine Entscheidung getroffen, wird das vorgeschlagene Stück gespielt.
  • Im mp3-Verzeichnis der SD-Karte sind die Stücke 1..ANZ_STUECKE als Beschreibung und ANZ_STUECKE+1..2*ANZ_STUECKE die abzuspielenden Stücke, Beispiel für 30 Stücke:
    • 1..30 Ansagen
    • 31..60 Spielstücke
Zustandsdiagramm
Zustandsdiagramm

Programm

Hinweis zu analogReference(INTERNAL) Zeile 51: Beim UNO ist die Referenzspannung mit INTERNAL und beim Mega mit INTERNAL1V1 einzutragen.
Siehe docs.arduino.cc/language-reference/en/functions/analog-io/analogReference/🔗

// Bäromat V0.5 © 2.2.25 Oliver Mezger MezMedia.de CC BY-SA 4.0
#include <SoftwareSerial.h>
#include <DFPlayerMini_Fast.h>
#include <EEPROM.h>
#include "SchwaetzoLib.h"

//#define DEBUG_ME 1  // Zum Messen der Ausführungsdauer von Schwaetzo:go()
#define ANZ_STUECKE 30 // 1..30 Frage 31..60 Stück
// Pin Definitionen
#define P_BUSY 2     // MP3-Player: Busy-Pin, low aktiv
#define P_POTI A2    // Pin des Potentiometers
#define P_TASTER 3   // Taster gegen GND zu Justieren
#define P_MUENZE A3  // Muenzpruefer prellend low aktiv
#define P_JA A4      // Ja-Taste prellend low aktiv
#define P_NEIN A5    // Nein-Taste prellend low aktiv
// Abfrage Definitionen
#define T_JA !digitalRead(P_JA)
#define PLAYER_FERTIG digitalRead(P_BUSY)

SoftwareSerial mySerial(10, 11); // RX, TX, fuer DF-Player
DFPlayerMini_Fast myMP3;
// Endpositionen der Servos für mundZu und mundAuf kennen z.B. 1800µs -> 180 eintragen
Schwaetzo schwaetzoL(A1,9,180,140); // AudioIn, ServoOut, mundZu, mundOffen
Schwaetzo schwaetzoR(A0,8,170,130);
//Schwaetzo schwaetzoL(A1,9); // AudioIn, ServoOut, noch unjustiert
//Schwaetzo schwaetzoR(A0,8);
Taster t_muenz(P_MUENZE,false); // Taster MUENZE entprellt low active
Taster t_nein(P_NEIN,false);    // Taster NEIN entprellt low active
Wecker weck1;                   // Wecker1

enum zustandstyp {RUHE_ENTRY,RUHE,MUENZE_ENTRY,MUENZE,OPTION_ENTRY,OPTION,SPIELEN_ENTRY,SPIELEN}; // definiere Aufzählungstyp
enum zustandstyp zustand = RUHE_ENTRY; // Definiere und initialisiere Variable
byte lautstaerke = 20; // 0..30
byte stueck=1; // das zu spielende Stueck

void einstellenLautstaerke(){ // Poti einlesen und MP3 Lautstärke einstellen
  unsigned int l = 20+analogRead(P_POTI)/100; // POTI einlesen und 0..1023 auf 20..30 umsetzen
  if (l>30) l=30;
  if (l!=lautstaerke){
    lautstaerke=l;
    Serial.print(F("Neue Lautstaerke: "));
    Serial.println(l);
    myMP3.volume(l);
  }
}

void setup() {
  Serial.begin(9600);      // Ausgabe ueber serieller Monitor
  mySerial.begin(9600);      // Anbindung des DF-Player
  myMP3.begin(mySerial);     // gibt immer true aus
  analogReference(INTERNAL); // A/D Referenzspannung https://docs.arduino.cc/language-reference/en/functions/analog-io/analogReference/
  einstellenLautstaerke();
  delay(1000);
  pinMode(P_BUSY,INPUT);    // Abfragen ob MP3-Player spielt
  pinMode(P_TASTER,INPUT_PULLUP);
  pinMode(P_MUENZE,INPUT_PULLUP);
  pinMode(P_JA,INPUT_PULLUP);
  pinMode(P_NEIN,INPUT_PULLUP);
  Serial.print(F("Anzahl Ordner auf SD-Karte: "));
  Serial.println(myMP3.numFolders());
}

#ifdef DEBUG_ME 
  unsigned long maxSchwaetzoTime = 0;
  unsigned long tmp=0;
#endif

void spieleStueck(byte s){
  Serial.print(F("Spiele Stueck: "));
  Serial.println(s);
  myMP3.playFromMP3Folder(s);
  delay(700); // warten bis Player spielt
}

void bewegeServos(){ // Hier während des Spielens Servobewegungen

}

void loop() {
  static byte i;
  switch (zustand){
    case RUHE_ENTRY: // alles zurücksetzen
      Serial.println(F("RUHE_ENTRY "));
      schwaetzoL.aus();
      schwaetzoR.aus();
      zustand=RUHE;
      break;
    case RUHE: // Warten auf Münzeinwurf
      if (t_muenz.enter())   zustand=MUENZE_ENTRY;
      break;  
    case MUENZE_ENTRY:
      Serial.println(F("MUENZE_ENTRY "));
      stueck=random(ANZ_STUECKE+1);
      einstellenLautstaerke();
      spieleStueck(stueck);
      zustand = MUENZE;
      break;
    case MUENZE:
      if(T_JA)                 zustand = SPIELEN_ENTRY;
      else if(PLAYER_FERTIG)   zustand = OPTION_ENTRY;
      else if(t_nein.enter())  zustand = MUENZE_ENTRY;
      break;
    case OPTION_ENTRY:
      Serial.println(F("OPTION_ENTRY "));
      weck1.stellen(8000); // 8 Sekunden
      zustand = OPTION;
      break;
    case OPTION:
      if(T_JA || weck1.fertig()) zustand = SPIELEN_ENTRY;
      else if(t_nein.enter())    zustand = MUENZE_ENTRY;
      break;    
    case SPIELEN_ENTRY:
      schwaetzoL.an(); // Servosignal anschalten
      schwaetzoR.an();
      spieleStueck(stueck+ANZ_STUECKE);
      Serial.println(F("SPIELEN_ENTRY "));
      zustand = SPIELEN;
      break;  
    case SPIELEN: 
      schwaetzoL.go();  // Audio messen und auf Servo ausgeben
      schwaetzoR.go();
      bewegeServos();
      if(PLAYER_FERTIG){ // MP3 spielt? Low aktiv
        zustand=RUHE_ENTRY;        
      }
      break;
  }
}

Schwätzomat-Library

// SchwaetzoLib.h V1.3 © 02.02.2025 Oliver Mezger MezMedia.de CC BY-SA 4.0
#ifndef SchwaetzoLib_h
#define SchwaetzoLib_h
#endif

#include "Arduino.h"
#include <Servo.h>
#define DEFAULT_MIN_AUDIO 20 // Voreinstellung Minimaler Audiolevel bei rauschenden Aufnahmen
#define DEFAULT_MAX_AUDIO 60 // Voreinstellung Maximaler Audiolevel
#define STILL_SCHWELLE 3     // Schwelle 1/4 Maximum (nicht verwendet)

class Schwaetzo{
  public:
    Schwaetzo(byte ein,byte aus); // AudioEingang ServoAusgang
    Schwaetzo(byte ein,byte aus,byte mzu, byte moffen); // AudioEingang ServoAusgang Servopositionen
    void setMundZu(byte zu);      // Servoposition für mundZu einstellen
    void setMundOffen(byte auf);  // Servoposition für mundAuf einstellen
    void go(); // periodisch aufrufen um Analog einzulesen und Servo zu steueren
    void an(); // Servo wird angeschlossen
    void aus();// Servo wird deaktiviert
    byte getMaxAudio();  // Audio Maximalwert
    byte getLastAudio(); // letzer ermittelter Audiowert für Servo
    void moveServo(byte n); // Servo an Position n bewegen zur Justage
    void schreibeEEPROM();  // Servodaten ins EEPROM schreiben
    void leseEEPROM();      // Servodaten aus EEPROM lesen
  private:
    static byte instanzen;           // Klassenvariable
    byte instanzNummer;              // die Schwaetzoinstanzen werden durchnummeriert für EEPROM Adressen
    Servo servo;                     // Servo-Instanz
    byte audioEingang;               // AnalogPin fuer Sound
    byte servoAusgang;               // Servoanschluss
    byte mundZu = 150;               // Servoposition wenn Mund zu {100..200}
    byte mundOffen = 150;            // Servoposition wenn Mund offen {100..200}
    byte servoWeg = 0;               // mundOffen-nunZu
    byte soundSampels = 0 ;          // Zaehler fuer Messungen in einer Epoche
    byte minAudio = DEFAULT_MIN_AUDIO; // leisester Wert eines Stueckes (Rauschen)
    byte maxAudio = DEFAULT_MAX_AUDIO; // lautester Wert eines Stueckes
    uint16_t audio = 0;              // lautester Wert von n Messungen
    byte audioShift = 1;             // analogReadWert >> 1 wird bei lautem Audio erhöht um innerhalb 8 Bit zu bleiben
    byte lastAudio = 0;              // für Ausgabe auf Seriellem Plotter
};
class Taster{ // Klasse zum Entprellen von Tastern
  public:
    Taster(byte p, bool ha); // Pin, highactive
    bool enter();            // Taste gedrückt
    bool exit();             // Taste losgelassen
  private:
    byte pin;
    bool highactive;
    bool old;
};
class Wecker{  // Klasse für Wecker
  public:
    Wecker();
    void stellen(unsigned int n);
    bool fertig();
  private:
    unsigned long weckzeit;
};
// SchwaetzoLib.cpp V1.3 © 02.02.2025 Oliver Mezger MezMedia.de CC BY-SA 4.0

#include "Arduino.h"
#include "SchwaetzoLib.h"
#include <EEPROM.h>

byte Schwaetzo::instanzen = 0; // Klassenvariable für Instanznummer

Schwaetzo::Schwaetzo(byte ein,byte aus){ // AudioEingang ServoAusgang für Verfahren Servo-Justage mit Automat
  instanzNummer = instanzen++; // die Instanzen nummerieren wegen EEPROM
  audioEingang = ein;
  servoAusgang = aus;
  pinMode(aus, OUTPUT);  // Servosignal = 0
  digitalWrite(aus,LOW); // einstellen
}

Schwaetzo::Schwaetzo(byte ein,byte aus,byte mzu, byte moffen){ // AudioEingang ServoAusgang MundZu MundOffen
  Schwaetzo(ein,aus);
  setMundZu(mzu);
  setMundOffen(moffen);
}

void Schwaetzo::setMundZu(byte zu){     // Servoposition für mundZu einstellen
  if (zu>=100 && zu<= 200) mundZu=zu;
  else{
    Serial.print(F("setMundZu: Wert passt nicht: "));
    Serial.println(zu);
  } 
  servoWeg = abs(mundOffen-mundZu);
}

void Schwaetzo::setMundOffen(byte auf){     // Servoposition für mundOffen einstellen
  if (auf>=100 && auf<= 200) mundOffen = auf;
  else{
    Serial.print(F("setMundOffen: Wert passt nicht: "));
    Serial.println(auf);
  } 
  servoWeg = abs(mundOffen-mundZu);
}

void Schwaetzo::go(){ // Sample aufnehmen braucht 130µs somit bei 2 Kanälen 3,846 kHz Abtastfrequenz
  static uint16_t n;
  if (soundSampels >=100){ // wurden genug Sampels in Epoche aufgenommen 3846Hz/50Hz=77 
    soundSampels = 0;
    if (audio > 255){ // wenn es zu laut ist Messungen abschwächen
      audio = 255;
      audioShift++;   // mehr Vorteilen, Messwert halbieren
      minAudio = DEFAULT_MIN_AUDIO;
      maxAudio = DEFAULT_MAX_AUDIO;
    } else {
      if (audio > maxAudio) maxAudio = audio;  // Maximallautstärke des Stückes merken
      if (audio < minAudio) minAudio = audio;  // Minimallautstärke des Stückes merken
      if(audio>=minAudio) audio -= minAudio;   // Rauschen abziehen
      if (audio < maxAudio/2){  // leise Stellen verstärken
        n = audio * servoWeg * 2 / maxAudio; // Servoausschlag berechnen
      } else {
        n = audio * servoWeg / maxAudio;     // Servoausschlag berechnen
      }
      if(n>servoWeg) n = servoWeg; // Servoausschlag begrenzen
      if(mundOffen>mundZu) n = mundZu + n;
      else n = mundZu - n;
      servo.writeMicroseconds(n*10); // Servo Pulsweite ausgeben
    }
    /*if (audio > (maxAudio>>stillSchwelle)){ // Schwelle 1/8 Maximum
    //sprachSituation |= 1;
    //ausTimer =0;
    }*/
    lastAudio = audio; // letzten Wert merken
    audio=0;
  }
  else{ // Audio-Messung
    n = analogRead(audioEingang)>>audioShift; // Audiosignal lesen 
    if(n>audio) audio=n; // Maximum finden
    soundSampels++;
    lastAudio=0;
  }
}

void Schwaetzo::an(){  // Servo wird angeschlossen, bekommt Signal
  minAudio = DEFAULT_MIN_AUDIO; // neues Stück neues Glück
  maxAudio = DEFAULT_MAX_AUDIO;
  audio = 0;
  soundSampels = 0;
  lastAudio = 0;
  audioShift = 1; // Abschwächen von analogRead
  servo.attach(servoAusgang,1000,2000);  // Servo anschließen 
  Serial.print(F("Servo angeschlossen "));
  Serial.println(servoAusgang);
}

void Schwaetzo::aus(){  // Servosignal wird abgeschaltet
  servo.writeMicroseconds(mundZu*10); // Mund schließen 
  delay(100);                         // warten bis ausgeführt
  servo.detach();                     // Servosignal abschalten
  Serial.print(F("Servo aus, audioShift: ")); // Infos über das Stück ausgeben
  Serial.print(audioShift);
  Serial.print(F(" MinLevel: "));
  Serial.print(minAudio);
  Serial.print(F(" MaxLevel: "));
  Serial.println(maxAudio);
}

byte Schwaetzo::getMaxAudio(){ // maximaler Audiowert
  return maxAudio;
}

byte Schwaetzo::getLastAudio(){ // letzter Audiowert, 0 während Messung
  return lastAudio;
}

void Schwaetzo::moveServo(byte n){ // Servo einstellen während Justage
  servo.writeMicroseconds(n*10);
}

void Schwaetzo::schreibeEEPROM(){  // Servodaten ins EEPROM schreiben
  EEPROM.update((instanzNummer+1)*2, mundZu);
  EEPROM.update((instanzNummer+1)*2+1, mundOffen);
}

void Schwaetzo::leseEEPROM(){      // Servodaten aus EEPROM lesen
  byte i;
  i = EEPROM.read((instanzNummer+1)*2);
  setMundZu(i);
  i = EEPROM.read((instanzNummer+1)*2+1);
  setMundOffen(i);
}

Taster::Taster(byte p, bool ha){ // Pin, highactive
  old=false; // Taste nicht gedrückt
  pin=p;
  highactive=ha;
}
bool Taster::enter(){          // Taste gedrückt
  bool ausgabe = false;
  bool test = digitalRead(pin);
  if (old != test){ // hat sich was getan?
    delay(10); // 10 ms warten
    test=digitalRead(pin); // noch mal einlesen
    if (old != test){  // immer noch anders?
      ausgabe = highactive?(!old & test):(old & !test); // steigende:fallende Flanke
      old = test;
    }
  }
  return ausgabe;
} 
bool Taster::exit(){             // Taste losgelassen
  bool ausgabe = false;
  bool test = digitalRead(pin);
  if (old != test){ // hat sich was getan?
    delay(10); // 10 ms warten
    test=digitalRead(pin); // noch mal einlesen
    if (old != test){  // immer noch anders?
      ausgabe = highactive?(old & !test):(!old & test); // fallende:steigende Flanke
      old = test;
    }
  }
  return ausgabe;
}

Wecker::Wecker(){
  weckzeit = millis();
}
void Wecker::stellen(unsigned int n){
  weckzeit = millis()+n;
}
bool Wecker::fertig(){
  return millis()>=weckzeit;
}