Über den Beitrag
Dieser Beitrag ist Teil einer Reihe über Licht-, Gestik-, Bewegungs- und Abstandssensoren, den ich mit einer Gesamtübersicht (hier) abschließen werde. Nachdem ich im letzten Beitrag über den APDS-9960 berichtet habe, ist diesmal der Lichtsensor bzw. das Lichtsensormodul TSL2561 an der Reihe. Dabei gehe ich zunächst auf die Eigenschaften ein und dann auf die Ansteuerung mittels der Sparkfun Bibliothek.
Eigenschaften des TSL2561
Der TSL2561 wandelt die Lichtintensität über zwei Fotodioden und nachgeschaltete Analog-Digitalwandler in digitale, über I2C auslesbare 16-Bit Werte um. Eine der Fotodioden (Channel 0) erfasst dabei den sichtbaren Wellenlängenbereich plus Infrarot, die andere (Channel 1) erfasst nur den Infrarotbereich. Die Messungen der beiden Kanäle erfolgen parallel. Die übermittelten Daten können über eine empirische Formel in Lux-Werte umgerechnet werden. Genau genommen handelt es sich um eine ganze Formelsammlung, die entsprechend der Lichtverhältnisse zur Anwendung kommt. Durch das Herausrechnen des IR-Anteils entsprechen die Lichtwerte besonders gut der Wahrnehmung des menschlichen Auges.
Messparameter des TSL2561
Die Lichtmessung erfolgt integral, d.h. die Fotodiodenströme werden über einen bestimmten Zeitraum addiert. Drei vordefinierte Messzeiten (im Datenblatt als „Integration time“ bezeichnet) lassen sich im Timing Register einstellen:
- 13.7 ms
- 101 ms
- 402 ms
Zusätzlich gibt es die Option einer manuellen Messung, die über ein bestimmtes Bit im Timing Register gesteuert wird. „1“ startet die Messung, „0“ beendet sie. Aber keine Sorge, ihr müsst euch nicht im Detail mit den Registern herumschlagen, denn ihr könnt die Einstellungen bequem über die Funktionen der Sparkfun Bibliothek vornehmen.
Im Timing Register lassen sich die zwei Verstärkungsfaktoren 1x oder 16x wählen. Je länger die Messzeit und je höher der Verstärkungsfaktor, desto größer werden die Rohdaten aus den beiden Kanälen. So kann man die Registerbreite optimal nutzen, muss aber auch darauf achten keinen Überlauf zu erzeugen, also Werte größer/gleich 216 = 65536.
Interruptfunktionen des TSL2561
Der TSL2561 besitzt einen LOW aktiven Interruptausgang. Es kann eine Ober- und eine Untergrenze angegeben werden, bei der ein Interrupt ausgelöst wird. Die Interruptgrenzen beziehen sich auf die Werte des Kanals 0. Sie sind also von den Messzeiten und dem Verstärkungsfaktor abhängig und beziehen sich nicht auf absolute Lux Werte.
Im Interrupt Kontroll Register läßt sich die Interrupfunktion ein- bzw. ausschalten. Darüber hinaus lassen sich folgende Optionen auswählen:
- Interrupt nach jeder Messung, unabhängig von Limits
- Interrupt nach x Messungen außerhalb der Limits mit x = 1 bis 15
Der Interrupt bleibt bestehen bis man das CLEAR Bit im Command Register setzt.
Wer noch mehr Details über den TSL2561 wissen möchte, der schaue in das Datenblatt, welches zum Beispiel hier erhältlich ist.
Das TSL2561 Modul
Für den normalen Hobbyelektroniker, der keine Lust hat SMD Bauteile zu verlöten, bietet sich der Kauf eines TSL2561 Moduls an. Man bekommt diese für ein paar Euros z. B. hier bei Amazon. Die meisten dieser Module sehen so aus:
Wichtig ist, dass das Bauteil im Bereich von 2.7 bis 3.6 Volt betrieben werden sollte. Am Arduino UNO kann man den 3.3 Volt Ausgang für die Stromversorgung benutzen. Allerdings sind dann immer noch die I2C Leitungen auf 5 Volt. Deshalb nimmt man entweder einen Microcontroller der auf 3.3 Volt läuft oder man baut Spannungsteiler in die Leitungen ein oder man verwendet einen Level Konverter, den man für ca. 1 Euro pro Stück z. B. hier bekommt.
Man kann drei verschiedene I2C Adressen einstellen. Ohne weitere Vorkehrungen ist die Adresse 0x39. Wenn ihr im Jumperbereich auf dem Modul den Kontakt „L“ mit dem mittleren Kontakt verbindet, so wechselt die Adresse auf 0x29. Verbindet Ihr „H“ mit dem mittleren Kontakt, so ist die Adresse 0x49.
Wollt ihr die I2C Adresse überprüfen, dann findet ihr hier einen Scanner Sketch. Das ist auch immer ein guter Startpunkt um seine Schaltung und sein I2C Bauteil zu prüfen.
Die Beschaltung des TSL2561
Die Schaltung ist keine Überraschung:
Der Level Konverter muss mindestens auf der 5 Volt Seite mit Spannung versorgt werden. Wenn es Schwierigkeiten gibt, dann schließt auch die 3.3 Volt Seite an. Pull-Up Widerstände für die I2C Leitungen waren nicht notwendig, da der Level Konverter diese schon integriert hat (ein weiteres Argument für Level Konverter). Für einen der Beispielsketche brauchen wir noch eine Leitung vom Interruptausgang des Moduls zum Arduino Pin D2 und eine LED an D13.
Die Sparkfun Bibliothek für den TSL2561
Ich habe mich für die Bibliothek TSL2561_Luminosity_Sensor_BOB von Sparkfun entschieden, da sie alle wichtigen Funktionen implementiert hat. Es gibt auch eine Bibliothek von Adafruit, aber dort fehlen die Interruptfunktionen (jedenfalls nach heutigem Stand).
Die Bibliothek von Sparkfun ist hier auf Github erhältlich. Sie ist mittlerweile archiviert, da Sparkfun das Modul aus seinem Programm genommen hat. Das stellt aber kein Problem dar.
Interruptprobleme mit der Sparkfun Bibliothek
Beim Ausprobieren war mir aufgefallen, dass die Interruptfunktion zwar implementiert war aber nicht funktionierte. Den Grund für den Fehler konnte ich identifizieren, habe es gemeldet und die Bibliothek wurde entsprechend geändert. Da es aber noch eine Version gibt, bei der der Fehler nicht korrigiert ist (SparkFun_TSL2561_Arduino_Library), folgt besser dem Link oben und sucht nicht manuell auf Github.
Der einzige Nachteil an der korrigierten Version ist, dass ihr nach dem Herunterladen und Entpacken der Zip Datei noch alles im Verzeichnis unter TSL2561_Luminosity_Sensor_BOB_master/Libraries/Arduino direkt in TSL_Luminosity_Sensor_BOB_master verschieben müsst. Der src Ordner liegt sonst zu tief und wird von der Arduino IDE nicht gefunden.
Falls ihr schon eine Version habt, bei der Probleme mit dem Interrupt auftreten, dann schaut mal in Zeile 266 der Bibliotheksdatei SparkFunTSL2561.cpp. Steht dort:
if (writeByte(TSL2561_REG_INTCTL,((control | 0B00000011) << 4) & (persist | 0B00001111)))
dann ersetzt die Zeile durch:
if (writeByte(TSL2561_REG_INTCTL,((control & 0B00000011) << 4) | (persist & 0B00001111)))
und schon sollte es funktionieren.
Wenn euch das alles zu kompliziert ist und ihr die Interruptfunktion nicht braucht, so könnt ihr alternativ auch auf die Adafruit Bibliothek zurückgreifen. Solltet ihr euch für diesen Weg entscheiden, dann empfehle ich einen Blick in das Tutorial.
Der Sparkfun Beispielsketch für den TSL2561
Den Beispielsketch habe ich unverändert übernommen. Er liefert die Rohdaten aus den beiden Kanälen und die daraus berechneten Lux-Werte. Mit den Informationen dieses Beitrages und den Kommentaren im Sketch bedarf er wohl keinen weiteren Erläuterungen, außer vielleicht, dass auf dem Sparkfun Modul die Adressjumper mit „0“ und „1“ bezeichnet sind und nicht mit „L“ und „H“ (siehe Zeile 70 – 76).
/* SparkFun TSL2561 library example sketch This sketch shows how to use the SparkFunTSL2561 library to read the AMS/TAOS TSL2561 light sensor. Product page: https://www.sparkfun.com/products/11824 Hook-up guide: https://learn.sparkfun.com/tutorials/getting-started-with-the-tsl2561-luminosity-sensor Hardware connections: 3V3 to 3.3V GND to GND (WARNING: do not connect 3V3 to 5V or the sensor will be damaged!) You will also need to connect the I2C pins (SCL and SDA) to your Arduino. The pins are different on different Arduinos: SDA SCL Any Arduino "SDA" "SCL" Uno, Redboard, Pro A4 A5 Mega2560, Due 20 21 Leonardo 2 3 You do not need to connect the INT (interrupt) pin for basic operation. Operation: Upload this sketch to your Arduino, and open the Serial Monitor window to 9600 baud. Have fun! -Your friends at SparkFun. Our example code uses the "beerware" license. You can do anything you like with this code. No really, anything. If you find it useful, buy me a beer someday. V10 Mike Grusin, SparkFun Electronics 12/26/2013 Updated to Arduino 1.6.4 5/2015 */ // Your sketch must #include this library, and the Wire library // (Wire is a standard library included with Arduino): #include <SparkFunTSL2561.h> #include <Wire.h> // Create an SFE_TSL2561 object, here called "light": SFE_TSL2561 light; // Global variables: boolean gain; // Gain setting, 0 = X1, 1 = X16; unsigned int ms; // Integration ("shutter") time in milliseconds void setup() { // Initialize the Serial port: Serial.begin(9600); Serial.println("TSL2561 example sketch"); // Initialize the SFE_TSL2561 library // You can pass nothing to light.begin() for the default I2C address (0x39), // or use one of the following presets if you have changed // the ADDR jumper on the board: // TSL2561_ADDR_0 address with '0' shorted on board (0x29) // TSL2561_ADDR default address (0x39) // TSL2561_ADDR_1 address with '1' shorted on board (0x49) // For more information see the hookup guide at: https://learn.sparkfun.com/tutorials/getting-started-with-the-tsl2561-luminosity-sensor light.begin(); // Get factory ID from sensor: // (Just for fun, you don't need to do this to operate the sensor) unsigned char ID; if (light.getID(ID)) { Serial.print("Got factory ID: 0X"); Serial.print(ID,HEX); Serial.println(", should be 0X5X"); } // Most library commands will return true if communications was successful, // and false if there was a problem. You can ignore this returned value, // or check whether a command worked correctly and retrieve an error code: else { byte error = light.getError(); printError(error); } // The light sensor has a default integration time of 402ms, // and a default gain of low (1X). // If you would like to change either of these, you can // do so using the setTiming() command. // If gain = false (0), device is set to low gain (1X) // If gain = high (1), device is set to high gain (16X) gain = 0; // If time = 0, integration will be 13.7ms // If time = 1, integration will be 101ms // If time = 2, integration will be 402ms // If time = 3, use manual start / stop to perform your own integration unsigned char time = 2; // setTiming() will set the third parameter (ms) to the // requested integration time in ms (this will be useful later): Serial.println("Set timing..."); light.setTiming(gain,time,ms); // To start taking measurements, power up the sensor: Serial.println("Powerup..."); light.setPowerUp(); // The sensor will now gather light during the integration time. // After the specified time, you can retrieve the result from the sensor. // Once a measurement occurs, another integration period will start. } void loop() { // Wait between measurements before retrieving the result // (You can also configure the sensor to issue an interrupt // when measurements are complete) // This sketch uses the TSL2561's built-in integration timer. // You can also perform your own manual integration timing // by setting "time" to 3 (manual) in setTiming(), // then performing a manualStart() and a manualStop() as in the below // commented statements: // ms = 1000; // light.manualStart(); delay(ms); // light.manualStop(); // Once integration is complete, we'll retrieve the data. // There are two light sensors on the device, one for visible light // and one for infrared. Both sensors are needed for lux calculations. // Retrieve the data from the device: unsigned int data0, data1; if (light.getData(data0,data1)) { // getData() returned true, communication was successful Serial.print("data0: "); Serial.print(data0); Serial.print(" data1: "); Serial.print(data1); // To calculate lux, pass all your settings and readings // to the getLux() function. // The getLux() function will return 1 if the calculation // was successful, or 0 if one or both of the sensors was // saturated (too much light). If this happens, you can // reduce the integration time and/or gain. // For more information see the hookup guide at: https://learn.sparkfun.com/tutorials/getting-started-with-the-tsl2561-luminosity-sensor double lux; // Resulting lux value boolean good; // True if neither sensor is saturated // Perform lux calculation: good = light.getLux(gain,ms,data0,data1,lux); // Print out the results: Serial.print(" lux: "); Serial.print(lux); if (good) Serial.println(" (good)"); else Serial.println(" (BAD)"); } else { // getData() returned false because of an I2C error, inform the user. byte error = light.getError(); printError(error); } } void printError(byte error) // If there's an I2C error, this function will // print out an explanation. { Serial.print("I2C error: "); Serial.print(error,DEC); Serial.print(", "); switch(error) { case 0: Serial.println("success"); break; case 1: Serial.println("data too long for transmit buffer"); break; case 2: Serial.println("received NACK on address (disconnected?)"); break; case 3: Serial.println("received NACK on data"); break; case 4: Serial.println("other error"); break; default: Serial.println("unknown error"); } }
Ein Beispielsketch mit Interruptfunktion
Ich habe den Beispielsketch noch dahingehend verändert, dass ein oberes und unteres Interrupt Limit definiert wird. Wird ein Interrupt ausgelöst, dann gibt es eine entsprechende Meldung im seriellen Monitor und die LED an Pin D13 leuchtet für eine Sekunde.
#include <SparkFunTSL2561.h> #include <Wire.h> SFE_TSL2561 light; boolean gain; // Gain setting, 0 = X1, 1 = X16; unsigned int ms; // Integration ("shutter") time in milliseconds byte interruptPin = 2; byte ledPin = 13; volatile bool event = false; void setup() { pinMode(ledPin, OUTPUT); pinMode(interruptPin, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(interruptPin), blink, FALLING); Serial.begin(9600); Serial.println("TSL2561 example sketch with interrupts"); light.begin(); // If gain = false (0), device is set to low gain (1X) // If gain = high (1), device is set to high gain (16X) gain = 0; // If time = 0, integration will be 13.7ms // If time = 1, integration will be 101ms // If time = 2, integration will be 402ms // If time = 3, use manual start / stop to perform your own integration unsigned char time = 1; Serial.println("Set timing..."); light.setTiming(gain, time, ms); Serial.print("ms: "); Serial.println(ms); // Sets up interrupt operations: setInterruptControl(control/persist) // If control = 0, interrupt output disabled // If control = 1, use level interrupt, see setInterruptThreshold() // If persist = 0, every integration cycle generates an interrupt // If persist = 1, any value outside of threshold generates an interrupt // If persist = 2 to 15, value must be outside of threshold for 2 to 15 integration cycles if (light.setInterruptControl(1, 2)) { //control=1, persist=2 Serial.println("Interrupt Control: success"); } if (light.setInterruptThreshold(150, 5000)) { Serial.println("Interrupt threshold set"); } light.clearInterrupt(); Serial.println("Powerup..."); light.setPowerUp(); } void loop() { unsigned int data0, data1; if (light.getData(data0, data1)) { Serial.print("data0: "); Serial.print(data0); Serial.print(" data1: "); Serial.print(data1); double lux; // Resulting lux value boolean good; // True if neither sensor is saturated good = light.getLux(gain, ms, data0, data1, lux); Serial.print(" lux: "); Serial.print(lux); if (good) Serial.println(" (good)"); else Serial.println(" (BAD)"); } else { byte error = light.getError(); printError(error); } if (event) { Serial.println("Interrupt!!"); digitalWrite(ledPin, HIGH); delay(1000); digitalWrite(ledPin, LOW); event = false; light.clearInterrupt(); } } void printError(byte error) { Serial.print("I2C error: "); Serial.print(error, DEC); Serial.print(", "); switch (error) { case 0: Serial.println("success"); break; case 1: Serial.println("data too long for transmit buffer"); break; case 2: Serial.println("received NACK on address (disconnected?)"); break; case 3: Serial.println("received NACK on data"); break; case 4: Serial.println("other error"); break; default: Serial.println("unknown error"); } } void blink() { event = true; }
Weitere Funktionen der Sparkfun Bibliothek
Mit setPowerDown() lässt sich der TSL2561 in den Schlaf schicken, um Strom zu sparen.
Manuelle Messungen, also mit freier Wahl der Messzeit (Integrationszeit), lassen sich durch die Funktionen manualStart() und manualStop() steuern.
Zum Schluss
Ich hoffe, dieser Beitrag hat euch gefallen und hilft euch gegebenenfalls Projekte mit dem TSL2561 entwickeln zu können. Über konstruktives Feedback würde ich mich freuen. Bis zum nächsten Beitrag!