AP3216 (CJMCU 3216) Licht- und Näherungssensor

Über den Beitrag

In meiner Reihe über Licht-, Gestik-, Bewegungs- und Abstandssensoren möchte ich diesmal das Licht- und Näherungssensormodul AP3216 vorstellen. Dieses findet ihr in Onlineshops auch unter der Bezeichnung CJMCU 3216. Ich erläutere das Funktionsprinzip, gehe auf die technischen Daten ein und stelle dann meine Arduino Bibliothek AP3216_WE vor, die es hier auf Github gibt.  

Eine Zusammenfassung dieser Beitragsreihe gibt es hier.

Das Funktionsprinzip

Vom Funktionsumfang und der Reichweite her erinnert der AP3216 sehr an den VL6180X, den ich in einem früheren Beitrag vorgestellt hatte. Allerdings hat der AP3216 ein anderes Funktionsprinzip.

Der AP3216 besitzt zwei Photodioden. Die eine detektiert Licht im sichtbaren, die andere im infraroten Bereich. Die Photodiodenspannungen werden verstärkt und mit einem A/D-Wandler digitalisiert. Das Ergebnis für das sichtbare Licht speichert der AP3216 als 16 Bit Wert im ALS Datenregister (ALS = ambient light sensor). Das Ergebnis aus der IR Messung speichert er als 10 Bit Wert im IR Datenregister. 

Zur Abstandsmessung emittiert eine LED Infrarotlicht. Die IR Photodiode detektiert die reflektierte Strahlung zusätzlich zum IR Umgebungslicht. Daraus berechnet der AP3216 dann den Abstand in Form eines 10 Bit Wertes und speichert diesen im PS Datenregister (PS = proximity sensor). 

Die im ALS Datenregister gespeicherten Daten können über im Datenblatt aufgeführte Faktoren in Luxwerte umgerechnet werden. Der PS Wert hingegen lässt sich nicht direkt in einen Abstand umrechnen. 

Technische Daten des AP3216 Sensors

AP3216 Modul
AP3216 Modul

Genau genommen handelt es sich bei dem eigentlichen Sensor des Moduls um den AP3216C. Ein Datenblatt zu diesem Bauteil gibt es z.B. hier.  Ich habe darin ein paar inkonsistente Angabe und auch einen Fehler gefunden. Insgesamt wirkt das Datenblatt deshalb irgendwie „unfertig“ (vielleicht deswegen Version 0.86?). Trotzdem lässt sich damit arbeiten.  

Die wichtigsten technischen Daten bzw. Eigenschaften sind: 

  •  Umgebungslichtsensor:
    • vier Lux Ranges einstellbar
    • Kalibrierfunktion
  • Näherungssensor (PS):
    • Reichweite: 2 cm bis zu ca. 30 cm, abhängig von den Einstellungen
    • Kalibrierfunktion
    • vier Verstärkungsstufen 
    • einstellbare Messzeiten
  • IR LED:
    • Einstellung der Anzahl Pulse pro Messung 
    • Einstellung des LED Stroms
  • Interruptfunktion: für PS und ALS oder beide; active low
  • kontinuierliche oder Single Shot Messungen
  • Versorgungsspannung VCC und VLED: 2.4 – 3.6 Volt
  • Kommunikation: I2C, Adresse 0x1E (nicht variabel)
  • Stromverbrauch: 1.7 mA, gemessen für den AP3216_Continuous Sketch mit der weiter unten abgebildeten Schaltung (also einschließlich Level Konverter) 
  • Power Down Funktion 

Besonderheiten des AP3216 Moduls

Das Modul scheint die im Datenblatt aufgeführte, typische Beschaltung implementiert zu haben. Dazu gehören Kondensatoren zur Stabilisierung der Spannungsversorgung und Pull-Up Widerstände für die I2C-Leitungen und den Interruptpin. VCC und VLED (also die Spannungsversorgung der IR LED) haben getrennte Eingänge. Sie lassen sich jedoch zusammenschließen, indem die beiden Jumper Anschlüsse auf der Rückseite des Moduls verbindet. Ihr könnt in dem Fall deshalb auf einen der Anschlüsse verzichten.

Jumper auf der Rückseite des AP3216 Moduls
Jumper zum Verbinden von VCC und VLED

Das Modul hat keine Spannungsregelung. Entsprechend geht die Spannung an VCC/VLED direkt auf den Sensor. Deshalb dürfen keine 5 Volt angelegt werden. Das gilt sowohl für VCC/VLED wie auch für die I2C Leitungen. Aus diesem Grund solltet ihr bei Verwendung eines Arduino UNO oder anderen 5V Boards einen Spannungsteiler oder Logik Levelkonverter verwenden.

Ihr bekommt das Modul in einer recht breiten Preisspanne (1,50€ – 6,50€) bei Amazon oder in noch etwas größerer Auswahl bei eBay. Meistens sitzen die Shops in China. Ihr müsst deshalb mit längerer Lieferzeit rechnen. 

Messungen und Parameter des AP3216

Wenn Euch der nun folgende Abschnitt zu langweilig wird, dann installiert als erstes die Bibliothek und probiert die Beispielsketche aus. Bei Fragen könnt ihr dann schauen ob hier vielleicht die Antwort steht.  

ALS Messung

Für die ALS Messung könnt ihr zwischen vier Bereichen (Ranges) wählen. Leider unterscheiden sich die Range Angaben auf der ersten Seite des Datenblattes von denen die weiter hinten angegeben werden. Aus verschiedenen Gründen scheinen mir die Range Angaben aus dem hinteren Teil die richtigen zu sein. Deswegen habe ich diese in meiner Bibliothek implementiert.

Eine breite Range lässt die Messung hoher Lux Werte zu, allerdings ist die Auflösung geringer. Eine schmalere Range hingegen bietet entsprechend eine hohe Auflösung:

  • 0 – 20661 lux (Default): 0.35 lux / Count
  • 0 – 5162 lux: 0.788 lux / Count
  • 0 – 1291 lux: 0.0197 lux / Count
  • 0 – 323 lux:  0.0049 lux / Count

Eine ALS Messung nimmt ca. 100 Millisekunden in Anspruch. Die Messung kann kontinuierlich oder als Single Shot durchgeführt werden. Im Single Shot Modus geht der AP3216 nach der Messung in den Power Down Modus. Im Power Down Modus verbraucht der AP3216 nur noch wenige Mikroampere.

Eine Abfrage liefert immer den Wert, der gerade im Datenregister steht. Man muss also, gerade im Single Shot Modus, dafür Sorge tragen, dass man zwischen Initialisierung der Messung und dem Auslesen genügend Zeit lässt. 

Wird der AP3216 hinter einem Fenster verbaut, dann fallen die ALS Werte durch Absorption und Reflektion entsprechend geringer aus als ohne Fenster. Deshalb bietet der AP3216 die Möglichkeit einen Kalibrierfaktor zu hinterlegen. Diesen errechnet ihr folgendermaßen:

Kalibrierfaktor = (ALS Wert ohne Fenster) / (ALS Wert mit Fenster)

Erlaubt sind Werte zwischen 1 (Default) und 3.98. 

PS Messung

Die Einstellmöglichkeiten für die PS (Abstand) Messung sind recht vielfältig. Ich reiße sie hier nur kurz an, aber mit Hilfe der Beispielsketche und der Funktionsliste, die ihr weiter unten findet, solltet Ihr klar kommen. 

Zunächst einmal könnt Ihr auch bei den PS Messungen zwischen einem kontinuierlichen und einem Single Shot wählen. Wichtig ist, dass ALS und PS Messungen hintereinander stattfinden. Die mögliche Messfrequenz wird dementsprechend durch die Summe beider Messzeiten festgelegt.  Die Messdauer (mean time, Mittlungsdauer) einer PS Messung könnt ihr zwischen 12.5 und 50 Millisekunden einstellen. Nach meiner Erfahrung bringt ein hoher Wert kein großes Plus an Reichweite oder Wiederholbarkeit. Ich empfehle deshalb die Voreinstellung von 12.5 Millisekunden beizubehalten. 

Die integration time ändert die Umwandlungszeit des A/D-Wandlers. Das hat einen erheblichen Effekt auf die Auflösung und die Reichweite. Versucht mal einen Wechsel der Voreinstellung 1 auf einen neuen Wert 8. Das Rauschen geht fast auf Null, und die Reichweite erhöht sich auf ein Vielfaches. 

Darüber hinaus könnt ihr folgende Parameter einstellen:

  • Verstärkungsfaktor (gain)
    • bringt höhere PS Werte, aber auch mehr Rauschen; hinsichtlich der Reichweite konnte ich keinen großen Effekt feststellen. 
  • LED Strom: der Default Wert von 100 % kann stufenweise reduziert werden
    • spart Strom, reduziert aber auch die Reichweite
  • Waiting Time: Wartezeitfaktor x zwischen den Messungen; Wartezeit = x ⋅ (ALS Messzeit + PS Messzeit)
    • hier ist ein Fehler im Datenblatt: der Faktor muss 6 Bit breit sein (also max 63) und nicht 5 wie angegeben
  • Pulsanzahl: Zahl der IR Pulse pro Messung
  • Kalibrierwert: ein fester Wert, der von den PS Werten abgezogen wird

Interrupteinstellung

Ihr könnt sowohl für die PS Werte wie auch für die ALS Werte Limits (Thresholds) festlegen außerhalb derer ein Interrupt ausgelöst wird. Der Interrupt Pin ist active low. Ich habe eine Funktion implementiert, die es erlaubt die ALS Limits als Lux Werte einzugeben. Ihr müsst dabei jedoch sicherstellen, dass ihr Lux Range nicht überschreitet, denn das ALS Limit in lux wird durch die Auflösung geteilt und der resultierende Wert darf 65535 (16 Bit Breite des ALS Threshold Registers) nicht überschreiten. 

Dann lässt sich noch der Interruptmodus für PS Messungen einstellen:

  • Zonenmodus: Interrupt wird immer ausgelöst, wenn Messwerte außerhalb der festgelegten Limits gemessen werden
  • Hysteresenmodus: Der Interrupt wird erst bei Überschreiten des oberen Limits aus ausgelöst. Danach werden immer wieder Interrupts ausgelöst bis das untere Limit unterschritten wird

Darüber hinaus könnt ihr folgende Parameter einstellen:

  • Interruptlöschung: Manuell oder Löschung durch Auslesen der Datenregister
  • Interruptfilter: Auslösen des Interrupts nach n Messungen außerhalb der Limits; getrennte Angaben für ALS und PS 

Infrarotwertmessung

Für die Infrarotlichtmessung gibt es keine eigenen Einstellparameter. 

Die AP3216_WE Bibliothek

Die Bibliothek einschließlich der Beispielsketche könnt ihr hier von Github herunterladen. Um den Umgang mit der Bibliothek zu erleichtern, habe ich eine Liste mit den Funktionen zusammengestellt: 

Liste der oeffentlichen Funktionen der AP3216_WE Bibliothek
Liste der öffentlichen Funktionen der AP3216_WE Bibliothek

Die Schaltung für die Beispiele

Fritzing Schema: Schaltung für die Beispielsketche
Schaltung für die Besipielsketche

Neben den vorgegebenen Anschlüssen für VCC, VLED, SDA und SCL habe ich den Interruptausgang an Pin 2 des Arduinos gehängt und der Schaltung noch eine LED an Pin 10 zum Anzeigen von Interrupts spendiert. Durch den Levelkonverter sieht alles etwas wirr aus. Es gibt aber auch einen Levelkonverter der ideal zum AP3216_WE passt (ich hatte ihn nur nicht als Fritzing Bauteil):

Beschaltung des AP3216 für die Beispielsketche
Schaltung mit ideal passendem Levelkonverter

Einen Levelkonverter mit einer solchen Pinbelegung gibt’s z.B. hier bei Amazon. 

Beispielsketche für den AP3216

Kontinuierliche Messung

Den AP3216_Continuous Sketch habe ich mit vielen Kommentaren versehen. Er ist deshalb ein guter Startpunkt.  

Der Sketch nutzt die kontinuierliche ALS, PS und IR Messung. Außerdem nutzt er die „Object is near“ Funktion und überprüft ob ein IR Overflow vorliegt. Spielt einfach mal ein wenig mit den Parametern. 

#include <Wire.h>
#include <AP3216_WE.h>

AP3216_WE myAP3216 = AP3216_WE();

void setup() {
  Serial.begin(9600);
  Wire.begin();
  myAP3216.init();

 /*
  * Choose between the modes:
  * ALS: ambient light continuous
  * PS: proximity sensor continous
  * ALS_PS: ambient Light and Proximity Sensor continuous (default)
  * ALS_ONCE: ambient light single 
  * PS_ONCE: proximity sensor single 
  * ALS_PS_ONCE: ambient light and proximity sensor single
  * POWER_DOWN: switch off
  * RESET: reset
  */
  //myAP3216.setMode(ALS_PS_ONCE); // Uncomment and adjust if needed 
  
   /*
   * Choose between 4 Lux Ranges: 
   * RANGE_20661 (default), RANGE_  RANGE_5162, RANGE_1291, RANGE_323
   * RANGE_x means range is from 0 to x Lux. Smaller range = higher resolution.
   */
  myAP3216.setLuxRange(RANGE_20661);
 
   /*
   * Choose between 4 gain values: 1, 2(default), 4, 8 
   * higher gain increases sensitivity as well as noise - 2 is good.
   */
  myAP3216.setPSGain(2);

  /*
   * Choose between 1(default),2,3,4 pulses of LED IR for proximity
   * more pulses increase (slightly) the max. distance
   */
  myAP3216.setNumberOfLEDPulses(1);
  
  /*
   * Choose meantime for proximity measurement: PS_MEAN_TIME_50, PS_MEAN_TIME_37_5, PS_MEAN_TIME_25, PS_MEAN_TIME_12_5
   * ==> 50, 37.5, 25 or 12.5 milliseconds; longer meantime provides less spreaded values
   */
  myAP3216.setPSMeanTime(PS_MEAN_TIME_50);
  
  /*
   * Choose lower and upper proximity threshold for "objectIsNear" function, not needed if function is not used
   */
  myAP3216.setPSThresholds(300, 800);
  
  /*
   * If you want to increase the max distance, this function helps. Choose value between 1 and 16.
   * 
   */
  myAP3216.setPSIntegrationTime(1);
  
  /*
   * the PS calibration value is substracted from the measured proximity value. Choose value between 0 and 512. 
   */
  //myAP3216.setPSCalibration(150);
  
  /*
   * Choose between INT_MODE_HYSTERESIS (default) and INT_MODE_ZONE. The "objectIsNear" function only works with this mode. 
   */
  myAP3216.setPSInterruptMode(INT_MODE_HYSTERESIS);

  /* 
   * ALS calibration is necessary when the sensor is installed behind a window. E.g. if an ALS value is 80% behind a window
   * then select 100/80 = 1.25 as calibration factor. 
   */
  //myAP3216.setALSCalibrationFactor(1.25); // uncomment if needed
  
  delay(1000); // without delay the first values will be zero
}

void loop() {
  float als = myAP3216.getAmbientLight();
  unsigned int prox = myAP3216.getProximity();
  unsigned int ir = myAP3216.getIRData(); // Ambient IR light
  bool isNear = myAP3216.objectIsNear();
  bool irIsValid = ~ myAP3216.irDataIsOverflowed();
  
  Serial.print("Lux: "); Serial.print(als);
  Serial.print("  Proximity: "); Serial.print(prox);
  Serial.print("  Object near? ");
  
  if(isNear){
    Serial.print("Yes");
  }
  else{
    Serial.print("No");
  }
  Serial.print("  Infrared: "); Serial.print(ir);
  Serial.print("  Valid: "); Serial.println(irIsValid); 

  delay(1000);
}

 

Single shot Messungen

Bei der Single Shot Messung („Once“) wird jede Messung manuell über die setMode Funktion gestartet. Die Funktionen getAmbientLight und getProximity lesen einfach nur aus, was sich aktuell im Datenspeicher befindet. Probiert es aus, indem ihr Zeile 20 deaktiviert. Ihr erhaltet immer denselben, zu Beginn gemessenen Werte. 

Wartet man nicht lang genug zwischen Auslösen der Messung und Auslesen des Ergebnisses, dann erhält man zuvor ermittelten Wert. Durch ein ausreichend langes Delay kann man das verhindern. Alternativ kann man PS / ALS Limits (Thresholds) setzen, die auf jeden Fall gerissen werden. Dadurch wird man per Interrupt informiert, wenn der Messwert zur Verfügung steht und liest dann aus.  

#include <Wire.h>
#include <AP3216_WE.h>

AP3216_WE myAP3216 = AP3216_WE();

void setup() {
  Serial.begin(9600);
  Wire.begin();
  myAP3216.init();
  myAP3216.setLuxRange(RANGE_20661);
  myAP3216.setMode(ALS_PS_ONCE);
  delay(1000);
}

void loop() {
  float als = myAP3216.getAmbientLight();
  unsigned int prox = myAP3216.getProximity();
  Serial.print("Lux: "); Serial.print(als);
  Serial.print("  Proximity: "); Serial.println(prox);
  myAP3216.setMode(ALS_PS_ONCE); // initiates next measurement
  delay(1000); 
}

 

Verwenden der Interruptfunktion

In diesem Sketch habe ich die Interruptfunktion sowohl für die PS, wie auch für die ALS Messung implementiert. Der Sketch erkennt und meldet auch, wenn die Interruptbedingungen für ALS und PS in einem Messzyklus erfüllt werden.  

Sofern ihr die Zeilen 39 und 40 nicht entkommentiert, dann werden keine Daten ausgelesen. Deshalb muss die Interruptlöschung manuell erfolgen. 

#include <Wire.h>
#include <AP3216_WE.h>
byte interruptPin=2;
byte ledPin=10;
volatile bool event = false;

AP3216_WE myAP3216 = AP3216_WE();

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, FALLING);
  Serial.begin(9600);
  Wire.begin();
  myAP3216.init();
  myAP3216.setLuxRange(RANGE_20661);
  
  /*
   * Interrupts can be cleared manually if CLR_INT_MANUALLY (or 1) is chosen
   * Interrupts will be cleared by reading PS or ALS data if CLR_INT_BY_DATA_READ (or 0) is chosen
   */
  myAP3216.setIntClearManner(CLR_INT_MANUALLY);
  myAP3216.setPSThresholds(0, 200); // Outside this window an interrupt is triggered

  /*
   * Outside the following window (lux values) an interrupt is triggered. Ensure that 
   * you don't chose values outside the defined lux range (setLuxRange).
   */
  myAP3216.setALSThresholds(3, 500); // 
  myAP3216.setPSIntegrationTime(8);
  myAP3216.setPSInterruptMode(INT_MODE_ZONE);
  delay(1000);
}

void loop() {
/*
 * Uncomment the following lines if you want to see the measured values
 */
//  float als = myAP3216.getAmbientLight();
//  unsigned int prox = myAP3216.getProximity();
//  Serial.print("Lux: "); Serial.print(als);
//  Serial.print("  Proximity: "); Serial.println(prox);
  if(event){
    interruptAction();
  }
  /*
   * without the following delay you will not detect ALS and PS interrupts together. 
   */
  delay(1000); 
}

void interruptAction(){
  byte intType = NO_INT;
  intType = myAP3216.getIntStatus();
  
  switch(intType){
    case(ALS_INT):
      Serial.println("Ambient Light Interrupt!");
      break;
    case(PS_INT):
      Serial.println("Proximity Interrupt!");
      break;
    case(ALS_PS_INT):
      Serial.println("Ambient Light and Proximity Interrupt!");
      break;
    default:
      Serial.println("Something went wrong...");
      break;      
  }
  
  digitalWrite(ledPin, HIGH);
  delay(1000);
  digitalWrite(ledPin, LOW);
  
  intType = myAP3216.getIntStatus();
  myAP3216.clearInterrupt(intType);
  event = false;
}

void blink(){
  event = true;
}

 

Danksagung

Der Hintergrund, in den ich den AP3216 für das Beitragsbild eingefügt habe, stammt von Alejandro Faletti auf Pixabay.

Schreibe einen Kommentar

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