Annäherungs-, Gesten- und Farberkennung mit dem ADPS-9960 Sensor

Das Multi Function Shield, das ich ja bereits vorgestellt habe , bietet mit der Möglichkeit zur einfachen Anzeige von Daten auf seinem 4-fach-7-Segment-Display und den freien Anschlusspins eine gute Grundlage, Sensoren ausprobieren.

Diesmal soll es als Plattform für einen Sensor dienen, der gleich drei Dinge kann: Er ist Annäherungssensor, Gestensensor und RGB-Farberkennungssensor in einem. Es handelt sich um den APDS-9960 von Avago, den es auch als fertiges Modul zum Anschluss an den Arduino gibt.

Ich habe gleich zwei von den Dingern günstig aus Fernost bestellt, wobei sich die eine Variante auf roter Platine leider als Fake herausgestellt hat: hier war kein 9960, sondern nur ein 9930 verbaut, bei dem wohl zudem noch die Sende-LED defekt ist, so dass er nur als Umgebungslichtsensor zu gebrauchen ist. Aber dafür einen I2C-Bus zu benutzen ist ein bisschen Overkill, da tut es doch auch ein Fotowiderstand mit Spannungsteilungswiderstand.

Auf den Fake (mit der größeren, roten Platine) will ich hier aber gar nicht groß eingehen, außer der Warnung, sich Verkäufer und Beschreibung vorher genau anzuschauen und bevor man u. U. größere Menge bestellt, immer erst ein Einzelexemplar zu ordern und zu testen.

Annäherungs-, Gesten- und Farberkennungsmodul ADPS-9960


Auf der vielleicht daumennagelgroßen Platine findet zwischen den beiden Bohrlöchern der eigentliche APDS-9960 Chip Platz. Er verfügt über eine Infrarot-Sende-LED unten und Empfangsdiode oben in einem kleinen, schwarzen Kästchen.

Durch das Aussenden von IR-Licht und Auswertung der reflektierten Strahlung kann der APDS-9960 nicht nur die Lichtstärke des einfallenden Lichtes messen, sondern auch, wie weit ein Gegenstand entfernt ist, was ihm zum Annäherungssensor macht.

Zuckerl ist aber wohl die Gestenerkennung, die selbständig erkennen kann, in welche Richtung sich ein Gegenstand bewegt, der im Nahbereich bis etwa 25 cm am Sensor vorbeigeführt wird. Hier kann zwischen links, recht, oben und unten unterschieden werden.

I-Tüpfelchen ist die Farberkennung, die in den Sensor (durch Reflektion an einem Objekt) anfallende Licht-Strahlung nach den Farbanteilen rot, grün, blau und "klar" analysieren kann.


Der ADPS-9960 kann über ein I2C-Interface und die Adresse 0x39 angesprochen werden. Außerdem gibt es einen Interrupt-Pin für den man einen Anäherungsalarm ab einer bestimmten Distanz per Software setzen kann und der dann von High auf Low gezogen wird. Benutzt man hier Pin 2 oder 3 des Arduino (am MFS ist nur noch Pin 2 frei, Pin 3 wird für den Beeper benutzt), so kann man eine Interrupt-Routine benutzen, die auch außerhalb des normalen Programmablaufs funktioniert und die man nicht ständig abfragen muss.

Betrieben wird der 9960er mit 3.3V. Bei einem Arduino Uno steht diese Spannung am Pin 3v zur Verfügung. 5 Volt dürfen nicht angeschlossen werden, das würde den Chip beschädigen.

Angeschlossen wird das Modul wie folgt: Ich habe ein paar Header-Pins an 3V, A4, A5 und Pin 2 anlöten müssen, damit ich hier die entsprechenden Jumperkabel einstecken kann. Das erfordert ein wenig Geschick, sollen die Header auch gerade sein. Mir ist das aber ganz gut und stabil gelungen.

Software: Bibliothek


Um das zu verwendende I2C-Protokoll müssen wir uns keine Gedanken machen, denn hier gibt es eine bereits fertige Bibliothek von Adafruit, die wir über den Bibliotheksverwalter in der Arduino IDE installieren können. Auch werden wie gewöhnlich Beispielprogrämmchen mitgeliefert, um das Ganze gleich auszutesten.

Ich habe daraus ein größeres Programm geschneidert und an das Multi Function Shield angepasst, dass alle Funktionen vereint.

Zu Beginn muss einer der drei Modi gewählt werden: Mit Taster 1 wird zwischen den Modi gewechselt, Taster 2 besätigt die Auswahl.

Annäherungssensor

Hier wird nit einem Wert zwischen 0 und 255 und den LEDs angezeigt, wie nah ein Objekt (oder die Hand) dem Sensor kommt. Je höher die Zahl bzw. je mehr LEDs an sind, desto näher ist das Objekt.

Ab ca. 10 cm beginnt die Annäherung. Wird der einstellbare Schwellenwert (derzeit 200) überschritten, wird ein Interrupt ausgelöst und ein Piepton ertönt.

Gestenerkennung


Um einen anderen Modu zu wählen, muss die Reset-Taste betätigt werden, damit die Wahl neu gestartet wird.

Es gibt vier Gesten: Dabei muss darauf geachten werden, dass der Abstand nicht mehr als ca. 25 cm beträgt und die Hand nicht zu langsam geführt wird, weil sonst keine Erkennung erfolgt. Außerdem muss die Hand gerade geführt werden. Der Sensor schmeißt sonst links und oben etc. durcheinander, wenn die Richtung nicht ganz eindeutig ist.

Am besten ist, man beschränkt sich auf die Richtungen links und rechts, das funktioniert am besten.

Farberkennung

Der Sensor kann natürlich nur das Licht auswerten, dass in den Sensor fällt. Darum müssen die auszuwertenden Objekte beleuchtet werden. Zu diesem Zweck habe ich zwei Leuchtdioden über einen 100 Ohm-Widerstand parallel geschaltet, die ich über Pin 5 (PinLed) einschalte, sobald dieser Modus gewählt wird.

Nun war das Problem, dass die beiden von mir verwendeten LEDs kein superweißen Licht abgeben, sondern eher eines mit Blaustich. Was wiederum dazu führt, dass die gemessenen Blauwerte zu hoch ausfallen. Darum habe ich hier eine kleine Kalibrierung einprogrammiert: Die Kalibrierung erfolgt mit einem gebleichtem, weißen Zettel. Nun wird z. B. die Taste 3 für Blau gedrückt, wenn diese zu hoch ist, bis alle drei Ziffern für RGB den selben Wert haben. Dadurch kann die Blauverfälschung korrigiert werden.

Als nächsten wird der farbige Gegenstand in zwei bis drei Zentimeter Abstand vor den Sensor gehalten. Dieser sollte jetzt die Farbanteile auf dem 7Segment-Display anzeigen und zwar in jeweils 16 Abstufungen (0 bis F, hex.) für rot, grün, blau und "clear". Wobei ich mir nicht ganz sicher bin, was "clear" zu bedeuten hat. Da die Library es zurückgibt und eh eine vierte Stelle auf dem Display frei ist, habe ich es auch ausgegeben.

Im Programm gibt es eine Variable hellQuotient (Default 20). Dieser kann für hellere bzw. dunklere LEDs angepasst werden, und zwar so, dass die hellsten ausgegebenen Werte bei F liegen. Darüber wird alles gekappt. Gehen die Farbwerte z. B. nur bis max. 9, dann hat man Genauigkeit veschenkt, denn es werden nicht alle Abstufungen angezeigt.

Die von der Library direkt zurückgelieferten Werte sind ein wenig mit Vorsicht zu genießen. Es werden Maximalwerte von 4097, nicht wie anzunehmen bis 4095, zurückgegeben. Dies sollte man wissen, wenn man mit diesen Werten rechnet, sie z. B. durch 16 teilt und sich dann wundert, warum das Ergebnis manchmal nicht in einer Byte-Variablen Platz findet und es zum Überlauf kommt.

Am besten lassen sich die Funktionen des Programmes mit einem kleinen Video veranschaulichen:



Das passende Programm stellt sich folgendermaßen dar:

Source-Code

//////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include <TimerOne.h> // für Timings und Interrupts #include <MultiFuncShield.h> // API für das Multi Function Shield #include "Adafruit_APDS9960.h" #define PinInt 2 // INT Pin des APDS-9960 #define PinLed 5 // Beleuchtungs-LED für RGB-Mode Adafruit_APDS9960 apds; // APDS9960-Objekt erstellen int Mode=0; // Betriebsmodus void setup() { Timer1.initialize(); MFS.initialize(&Timer1); // initialize multi-function shield library pinMode (PinInt, INPUT_PULLUP); // Interrupt-Pin normalerweise HIGH pinMode (PinLed, OUTPUT); Serial.begin (115200); if(!apds.begin()){ Serial.println("APDS-9960 nicht oder nciht korrekt angeschlossen."); } } void loop() { boolean chosen=false; int wert=0; int btn; MFS.beep(1, 5, 2); // bereit // zuerst Betriebsmodus wählen while (!chosen) { if (Mode==0) { MFS.write("Anna."); // Annäherung / Proximity } else if (Mode==1) { MFS.write("Gest."); // Gestenerkennung } else if (Mode==2) { MFS.write("RGBC"); // Farberkennung } btn=MFS.getButton(); if (btn == BUTTON_1_PRESSED) { MFS.beep(5); Mode++; if (Mode>2) Mode=0; } if (btn == BUTTON_2_PRESSED) { MFS.beep(10); chosen=true; } delay(1); } if (Mode==0) { // Annäherung / Proximity apds.enableProximity(true); apds.setProximityInterruptThreshold(0, 200); // Interrupt wird bei 175 ausgelöst apds.enableProximityInterrupt(); // Interrupt scharf schalten while (1) { wert=apds.readProximity(); MFS.write(wert); MFS.writeLeds(LED_1, (wert>50)); MFS.writeLeds(LED_2, (wert>100)); MFS.writeLeds(LED_3, (wert>150)); MFS.writeLeds(LED_4, (wert>200)); if(!digitalRead(PinInt)){ MFS.beep(25); apds.clearInterrupt(); // Interrupt wieder löschen } delay(1); } } else if (Mode==1) { // Gestenerkennung (wird aktiviert, wenn dafür nah genug) apds.enableProximity(true); apds.enableGesture(true); while (1) { uint8_t gesture = apds.readGesture(); if(gesture == APDS9960_DOWN) { MFS.beep(5); MFS.write("DOWN"); } if(gesture == APDS9960_UP) { MFS.beep(5); MFS.write("UP"); } if(gesture == APDS9960_LEFT) { MFS.beep(5); MFS.write("LEFT"); } if(gesture == APDS9960_RIGHT){ MFS.beep(5); MFS.write("RGHT"); } delay (1); } } else if (Mode==2) { // Farberkennung apds.enableColor(true); int r, g, b, c; char msg[10]; // Kalibrierungswerte minus, Defaultwerte hier ggf. eintragen int rm=0; int gm=0; int bm=0; digitalWrite (PinLed,HIGH); // Beleuchtungs-LEDs an while(1) { while(!apds.colorDataReady()) { // auf Farbwerte warten delay(5); } apds.getColorData(&r, &g, &b, &c); // und lesen int hellQuotient=20; r/=hellQuotient; g/=hellQuotient; b/=hellQuotient; c/=hellQuotient; r-=rm; g-=gm; b-=bm; if (r<0)r=0; if (g<0)g=0; if (b<0)b=0; if (c<0)c=0; if (r>15)r=15; if (g>15)g=15; if (b>15)b=15; if (c>15)c=15; sprintf(msg,"%0x%0x%0x%0x", r, g, b, c); MFS.write (msg); // Kalibrierung der Beleuchtungs-LEDs: weißes Blatt Papier davor // halten und Button 1 bis 3 (RGB) drücken, bis alle Ziffern gleich btn=MFS.getButton(); if (btn == BUTTON_1_PRESSED) { MFS.beep(5); rm++; } if (btn == BUTTON_2_PRESSED) { MFS.beep(5); gm++; } if (btn == BUTTON_3_PRESSED) { MFS.beep(5); bm++; } delay(10); } } }