Der sprechende Farbdetektor - Teil 3

Einführung

Nachdem wir im ersten Teil dieser Blogreihe den MP3-Player für die Sprachausgabe in Betrieb genommen und einen Programmablauf entwickelt haben, habe ich Ihnen im zweiten Teil gezeigt, wie der TCS3200 Farbsensor angeschlossen und programmiert wird. Im dritten und letzten Teil werden wir nun beides zusammenfügen, die Farben scannen und den Farbdetektor sprechen lassen. Wir werden einen Live-Kalibrierungsmodus implementieren und die Stromversorgung mit einem Akku mobil machen. Los geht's.

Was wir benötigen

Anzahl Bauteil
1 TCS3200 Farbsensor Modul
1 DFPlayer Mini MP3 Player Modul
1 Mikro-SD Karte
1 Arduino Nano V3.0
1 Lautsprecher (Max 3W)
Verbindungskabel
Widerstand 1 KOhm
1 Veränderbarer Widerstand (Potentiometer)
2 Taster
Computer mit Arduino IDE und Internetverbindung
Externe Spannungsquelle (empfohlen), 7 - 12V
Farbreferenzkarten
LiPo Akku 3,7 V
1 MT3608 Step up Modul
1 TP4056 Laderegler Modul
1 Voltmeter


Vorbereitung 

Ich gehe davon aus, dass Sie die Schaltung aus Teil 2 aufgebaut und die SD-Karte mit den Sprachdateien in den SD-Slot des MP3-Players eingeschoben haben.


DFPlayer Mini Pins

Arduino Nano Pins

VCC

5V

GND

GND

RX

über 1 KOhm an D11 (TX)

TX

D10 (RX)

 

Lautsprecher

SPK_1

Rot (Plus)

SPK_2

Schwarz (Minus)

Potentiometer

Arduino Nano Pins

1

GND

2 (Mitte)

A0

3

+5V

Taster

Arduino Nano Pins

1

D12

2

GND

TCS3200 Pins

Arduino Nano Pins

VCC

5V

GND

GND

OE

D3

S0

D4

S1

D8

S2

D6

S3

D7

OUT

D5

Für einen Funktionstest laden wir das Programm aus Teil 1 auf den Arduino:

Kompletter Quellcode: 1.2DFPlayerStartsoundPotiTaster.ino

Außerdem testen wir den Farbsensor mit dem Programm aus Teil 2:

Kompletter Quellcode: 2.0TCS3200Test.ino

Programme zusammenfügen

Unsere Aufgabe besteht nun darin, die Programme aus Teil 1 und 2 zusammenzufügen. Mit dem Programm aus Teil 1 können wir das Gerät schon relativ simpel bedienen. Es kann sogar schon Farben über die Sprachausgabe ausgeben. Mit den Programmen aus Teil 2 können wir die richtigen Farben scannen. Wir müssen sie also "nur" noch an den Quellcode aus Teil 1 übergeben.

Zuerst ändern wir noch einmal das Programm "tcs3200calibration" für die Kalibrierung aus der tcs3200-Bibliothek, das wir bereits in Teil 2 verwendet haben. Wie ich am Ende von Teil 2 erwähnt hatte, erweitern wir die Arrays distinctRGB, distinctColors und colorNames. Wir brauchen das, da wir mehr Farben nutzen. Folgende Zeilen müssen wir also ändern:

#define num_of_colors 9

int distinctColors[num_of_colors] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
String colorNames[num_of_colors] = {"Rot", "Gruen", "Blau", "Schwarz", "Weiss", "Gelb", "Orange", "Pink", "Braun"};

Damit haben wir die Anzahl und die Reihenfolge für unsere Farben festgelegt. Die Liste passt jetzt zu den Audiodateien, die wir in Teil 1 erstellt haben.

Damit wir die Farben später leichter übernehmen können, füge ich in den Programmablauf eine Unterbrechung ein. So hatten wir das auch in dem Beispiel "Calibrate_TCS230" bereits in Teil 2 gemacht. Außerdem formatieren wir die Ausgabe so, dass wir die Werte leichter kopieren können. das Array distinctRGB[] [] schreiben wir für die Lesbargkeit um, ohne den Inhalt zu verändern:

int distinctRGB[num_of_colors][3] = {
  {17, 4, 7},
  {9, 11, 11},
  {4, 8, 18},
  {3, 3, 4},
  {32, 26, 45},
  {27, 20, 14},
  {28, 15, 12},
  {18, 7, 17},
  {7, 4, 6}
};

Damit können wir leichter auf die drei Werte pro Farbe zugreifen. Wir deklarieren nun noch einen Zeichenpuffer für die formatierte Ausgabe:

char serialBuffer[55];

Die loop()-Funktion ändern wir folgendermaßen:

void loop() {
  // Auf Eingabe warten
  while (Serial.available() == 0) {
    if (Serial.available() != 0) {
      break;
    }
  }
  // Serial Puffer loeschen
  while (Serial.read() != -1) {}

  Serial.println(colorNames[tcs.closestColor(distinctRGB, distinctColors, num_of_colors)] );

  red = tcs.colorRead('r');   //reads color value for red
  Serial.print("R= ");
  Serial.print(red);
  Serial.print("    ");
 
  green = tcs.colorRead('g');   //reads color value for green
  Serial.print("G= ");
  Serial.print(green);
  Serial.print("    ");

  blue = tcs.colorRead('b');    //reads color value for blue
  Serial.print("B= ");
  Serial.print(blue);
  Serial.print("    ");

  Serial.println();
 
  // Ausgabe
  sprintf(serialBuffer, "Rot Gruen Blau: {%d, %d, %d}  ", red, green, blue);
  Serial.println(serialBuffer);

}

Das Programm wartet auf die Eingabe am Seriellen Monitor. Dann wird die Farbe einmal aufgezeichnet und die Werte so ausgegeben, dass wir sie direkt kopieren und hier im Quelltext wieder einfügen können. Das machen wir nacheinander mit allen Farben, die wir oben in der Liste eingetragen haben. Achten Sie dabei auf die richtige Position im entsprechenden Array, damit keine Farbe vertauscht wird.


Die Ausgabe des Farbnamens können Sie vorerst noch ignorieren. Nachdem wir alle Farben aufgezeichnet und hier in den Quellcode eingetragen haben, übertragen wir das Programm noch einmal auf den Arduino. Wenn wir die Farben jetzt scannen, sollte das angezeigte Wort zur Farbe passen.

Kompletter Quellcode: 3.0tcs3200calibration_new.ino

Diesen Quellcode übertragen wir nun in das Programm, das wir in Teil 1 geschrieben hatten. Wir übernehmen die Konstanten, Variablen und Arrays. Jetzt müssen wir nur noch in die Zustandsmaschine in Case 6 eintragen, welche Farbe ausgegeben werden soll. Aktuell wird mit dieser Zeile die Audiodatei mit dem Wort "Rot" ausgegeben:

myDFPlayer.play(4);

Mit dieser Zeile geben wir im Kalibrierungsprogramm den Namen der Farbe auf dem Bildschirm aus:

Serial.println(colorNames[tcs.closestColor(distinctRGB, distinctColors, num_of_colors)]);

wir brauchen daraus nur den Funktionsaufruf closestColor():

tcs.closestColor(distinctRGB, distinctColors, num_of_colors)

Dieser gibt uns die Nummer der erkannten Farbe zurück. Allerdings beginnen die Farbnummern bei 0. Unsere Nummern für Audiodateien, die die Farben enthalten, beginnen mit 1. Die erste Farbe hat die Nummer 4. Werfen Sie dafür noch einmal einen Blick in die Tabelle aus Teil 1 in Abschnitt "Sprachausgabe". Wir brauchen ein Offset für die Nummern in der Tabelle. Unsere erkannte Farbe Rot hat die Nummer 0. In der Tabelle ist das Nummer 4. Also ein Offset von 4. Dafür deklarieren wir uns eine weitere Konstante:

#define FILEOFFSET 4

Die Zeile für die Ausgabe der Audiodatei ist dann eine Kombination aus den eben genannten Zeilen:

myDFPlayer.play(tcs.closestColor(distinctRGB, distinctColors, num_of_colors) + FILEOFFSET);

Wir übergeben die erkannte Nummer an den Audioplayer und addieren zusätzlich unser Offset. Für die Ausgabe des Wortes der Farbe übernehmen wir die Zeile aus dem Programm für die Kalibrierung:

Serial.println(colorNames[tcs.closestColor(distinctRGB, distinctColors, num_of_colors)]);

Wenn sie das Programm nun auf den Arduino laden, sollte nach dem Scannen der Farbe das passende Wort dafür wiedergegeben und angezeigt werden, sobald Sie den Taster loslassen.

Kompletter Quellcode: 3.1sprechenderFarbdetektor.ino

An dieser Stelle funktioniert der sprechende Farbdetektor schon wie gewünscht. Er kann Farben erkennen und sie uns laut aussprechen.

Weitere Stimmen

Wir möchten nun noch andere Stimmen nutzen. Dafür brauchen wir neue Audiodateien. Nutzen Sie wieder die verlinkten Webseiten aus Teil 1, oder nehmen Sie sie selbst auf. Anschließend bearbeiten Sie sie so, dass sie die gleiche Reihenfolge haben, wie unsere bereits vorhandenen Audiodateien. Ich habe eine männliche Stimme und eine weitere Sprache hinzugefügt. Somit habe ich nun weiblich und männlich in deutscher, sowie weiblich und männlich in englischer Sprache. Nachdem ich die Audiodateien bearbeitet habe, kopiere ich sie auf die SD-Karte. Achten Sie darauf, die Dateien in der richtigen Reihenfolge zu übertragen, am besten einzeln nacheinander. Meine Nummerierung der Audiodateien reicht nun bis 53.

Wir hatten zuvor bereits ein Offset eingefügt, damit die Dateinummer zur Farbnummer passt. Solch ein Offset können wir nun auch nutzen, um die Stimme zu wechseln. Hier mal eine Auflistung der Nummerierung meiner Stimmen:

Nummern

Stimme

2 - 14

weiblich, deutsch

15 - 27

männlich, deutsch

28 - 40

weiblich, englisch

41 - 53

männlich, englisch

Zwei wichtige Überlegungen müssen nun umgesetzt werden:

  • Wie wechseln wir die Stimme?
  • Wo ändern wir das im Programm?

Zur ersten Frage sollten wir überlegen, für welche Zielgruppe wir das Gerät normalerweise konstruieren. Da ich das Gerät vorrangig für Menschen mit Sehbehinderung konsturieren würde, würde ich die Bedienung so einfach wie möglich halten. Den Taster, den wir bereits verwenden, nutzen wir mit kurzem Tastendruck und Taste gedrückthalten. Man könnte jetzt noch Doppelklicks oder Dreifachklicks hinzufügen. Eine einfachere Bedienung erreichen wir jedoch, wenn wir einen weiteren Taster hinzufügen. Wir schließen ihn an A5 des Arduinos und GND an:


Wir definieren und initialisieren den Tasterpin im Programm (warum ich hier den analogen Pin A5 als digitalen Eingang verwende, erkläre ich im Fazit):

#define TasterPin2 A5

// im setup:
pinMode(TasterPin2, INPUT_PULLUP);

Wir brauchen weitere Variablen. Einen Zähler, mit dem wir die Stimme festlegen, ein Offset zu den anderen Stimmen und eine maximale Anzahl der Stimmen:

int voiceNumber = 0;
unsigned int offsetVoice = 13;
int maxVoices = 4;

Wir benötigen eine Funktion, mit der wir den zweiten Taster auslesen und auf eine Änderung reagieren. Wir nutzen als Vorlage die Funktion taster(), die wir bereits haben. So können wir das Entprellen übernehmen. Eventuell könnten wir später beide Funktionen miteinander verbinden, um redundanten Quellcode zu verhindern. Fürs erste kopieren wir die Funktion, benennen sie um in changeVoice() und ändern sie wie folgt:

void changeVoice() {
  taster_status_neu_2 = digitalRead(TasterPin2);          //taster ist active low

  // Tasteraenderung erkennen und Prelltimer starten
  if (taster_status_neu_2 != taster_status_alt_2 && !taster_change_2) {
    start_zeit = millis();
    taster_change_2 = true;
  }
  // wenn Taster gedrueckt
  if (taster_status_neu_2 == LOW && taster_status_alt_2 == LOW && taster_change_2) {
    // Taster entprellen
    if (millis() - start_zeit_2 > prell_delay) {
      taster_change_2 = false;
      voiceNumber++;
      if (voiceNumber > maxVoices - 1) {
        voiceNumber = 0;
      }
      myDFPlayer.play(14 + voiceNumber * offsetVoice);
      Serial.println("Sprache geaendert");
      state = 1;
    }
  }
  taster_status_alt_2 = taster_status_neu_2;
}

Wir brauchen dafür auch die passenden Variablen:

unsigned long neue_zeit_2 = 0;
unsigned long start_zeit_2 = 0;
bool taster_status_neu_2 = HIGH;         
bool taster_status_alt_2 = HIGH;
bool taster_change_2 = false;

Wenn der Taster gedrückt wurde, zählen wir die Nummer für die Stimmen um 1 hoch. Haben wir die maximale Anzahl der Stimmen erreicht, wird der Wert zurück auf 0 gesetzt und somit wieder die erste Stimme verwendet. Als Nächstes geben wir die nun eingestellte Sprache per Audiowiedergabe aus. Der Player bekommt die Nummer der Audiodatei. Das wäre für die erste Stimme die Datei Nummer 14. Wir addieren die Multiplikation aus Stimmnummer und Offset hinzu. Das ergibt für die erste Stimmee wie gesagt die 14 (denn 0 * 13 ist 0 und 14 + 0 bleibt 14), für die zweite Stimme ist es 14 + 1 * 13, was 27 ergibt. Das ist bei mir das gleiche Wort nur von der männlichen Stimme gesprochen. Das geht dann immer so weiter. Die zweite Stimme ist 14 + 2 * 13 = 40 und 14 + 3 * 13 = 53. Wir haben also einen kleinen Algorithmus, der uns immer das gleiche Wort von einer anderen Stimme ausgibt. Daher ist es wichtig, die Reihenfolge der Dateien für alle Stimmen gleich zu lassen.

Wir ändern dann die state-Variable auf den Wert 1. Damit springen wir in der Zustandsmaschine zurück zum Zeitpunkt, bevor der Infotext gesprochen wurde. Wenn jemand bereits die Farbe eingescant hat und ändert dann die Sprache, soll der Info-Text noch einmal in der neuen Sprache ausgegeben werden, bevor neu gescant wird.

In der statemachine()-Funktion ergänzen wir nun noch die jeweiligen Ausgaben:

...
case 2: {                                 // Hilfe abspielen
      myDFPlayer.play(2 + voiceNumber * offsetVoice);
...
case 4: {                                 // Starte Scan
      myDFPlayer.play(3 + voiceNumber * offsetVoice);
...
case 6: {                                 // Farbe ausgeben
      myDFPlayer.play(tcs.closestColor(distinctRGB, distinctColors, num_of_colors) + FILEOFFSET + voiceNumber * offsetVoice);
...

Als letzten Schritt müssen wir die changeVoice()-Funktion noch in der Hauptschleife aufrufen:

void loop() {
  taster();
  changeVoice();
  lautstaerke();
  statemachine();
}

Wenn wir das Programm auf den Arduino laden, können wir den Farbdetektor genauso ausführen wie vorher. Zusätzlich können wir mit dem zweiten Taster die Stimme bzw. die Sprache ändern.

Kompletter Quellcode: 3.2sprechenderFarbdetektormehrSprachen.ino

Live-Kalibrierung

Wir haben gesehen, dass wir den Sensor kalibrieren, also auf unsere Farben einstellen müssen. Im Moment brauchen wir dafür ein separates Arduino-Programm, müssen die Farbwerte kopieren und dann alles in unser Programm übernehmen. Wenn sich der Sensor in einem festen Gehäuse befindet, in dem er vor Umgebungslicht geschützt ist, muss man die Kalibrierung in der Regel nur ein einziges Mal durchführen. Denn der Abstand des Sensors zur Farbfläche und das Umgebungslicht sind zwei Störfaktoren, sobald sie sich verändern. Um es trotzdem zu lösen, dass man die Kalibrierung durchführen kann, ohne die Werte hin und her zu kopieren, brauchen wir eine Möglichkeit, mit der wir die Daten erfassen und dann direkt in das Array mit den Farbwerten übertragen. Allerdings nur dann, wenn wir einen Kalibrierungsmodus ausführen.

Sinnvoll wäre es, wenn die Kalibrierung bei Programmstart ausgeführt wird. Allerdings nicht immer, sondern nur, wenn ein Taster oder Schalter betätigt wurde. Die Frage ist, ob die Kalibrierung im Livebetrieb immer wieder ausgeführt werden soll, oder nur einmalig z.B. nach einem Neustart mit gedrückter Taste. Ich entscheide mich dazu, beide Taster zu verwenden. Wenn sie zusammen gedrückt sind und man startet den Arduino, gelangt man in den Kalibrierungsmodus. Dann kann man mit dem ersten Taster durch alle Farben schalten und dafür jeweils die Referenzfarbe einscannen. Danach gelangt man wie üblich in den normalen Modus.

Wir benötigen einige neue Variablen. Der Kalibrierungsmodus muss als Flag gesetzt werden. Dann möchte ich nur zu Beginn eine Schleife durchlaufen, die darauf wartet, dass beide Taste wieder losgelassen werden. Außerdem zählen wir unsere Farbnummern von 0 an aufwärts, damit wir sie einzeln einscannen können. Für die formatierte Ausgabe nutze ich wie im Programm für die separate Kalibrierung ein char Array:

bool kalibrieren = false;
bool firstStart = true;
int kalCounter = 0;
char serialBuffer[55];

Sämtliche Werte im Array mit den Farbwerten setzen wir auf 0:

int distinctRGB[num_of_colors][3] = {0};

Zu Beginn des Programms, also in der setup()-Funktion, erkennen wir das gleichzeitige Gedrückthalten beider Taster:

  //kalibrieren, wenn beim Start beide Taster gedrueckt sind
  if (!digitalRead(TasterPin) && !digitalRead(TasterPin2)) {
    kalibrieren = true;
  }

Die Tasterpins sind low active und durch das doppelte logische & ist der Ausdruck nur wahr, wenn beide den Zustand LOW haben. Dann wird der Flag, also die boolsche Variable kalibrieren, auf true gesetzt. Damit ist der Kalibrierungsmodus aktiv.

Meine erste Überlegung war nun, eine Funktion zu schreiben, in der die Kalibrierung stattfindet. Mir ist dabei aufgefallen, dass ich viel von dem bereits geschriebenen Programmcode dort hineinkopieren müsste. Ich kam dann zu dem Schluss, dass ich das gar nicht brauche. Ich muss nur in meinem bisherigen Quellcode an einigen Stellen unterscheiden, ob der Kalibrierungsmodus aktiv ist, oder nicht.

In diesem Modus möchte ich wie bisher die Stimme umstellen können. Allerdings soll dann beim Wechsel nicht das Wort für die Sprache ausgegeben werden und ich möchte an den kompletten Programmanfang zurück. Wir ergänzen daher in der Funktion changeVoice() folgende Passage:

myDFPlayer.play(14 + voiceNumber * offsetVoice);
Serial.println("Sprache geaendert");
state = 1;

wird geändert in:

Serial.println("Sprache geaendert");
if (kalibrieren) {
      state = 0;
}
else {
      myDFPlayer.play(14 + voiceNumber * offsetVoice);
    state = 1;       
}

Ich habe überall, wo ich zwischen Kalibrierungs- und normalem Modus unterscheide, den bisherigen Programmteil in den else-Zweig der if-Abfragen geschrieben. Ist der kalibrieren-Flag nicht gesetzt, funktioniert das Programm dadurch genau wie vorher. Daher beziehe ich mich im Folgenden immer nur auf den if-Zweig.

Alle weiteren Änderungen finden nun in der Funktion statemachine() statt. Im case 0 unterscheiden wir, ob wir im Kalibrierungsmodus sind, oder nicht. Wenn ja, geben wir das Wort "kalibriere" oder die englischen Äquivalente dazu aus. Wir unterscheiden nun, ob wir diesen Teil des Programms zum ersten Mal ausführen (zur Erinnerung: alles läuft in einer Endlosschleife), oder nicht. Nur beim ersten Mal wollen wir erkennen, ob noch beide Taster gedrückt sind. Bis beide losgelassen wurden, läuft das Programm an dieser Stelle in einer weiteren Endlosschleife. Das Loslassen müssen wir auch entprellen. Da wir uns hier am Beginn des Programms befinden, habe ich es mit einem delay() gelöst, da wir hier keinen unterbrechungsfreien Ablauf benötigen. Wir setzen dann noch den firstStart-Flag auf false, damit dieser Teil nicht noch einmal ausgeführt wird. Ändern wir nämlich die Stimme, gelangen wir wieder in diesen case. Das habe ich deshalb so gewählt, da ich nach der Änderung der Stimme nicht das Wort für die Sprache abspiele, sondern das Wort "Kalibriere" mit der neuen Stimme:

case 0: {
  if (kalibrieren) {
      myDFPlayer.play(13 + voiceNumber * offsetVoice);     // "Kalibriere." abspielen
      Serial.println("Kalibriere.");
      if (firstStart) {
        while (!(digitalRead(TasterPin) && digitalRead(TasterPin2))) {
              // Serial.println("beide Taster loslassen");
        }
        // dirty realease debounce
        // nonblocking hier noch nicht notwendig
        delay(prell_delay);
        firstStart = false;
      }
  }
  else {
      myDFPlayer.play(1);                     // Startsound abspielen
      Serial.println("Programm wird gestartet.");       
  }
  state = 1;
} break;

Der nächste case, der geändert wird, ist Nummer 4. Im normalen Modus wird hier das Wort "Scanne." wiedergegeben. Im Kalibrierungsmodus möchte ich, dass die Farbe genannt wird, die als Nächstes eingescannt werden soll. Um die richtige Farbe auszugeben, brauchen wir wieder ein Offset:

case 4: {                                 // Starte Scan
  if (kalibrieren) {
      myDFPlayer.play(FILEOFFSET + kalCounter + voiceNumber * offsetVoice);
      Serial.println(colorNames[kalCounter]);
  }
  else {
      myDFPlayer.play(3 + voiceNumber * offsetVoice);
      Serial.println("Scanne...");       
  }
  ausgegeben = false;
  state = 5;
} break;

Das Offset setzt sich zusammen aus mehreren Teiloffsets. FILEOFFSET schiebt den Zähler bis zur ersten Farbe, in unserem Fall Rot. Der kalCounter schiebt bis zur aktuellen Nummer, der einzuscannenden Farbe. Die Multiplikation aus voiceNumber und offsetVoice ergibt wie gewohnt die Stimme, die wir gewählt haben.

Die letzte Änderung findet in case 6 statt. Hier hatten wir ursprünglich die Farbe ausgegeben, die wir gescannt haben. Diese wollen wir im Kalibrierungsmodus in das Farbarray speichern. der kalCounter gibt an, welche Farbnummer. Er wird hier in jedem Durchlauf um 1 hochgezählt. Das Auslesen der einzelnen Farbwerte aus dem Sensor nutzen wir aus dem Programm für die separate Kalibrierung. Dort wurde das bereits aus dem Beispiel der Bibliothek übernommen.

Wenn alle Farben durchlaufen wurden, entspricht der kalCounter der Anzahl der Farben numofcolors. An dieser Stelle wird noch einmal das gesamte Array mit den Farbwerten auf dem Seriellen Monitor ausgegeben, der Kalibrierungsmodus beendet und die Zustandsmaschine wieder auf case 0 zurückgesetzt. Damit läuft das Programm im normalen Modus weiter und kann benutzt werden wie bisher:

case 6: {                                 // Farbe ausgeben
  if (kalibrieren) {
      distinctRGB[kalCounter][0] = tcs.colorRead('r');
      distinctRGB[kalCounter][1] = tcs.colorRead('g');
      distinctRGB[kalCounter][2] = tcs.colorRead('b');
      Serial.print(distinctRGB[kalCounter][0]);
      Serial.print(" ");
      Serial.print(distinctRGB[kalCounter][1]);
      Serial.print(" ");
      Serial.println(distinctRGB[kalCounter][2]);
      kalCounter++;
      if (kalCounter >= num_of_colors) {
        for (int n = 0; n < num_of_colors; n++) {
            sprintf(serialBuffer, " - Rot Gruen Blau: {%3d, %3d, %3d}",distinctRGB[n][0], distinctRGB[n][1], distinctRGB[n][2]);
            Serial.print(colorNames[n]);
            Serial.println(serialBuffer);
        }
        kalibrieren = false;
        state = 0;
        break;
      }
  }
  else {
      myDFPlayer.play(tcs.closestColor(distinctRGB, distinctColors, num_of_colors) + FILEOFFSET + voiceNumber * offsetVoice);
      Serial.println("Farbe: ");
      Serial.println(colorNames[tcs.closestColor(distinctRGB, distinctColors, num_of_colors)]);       
  }
  state = 3;
} break;

Unser Farbdetektor kann nun kalibriert werden, ohne dass wir Werte aus dem Seriellen Monitor kopieren und in den Quellcode einfügen müssen. Halten Sie die Referenzfarbkarten bereit, laden das Programm auf den Arduino und halten bei Programmstart beide Taster gedrückt.

Kompletter Quellcode: 3.3sprechenderFarbdetektormehrSprachen_LiveKalibrierung.ino

Daten dauerhaft speichern

Die nächste Aufgabe besteht darin, die Farbwerte aus der Kalibrierung irgendwo zu speichern, denn sonst würden sie nach einem Neustart verloren gehen.

Als Speicher gibt es verschiedene Lösungen. Im Handel sind externe Flashspeicher in I²C- und SPI-Ausführung als Breakoutboard erhältlich. Man könnte auch ein externes Micro-SD-Card-Module verwenden und die Daten auf einer SD-Karte speichern. Ebenso wäre ein externes I²C-EEPROM-Modul denkbar. Die SD-Karte wäre etwas überdimensioniert für so wenig Daten. Externer Flash oder EEPROM würden zusätzliche Kosten verursachen, würden aber als Plan B eine Lösung darstellen. Die I²C-Schnittstelle des Arduino Nanos liegen auf den Pins A4 und A5. Wir könnten den zweiten Taster auf einen anderen Pin legen, dann könnte man das realisieren.

Da der Arduino Nano allerdings einen EEPROM onboard hat, können wir diesen auch verwenden. Dabei ist zu beachten, dass dieser Speicher nicht endlos beschrieben werden kann (100.000 Schreibvorgänge). Lesen dagegen ist immer möglich.

Hinweis: Ich habe in der Blogreihe "Arduino: Multi-IO und EEPROM" das Thema schon einmal bearbeitet. In Teil 2 habe ich dort den internen EEPROM verwendet.

Ich gehe davon aus, dass wir nicht oft kalibrieren müssen. Daher entscheide ich mich für diese Lösung.

An der Stelle im Programm, an der alle Farben im Kalibrierungsmodus durchlaufen wurden und noch einmal das gesamte Farbarray auf dem Seriellen Monitor ausgegeben werden, wollen wir die Daten zusätzlich ins EEPROM übernehmen. Das geschieht in case 6. Wir fügen vor oder nach dieser Zeile:

kalibrieren = false;

folgende neue Zeile ein:

EEPROM.put(0, distinctRGB);

Die Funktion put() erlaubt es uns, komplette Objekte in das EEPROM zu übertragen. Dabei werden nur Werte überschrieben, die sich unterscheiden. Wir schreiben das komplette Array mit den neu eingescannten Farben in das EEPROM beginnend bei Adresse 0.

Das war einfach. Was jetzt noch fehlt, ist das Auslesen des EEPROM, wenn der Arduino gestartet wird. Aber nur dann, wenn nicht der Kalibrierungsmodus ausgeführt wird. Dafür fügen wir im setup() an der Stelle:

//kalibrieren, wenn beim Start beide Taster gedrueckt sind
if (!digitalRead(TasterPin) && !digitalRead(TasterPin2)) {
      kalibrieren = true;
}

einen else-Zweig hinzu:

else {
      EEPROM.get(0, distinctRGB);                 // Daten aus EEPROM lesen
      for (int n = 0; n < num_of_colors; n++) {
            sprintf(serialBuffer, " - Rot Gruen Blau: {%3d, %3d, %3d}",distinctRGB[n][0], distinctRGB[n][1], distinctRGB[n][2]);
            Serial.print(colorNames[n]);
            Serial.println(serialBuffer);
      }
}

Mit der Funktion get() lesen wir das komplette Array ab der Adresse 0 aus und schreiben es in unser leeres Farbarray. Um zu prüfen, ob das geklappt hat, geben wir die Werte noch auf dem Seriellen Monitor aus.

Kompletter Quellcode: 3.4sprechenderFarbdetektormehrSprachenLiveKal_EEPROM.ino

Laden Sie das Programm auf den Arduino, halten beide Tasten gedrückt und durchlaufen einmal den Kalibrierungsmodus. Danach starten Sie den Arduino über die Reset-Taste neu, ohne die Tasten zu betätigen. Sie werden dann sehen, ob die Werte geladen wurden und ob der Farbdetektor Ihre Farben erkennt.

Mobile Spannungsquelle

Wenn jemand das Gerät nutzen möchte, möchte er sicher nicht Abhängig von der nächsten Steckdose sein. Ich werde also auf Akkubetrieb umstellen. Sie können entweder direkt einen 9V-Block am Vin des Arduinos anschließen, da dafür ein Spannungsregler verbaut ist. Es ist auch möglich, eine Powerbank (darf sich bei zu geringer Last nicht abschalten) oder das Raspi Akku Pack am USB-Anschluss anzuschließen.

Ich möchte einen LiPo Akku (Lithium-Ion Polymer) verwenden. Es gibt sie in verschiedenen Formen, Größen und mit unterschiedlicher Kapazität. Ich nutze einen flachen Akku mit einer Kapazität von 2000 mAh und einer Spannung von 3,7 V. Diese Bauform würde den Bau eines flachen Gehäuses begünstigen. Ich möchte jetzt nicht weiter darauf eingehen, wie man die Akkulaufzeit berechnet.

Da die externe Spannungsversorgung des Arduino am Vin mindestens 7 V betragen muss, brauchen wir einen Step-up-Konverter. Damit regeln wir die Spannung auf 9 V. Das sollte passen. Damit wir den Akku auch laden können, nutzen wir ein Mikro-USB-Ladereglermodul. Wie diese Komponenten angeschlossen werden, zeigt Gerald Lechner in seinem Blogbeitrag "5V Akku Stromversorgung mit 3.7 V LiPo Akku und Laderegler". Haben wir das Voltmeter am Vout des Spannungswandlers angeschlossen, drehen wir das Potentiometer, bis wir die 9 V erreicht haben.

Hinweis: Eventuell müssen Sie das Potentiometer sehr lange gegen den Uhrzeiger (als würden Sie einen Wasserhahn auf-) drehen, bis die Spannung erhöht wird. Ich brauchte viele Umdrehungen und hatte schon vermutet, das Gerät sei defekt.

Wir ersetzen nun die bisherige externe Spannungsquelle (sollten Sie eine verwendet haben) und schließen stattdessen die Vout-Anschlüsse des Step-up-Konverters an Vin bzw GND des Arduinos an. Ich empfehle Ihnen, zwischen Vin des Arduinos und Vout des Konverters einen Umschalter einzubauen. So müssen sie nicht jedes Mal den Akku abziehen, um das Gerät auszuschalten. Im folgenden Bild ist der komplette Schaltplan noch einmal abgebildet:


Fazit

Wir haben nun die Elektronik eines mobilen, sprechenden, kalibrierbaren Farbdetektors konstruiert. Man bedient ihn lediglich mit zwei Tastern, einem Einschalter und einem Potentiometer. Eigene Farben zu verwenden, ist etwas eingeschränkt. Je weniger Farben man nutzt, desto ungenauer ist die Gewichtung. Ein Dunkelrot wird dann auch mal als Braun erkannt. Für die Steuerung des Farbsensors habe ich letztendlich die Bibliothek tcs3200 gewählt. Die MD_TCS230 Bibliothek zu verwenden, ist auch möglich. Der Beispielcode ist nur etwas umfangreicher und man ist auf den Pin D5 angewiesen. Daher habe ich mich so entschieden.

Es ist nun sicher noch möglich, den Quellcode zu vereinfachen. Den Farbsensor könnte man mit einem Timeout schlafen legen, um die Akkulaufzeit zu verlängern. Dafür kann man den Frequenzteiler auf LOW / LOW setzen und den OE Pin auf HIGH. Die vier LEDs kann man leider nicht deaktivieren. Dafür müsste man eine Transistorschaltung entwerfen, die den Sensor komplett deaktiviert.

Ein kurzes Wort noch zur Wahl der digitalen Pins. Ich habe dafür leider noch keine Lösung gefunden. Wenn man den Farbsensor anschließt und im Programm initialisiert, funktionieren die internen Pullup-Widerstände der Pins D3, D9 und D13 nicht mehr. Deswegen habe ich die Taster an die Pins D12 und A5 angeschlossen. Wie Sie sehen, kann man analoge Pins wie digitale Pins verwenden. Ich habe Tests mit externen Pull-down-Widerständen gemacht. Damit sollte es funktionieren. Man hätte allerdings wieder mehr Komponenten und man müsste den Quellcode ändern. Sollten Sie die Ursache dafür finden, schreiben Sie das gern als Kommentar.

Bedienungsanleitung

Kalibrierungsmodus

Muss bei erster Benutzung oder Änderung des Gehäuses durchgeführt werden.

  • Taster 1 und 2 gedrückt halten und Gerät einschalten
  • Audioausgabe "Kalibriere" (eventuell Lautstärke erhöhen)
  • Farben werden vorgegeben
  • Scanner an eine Fläche mit der Entsprechenden Farbe halten
  • Taster 1 einmal drücken
  • Information wird ausgegeben
  • Danach Taster gedrückt halten
  • Farbvorgabe wird ausgegeben
  • Sensor an die Farbfläche halten und Taster loslassen
  • Taster wieder gedrückt halten
  • nächste Farbe wird vorgegeben
  • Wenn alle vorgegebenen Farben eingescant wurden, ertönt der Startsound
  • Farbdetektor läuft dann im normalen Scanmodus

Farbscanmodus

  • Gerät einschalten, ohne Tasten zu betätigen
  • Taster 1 einmal betätigen
  • Hinweis wird ausgegeben
  • Taster 1 gedrückt halten und an die gewünschte Farbfläche halten
  • Taster 1 loslassen
  • Farbe wird ausgegeben (eventuell Lautstärke erhöhen)
  • Erneutes Scannen ohne Hinweis
  • Taster gedrückt halten, dann loslassen für Ergebnis

Stimme wechseln

  • Taster 2 kurz betätigen
  • Stimme wechselt
  • Im Scanmodus wird die Sprache ausgegeben
  • Im Kalibrierungsmodus ertönt der Hinweis auf den Modus
  • Wurde die Stimme gewechselt, muss für den Scan in beiden Modi Taster 1 noch einmal kurz gedrückt werden
  • Hinweis ertönt
  • Dann Taster 1 gedrückt halten
  • Stimme kann zu jeder Zeit gewechselt werden

Änderung der Sprachdateien

  • Reihenfolge beim Kopieren ist zu beachten
  • Texte im Array colorNames[] ändern für andere Farben
  • Bei Änderung der Anzahl der Farben: numofcolors, distinctColors, colorNames und offsetVoice ändern
  • Bei Änderung der Anzahl der Stimmen: maxVoices ändern
  • Kalibrierung neu durchführen

Demovideo

Youtube-Video: Arduino Projekt - Der sprechende Farbdetektor - DFPlayer Mini, TCS3200 //AZ-Delivery.de Blog

 

Andreas Wolter

für AZ-Delivery Blog

Für arduinoProjekte für anfängerSensorenStromversorgung

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert

Empfohlene Blogbeiträge

  1. ESP32 jetzt über den Boardverwalter installieren
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP Programmieren über WLAN