Sunday, April 5, 2015

Adafruit TSL2591 Lux Sensor

This post is about the Adafruit TSL2591 Lux Sensor which I have also used with the Arduino.  Adafruit has a good write-up and it ports very easily to the MSP-EXP430F5529.  I am using the Adafruit library, however there is one thing that keeps their library from compiling on the LaunchPads...

In the Adafruit library for the TL2591, the cpp file (Adafruit_TSL2591.cpp)has a preprocessor directive for delay.h.  Either remove it or comment the line out.

One thing the example code Adafruit supplies does not do is auto-range the gain on the sensor.  If the light is too bright it can saturate and if too dim you may not get good readings.  I've written a function named  configureSensor(void) below.  The magic numbers are experimental results that seem to work well for me.  It needs an iteration or two to make this adjustment.  I've not added code to discard bad readings but that could be easily done.

Here is the sketch...

/*
PURPOSE:

  This sketch uses the Adafruit TLS2591 light sensor to display lux values
  on the serial monitor.  It also "autoranges" the gain and time photons
  are collected on the sensor to keep the sensor in range.  In order to
  get a good reading after a bad reading due to a drastic light change it
  will be necessary to let it iterate two times or so.  Try covering the
  sensor and watching what happens.  Then remove the cover and shine a
  bright light on it.


PORT TO MSP-EXP430F5529:

  In order for this to compile, comment out or remove the following
  line of Adafruit_TSL2591.cpp in the library
  =========================================================================
  #include <util/delay.h>           
  =========================================================================


SENSOR:
  TSL2591 Digital Light Sensor
  Dynamic Range: 600M:1
  Range: 188 ulux up to 88,000 lux

  The lux (symbol: lx) is the SI unit of illuminance and luminous emittance,
  measuring luminous flux per unit area. It is equal to one lumen per square
  meter.  The table below gives examples:
 
               0.0001   lux   Moonless, overcast night sky (starlight)
               0.002    lux   Moonless clear night sky with airglow
               0.27–1.0 lux   Full moon on a clear night
               3.4      lux   Dark limit of civil twilight under a clear sky
              50.       lux   Family living room lights
              80.       lux   Office building hallway/toilet lighting
             100.       lux   Very dark overcast day
         320–500.       lux   Office lighting
             400.       lux   Sunrise or sunset on a clear day.
            1000.       lux   Overcast day; typical TV studio lighting
     10000–25000.       lux   Full daylight (not direct sun)
    32000–100000.       lux   Direct sunlight
   
    Source: Wikipedia


HARDWARE AND CONNECTIONS:

  Adafruit TLS2591 connections for MSP-EXP430F5529LP
  ======================================================================
  SCL to Pin 14 (P3.1)                  Must be I2C Pin
  SDA to Pin 15 (P3.0)                  Must be I2C Pin
  connect Vin to 3.3 DC
  GND to common ground
  3Vo (no connection)
  Int (no connection)
*/

 
//============================ G L O B A L ============================
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_TSL2591.h"

Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591);  // sensor identifier

float luxVal = 10000;  

                          
void setup(void)
{
  //============================== S E T U P =============================
  Serial.begin(9600);
 
  Serial.println("Starting Adafruit TSL2591 Test!"); 
  if (tsl.begin())
  {
    Serial.println("Found a TSL2591 sensor");
  }
  else
  {
    Serial.println("No sensor found ... check your wiring?");
    while (1);
  }
 
  /* Display some basic information on this sensor */
  displaySensorDetails();

}

void loop(void)
{
  //============================== L O O P ==================================
 
  configureSensor();
  unifiedSensorAPIRead();
  delay (1000);
 
}


void unifiedSensorAPIRead(void)
{
  //============== U N I F I E D   S E N S O R   A P I   R E A D =============
  //  Performs a read using the Adafruit Unified Sensor API
  //  Updates the lux value in the global variable luxVal
  //  Get a new sensor event
 
  sensors_event_t event;
  tsl.getEvent(&event);
 
  if ((event.light == 0) |
      (event.light > 4294966000.0) |
      (event.light <-4294966000.0))
  {
    // If event.light == 0 lux the sensor is probably saturated
    // and no reliable data could be generated!
    // if event.light is +/- 4294967040 there was a float over/underflow
    Serial.println("Invalid data - auto adjusting gain gain and timing)");
    luxVal = 10000.0;    // Set luxVal to get minimum time and gain for next pass
                         // so as to get a valid starting point
  }
  else
  {
    int f;            // format number for decimal places in the print statement
    if (luxVal >= 100.0)
    {
      f = 0;
    }
    else if (luxVal < 100.0 && luxVal >= 100.0)
    {
      f = 1;
    }
    else if (luxVal < 100.0 && luxVal >= 1.0)
    {
      f = 2;
    }
    else
    {
      f = 3;
    }
          
    Serial.print(event.light,f);     Serial.println(" lux");
    Serial.println("------------------");
    Serial.println("");

    luxVal = event.light;
  }
}


void displaySensorDetails(void)
{
  //============= D I S P L A Y   S E N S O R   D E T A I L S ================
  // Displays some basic information on this sensor from the unified
  // sensor API sensor_t type (see Adafruit_Sensor for more information)
  sensor_t sensor;
  tsl.getSensor(&sensor);
  Serial.println("------------------------------------");
  Serial.print  ("Sensor:       "); Serial.println(sensor.name);
  Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
  Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" lux");
  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" lux");
  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" lux"); 
  Serial.println("------------------------------------");
  Serial.println("");
 
  delay(2000);
}


void configureSensor(void)
{
  //================== C O N F I G U R E   S E N S O R    =====================
  //
  //  Configures the gain and integration time for the TSL2561 depending  

  //  on luxVal
  //  NOTE:  The variable luxVal is global.  The following calls are valid:
  // 
  //  Set gain according to the light level
  //  tsl.setGain(TSL2591_GAIN_LOW);    // 1x gain (bright light)
  //  tsl.setGain(TSL2591_GAIN_MED);    // 25x gain
  //  tsl.setGain(TSL2591_GAIN_HIGH);   // 428x gain
  //  tsl.setGain(TSL2591_GAIN_MAX);    // 9876x gain (extremely low light)
  //
  //  Changing integration time gives you a longer time over which to sense light
  //  Longer timelines are slower, but improve accuracy in low light situations
  //  tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);  // shortest integration time (bright light)
  //  tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
  //  tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
  //  tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS);
  //  tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS);
  //  tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);  // longest integration time (dim light)
  //
  //  The values used below are empirical ones that seemed to get things in
  //  a good range for accurate measurement...  F Milburn
  //
  Serial.println("------------------");
  Serial.print  ("Gain: ");
  if (luxVal > 200.0)
  { 
    tsl.setGain(TSL2591_GAIN_LOW);              
    tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);
    Serial.println("1x (Low)");
    Serial.println("Timing: 100 ms");
  }
  else if (luxVal <=200.0 && luxVal > 40.0)
  {
    tsl.setGain(TSL2591_GAIN_MED);                
    tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
    Serial.println("25x (Med)");
    Serial.println("Timing: 200 ms");
  }
  else if (luxVal <=40.0 && luxVal > 10.0)
  {
    tsl.setGain(TSL2591_GAIN_MED);                
    tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);
    Serial.println("25x (Med)");
    Serial.println("Timing: 600 ms");
  }
  else if (luxVal <=10.0 && luxVal > 0.1)
  {
    tsl.setGain(TSL2591_GAIN_HIGH);                
    tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);
    Serial.println("428x (High)");
    Serial.println("Timing: 600 ms");
  }
  else
  {
    tsl.setGain(TSL2591_GAIN_MAX);                
    tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);
    Serial.println("9876x (Max)");
    Serial.println("Timing: 600 ms");
  }
}

6 comments:

  1. Hi, thanks for your script, especially for auto adjusting function.
    The problem with Adafruit library is that the resolution of measurements is 1 Lux, which is kinda pointless with a sensor that can measure uLux. In fact I am trying to use it specifically to measure light levels below 1 Lux and I just get 0 Lux... The raw measurement is in fact a count (two 16 bit values - integers for visible and ir sensor) and it is processed to calculate Lux value (dividing number of counts by time and by gain and multiplied by coefficient of the sensor) and as far as I understand (and I am a very beginner) calculated Lux value is an integer. Even though you are changing precision, low values are still integers just displayed as 3.00 Lux.
    I am trying to modify library but my knowledge of C is minimal. I also asked Adafruit for help, let see if they are willing to help. But maybe solution is simple and you can do it?

    ReplyDelete
  2. Ok, I actually wrote a little workaround to measure values down to uLux. I am manually reading integration time and gain from functions, translating it into actual values and I apply functions from the library that, supposedly, come from the manufacturer. It actually appears to work:

    uint32_t lum = tsl.getFullLuminosity();
    uint16_t ir, full;
    ir = lum >> 16;
    full = lum & 0xFFFF;

    //reading integration value from function get. Timing

    uint16_t integration = tsl.getTiming();
    float time;

    //translating integration value into time in ms

    if (integration == 0)
    { time = 100;}
    else if (integration == 1)
    { time = 200;}
    else if (integration == 2)
    {time = 300;}
    else if (integration == 3)
    {time = 400;}
    else if (integration == 4)
    {time = 500;}
    else{
    time = 600;}



    //reading gain value from function get.Gain


    uint16_t gain = tsl.getGain();
    float gain1;

    //translating gain value into actual gain

    if (gain == 0)
    { gain1 = 1;}
    else if (gain == 16)
    { gain1 = 25;}
    else if (gain == 32)
    {gain1 = 428;}
    else{
    gain1 = 9876;}


    float cp2;
    cp2 = (time * gain1) / TSL2591_LUX_DF;

    float lux1, lux2, lux3;
    lux1 = ( (float)full - (TSL2591_LUX_COEFB * (float)ir) ) / cp2;
    lux2 = ( ( TSL2591_LUX_COEFC * (float)full ) - ( TSL2591_LUX_COEFD * (float)ir ) ) / cp2;
    luxVal = lux1 > lux2 ? lux1 : lux2;

    ReplyDelete
  3. Neat! Sorry I'm slow getting back to you. I've been away from my computer. To be honest, I had not tried this down in the uLux range. Glad you were able to accomplish - great work.

    ReplyDelete
  4. I figured out the problem with the resolution is the calculateLux function returning a uint32_t when it should be returning a float. This is a problem with the adafruit library. I'm pretty sure I told them about this problem 6 months ago.

    Still can't stop it from truncating the result to 3 decimal places.

    ReplyDelete
  5. actually now I can measure the light transmitting through my hand from the monitor, lol.

    ReplyDelete
  6. Took me time to read all the comments, but I really enjoyed the article. It proved to be Very helpful to me and I am sure to all the commenters here! It’s always nice when you can not only be informed, but also entertained! luxjunky.com

    ReplyDelete