APDS-9960 – das Multitalent

Über den Beitrag

Dieser Beitrag über den APDS-9960, genauer gesagt das APDS-9960 Modul, ist der erste in einer Reihe über Abstands-, Bewegungs-, Farb- und Lichtsensoren (eine Zusammenfassung der Reihe gibt es hier). Der APDS-9960 ist unter diesen Sensoren sozusagen das Schweizer Taschenmesser, denn er bietet:

  • Gestenerkennung
  • Farberkennung (RGB)
  • Abstandsmessung
  • Umgebungslichtmessung

Über die Abstandsmessung ist der APDS-9960 auch als Bewegungssensor einsetzbar, allerdings nur innerhalb seines begrenzten Arbeitsbereiches von bis zu ca. 30 cm. Das folgende Video gibt einen Überblick über die verschiedenen Features. 

Der APDS-9960 besitzt eine erstaunliche Zahl an Einstellmöglichkeiten und viele Register. Das in einem Sketch zu implementieren, ist ein erheblicher Aufwand. Deshalb bietet es sich an auf eine fertige Bibliothek zurückzugreifen, die schon mal eine Reihe von Voreinstellungen übernimmt. Ich werde in diesem Beitrag die Bibliothek von Sparkfun nutzen. Die Bibliothek von Adafruit habe ich auch ausprobiert und sie funktioniert ebenso problemlos. Es sind aber noch weitere auf Github erhältlich.

Auch wenn Ihr eine dieser Bibliotheken benutzt, würde ich trotzdem dazu raten mal ein wenig in das Datenblatt des APDS-9960 zu schauen und sich mit der Bibliothek selbst auseinanderzusetzen, denn nicht alles, was das Teil kann, ist auch in den Beispielen dokumentiert. Darüber hinaus könnte es sich für die eine oder andere Anwendung anbieten, nicht einfach kritiklos die Voreinstellungen zu übernehmen.  

Grundsätzliches über den APDS-9960

Der APDS-9960 Sensor

Der eigentliche APDS-9960 Sensor ist der kleine rechteckige, schwarze Baustein mit den zwei linsenähnlichen Öffnungen. Das Datenblatt dazu ist zum Beispiel hier erhältlich. Es lohnt sich aus meiner Sicht auf jeden Fall da mal hineinzuschauen. 

Der eigentliche APDS-9960 Sensor
Der APDS-9960 Sensor

Der APDS-9960 besitzt eine IR-LED, deren reflektiertes Licht für die Gesten- und Abstandsdetektion verwendet wird. Da er über vier gerichtete Fotodioden verfügt, ist er in der Lage einfache Bewegungen zu detektieren. Diese Fotodioden dienen auch der Abstandsmessung. Weitere Fotodioden messen das rote, grüne, blaue und weiße Licht für die Farb- und Umgebungslichtmessung. 

Die Kommunikation mit dem APDS-9960 erfolgt über I2C mit einer Taktrate bis 400 kHz. Dabei ist die Adresse mit 0x39 festgelegt und kann nicht geändert werden. Das Auslesen der I2C Adresse ist immer ein guter erster Schritt um zu sehen, ob man alles richtig verkabelt hat. Dafür bietet sich ein I2C Scanner Sketch an.  

Das APDS-9960 Modul

Zwei APDS-9960 Module
Zwei APDS-9960 Module

Theoretisch kann man natürlich den blanken Sensor kaufen und die notwendige Elektronik drumherum selbst zusammenlöten. Meine Fertigkeiten übersteigt das jedoch und deshalb bin ich froh, dass es den APDS-9960 als Modul gibt. 

Solche Module kann man z.B. bei Amazon interessanterweise in zwei Preisklassen kaufen. Die eine liegt zwischen drei und zehn Euro und umfasst im wesentlichen Hersteller aus China, die andere liegt bei Anfang-Mitte zwanzig, z.B. von Sparkfun. Ob der Preisunterschied gerechtfertigt ist, kann ich nicht sagen, da ich nur einige der Billigheimer ausprobiert habe. Ich kann aber zumindest bestätigen, dass letztere Probleme mit der Verstärkung haben (dazu später mehr). Das wird auch an anderer Stelle, z.B. hier, beschrieben. 

Die meisten APDS-9960 Module haben die folgenden Pins:

  • GND – Ground
  • VCC – Spannungsversorgung: 2.4 – 3.6 Volt
  • SDA/SCL – I2C Anschlüsse
  • INT – low aktiver Interrupt
  • VL – Spannungsversorgung für die IR-LED: 3.0 – 4.5 Volt

Man kann den APDS-9960 über den 3.3 Volt Ausgang eines Arduino UNO versorgen, hat dann aber immer noch das Problem, dass die I2C Kommunikationsleitungen auf 5 Volt laufen. Das kann den APDS-9960 beschädigen. Deswegen nimmt man entweder einen Microcontroller der mit 3.3 Volt betrieben wird, wie z.B. den ESP-8266, man baut Spannungsteiler in die Leitungen oder man nimmt Levelkonverter, wie z.B. diesen hier.

Auf den meisten Modulen gibt es zwei Jumper und zwar  „PS“ und „I2C PU“:

  • PS – ist der Kontakt geschlossen, dann wird die IR-LED über die Spannung an VCC versorgt. Ist der Kontakt offen, so muss die LED separat über VL versorgt werden. Es kann Sinn machen PS offen zu lassen und die LED über VL an- und auszuschalten. Die IR-LED kann laut Datenblatt die Farb- und Lichtmessung beeinflussen obwohl die Sensoren einen IR-Filter besitzen.
  • I2C PU – ist der Kontakt geschlossen, dann werden die I2C Leitungen mit einem Pull-Up Widerstand verbunden, was ziemlich praktisch ist. 

Wie man oben auf dem Foto mit den zwei APDS-9960 Modulen erkennen kann, hat das eine die Jumper geschlossen, bei dem anderen sind sie offen (beide so gekauft). Also schaut, welche Version ihr habt. 

Schaltung: APDS-9960 Modul am Arduino UNO

Die Beschaltung ist aufgrund der vorherigen Erklärungen keine Überraschung. 

APDS 9960 Schaltung am Arduino UNO
Beschaltung APDS 9960 am Arduino UNO

Ob VL – wie abgebildet – wirklich mit 3.3 V verbunden werden muss, hängt wie gesagt vom Zustand des Jumpers PS ab. Die I2C Leitungen haben keinen externen Pull-up, da der Jumper I2C PU bei mir geschlossen war. Die LED an D13 wird für einige Beispielsketche benötigt. Das Gleiche gilt für den Interrupt an D2. Der Levelkonverter braucht eine Spannungsversorgung zumindest auf der 5 Volt Seite. 

Die Sparkfun Bibliothek für den APDS-9960

Die Bibliothek ist hier auf Github erhältlich. Wie gewohnt holt man sich die Zip Datei und entpackt sie im library Ordner. Dort findet man dann auch die Beispielsketche, die ich gleich vorstelle. 

Gestendetektion

Die Bibliothek hat die folgenden Gesten implementiert:

  • Up/Down
  • Right/Left
  • Far/Near
  • None 

Die ersten beiden Paare sind selbsterklärend. Ein „Far“ wird gemeldet wenn man die Hand sehr nah an den Sensor hält und dann senkrecht dazu entfernt bis sie sich außerhalb des Erfassungsbereiches (ca. 30 cm) befindet. Für ein „Near“ nähert man sich senkrecht und zieht die Hand dann zur Seite weg. Das „Near“ wird erst gemeldet, wenn die Hand weggezogen wurde. Ein „None“ wird bei uneindeutigen Bewegungen gemeldet. 

Bibliotheksintern werden die Richtungen als DIR_UP, DIR_DOWN, DIR_RIGHT, usw. bezeichnet. 

Der mitgelieferte Beispielsketch für die Gestendetektion heißt GestureTest.ino. Ich habe hier die allgemeinen Kommentare aus dem Sketchkopf entfernt. Dann habe ich ihm lediglich noch die wichtige (!) Zeile 34 apds.setGestureGain(GGAIN_1X) spendiert. Dazu gleich mehr. Ansonsten ist der Sketch aus meiner Sicht selbsterklärend.  

#include <Wire.h>
#include <SparkFun_APDS9960.h>

// Pins
#define APDS9960_INT    2 // Needs to be an interrupt pin

// Constants

// Global Variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
int isr_flag = 0;

void setup() {

  // Set interrupt pin as input
  pinMode(APDS9960_INT, INPUT);

  // Initialize Serial port
  Serial.begin(9600);
  Serial.println();
  Serial.println(F("--------------------------------"));
  Serial.println(F("SparkFun APDS-9960 - GestureTest"));
  Serial.println(F("--------------------------------"));
  
  // Initialize interrupt service routine
  attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);

  // Initialize APDS-9960 (configure I2C and initial values)
  if ( apds.init() ) {
    Serial.println(F("APDS-9960 initialization complete"));
  } else {
    Serial.println(F("Something went wrong during APDS-9960 init!"));
  }
  apds.setGestureGain(GGAIN_1X); // ohne diese Zeile geht es nicht zuverlässig
  
  // Start running the APDS-9960 gesture sensor engine
  if ( apds.enableGestureSensor(true) ) {
    Serial.println(F("Gesture sensor is now running"));
  } else {
    Serial.println(F("Something went wrong during gesture sensor init!"));
  }
}

void loop() {
  if( isr_flag == 1 ) {
    detachInterrupt(digitalPinToInterrupt(APDS9960_INT));
    handleGesture();
    isr_flag = 0;
    attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
  }
}

void interruptRoutine() {
  isr_flag = 1;
}

void handleGesture() {
    if ( apds.isGestureAvailable() ) {
    switch ( apds.readGesture() ) {
      case DIR_UP:
        Serial.println("UP");
        break;
      case DIR_DOWN:
        Serial.println("DOWN");
        break;
      case DIR_LEFT:
        Serial.println("LEFT");
        break;
      case DIR_RIGHT:
        Serial.println("RIGHT");
        break;
      case DIR_NEAR:
        Serial.println("NEAR");
        break;
      case DIR_FAR:
        Serial.println("FAR");
        break;
      default:
        Serial.println("NONE");
    }
  }
}

 

Das Problem mit der Verstärkung

Der APDS-9960 hat für die Gestendetektion die Verstärkungsfaktoren 1, 2, 4 und 8 vorgesehen, die die Empfindlichkeit der Messung steuern. Bibliotheksintern werden diese als GGAIN_1X, GGAIN_2X, usw. bezeichnet. Bei der Initialisierung (apds.init()) wird DEFAULT_GAIN (= GGAIN_4x) in das entsprechende Kontrollregister eingetragen. Leider kommen die Module mit diesem Verstärkungsfaktor nicht klar. Das gilt zumindest die preisgünstigen Teile, ich aus drei verschiedenen chinesischen Quellen bezogen habe. Mit acht- und vierfacher Verstärkung ging es gar nicht, mit zweifacher Verstärkung funktionierte es unzuverlässig, mit einfacher Verstärkung hingegen ging es wunderbar. Es ist auch kein Fehler der Bibliothek, da die Adafruit Bibliothek dasselbe Verhalten zeigte. Das Ganze hat mich viel Zeit und Nerven gekostet. 

Wenn man übrigens die GGAIN Einstellung erst nach apds.enableGestureSensor vornimmt, dann erscheint beim Starten des Sketches die Gestenmeldung „None“, ohne das man irgendetwas getan hätte. Also besser erst die Verstärkung einstellen und dann die Gestendetektion einschalten.  

Andere Einstellungen der Gestendetektion

Wie schon erwähnt, ist man von den Einstelloptionen beim Blick ins Datenblatt zunächst erschlagen. Aber abgesehen von dem GGAIN Problem funktionieren die anderen von der Bibliothek gewählten Voreinstellungen sehr gut. Trotzdem möchtet ihr vielleicht auch mit ihnen mal „herumspielen“. Allerdings sind nicht alle Parameter über öffentliche Funktionen zugänglich. 

  •  IR-LED Strom: setGestureLEDDrive;
    • durch den Strom wird die Reichweite der Gestendetektion gesteuert 
    •  möglich sind 12.5, 25, 50 und 100 mA
      • LED_DRIVE_12_5MA, LED_DRIVE_25_MA, usw.
    •  mit 100 mA (Voreinstellung) liegt man bei 30 cm Reichweite und erreicht bei ca. 5 cm Abstand den maximalen Proximity Wert
    • da die IR-LED pulst, ist der effektive Stromverbrauch viel geringer als der IR-LED Strom
  • IR_LED Boost: setLEDBoost 
    • private Funktion, nicht direkt zugänglich
    • um den Wert zu ändern, müsstet ihr in die Funktion enableGestureSensor in Sparkfun_APDS9960.cpp gehen und dort den setLEDBoost Aufruf editieren oder eine öffentliche Funktion daraus machen
    • Default Wert ist 300, das ist schon das Maximum
    • für weitere Details bitte ins Datenblatt schauen 
  • Pulslänge/Pulscount der IR-LED:
    • nicht als Funktion implementiert
    • variierbar durch Veränderung von DEFAULT_GESTURE_PPULSE in Sparkfun_APDS9960.h
  • Gesture Waiting Time: setGestureWaitTime
    • kontrolliert die Zeit im Low Power Mode zwischen zwei Gestendetektionszyklen
    • 8 Werte zwischen 0 und 39.2 ms sind möglich; die Parameter in der Bibliothek dazu heißen: GWTIME_0MS bis GWTIME_39_2MS; default: 2.8 ms
  • plus einiges mehr -> wenn ihr wirklich mehr wissen wollt: schaut in die Bibliothek und ins Datenblatt. 

Näherungsdetektion

Der Näherungssensor liefert keine Abstände in Zenti- oder Millimeter, sondern einen Wert zwischen 0 und 255. Der korrespondierende Abstand zu diesem Wert hängt unter anderem vom LED Strom und der Verstärkung ab. Der mitgelieferte Beispielsketch heißt ProximitySensor.ino. Hier ist er, allerdings ohne die einleitenden Kommentarzeilen und mit einer kleinen Veränderung auf die ich gleich noch eingehen werde: 

#include <Wire.h>
#include <SparkFun_APDS9960.h>

// Global Variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
uint8_t proximity_data = 0;

void setup() {
  
  // Initialize Serial port
  Serial.begin(9600);
  Serial.println();
  Serial.println(F("------------------------------------"));
  Serial.println(F("SparkFun APDS-9960 - ProximitySensor"));
  Serial.println(F("------------------------------------"));
  
  // Initialize APDS-9960 (configure I2C and initial values)
  if ( apds.init() ) {
    Serial.println(F("APDS-9960 initialization complete"));
  } else {
    Serial.println(F("Something went wrong during APDS-9960 init!"));
  }
  
  // Start running the APDS-9960 proximity sensor (no interrupts)
  if ( apds.enableProximitySensor(false) ) {
    Serial.println(F("Proximity sensor is now running"));
  } else {
    Serial.println(F("Something went wrong during sensor init!"));
  }
  // Adjust the Proximity sensor gain
  if ( !apds.setProximityGain(PGAIN_2X) ) { // muss nach enableProximitySensor aufgerufen werden
    Serial.println(F("Something went wrong trying to set PGAIN"));
  }
}


void loop() {
  
  // Read the proximity value
  if ( !apds.readProximity(proximity_data) ) {
    Serial.println("Error reading proximity value");
  } else {
    Serial.print("Proximity: ");
    Serial.println(proximity_data);
  }
  
  // Wait 250 ms before next reading
  delay(250);
}

 

Die Veränderung, die ich vorgenommen habe, ist das Vertauschen der Reihenfolge der Funktionsaufrufe apds.enableProximitySensor und apds.setProximityGain. Letzterer setzt den Verstärkungsfaktor (PGAIN_1X, …_2X, …4X, …_8X) für den Näherungssensor. Allerdings wird in der enable Funktion der Default Wert gesetzt, nämlich PGAIN_4X. Will man ihn verändern, muss man das also nach der enable Funktion tun. Ich habe diesen kleinen Bug auf Github als „issue“ gemeldet, vielleicht ist er ja schon behoben, wenn ihr die Bibliothek installiert.

Andere Einstellungen der Näherungsdetektion

Die Einstellungen ähneln sehr den Einstellungen der Gestendetektion:

  • IR-LED Strom: apds.LEDDrive; 
    • 12.5, 25, 50 und 100 mA sind wählbar
  • IR-LED Boost: identisch mit IR-LED Boost bei der Gestendetektion

Weiter will ich das nicht ausführen – wieder mit dem Verweis auf Bibliothek und Datenblatt für diejenigen, die tiefer einsteigen wollen.

Näherungsdetektion mit Interrupt

Man kann ein oberes und ein unteres Limit für den Proximity Wert setzen, bei dem ein Interrupt ausgelöst wird. Setzt man als unteres Limit (PROX_INT_LOW) die 0, dann wird nur ein Nah-Interrupt ausgelöst, da 0 aufgrund des Rauschens nie erreicht wird.

Der mitgelieferte Beispielsketch dazu heißt ProximityInterrupt.ino. Ich habe hier wieder die Funktion für die Einstellung des PGAIN Wertes hinter die enable Funktion gesetzt, da sie sonst wirkungslos wäre. Ansonsten sollte der Sketch selbsterklärend sein. 

#include <Wire.h>
#include <SparkFun_APDS9960.h>

// Pins
#define APDS9960_INT    2  // Needs to be an interrupt pin
#define LED_PIN         13 // LED for showing interrupt

// Constants
#define PROX_INT_HIGH   200 // Proximity level for interrupt
#define PROX_INT_LOW    0  // No far interrupt

// Global variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
uint8_t proximity_data = 0;
int isr_flag = 0;

void setup() {
  
  // Set LED as output
  pinMode(LED_PIN, OUTPUT);
  pinMode(APDS9960_INT, INPUT);
  
  // Initialize Serial port
  Serial.begin(9600);
  Serial.println();
  Serial.println(F("---------------------------------------"));
  Serial.println(F("SparkFun APDS-9960 - ProximityInterrupt"));
  Serial.println(F("---------------------------------------"));
  
  // Initialize interrupt service routine
  attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
  
  // Initialize APDS-9960 (configure I2C and initial values)
  if ( apds.init() ) {
    Serial.println(F("APDS-9960 initialization complete"));
  } else {
    Serial.println(F("Something went wrong during APDS-9960 init!"));
  }
  
   // Set proximity interrupt thresholds
  if ( !apds.setProximityIntLowThreshold(PROX_INT_LOW) ) {
    Serial.println(F("Error writing low threshold"));
  }
  if ( !apds.setProximityIntHighThreshold(PROX_INT_HIGH) ) {
    Serial.println(F("Error writing high threshold"));
  }
  
  // Start running the APDS-9960 proximity sensor (interrupts)
  if ( apds.enableProximitySensor(true) ) {
    Serial.println(F("Proximity sensor is now running"));
  } else {
    Serial.println(F("Something went wrong during sensor init!"));
  }
   // Adjust the Proximity sensor gain
  if ( !apds.setProximityGain(PGAIN_4X) ) {
    Serial.println(F("Something went wrong trying to set PGAIN"));
  }
}

void loop() {
  
  // If interrupt occurs, print out the proximity level
  if ( isr_flag == 1 ) {
  
    // Read proximity level and print it out
    if ( !apds.readProximity(proximity_data) ) {
      Serial.println("Error reading proximity value");
    } else {
      Serial.print("Proximity detected! Level: ");
      Serial.println(proximity_data);
    }
    
    // Turn on LED for a half a second
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    
    // Reset flag and clear APDS-9960 interrupt (IMPORTANT!)
    isr_flag = 0;
    if ( !apds.clearProximityInt() ) {
      Serial.println("Error clearing interrupt");
    }
    
  }
}

void interruptRoutine() {
  isr_flag = 1;
}

 

Setzt Ihr einen Wert für  PROX_INT_LOW oberhalb des Grundrauschens, z.B. 50, dann habt ihr zwei Grenzen, bei denen ein Interrupt ausgelöst wird. 

Umgebungslicht- und Farbmessung

Umgebungslicht- und Farbmessung lassen sich gut zusammen abhandeln. Viel zu erklären gibt es eigentlich auch nicht. Der Beispielsketch AmbientLightInterrupt.ino umfasst die wesentlichen Funktionen, einschließlich Interrupts. Die Stärke des Umgebungslichtes und der Farbkanäle wird jeweils als dimensionslose 16 Bit Zahl geliefert. Eine Umrechnung in Lux o. ä. gibt es nicht. Für das Umgebungslicht können zwei Interruptlimits gesetzt werden. 

#include <Wire.h>
#include <SparkFun_APDS9960.h>

// Pins
#define APDS9960_INT    2  // Needs to be an interrupt pin
#define LED_PIN         13 // LED for showing interrupt

// Constants
#define LIGHT_INT_HIGH  800 // High light level for interrupt
#define LIGHT_INT_LOW   100   // Low light level for interrupt

// Global variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
uint16_t ambient_light = 0;
uint16_t red_light = 0;
uint16_t green_light = 0;
uint16_t blue_light = 0;
int isr_flag = 0;
uint16_t threshold = 0;

void setup() {
  
  // Set LED as output
  pinMode(LED_PIN, OUTPUT);
  pinMode(APDS9960_INT, INPUT);
  
  // Initialize Serial port
  Serial.begin(9600);
  Serial.println();
  Serial.println(F("-------------------------------------"));
  Serial.println(F("SparkFun APDS-9960 - Light Interrupts"));
  Serial.println(F("-------------------------------------"));
  
  // Initialize interrupt service routine
  attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
  
  // Initialize APDS-9960 (configure I2C and initial values)
  if ( apds.init() ) {
    Serial.println(F("APDS-9960 initialization complete"));
  } else {
    Serial.println(F("Something went wrong during APDS-9960 init!"));
  }
  
  // Set high and low interrupt thresholds
  if ( !apds.setLightIntLowThreshold(LIGHT_INT_LOW) ) {
    Serial.println(F("Error writing low threshold"));
  }
  if ( !apds.setLightIntHighThreshold(LIGHT_INT_HIGH) ) {
    Serial.println(F("Error writing high threshold"));
  }
  
  // Start running the APDS-9960 light sensor (no interrupts)
  if ( apds.enableLightSensor(false) ) {
    Serial.println(F("Light sensor is now running"));
  } else {
    Serial.println(F("Something went wrong during light sensor init!"));
  }
  
  // Read high and low interrupt thresholds
  if ( !apds.getLightIntLowThreshold(threshold) ) {
    Serial.println(F("Error reading low threshold"));
  } else {
    Serial.print(F("Low Threshold: "));
    Serial.println(threshold);
  }
  if ( !apds.getLightIntHighThreshold(threshold) ) {
    Serial.println(F("Error reading high threshold"));
  } else {
    Serial.print(F("High Threshold: "));
    Serial.println(threshold);
  }
  
  // Enable interrupts
  if ( !apds.setAmbientLightIntEnable(1) ) {
    Serial.println(F("Error enabling interrupts"));
  }
  
  // Wait for initialization and calibration to finish
  delay(500);
}

void loop() {
  
  // If interrupt occurs, print out the light levels
  if ( isr_flag == 1 ) {
    
    // Read the light levels (ambient, red, green, blue) and print
    if (  !apds.readAmbientLight(ambient_light) ||
          !apds.readRedLight(red_light) ||
          !apds.readGreenLight(green_light) ||
          !apds.readBlueLight(blue_light) ) {
      Serial.println("Error reading light values");
    } else {
      Serial.print("Interrupt! Ambient: ");
      Serial.print(ambient_light);
      Serial.print(" R: ");
      Serial.print(red_light);
      Serial.print(" G: ");
      Serial.print(green_light);
      Serial.print(" B: ");
      Serial.println(blue_light);
    }
    
    // Turn on LED for a half a second
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    
    // Reset flag and clear APDS-9960 interrupt (IMPORTANT!)
    isr_flag = 0;
    if ( !apds.clearAmbientLightInt() ) {
      Serial.println("Error clearing interrupt");
    }
    
  }
}

void interruptRoutine() {
  isr_flag = 1;
}

 

Auch hier gibt es wieder verschiedene Einstellmöglichkeiten, vor allem den Verstärkungsfaktor (AGAIN_YX mit Y=1, 4, 16, 64), den ihr über die öffentliche Funktion setAmbientLightGain einstellen könnt. 

Fazit

Ich finde den APDS-9960 mit seinen vielen Funktionen und Optionen wirklich sehr beeindruckend. Besonders schön ist die Gestenfunktion, mit der man so schöne Sachen machen kann wie z.B. mit bestimmten Bewegungen das Licht an- oder auszuschalten. Vielleicht teilt ihr ja meine Freude. Viel Spaß! 

20 thoughts on “APDS-9960 – das Multitalent

  1. Hallo Wolfgang,

    ich wollte nur auf den APDS-9930 hinweisen, der erheblich günstiger zu beschaffen ist. Gibt auch eine eigene Library für den.

    Ich bin gerade dabei, diesen für einen IoT-Kartenhalter einzusetzen. Und sieht eigentlich gut aus.

    1. Hallo Bernhard,
      vielen Dank für den Hinweis. Ich habe so ein Teil bestellt und schaue es mir mal an.
      VG, Wolfgang

    2. Hallo Bernhard,
      jetzt habe ich mich mal mit dem APDS9930 beschäftigt. Ich würde ihn als abgespeckten APDS9930 bezeichnen. Er beherrscht die Näherungs- und Umgebungslichtmessung, erkennt aber keine Gesten und keine Farben. Wenn man nur an Näherung und Umgebungslicht interessiert ist, dann reicht er aus.
      Als Bibliothek habe ich diese hier gewählt:
      https://github.com/depau/APDS9930/tree/master/examples
      Ich habe sie manuell installiert. Eine Bibliothek, die über den Arduino Bibliotheksmanager installierbar ist, habe ich nicht gefunden.
      Kurioserweise haben meine Module nur funktioniert, wenn ich VL an 5V und nicht an 3,3 V angeschlossen habe. Könnte ein spezifisches Problem meiner Module sein.
      Grüße, Wolfgang

  2. Hallo,

    ich versuche krampfhaft, den GestureTest.ino zum Laufen zu bringen. Allerdings nicht mit einem Arduino Uno, den habe ich nämlich nicht, sondern mit einem Lolin (Wemos) D1 R2. Ich möchte dieses Teil benutzen, weil ich auf unserer Modellbahn-Clubanlage (Spur null, ca. 300 m²) eine Menge Sensoren installieren will, die Züge melden sollen. Dazu ist der Uno leider zu gross und zu teuer. Ausserdem läuft der Wemos auf 3,3 V, auch auf den Ein-/Ausgängen. Die WiFi-Eigenschaften sind dabei unwichtig.
    Die Geschichte mit dem ISR im IRAM habe ich verstanden und gelöst, so, wie im Beitrag von Bernd beschrieben.

    Ich glaube, dass mein Problem aus der Library für den APDS 9960 stammt. Im Programmcode werden keine Anschlüsse für SDA und SCL definiert, also können sie nur in der Library zugewiesen werden. Der WEMOS hat aber keine A4 und A5 Anschlüsse, deshalb verwende ich D1 (SDA) und D2 (SCL), das kann vermutlich nicht funktionieren (!?). Weiterhin scheint mir, dass in der Library die Pullup-Widerstände für SDA und SCL eingeschaltet werden, ich habe die Brücken auf dem BOB entfernt und es liegen weiterhin 3,3 V an den Anschlüssen an.
    Die Lösung wäre vermutlich, die Libray zu ändern, aber dazu bin ich leider zu blöd :(((

    Gibt es vielleicht eine Library, bei der man die Anschlüsse im Sketch passend zuordnen kann?

    1. Hallo Christoph,
      ich schätze, ich weiß, wo das Problem liegt, und zwar beim Interrupt. Der Autor des Beispielsketches verwendet für attachInterrupt und detachInterrupt die Interruptnummer. Das ist bei einem ATmega328P basierten Board die 0 für den Pin 2. Stattdessen sollte man immer digitalPinToInterrupt() verwenden. Ich habe es gerade ausprobiert. Als Interruptpin habe ich D5 gewählt, also:

      #define APDS9960_INT  D5
      

      Und dann habe ich die attachInterrupts / detachInterrupts modifiziert, also z.B.:

      attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
      

      Dann habe ich noch das IRAM_ATTR zur ISR zugefügt und die ISR vor dem setup positioniert. Mit diesen Veränderungen ging es dann.
      VG, Wolfgang

      1. Hallo Wolfgeng,

        Vielen Dank für die prompte Antwort. Ich habe noch etwas im Internet recherchiert und bei github ein Beispielprogramm für den ESP 8266 gefunden, das nach leichten Anpassungen für mich funktioniert. Ich würde Dir mein Programm gerne schicken, möchte aber nicht Dein Forum damit zumüllen. Gibt es eine andere Möglichkeit?

        1. Hallo Christoph,

          ja, gerne an: Wolfgang.Ewald@wolles-elektronikkiste.de. Bin mal gespannt, wie sehr sich das von meinem Ansatz unterscheidet, der, wie gesagt auch funktioniert. Vielleicht hast du auch gesehen, dass ich die Beispielsketche hinsichtlich der Interruptnummern geändert habe

          VG, Wolfgang

  3. Hallo Wolfgang,
    ich habe in der Zeitschrift DigitaleModellbahn 4/2022 über ein Projekt gelesen, das den ADPS9930 an einem ESP32 beschreibt. Ich gehe davon aus, dass dafür auch der ADPS9960 verwendbar sein sollte. Meine Frage ist eine andere: Kann man zwei ADPS9960 gemeinsam an einem Arduino oder ESP32 betreiben und deren Werte einzeln in Berechnungen einbeziehen?
    Viele Grüße
    Andreas

      1. Herzlichen Dank für die schnelle Antwort. Dann werde ich mir erst mal die Bauteile besorgen …
        Grüße
        Andreas

  4. Im Datenblatt wird empfohlen einen 22 Ohm Widerstand / 1uF Tiefpass in die Versorgungsleitung einzubauen.
    Dies kann man einfach erreichen, indem man auf dem Breakout Bord das PS Pad offen lässt und die beiden daneben liegenden offenen Pads mit einem 22 Ohm Widerstand überbrückt.
    Dann muss man allerdings die Versorgung an VL anschließen.

  5. Hallo Bernd
    danke für deine Tipps!
    Ich arbeite mit dem ESP32 Heltec und Dank deinen Hinweisen läuft alles perfekt. Das Dazwischenschalten eines LevelConverters erübrigt sich natürlich mit dem 3.3V-betriebenen ESP32.
    Die I2C liegen bei mir auf D21 und D22, als Interruptpin wählte ich 25.
    #define APDS9960_INT 25
    Andreas

  6. Hallo Wolfgang.
    Besten Dank für den großartigen Blog-Artikel zum APDS-9960 !!!
    Zum Code der Gestenerkennung habe ich zwei Hinweise für alle die einen ESP8266 oder ESP32 nutzen:
    1. Beim ESP8266 verlangt die Bibliothek mittlerweile, dass der ISR im IRAM ablaufen muss, weil offenbar sonst Stabilitätsprobleme auftreten. Hierzu muss im Codel Lsting oben folgende Deklaration hinzgefügt werden:
    > void ICACHE_RAM_ATTR interruptRoutine ();
    2. Damit die Wahl eines anderen Interrupt Pins gelang habe ich in allen attachInterrupt und detachInterrupt Aufrufen den ersten Paramater durch den Aufruf „digitalPinToInterrupt(APDS9960_INT)“ ersetzt. Also z.B.
    > attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
    Dann läuft es auch beim ESP aus dem Stand.
    VG Bernd

      1. Beim Wemos und ESP8266 sind es die Pins D2 (GPIO4) für SDA und D1 (GPIO5) für SCL.

          1. Wie man die Interrupts für den ESP8266 anpasst, hat Bernd ja schon beschrieben. Ausprobiert habe ich es allerdings nicht. Aber evtl. musst du auch noch das wire.begin() anpassen. Wenn ich Bibliotheken schreibe, dann kommt die Funktion da nicht rein, sondern in den Sketch. Wenn der Sketch deswegen nicht kompiliert, müsstest du mal in die cpp Datei der Bibliothek gehen und in Zeile 60 den Wire.begin() anpassen – wahrscheinlich auf Wire.begin(D2, D1). Probiere einfach mal.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert