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.

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