Der sprechende Farbdetektor - Teil 3


After putting the MP3 player for the voice output into operation in the first part of this blog series and developed a program sequence, I have shown in the second part how the TCS3200 color sensor is connected and programmed. In the third and last part we will now combine both, scan the colors and let the color detector speak. We will implement a live calibration mode and make the power supply mobile. Here we go.

What we need

Number Component
1 TCS3200 Color Sensor Module
1 DFPlayer Mini MP3 Player Modul
1 Mikro-SD card
1 Arduino Nano V3.0
1 Speaker (Max 3W)
Connecting cable
1 Resistance 1 KOhm
1 Variable resistance (potentiometer)
2 Push buttons
Computer with Arduino IDE and internet connection
External voltage source (recommended), 7 - 12V
Color reference cards
LiPo battery 3.7 V
1 MT3608 Step up module
1 TP4056 charge controller module
1 Voltmeter


I assume that you have built up the circuit from part 2 and inserted the SD card with the voice files in the SD slot of the MP3 player.

DFPlayer Mini Pins

Arduino Nano Pins






Over 1 KOHM to D11 (TX)


D10 (RX)




Red (plus)


Black (minus)


Arduino Nano Pins



2 (center)



+ 5V


Arduino Nano Pins





TCS3200 Pins

Arduino Nano Pins

















For a function test, we load the program from Part 1 to the Arduino:

Complete source code: 1.2DfplayerStarting soundPotionTaster.ino

In addition, we test the color sensor with the program from Part 2:

Complete source code: 2.0TCS3200Test.ino

Merge programs

Our task now consists of putting together the programs from Part 1 and 2. With the program from Part 1 we can already serve the device relatively simple. It can even output colors per voice. With the programs from Part 2 we can scan the right colors. So we  "only" have to transfer the source code from Part 1.

First, we change the program "tcs3200calibration" for the calibration from the tcs3200 library we have already used in Part 2. As I mentioned at the end of Part 2, we expand the arrays distinctRGB, distinctColors and colorNames. We need that, beacuse we use more colors. So we have to change the following lines:

#define num_of_colors 9

int DISTINCTCOLORS [NUM_OF_COLORS] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
String COLORNAMES [NUM_OF_COLORS] = {"Red", "Green", "Blue", "Black", "White", "Yellow", "Orange", "Pink", "Brown"};

This sets the number and order for our colors. The list is now fitting to the audio files we created in Part 1.

So that we can take over the colors later easier, I add an interruption in the program sequence. So we had already made in the example "calibrate_TCS230" already in part 2. In addition, we format the output so that we can copy the values ​​easier. the array DistinCtrGB [] [] Write for the readability without changing the content:

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

This allows us to access the three values ​​per color easier. We now declare a drawing buffer for the formatted edition:

Char Serialbuffer [55];

The LOOP () function we change as follows:

void Loop () {
  // wait for input
  While (Serial.available () == 0) {
    IF (Serial.available ()! = 0) {
  // serial buffer toilet
  While (Serial.Read ()! = -1) {}


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 ();
  // Output
Sprintf (serial buffer, "Red Gruen Blue: {%d, %d, %d}  ", Red, Green, Blue);
Serial.Println (serial buffer);


The program is waiting for the input to the serial monitor. Then the color is recorded once and the values ​​are output so that we can copy them directly and insert them again in the source code. We make this successively with all the colors we have entered in the list. Pay attention to the correct position in the corresponding array so that no color is inadvertantly changes.

You can still ignore the output of the color name for the time being. After we recorded all colors and have entered the source code here, we transfer the program to the Arduino again. If we scan the colors now, the displayed word should fit the color.

Complete source code: 3.0TCS3200Calibration_new.ino

This source code is now transferred to the program that we had written  in Part 1 . We take over the constants, variables and arrays. Now we only have to enter into the state machine in Case 6, which color should be output. Currently this line is output the audio file with the word "red": (4);

With this line we output the name of the color on the screen in the calibration program:


We only need the function call ClosestColor ():

TCS.ClosestColor (DistinctrGB, DistinctColors, NUM_OF_COLORS)

This returns us the number of the recognized color. However, the color numbers start at 0. Our numbers for audio files that contain the colors start with 1. The first color has the number 4. Throw a look into the table from Part 1 in section "Voice output". We need an offset for the numbers in the table. Our recognized color red has the number 0. The table is the number 4. So an offset of 4. For this we declare ourselves another constant:

#define file offset 4

The line for the output of the audio file is then a combination of the above-mentioned lines: (TCS.ClosestColor (DistinctrGB, DistinctColors, Num_OF_Colors) + FileOffset);

We transfer the recognized number to the audio player and additionally add our offset. For the output of the word of the color, we take the line from the program for calibration:


If you now upload the program on the Arduino, the right word should be played back and displayed after scanning the color as soon as you release the button.

Complete source code: 3.1speakingColor detector.ino

At this point, the speaking color detector already works as desired. He can recognize colors and pronounce them loudly.

Other voices

We want to use other voices now. For this we need new audio files. Use the linked websites from Part 1 again, or record them yourself. Then edit them so that they have the same order as our existing audio files. I have added a male voice and another language. So I have female and male in German, as well as female and male in English. After working on the audio files, I copy them to the SD card. Be sure to transfer the files in the correct order, best individually one after the other. My numbering of the audio files is now reaching to 53.

We had previously inserted an offset so that the file number fits the color number. Such an offset we can now use to change the voice. Here's a list of numbering of my votes:



2 - 14

female, German

15 - 27

male, german

28 - 40

Female, English

41 - 53

male, English

Two important considerations must now be implemented:

  • How do we change the voice?
  • Where do we change this in the program?

We should consider the first question for which target group we usually construct the device normally. Since I would prioritize the device for people with visual impairment, I would keep the operation as easy as possible. We take the button already in use with a short touch or klick and hold. You could now add double clicks or triple clicks. However, we can achieve easier operation if we add another button. We connect him to A5 of the Arduinos and GND:

We define and initialize the pushbutton in the program (why I use the analog PIN A5 here as a digital input, I declare in conclusion):

#define tasterpin2 A5

// in setup:

We need additional variables. A counter with which we set the voice, an offset to the other voices and a maximum number of votes:

int Voicenumber = 0;
unsigned int OffsetVoice = 13;
int Maxvoices = 4;

We need a function that we read the second button and react to a change. We use the function as a template Button ()we already have. So we can take on the debt. Maybe we could later connect both functions to prevent redundant source code. For the first we copy the function, rename around in Changevoice () and change as follows:

void Changevoice () {

  // Recognize the button endure and start bounce timers
Start_zeit = Millis ();
  // if push button
  IF (Button_Status_NEU_2 == LOW && TASTON_STATUS_ALT_2 == LOW && TASTON_CHANGE_2) {
    // Dempering button
    IF (Millis () - start_zeit_2> Prell_Delay) {
TASTON_CHANGE_2 = false;
Voicenumber ++;
      IF (Voicenumber> MaxVoices - 1) {
Voicenumber = 0;
      } (14 + voicenumber * offsetvoice);
Serial.Println ("Language Gaendert");
State = 1;

We also need the appropriate variables for this:

unsigned long new_zeit_2 = 0;
unsigned long start_zeit_2 = 0;
boot TASTON_STATUS_NEU_2 = high;
boot TASTON_STATUS_ALT_2 = High;
boot TASTON_CHANGE_2 = false;

When the button has been pressed, we count the number for the votes by 1 high. If we reached the maximum number of votes, the value is set back to 0 and thus again used the first voice. Next, we output the now set language via audio playback. The player gets the number of the audio file. That would be for the first voice the file number 14. We add the multiplication of voting number and offset. This results for the first voice as I said the 14 (because 0 * 13 is 0 and 14+ 0 remains 14), for the second voice it is 14+ 1 * 13, resulting in 27. That's the same word only spoken of the male voice. That's always so on. The second voice is 14+ 2 * 13 = 40 and 14+ 3 * 13 = 53. So we have a small algorithm that always spends the same word from another voice. Therefore, it is important to leave the order of files right for all voices.

We then change the state- Variable to the value 1. We jump back in the state machine back at the time before the info text was spoken. If someone already saves the color and then change the language, the info text should be issued again in the new language before new.

In the statemachine()-function we now complement the respective output:

cube 2: {                                 // Play Help (2 + voicenumber * offsetvoice);
cube 4: {                                 // Start Scan (3 + voicenumber * offsetvoice);
cube 6: {                                 // Output color (TCS.ClosestColor (DistinctrGB, DistinctColors, Num_OF_Colors) + FileOffset + Voicenumber * OffsetVoice);

As the last step we have to Changevoice ()Function still call in the main loop:

void Loop () {
Button ();
Changevoice ();
Stemachine ();

If we load the program on the Arduino, we can do the color detector just as before. In addition, we can change the voice or language with the second button.

Complete source code: 3.2speakingColor detectormoreLanguages.ino

Live calibration

We have seen that we must calibrate the sensor to set our colors. At the moment we need a separate Arduino program, must copy the color values ​​and then take everything into our program. If the sensor is in a fixed housing, in which it is protected from ambient light, the calibration usually has to perform only once. Because the distance of the sensor to the color area and the ambient light are two noise factors as soon as they change. To solve it anyway, that you can perform the calibration without copying the values ​​back and forth, we need a way we capture the data and then transfer directly to the array with the color values. However, only if we run a calibration mode.

It would be useful if the calibration is running at program start. However, not always, but only when a pushbutton or switch has been pressed. The question is whether the calibration in live operation is to be performed again and again, or only once e.g. after a restart with pressed button. I decide to use both buttons. If you are pressed together and you start the Arduino, you get into the calibration mode. Then you can switch with the first button by all colors and cone each of the reference color for it. Then you get as usual in normal mode.

We need some new variables. The calibration mode must be set as a flag. Then I just want to go through a loop at the beginning, which waits for both key to be released again. We also count our color numbers from 0 to upwards so that we can scan them individually. For the formatted edition, I use a char array like in the program for the separate calibration:

boot Calibrate = false;
boot FIRSTSTART = true;
int Calcounter = 0;
Char Serialbuffer [55];

All values ​​in the array with the color values ​​are set to 0:


At the beginning of the program, ie in the set up()Feature, we recognize the simultaneous holding holding of both buttons:

  // calibrate when starting both buttons at startup
  IF (! DigitalRead (TasterPin) &&! DigitalRead (TasterPin2) {
Calibrate = true;

The pushbuttpins are Low Active and double logical & is the expression only if both have the state low. Then the flag, so the Boolean variable calibrate, set on true. Thus, the calibration mode is active.

My first consideration was now to write a function in which the calibration takes place. I noticed that I would have to copy much of the already written program code there. I then came to the conclusion that I do not need that. I only have to distinguish in my previous source code in some places as to whether the calibration mode is active, or not.

In this mode, I want to change the voice as before. However, when changing, the word should not be issued for the language and I would like to return to the complete program beginning. We therefore complement in the function Changevoice () Following passage: (14 + voicenumber * offsetvoice);
Serial.Println ("Language Gaendert");
State = 1;

is changed in:

Serial.Println ("Language Gaendert");
IF (Calibrate) {
State = 0;
Else { (14 + voicenumber * offsetvoice);
State = 1;       

I have everywhere, where I distinguish between calibration and normal mode, the previous program part in the ElseBranch of IFQueries written. Is the calibrateFlag not set, the program works just as before. Therefore, I always only refer to the following IF-Branch.

All other changes are now in the function Stemachine () instead of. In case 0 we distinguish whether we are in the calibration mode, or not. If so, we give the word "calibration" or the English equivalents. We now distinguish whether we run this part of the program for the first time (as a reminder: everything runs in an endless loop), or not. Only the first time we want to recognize whether both buttons are pressed. Until both were released, the program will run at this point in another continuous loop. We also have to relieve the release. Since we are here at the beginning of the program, I have it with one Delay () Solved because we do not need an uninterrupted expiration here. We still set the FirstStartFlag falseso that this part is not running again. If we change the voice, we will get back to this case. That's what I chose that, because after the change of the voice, I do not play the word for the language, but the word "calibration" with the new voice:

cube 0: {
  IF (Calibrate) { (13 + voicenumber * offsetvoice); // "calibration." play
Serial.Println ("Calibrate.");
      IF (FirstStart) {
        While (! (DigitalRead (TasterPin) && DigitalRead (TasterPin2)) {
              // serial.println ("Release both buttons");
        // Dirty Realeease Debounce
        // Nonblocking here not necessary
Delay (Prell_Delay);
  Else { (1);                     // Play start sound
Serial.Println ("Program is started.");       
State = 1;
} break;

The next case that is changed is number 4. In normal mode, the word "scanny." reproduced. In the calibration mode, I want the color to be called, which should be scanned next. To spend the right color, we need an offset again:

cube 4: {                                 // Start Scan
  IF (Calibrate) {
MyDFPlayer.Play (FileOffset + KalCounter + Voicenumber * OffsetVoice);
  Else { (3 + voicenumber * offsetvoice);
Serial.Println ("Scan ...");       
spoke = false;
State = 5;
} break;

The offset is composed of several parts offices. FileOffset pushes the counter to the first color, in our case red. The calcounter pushes to the current number, the color to be scanned. The multiplication voicenumber and offsetvoice As usual, the voice we have chosen.

The last change takes place in Case 6. Here we had originally spent the color we scanned. We want to save these in the calibration mode to the Farbaray. the calcounter Specifies which color number. It is counted up here in each pass by 1. The reading of the individual color values ​​from the sensor is used from the program for the separate calibration. There that was already taken from the example of the library.

If all colors have been run through, the same calcounter the number of colors Numofcolors. At this point, the entire array with the color values ​​is output on the serial monitor, the calibration mode terminates and the state machine reset back to Case 0. Thus, the program continues in normal mode and can be used as before:

cube 6: {                                 // Output color
  IF (Calibrate) {
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 (serial buffer, "- Red green blue: {% 3D,% 3D,% 3D}", DistinctrGB [N] [0], DistinCtrGB [n] [1], DistinCtrGB [n] [2]);
Serial.Print (COLORNAMES [N]);
Serial.Println (serial buffer);
Calibrate = false;
State = 0;
  Else { (TCS.ClosestColor (DistinctrGB, DistinctColors, Num_OF_Colors) + FileOffset + Voicenumber * OffsetVoice);
Serial.Println ("Colour: ");
State = 3;
} break;

Our color detector can now be calibrated without having to copy values ​​from the serial monitor and insert it into the source code. Keep the reference color cards ready to load the program on the Arduino and hold the program starts both buttons.

Complete source code: 3.3speakingColor detectormoreLanguages_livecalibration.ino

Save data permanently

The next task is to save the color values ​​from the calibration somewhere, otherwise they would be lost after a restart.

As a memory, there are different solutions. In the trade are external flash memories in I²C and SpicyVersion available as breakoutboard. You could also have an external Micro SD Card Modules Use and save the data on an SD card. As well as an external I²C-EEPROMModule conceivable. The SD card would be slightly oversized for so little data. External Flash or EEPROM would cause additional costs, but would be a solution as Plan B. The I²C interface of the Arduino Nanos lie on pins A4 and A5. We could place the second button on another PIN, then you could realize that.

However, since the Arduino Nano has an EEPROM onboard, we can also use it. It should be noted that this memory can not be continuously described (100,000 write operations). Read, however, is always possible.

Note: I've already edited the topic in the blog series "Arduino: Multi-IO and EEPROM". In Part 2 I used the internal EEPROM there.

I assume that we do not often have to calibrate. Therefore, I decide for this solution.

At the point in the program where all colors in the calibration mode have been passed through and again the entire color fray on the serial monitor, we want to take the data in addition to the EEPROM. This happens in Case 6. We add before or after this line:

Calibrate = false;

The following new line:

Eeprom.put (0, DistinctrGB);

The function Put () Allows us to transfer complete objects to the EEPROM. Only values ​​are overwritten, which differ. We write the complete array with the newly scanned colors in the EEPROM starting at address 0.

That was easy. What is still missing is the reading of the EEPROM when the Arduino is started. But only if the calibration mode is performed. For this we add in set up() At the point:

// calibrate when starting both buttons at startup
IF (! DigitalRead (TasterPin) &&! DigitalRead (TasterPin2) {
Calibrate = true;

one ElseAddition to:

Else {
EEPROM.GET (0, DistinctrGB); // Read data from EEPROM
      for (int N = 0; N <NUM_OF_COLORS; N ++) {
Sprintf (serial buffer, "- Red green blue: {% 3D,% 3D,% 3D}", DistinctrGB [N] [0], DistinCtrGB [n] [1], DistinCtrGB [n] [2]);
Serial.Print (COLORNAMES [N]);
Serial.Println (serial buffer);

With the function Get () Let's read the complete array from the address 0 and write it in our empty color fray. To check if that worked, we still output the values ​​on the serial monitor.

Complete source code: 3.4speakingColor detectormultilingualLivekal_eprom.ino

Download the program to the Arduino, hold down both keys and go through the calibration mode. Then restart the Arduino via the RESET button without pressing the keys. You will then see if the values ​​have been loaded and whether the color detector recognizes your colors.

Mobile voltage source

If someone wants to use the device, he certainly does not want to be dependent on the next socket. So I will change to battery operation. You can either connect a 9V block on the VIN of the Arduinos directly, as a voltage regulator is installed. It is also possible, a powerbank (can not be switched off at low load) or the Raspi battery pack to connect to the USB port.

I want a Lipo battery Use lithium-ion polymer. There are you in different shapes, sizes and with different capacity. I use a flat battery with a capacity of 2000 mAh and a tension of 3.7 V. This design would favor the construction of a flat housing. I do not want to take more on how to calculate the battery life.

Since the external power supply of the Arduino on the VIN must be at least 7V, we need one Step-up converter. Thus, we regulate the tension to 9 V. That should fit. So that we can also load the battery, we use one Micro USB loading controller module. How these components are connected shows Gerald Lechner in his Blog Post "5V Battery Power Supply with 3.7 V LiPo Battery and Load Controller". If we connected the voltmeter on the vout of the voltage transformer, we turn the potentiometer until we reach the 9 V.

Note: You may need to turn the potentiometer for a very long time to turn the clock counter (as if you have a faucet) until the voltage is increased. I needed many turns and had already suspected that the device was defective.

We now replace the existing external power source (if you have used) and instead connect the vout connections of the step-up converter to VIN or GND of the Arduinos. I recommend you to build a switch between VIN of the Arduinos and Vout of the converter. So you do not have to pull off the battery every time to turn off the device. The following picture shows the complete circuit diagram again:


We have now constructed the electronics of a mobile, speaking, calibratable color detector. You only operate it with two buttons, a power switch and a potentiometer. To use own colors is a bit limited. The fewer colors you use, the lesser the weighting. A dark red is then recognized as a brown. For the control of the color sensor I ultimately have the library TCS3200 chosen. The MD_TCS230 Use library is also possible. The sample code is just more extensive and one depends on the Pin D5. That's why I decided so.

It is now certainly still possible to simplify the source code. The color sensor could be seen with a timeout to extend the battery life. For this you can set the frequency divider on low / low and the OE PIN on high. Unfortunately, the four LEDs can not be disabled. For this one would have to design a transistor circuit that completely deactivates the sensor.

A short word still to choose digital pins. Unfortunately, I have not found a solution for this. If you connect the color sensor and initializes in the program, the internal pullup resistors of PINS D3, D9 and D13 will no longer work. That's why I connected the buttons to Pins D12 and A5. As you can see, you can use analog pins like digital pins. I made tests with external pull-down resistors. It should work with that. However, you would have more components again and you would have to change the source code. If you find the cause, you like to write that as a comment.

Operation Manual

Calibration mode

Must be performed on the first use or change of the housing.

  • Hold the pushbutton 1 and 2 and turn on the device
  • Audio output "Calibration" (increasing volume)
  • Colors are given
  • Keep scanner to a surface with the corresponding color
  • Press the button 1 once
  • Information is issued
  • Then hold down the button
  • Color specification is output
  • Hold the sensor to the color area and release the button
  • Hold down the button again
  • Next color is given
  • When all given colors were recessed, the start sound sounds
  • Color detector then runs in normal scan mode

Color scan mode

  • Switch on the device without pressing buttons
  • Press the button 1 once
  • Note is output
  • Hold down the button 1 and hold to the desired color area
  • Pick up button 1
  • Color is output (increasing volume)
  • Recovery scan without hint
  • Hold down the button, then release for result

Change voice

  • Press button 2 short
  • Voice changes
  • The scan mode issues the language
  • In the calibration mode, the reference to the mode sounds
  • If the voice has changed, must be pressed for the scan in both modes button 1 again briefly
  • Note sounds
  • Then hold the button 1 pressed
  • Voice can be changed at all times

Change of language files

  • Order when copying is to be observed
  • Texts in the array COLORNAMES [] Change for other colors
  • When changing the number of colors: Numofcolors, distinctcolors, COLORNAMES and offsetvoice to change
  • When changing the number of votes: maxvoices to change
  • Perform calibration



YouTube Video: Arduino Project - The Talking Color Detector - DFPlayer Mini, TCS3200 // Blog


Andreas Wolter

For AZ-Delivery Blog

For arduinoProjects for beginnersSensorsPower supply

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. Install ESP32 now from the board manager
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP programming via WLAN