SI1145 ALS, IR, UV und PS Sensor

Über diesen Beitrag

Ich hatte über den SI1145 schon in meinem letzten Beitrag zu UV-Sensoren berichtet. Der SI1145 kann aber wesentlich mehr, nämlich Umgebungs- (ALS/VIS) und Infrarotlicht (ALS/IR) messen, außerdem könnt ihr ihn als Näherungssensor einsetzen (PS). Leider hatte ich keine Bibliothek gefunden, die all diese Möglichkeiten mit den dazugehörigen Einstellmöglichkeiten zugänglich macht. Deshalb habe ich nun selbst eine Bibliothek geschrieben (SI1145_WE), die ihr hier auf Github findet.

Ich möchte in diesem Beitrag die Bibliothek vorstellen, werde vorher aber noch auf die Eigenschaften des SI1145 (Moduls) eingehen. Der Beitrag ist doch sehr lang geworden – aber der SI1145 ist auch ein komplexes Bauteil. Falls es euch zu langweilig ist, geht gleich auf die Bibliothek. Aber mit mehr Hintergrundwissen ist sie besser verständlich. 

Eigenschaften des SI1145

Technische Eckdaten

  • Abstandsmessung von 1cm bis > 50cm
  • Lichtmessung von 1 – 128000 Lux
  • Stromverbrauch im Mikroamperebereich
    • <500 Nanoampere im Standby
  • Versorgungsspannung: 1.71 – 3.6 Volt
    • als Modul meistens 3 – 5 Volt
  • Kommunikation über I2C
    • Adresse: 0x60 vorgegeben, über Register einstellbar

Umgebungslichtmessungen (ALS VIS/IR)

Messprinzip / Ablauf

Der SI1145 besitzt eine Fotodiode für den sichtbaren Bereich und zwei Fotodioden für den Infrarotbereich. Die zwei IR-Fotodioden unterscheiden sich in ihrer Größe und damit in ihrer Empfindlichkeit. Sie haben zwei Aufgaben. Zum einen dienen sie zur Korrektur bei der Umgebungslichtmessung, denn diese wird durch den Infrarotanteil des Lichts verfälscht. Zum anderen dienen sie als Detektoren für die Abstandsmessungen. Dabei könnt ihr wählen, welche IR-Fotodiode ihr welcher Funktion zuordnet. Beispielsweise wird man im direkten Sonnenlicht mit der kleinen Diode arbeiten, da die große Diode unter diesen Bedingungen überlaufen wird (bzw. der nachgeschaltete A/D Wandler).

Die VIS- und die IR-Messung finden hintereinander statt. Der SI1145 ist dabei sehr schnell. Typischerweise braucht eine VIS/IR-Messung im Doppelpack lediglich 285 Mikrosekunden.

Die Ergebnisse der Messungen speichert der SI1145 in den 16 Bit Datenregistern (je 2 x 8 Bit um genau zu sein) für den sichtbaren und den infraroten Bereich. Diese Rohdaten müssen weiter verarbeitet werden, um zum Beispiel Lichtstärken in Lux zu errechnen.

Aus dem Datenblatt des SI1145: Empfindlichkeit der Fotodioden vs. Wellenlänge
Aus dem Datenblatt: Empfindlichkeit der Fotodioden vs. Wellenlänge

Einstellmöglichkeiten

Für die VIS und IR Messungen lässt sich vor allem die Messzeit (Gain, Integration Time) einstellen. Ihr könnt einen Faktor zwischen 1 und 128 wählen. Die Messzeit ist dann 25.6 µs x 2^GAIN mit GAIN = 0 bis 7. Für besonders starkes Licht stellt der SI1145 einen „High Signal Range Modus“ zur Verfügung, der einem Gainfaktor von 1/14.5 entpricht.

Darüber hinaus könnt Ihr (theoretisch) die Mindestregenerationszeit für den A/D-Wandler vor Durchführung der nächsten Messung einstellen. Allerdings habe ich mich in meiner Bibliothek an die Datenblattempfehlung gehalten und einen gainabhängigen Algorithmus fest implementiert.  

Der A/D-Wandler hat eine ungewöhnliche Auflösung von 17 Bit. In der Voreinstellung werden die oberen 16 Bits in die Datenregister übertragen. Man kann aber auch die unteren 16 Bits nutzen. Dadurch wird die Auflösung verdoppelt, das obere Limit aber entsprechend halbiert. 

Die Bereitstellung der Messwerte im Datenregister kann durch einen Interrupt angezeigt werden.

Luxberechnungen

Aus Lichtsensoren verlässliche Luxwerte herauszuholen ist allgemein eine Wissenschaft für sich, denn:

  • die Fotodioden für den sichtbaren Bereich werden durch das für uns unsichtbare Infrarotlicht beeinflusst
  • innerhalb des sichtbaren Bereiches haben die Fotodioden wellenlängenabhängig unterschiedliche Empfindlichkeiten und sprechen somit unterschiedlich auf verschiedene Lichtquellen (Sonnenlicht, Glühlampe, LED (kalt/weiß), Halogen, etc.) an.  

Das Infrarotlichtproblem lässt sich über entsprechende zusätzliche Sensoren lösen, indem man den Infrarotanteil herausrechnet.

Das Problem der Sensorcharakteristik lässt sich hingegen nur lösen, indem man auf unterschiedliche Lichtquellen kalibriert. Selbst kommerzielle Luxmeter haben dieses Problem. Oft sind sie auf Glühlampenlicht kalibriert, was eigentlich kaum noch zu finden ist.

Hier ein Beispiel zweier Luxmeter, die bei Glühlampenlicht nach meinen Messungen <5 % voneinander abweichen. Die Ergebnisse bei bedeckten Himmel (also sehr kaltem Licht) dagegen weichen um ca. 40% voneinander ab, bezogen auf den niedrigeren Wert: 

Zwei Luxmeter bei bedecktem Himmel: 5200 vs. 7255 Lux
Zwei Luxmeter bei bedecktem Himmel: 5200 vs. 7255 Lux

Vor diesem Hintergrund sollte man seine Ansprüche an die Exaktheit von Eigenbau-Luxmeter vielleicht etwas zurückschrauben. Zumindest aber sollte man sich bewusst sein, dass exakte Messungen mit einem gewissen Aufwand verbunden sind. 

Das Ganze wird noch komplexer, wenn man den SI1145 unter Abdeckungen einsetzt, die einen gewissen Anteil des sichtbaren und des infraroten Lichts absorbieren.

Berechnungsformel für den SI1145

In den Application Notes AN523 (Overlay considerations for the Si114X sensor) habe ich zur Luxberechnung auf Seite 3 folgende Formel gefunden:

Die beiden Koeffizienten für den Fall, dass keine Abdeckung verwendet wird, findet ihr auf der nächsten Seite der Application Notes AN523:

Koeffizienten für die Luxberechnung für den SI1145

Die ALS / ALSIR Werte in der obigen Gleichung sind die aus dem Sensor ausgelesenen Werte bei aktuellen Licht bzw. im Dunkeln („dark reading“). Die Dunkelwerte müssen einmalig ermittelt werden. Die Formel gilt nur für die Randbedingungen:

  • IR- und VIS-Gain = 0
  • Normal Signal Range 
  • Verwendung der kleinen IR-Fotodiode.

Weicht ihr davon ab, so müsst ihr Korrekturfaktoren einfügen. 

Mit der Formel habe ich Ergebnisse erzielt, die im Bereich der Ergebnisse meiner Luxmeter lagen. Aus meiner Sicht müsste aber noch eine Wichtung durch die Count-per-Lux Faktoren (siehe Datenblatt, S. 6-7) erfolgen. In meinem Beispielsketch SI1145_lux_calculation habe ich deshalb neben der Standardformel eine Alternative angeboten.

Wer sich tiefer in dieses Thema einarbeiten möchte, der sollte neben den AN523 auch noch die AN576 und die AN498 lesen. Ich habe mich mehr auf die Beherrschung des SI1145 konzentriert als auf die nachfolgende Verarbeitung der Rohdaten. Deshalb und weil die Luxberechnung sehr individuell ausgestaltet werden kann, habe ich sie nicht in der Bibliothek selbst implementiert. 

Abstandsmessungen (PS)

Messprinzip / Ablauf

Der SI1145 besitzt keine integrierte IR-LED. Ihr müsst sie separat an den LED Pin anschließen (mit dem Minuspol an den Sensor by the way). Die größeren Brüder SI1146 und SI1147 besitzen sogar die Möglichkeit zwei bzw. drei IR-LEDs anzuschließen und separate PS-Messungen durchzuführen. Leider gibt es die aber nicht als Modul. 

Die Mindestpulsdauer für die IR-LED beträgt 25.6 Mikrosekunden. Die von Objekten reflekierte Strahlung wird – je nach Einstellung – von der großen oder der kleinen IR-Fotodiode detektiert. Innerhalb eines Messzyklus führt der SI1145 immer zuerst die PS-Messung aus und dann die VIS/IR-Messungen. 

Die Ergebnisse der PS-Messungen könnt Ihr im 16 Bit PS-Datenregister abrufen. 

Einstellmöglichkeiten

Für die PS Messungen lässt der SI1145 folgende Einstellungen zu:

  • LED-Pulsbreite (Gain): Faktor 1 bis 32
  • LED-Stromstärke: von 5.6 bis 360 mA
  • High Signal Range Modus (wie bei ALS Messungen)
  • Auswahl der oberen oder unteren 16 Bit des A/D-Wandlers 
  • (Regenerationszeit: auch hier bin ich den Datenblattempfehlungen gefolgt – also automatische Einstellung mit meiner Bibliothek)
  • Interrupt bei Bereitstellung des Messergebnisses 

UV-Indexmessungen

Hier verweise ich nochmal auf meinen letzten Beitrag. Ich möchte nur noch einmal hervorheben, dass eigentlich keine echte UV-Strahlungsmessung stattfindet, sondern die Daten aus den VIS/IR Daten extrapoliert werden. Die UV-Index Daten werden im zugehörigen Datenregister gespeichert. Die UV-Indexermittlung schließt einen Messzyklus ab.

Allgemeine Einstellungen

Neben den schon aufgezählten speziellen Einstellungen gibt noch ein paar allgemeine Einstellungen:

  • Kontinuierlicher oder Single-shot (forced) Modus
  • Measurementrate: der SI1145 geht nach einer Messung in den Schlafmodus und wacht periodisch auf. Die Measurementrate legt fest nach wieviel Schlafperioden eine Messung durchgeführt wird.
  • Abfrage nach Überläufen des A/D-Wandlers bei zu starkem Licht bzw. zu hohem Gain

SI1145 Module

SI1145 Modul Ausführungen
Zwei Beispiele von SI1145 Modulen

SI1145 Module gibt es in verschiedenen Ausführungen z.B. bei Amazon oder eBay zu kaufen. Die Preisspanne ist mit ca. 5 bis 22 Euro je nach Shop recht groß. Die meisten Module besitzen eine Spannungsregelung, so dass man sie zwischen 3 und 5 Volt betreiben kann. SDA, SCL und INT scheinen meistens Pull-Up Widerstände zu haben, so dass man sich nicht darum kümmern muss. Das gilt jedenfalls für die Module, die ich ausprobiert habe.

Die SI1145_WE Bibliothek

Nun zur Bibliothek. Die gibt es hier auf Github, zusammen mit den sechs Beispielsketchen, die ich gleich vorstellen werde. Ich habe die meisten Einstellmöglichkeiten zugänglich gemacht. Hier die Liste der öffentlichen Funktionen:

Liste öffentlicher Funktionen der SI1145 Bibliothek
Liste öffentlicher Funktionen der SI1145_WE Bibliothek

Wenn ihr die Bibliothek herunterladen solltet, dann bekommt damit auch die Funktionsliste im etwas besser lesbaren PDF Format.

Solltet ihr Fehler finden oder Funktionen vermissen, dann meldet euch. Ich verspreche kurzfristige Nachbesserung!

Die Beispielsketche

Beschaltung des SI1145

Für die ersten fünf Sketche, die nur einen SI1145 verwenden, kann die folgende Schaltung verwendet werden. Dabei braucht ihr die IR-LED aber nur dann, wenn ihr PS-Messungen durchführen wollt.

Schaltung für die Beispielsketche
Schaltung für die Beispielsketche (mit einem Sensor). Die LED ist eine IR-LED.

Auf dem Breadboard sieht das dann so aus:

Schaltung auf dem Breadboard
Schaltung auf dem Breadboard

Beispielsketch 1 – Basics, kontinuierliche Messung

Ich habe die Sketche mit vielen Kommentaren versehen. Deswegen brauche ich – glaube ich zumindest – nicht viel dazu zu schreiben. Grundsätzlich müsst ihr bei allen Sketchen über enableMeasurements() festgelegen, welche Messungen ihr aktivieren wollt und ob diese kontinuierlich (AUTO) laufen oder als Einzelmessung (FORCE) gestartet werden. Über PAUSE könnt ihr kontinuierliche Messungen aussetzen.

In diesem ersten Sketch führt der SI1145 ALS IR/VIS, PS und UV-Indexmessungen im kontinuierlichen Modus durch. Spielt zum Kennenlernen einfach mal ein bisschen mit den Einstellungen. 

#include <SI1145_WE.h>
#include <Wire.h>

SI1145_WE mySI1145 = SI1145_WE();

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

  /* in case you want to change the I2C Address */
  //mySI1145.setI2CAddress(0x59); 
  
  mySI1145.enableHighSignalVisRange(); // Gain divided by 14.5
  mySI1145.enableHighSignalIrRange(); // Gain divided by 14.5
  
  /* choices: PS, ALS, PSALS, ALSUV, PSALSUV || FORCE, AUTO, PAUSE */
  mySI1145.enableMeasurements(PSALSUV, AUTO);
  
  /* choose gain value: 0, 1, 2, 3, 4, 5, 6, 7 */ 
  //mySI1145.setAlsVisAdcGain(0);
  
  /* choose gain value: 0, 1, 2, 3, 4, 5 */
  //mySI1145.setPsAdcGain(0);

  /* if uncommented, the least 16 bit of the 17 bit ADC will be read */
  //mySI1145.enableHighResolutionVis();
  Serial.println("SI1145 - basics");
}

void loop() {
  byte failureCode = 0;
  unsigned int amb_als = 0;
  unsigned int amb_ir = 0;
  unsigned int proximity = 0;
  float uv = 0.0;
  
  amb_als = mySI1145.getAlsVisData();
  amb_ir = mySI1145.getAlsIrData();

  /* uncomment if you want to perform PS measurements */ 
  // proximity = mySI1145.getPsData();

  uv = mySI1145.getUvIndex();
  
  Serial.print("Ambient Light: ");
  Serial.println(amb_als);
  Serial.print("Infrared Light: ");
  Serial.println(amb_ir);
  Serial.print("UV-Index: ");
  Serial.println(uv);
  /* uncomment if you perform PS measurements */
  // Serial.print("Proximity: ");
  // Serial.println(proximity);
  failureCode = mySI1145.getFailureMode();  // reads the response register
  if((failureCode&128)){  // if bit 7 is set in response register, there is a failure
    handleFailure(failureCode);
  }
  Serial.println("---------");
  delay(1000);
}

void handleFailure(byte code){
  String msg = "";
  switch(code){
    case SI1145_RESP_INVALID_SETTING:
      msg = "Invalid Setting";
      break;
    case SI1145_RESP_PS1_ADC_OVERFLOW:
      msg = "PS ADC Overflow";
      break;
    case SI1145_RESP_ALS_VIS_ADC_OVERFLOW:
      msg = "ALS VIS ADC Overflow";
      break;
    case SI1145_RESP_ALS_IR_ADC_OVERFLOW:
      msg = "ALS IR Overflow";
      break;
    case SI1145_RESP_AUX_ADC_OVERFLOW:
      msg = "AUX ADC Overflow";
      break;
    default:
      msg = "Unknown Failure";
      break;
  }
  Serial.println(msg); 
  mySI1145.clearFailure();
}

 

Beispielsketch 2 – kontinuierlich, interruptgesteuert

Auch in diesem Sketch läuft der SI1145 im automatischen Modus. Allerdings seht ihr, dass die loop Schleife kein delay enthält bzw. dass das delay auskommentiert ist. Die Geschwindigkeit wird über die Measurementrate gesteuert. Bei dem hier angewandten Maximum (0xFFFF = 65535) findet ca. alle 2 Sekunden eine Messung statt. Ihr könnt ja mal einen anderen Wert testen. Z.B. 0x8FFF, dann landet ihr bei ca. einer Sekunde. Voreinstellung in meiner Bibliothek ist übrigens 0x00FF, was einer Messfrequenz von ca. 8 Millisekunden entspricht. Im FORCED Modus ist die Measurementrate definitionsgemäß Null.

Der Arduino wird über den Interruptpin „informiert“ wenn ein Messwert vorliegt. Es wird generell empfohlen, im kontinuierlichen Modus so zu verfahren. Denn wenn es dumm läuft, dann liest man die Datenregister während einer Aktualisierung aus und erhält so das untere Byte aus dem alten Messwert und das obere Byte aus dem neuen Messwert. 

#include <SI1145_WE.h>
#include <Wire.h>
const int interruptPin = 2;

SI1145_WE mySI1145 = SI1145_WE();

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(interruptPin, INPUT_PULLUP);
  mySI1145.init();
  //mySI1145.enableHighSignalVisRange();
  //mySI1145.enableHighSignalIrRange();
  
  /* SI1145 wakes up periodically - a measurement rate of x means that every x 
   *  wake up a measurement is done; chose between 0 and 65535 - if you chose a low value 
   *  you should uncomment the delay in the loop */
  mySI1145.setMeasurementRate(0xFFFF);
  
  /* choices: PS, ALS, PSALS, ALSUV, PSALSUV || FORCE, AUTO, PAUSE */
  mySI1145.enableMeasurements(PSALSUV, AUTO);

   /* choose gain value: 0, 1, 2, 3, 4, 5, 6, 7 */ 
  mySI1145.setAlsVisAdcGain(0);
  
   /* choose gain value: 0, 1, 2, 3, 4, 5 */
  mySI1145.setPsAdcGain(0);
  Serial.println("SI1145 - basics, interrupt controlled");
}

void loop() {
  byte failureCode = 0;
  unsigned int amb_als = 0;
  unsigned int amb_ir = 0;
  unsigned int proximity = 0;
  float uv = 0.0;
  
  mySI1145.clearAllInterrupts();

  /* wait until a measurement is completed -> interrupt goes LOW */
  while(digitalRead(interruptPin)){
  /* just wait for interruptpin to go LOW; if you have not set up the 
     interrupt correctly you might wait forever! */  
  } 
  
  amb_als = mySI1145.getAlsVisData();
  amb_ir = mySI1145.getAlsIrData();
  proximity = mySI1145.getPsData();
  uv = mySI1145.getUvIndex();
  
  Serial.print("Ambient Light: ");
  Serial.println(amb_als);
  Serial.print("Infrared Light: ");
  Serial.println(amb_ir);
  Serial.print("UV-Index: ");
  Serial.println(uv);
  Serial.print("Proximity: ");
  Serial.println(proximity);
  
  failureCode = mySI1145.getFailureMode();  // reads the response register
  if((failureCode&128)){  // if bit 7 is set in response register, there is a failure
    handleFailure(failureCode);
  }
  Serial.println("---------");
  
  //Measuring frequency in this sketch is controlled by setMeasurementRate
  //delay(500); 
}

void handleFailure(byte code){
  String msg = "";
  switch(code){
    case SI1145_RESP_INVALID_SETTING:
      msg = "Invalid Setting";
      break;
    case SI1145_RESP_PS1_ADC_OVERFLOW:
      msg = "PS ADC Overflow";
      break;
    case SI1145_RESP_ALS_VIS_ADC_OVERFLOW:
      msg = "ALS VIS ADC Overflow";
      break;
    case SI1145_RESP_ALS_IR_ADC_OVERFLOW:
      msg = "ALS IR Overflow";
      break;
    case SI1145_RESP_AUX_ADC_OVERFLOW:
      msg = "AUX ADC Overflow";
      break;
    default:
      msg = "Unknown Failure";
      break;
  }
  Serial.println(msg); 
  mySI1145.clearFailure();
}

 

Beispielsketch 3 – Forced ALS

Es mag euch aufgefallen sein, dass die VIS Messwerte recht stark schwanken, wenn sich der SI1145 nicht im High Signal Range Modus befindet. Das lässt sich mindern, indem man mehrere Messwerte mittelt.

In diesem Beispielsketch werden 50 Messwerte hintereinander gemessen. Das passiert hier im Forced Modus. Jede einzelne Messung muss über startSingleMeasurement() gestartet werden. Mit getAlsVisData() und getAlsIrData() werden die Datenregister ausgelesen. Diese Funktionen kümmern sich allerdings nicht darum, ob die Messungen schon abgeschlossen sind. Ihr könntet ein delay einfügen, müsstet aber sicher stellen auf der sicheren Seite zu sein und würdet einen mehr oder weniger großen Puffer einbauen. Der schnellste Weg, die Messungen hintereinander durchzuführen, ist die Interruptsteuerung. Die Messwerte werden sofort nach Bereitstellung abgeholt und die nächste Messung kann beginnen. 

#include <SI1145_WE.h>
#include <Wire.h>
const int interruptPin = 2;

SI1145_WE mySI1145 = SI1145_WE();

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(interruptPin, INPUT_PULLUP);
  mySI1145.init();
  //mySI1145.enableHighSignalVisRange();
  //mySI1145.enableHighSignalIrRange();
  
  /* choices: PS, ALS, PSALS, ALSUV, PSALSUV || FORCE, AUTO, PAUSE */
  mySI1145.enableMeasurements(ALS, FORCE);

   /* choose gain value: 0, 1, 2, 3, 4, 5, 6, 7 */ 
  mySI1145.setAlsVisAdcGain(0);

  //mySI1145.enableHighResolutionVis();
  Serial.println("SI1145 - forced ALS");
}

void loop() {
  byte failureCode = 0;
  unsigned long amb_als = 0;
  unsigned long amb_ir = 0;
   
  for(int i=0; i<50; i++){
    mySI1145.clearAllInterrupts();
    mySI1145.startSingleMeasurement();
    while(digitalRead(interruptPin)){
    /* just wait for interruptpin to go LOW; if you have not set up the 
       interrupt correctly you might wait forever! */  
    }
    amb_als += mySI1145.getAlsVisData();
    amb_ir += mySI1145.getAlsIrData();
  }
  amb_als /= 50;
  amb_ir /= 50;
  Serial.print("Ambient Light: ");
  Serial.println(amb_als);
  Serial.print("Infrared Light: ");
  Serial.println(amb_ir);
  failureCode = mySI1145.getFailureMode();  // reads the response register
  if((failureCode&128)){   // if bit 7 is set in response register, there is a failure
    handleFailure(failureCode);
  }
  Serial.println("---------");
  delay(1000);
}

void handleFailure(byte code){
  String msg = "";
  switch(code){
    case SI1145_RESP_INVALID_SETTING:
      msg = "Invalid Setting";
      break;
    case SI1145_RESP_PS1_ADC_OVERFLOW:
      msg = "PS ADC Overflow";
      break;
    case SI1145_RESP_ALS_VIS_ADC_OVERFLOW:
      msg = "ALS VIS ADC Overflow";
      break;
    case SI1145_RESP_ALS_IR_ADC_OVERFLOW:
      msg = "ALS IR Overflow";
      break;
    case SI1145_RESP_AUX_ADC_OVERFLOW:
      msg = "AUX ADC Overflow";
      break;
    default:
      msg = "Unknown Failure";
      break;
  }
  Serial.println(msg); 
  mySI1145.clearFailure();
}

 

Beispielsketch 4 – Advanced Proximity

In diesem Beispielsketch geht es um PS Messungen. Da auch PS Messungen recht stark schwanken, mittle ich hier zehn Einzelmessungen. Um die Reichweite zu vergrößern, könnt ihr den LED Strom mit setLEDcurrent() erhöhen. Ihr müsst allerdings selbst dafür Sorge tragen, dass eure LED den Strom auch verträgt. Gleiches gilt auch für den ggf. notwendigen Einbau von Vorwiderständen. 

Die Erhöhung des IR-LED Stromes geht mit höheren Basiswerten, also Werte ohne Objekt in Reichweite, einher. Das liegt daran, dass die IR-LED auch direkt auf den Sensor strahlt. Stellt mal etwas zur Abschirmung zwischen IR-LED und Sensor, z.B. einfach ein Stück Pappe oder Plastik. Das Messsignal eines Objektes hebt sich dann sehr viel deutlicher vom Basiswert ab.  

#include <SI1145_WE.h>
#include <Wire.h>
const int interruptPin = 2;

SI1145_WE mySI1145 = SI1145_WE();

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(interruptPin, INPUT_PULLUP);
  mySI1145.init();

  /* choices: PS, ALS, PSALS, ALSUV, PSALSUV || FORCE, AUTO, PAUSE */
  mySI1145.enableMeasurements(PS, FORCE);
  
  /* choose gain value: 0, 1, 2, 3, 4, 5 */
  mySI1145.setPsAdcGain(0);
  mySI1145.enableInterrupt(PS_INT);
  
  /* choose LEDCurrent: value between 1 (= 5.6 mA) and 15 (= 359 mA)
   * ensure your LED is compatible */
  mySI1145.setLEDCurrent(10);

  /* select PS diode: SMALL_DIODE or LARGE_DIODE (default)
   * LARGE_DIODE is more sensitive, but might lead to 
   * overflow e.g. in sunlight */
  //mySI1145.selectPsDiode(SMALL_DIODE);
  Serial.println("SI1145 - advanced proximity");
}

void loop() {
  byte failureCode = 0;
  unsigned long proximity = 0;
   
  /* 10 proximity measurements are done to increase repeatibility */
  for(int i=0; i<10; i++){
    mySI1145.clearAllInterrupts();
    mySI1145.startSingleMeasurement();
    while(digitalRead(interruptPin)){
    /* just wait for interruptpin to go LOW; if you have not set up the 
       interrupt correctly you might wait forever! */  
    }
    proximity += mySI1145.getPsData();
  }
  proximity /= 10;
   
  Serial.print("Proximity: ");
  Serial.println(proximity);
 
  failureCode = mySI1145.getFailureMode();  // reads the response register
  if((failureCode&128)){ // if bit 7 is set in response register, there is a failure
    handleFailure(failureCode);
  }
  Serial.println("---------");
  delay(1000);
}

void handleFailure(byte code){
  String msg = "";
  switch(code){
    case SI1145_RESP_INVALID_SETTING:
      msg = "Invalid Setting";
      break;
    case SI1145_RESP_PS1_ADC_OVERFLOW:
      msg = "PS ADC Overflow";
      break;
    case SI1145_RESP_ALS_VIS_ADC_OVERFLOW:
      msg = "ALS VIS ADC Overflow";
      break;
    case SI1145_RESP_ALS_IR_ADC_OVERFLOW:
      msg = "ALS IR Overflow";
      break;
    case SI1145_RESP_AUX_ADC_OVERFLOW:
      msg = "AUX ADC Overflow";
      break;
    default:
      msg = "Unknown Failure";
      break;
  }
  Serial.println(msg); 
  mySI1145.clearFailure();
}

 

Beispielsketch 5 – Lux Calculation

Hier nun der Beispielsketch zur Luxberechnung. Spielt einfach mal ein bisschen mit den Gleichungen und verschiedenen Lichtquellen. 

#include <SI1145_WE.h>
#include <Wire.h>
const int interruptPin = 2;

SI1145_WE mySI1145 = SI1145_WE();

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(interruptPin, INPUT_PULLUP);
  mySI1145.init();
  
  /* choices: PS, ALS, PSALS, ALSUV, PSALSUV || FORCE, AUTO, PAUSE */
  mySI1145.enableMeasurements(ALS, FORCE);
  
  /* if gain is changed, this must be considered in lux calculation */ 
  mySI1145.setAlsVisAdcGain(0);
  
 /* if gain is changed, this must be considered in lux calculation */ 
  mySI1145.setPsAdcGain(0);
 
  Serial.println("SI1145 - lux calculation");
}

void loop() {
  byte failureCode = 0;
  unsigned long amb_als = 0;
  unsigned long amb_ir = 0;
  float lx = 0.0;
   
  for(int i=0; i<50; i++){
    mySI1145.clearAllInterrupts();
    mySI1145.startSingleMeasurement();
    while(digitalRead(interruptPin)){
    /* just wait for interruptpin to go LOW; if you have not set up the 
       interrupt correctly you might wait forever! */  
    }
    amb_als += mySI1145.getAlsVisData();
    amb_ir += mySI1145.getAlsIrData();
  }
  amb_als /= 50;
  amb_ir /= 50;
  lx = calcLux(amb_als,amb_ir); 
  Serial.print("Ambient Light: ");
  Serial.println(amb_als);
  Serial.print("Infrared Light: ");
  Serial.println(amb_ir);
  Serial.print("Lux: ");
  Serial.println(lx);

  failureCode = mySI1145.getFailureMode();  // reads the response register
  if((failureCode&128)){   // if bit 7 is set in response register, there is a failure
    handleFailure(failureCode);
  }
  Serial.println("---------");
  delay(1000);
}

void handleFailure(byte code){
  String msg = "";
  switch(code){
    case SI1145_RESP_INVALID_SETTING:
      msg = "Invalid Setting";
      break;
    case SI1145_RESP_PS1_ADC_OVERFLOW:
      msg = "PS ADC Overflow";
      break;
    case SI1145_RESP_ALS_VIS_ADC_OVERFLOW:
      msg = "ALS VIS ADC Overflow";
      break;
    case SI1145_RESP_ALS_IR_ADC_OVERFLOW:
      msg = "ALS IR Overflow";
      break;
    case SI1145_RESP_AUX_ADC_OVERFLOW:
      msg = "AUX ADC Overflow";
      break;
    default:
      msg = "Unknown Failure";
      break;
  }
  Serial.println(msg); 
  mySI1145.clearFailure();
}

float calcLux(uint16_t vis, uint16_t ir){
  const unsigned int vis_dark = 256; // empirical factor
  const unsigned int ir_dark = 250; // empirical factor
  const float gainFactor = 1.0;
  const float visCoeff = 5.41; // AN523
  const float irCoeff = 0.08; // AN523
  const float visCountPerLux = 0.319; // for incandescent bulb (datasheet)
  const float irCountPerLux = 8.46; // for incandescent bulb (datasheet)
  const float corrFactor = 0.18; // my empirical correction factor
  
  // According to application notes AN523: 
  float lux = ((vis - vis_dark) * visCoeff - (ir - ir_dark) * irCoeff) * gainFactor;

  // the equation above does not consider the counts/Lux depending on light source type
  // I suggest the following equation:
  // float lux = (((vis - vis_dark) / visCountPerLux) * visCoeff - ((ir - ir_dark) / irCountPerLux) * irCoeff) * gainFactor * corrFactor;
  
  return lux;
}

 

Beispielsketch 6 – Verwendung von zwei SI1145

Im letzten Beispiel möchte ich noch zeigen wie man zwei SI1145 Module mit einem Microcontroller ansteuern kann. Dazu versorgt ihr zunächst nur einen der Sensoren mit Strom, ändert seine Adresse und schaltet dann den zweiten dazu. Da der SI1145 weniger als 1mA Strom zieht, könnt ihr ihn direkt über einen Arduinopin mit Strom versorgen. Alternativ nehmt ihr einen Transistor als Schalter. 

Schaltung für zwei SI1145
Schaltung für zwei SI1145
#include <SI1145_WE.h>
#include <Wire.h>
const int sensor2EnablePin = 10;

SI1145_WE sensor1 = SI1145_WE();
SI1145_WE sensor2 = SI1145_WE();

void setup() {
  pinMode(sensor2EnablePin, OUTPUT);
  Serial.begin(9600);
  Wire.begin();
  sensor1.init();
  sensor1.setI2CAddress(0x59); // change from 0x60 to 0x59
  digitalWrite(sensor2EnablePin, HIGH);
  sensor2.init(); // gets the standard address 0x60
  
  sensor1.enableMeasurements(ALS, AUTO);
  sensor2.enableMeasurements(ALS, AUTO);
  
  Serial.println("SI1145 - using two sensors");
}

void loop() {
  byte failureCode = 0;
  unsigned int amb1_als = 0, amb2_als = 0;
   
  amb1_als = sensor1.getAlsVisData();
  amb2_als = sensor2.getAlsVisData();

  Serial.print("Ambient Light Sensor 1: ");
  Serial.println(amb1_als);

  Serial.print("Ambient Light Sensor 2: ");
  Serial.println(amb2_als);
 
  Serial.println("---------");
  delay(1000);
}

 

Danksagung

Der Firma Adafruit danke ich für die Veröffentlichung des Fritzing Schemas für den SI1145.

Schreibe einen Kommentar

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