# AMS TSL2591 light-to-digital converter

Ambient [light](../parameters/light.md) sensor (ALS) senses

- full spectrum light intensity
- IR light intensity

The two channels are used to estimate illuminance.

This chip is controlled via an I2C bus.


## Resources

- [AMS Website](https://ams.com)
- [TSL2591 product page](https://ams.com/en/tsl25911)
- [Datasheet](https://ams.com/documents/20143/36005/TSL2591_DS000338_6-00.pdf)
- [Application notes](https://ams.com/en/tsl25911#tab/documents)


## ESensors Software

The [LightTSL2591.h](../../src/LightTSL2591.h) header declares classes
supporting the TSL2591 within the [ESensors](../classes.md) framework.
It is based on the
[TSL2591MI](https://bitbucket.org/christandlg/tsl2591mi/) library.

```cpp
#include<LightTSL2591.h>
```

It provides the following classes:

### ESensor device

- `LightTSL2591`

```cpp
LightTSL2591 tsl;

void setup() {
  Wire.begin();       // init the default I2C bus.
  tsl.begin(&Wire);   // init the TSL2591 sensor on the defaul Wire I2C bus.
  tsl.setGain(LightTSL2591::AUTO_GAIN); // enable the auto gain feature.
}
```

If auto gain is enabled via `setGain(LightTSL2591::AUTO_GAIN)`, then
every time a reading is retrieved from the sensor it is checked
whether it is too small or too large and the gain of the sensor is
increased or decreased accordingly.

The sensitivity of the sensor depends quite strongly on
temperature. You should use a temperature sensor and provide the
temperature to the `LightTSL2591` device via the `setTemperature()`
function in `loop()`.  See the [sensors
example](../../examples/sensors/).


### ESensors

- `Channel0TSL2591`: raw 16bit sensor reading of the full spectrum.
- `Channel1TSL2591`: raw 16bit sensor reading of the IR spectrum.
- `GainTSL2591`: index of the gain used to obtain the reading.
- `IRRatioTSL2591`: ratio of IR versus full spectrum reading.
- `IrradianceFullTSL2591`: irradiance of the full spectrum in W/m^2.
- `IrradianceIRTSL2591`: irradiance of the IR spectrum in W/m^2.
- `IrradianceVisibleTSL2591`: irradiance of the full minus IR spectrum in W/m^2.
- `IlluminanceTSL2591`: illuminance in Lux.

```cpp
IlluminanceTSL2591 illum(&tsl);
```


### Examples

The [tsl2591gains](../../examples/tsl2591gains/) example sketch prints
averaged count readings for all gains and integration times, in order
to evaluate the actual gains of the chip.


## Calibration

The TSL2591 provides two 16-bit readings (`counts`) that cover
different wavelengths:

- `C0DATA`: channel 0, full spectrum, and
- `C1DATA`: channel 1, IR spectrum

<img src="https://user-images.githubusercontent.com/181073/48271825-8593e400-e43d-11e8-947f-8950b84dbdd9.png" width=80%>

To get a [photometric](../parameters/light.md#units) measure of light
intensity, one would ideally use a sensor that has the same spectral
sensitivity as the human eye. However, silicon photo diodes have a
much wider spectral sensitivity. One common solution is to add an
optical filter in front of the photodiode. The solution taken by the
TSL2591 is to provide two measurements from the photodiode, one with
the broad spectrum and one with a reduced spectrum for IR, as shown in
the figure.  Knowing the spectrum of the measured light source, the
appropriately weighted IR signal can then be subtracted from the full
sepctrum signal to get an estimate of photometric illuminance measured
in lux.

In addition, any mechanical and optical settings also influence the amount
and probably the spectral composition of light reaching the sensor.

This implies that the sensor has to calibrated for each specific
application, in order to get an appropriate reading of illuminance.
There can not be a default calibration that fits all use cases.

What would be nice to have, however, would be calibration data for a
plain sensor in sunlight. Somebody with a lux-meter should measure
them eventually!


## IR ratio

The spectral characteristics of a light source are reflected by the
ratio of the two raw readings:

![IRratio](images/irratio.svg)

The IR ratio is a property of the light source and is approximately
independent of the intensity of a given light source. The stronger the
IR component of a light source (higher `C1DATA` in comparison to
`C0DATA`), the larger the IR ratio gets.

The IR factor equals 0 for light without IR components (`C1DATA`=0).
If the light is a pure IR source, then `C0DATA` approaches `C1DATA`
and the IR ratio approaches 1.  According to the [application
note](https://ams.com/documents/20143/36005/AmbientLightSensors_AN000170_2-00.pdf),
the IR ratio is about 0.25 (0.2 - 0.35) for sunlight, and about 0.2
(0.15 - 0.25) for incandescent light.

Note, however, that the two channels depend on temperature in
different ways. Without temperature compensation, the IR ratio of a
given light source will quite strongly depend on temperature.


## Irradiance

First, let's put illuminance aside, and just consider how to scale the
raw sensor readings, `C0DATA` and `C1DATA`, to a radiometric measure
of irradiance, i.e. the amount of light energy per time and area.

Scaling raw readings to irradiance also depends on the spectrum of the
light source in relation to the spectral responsivity.  And it
depends, of course, on the integration time and the gain settings of
the TSL2591.

On the
[datasheet](https://ams.com/documents/20143/36005/TSL2591_DS000338_6-00.pdf),
Figure 8, we find, for the example of two different light sources, a
white-light emitting diode and a 850nm GaAs LED (IR light), values for
the irradiance responsitivity *R<sub>e</sub>* in counts per
&micro;W/cm<sup>2</sup>:

| light source | channel0 | channel 1 | IR ratio |
| ------------ | -------- | --------- | -------- |
| white light  |    264.1 |      34.9 |     0.13 |
| 850nm        |    257.5 |     154.1 |     0.60 |

These values were measured at high gain (x400) and an integration time
of 100ms. Unfortunately, in comparison to sunlight, the white light
source has less energy in IR.

The irradiance *E* is then given by

![irradiancefull](images/irradiancefull.svg)

where we first multiply the sensor reading `C0DATA` (or `C1DATA`) by
the gain and integration time for which the responsitivity from the
table were obtained, and then divide by the responsitivity
*R<sub>e</sub>* from the table and the actual gain, *g*, and
integration time, &tau;, for which the sensor reading was obtained.
The gain, integration time, and resistivity can be combined into a
single factor *c<sub>R</sub>* and we get

![irradiance](images/irradiance.svg)

with the following values for *c<sub>R</sub>* in counts per
W/m<sup>2</sup> and ms:

| light source | channel0 | channel 1 |
| ------------ | -------- | --------- |
| white light  |    0.660 |     0.087 |
| 850nm        |    0.644 |     0.385 |

The values used in the
[TSL2591MI](https://bitbucket.org/christandlg/tsl2591mi/) library
probably come from an older datasheet referenced in this
[issue](https://github.com/adafruit/Adafruit_CircuitPython_TSL2591/issues/7#issuecomment-437404689)
that have been measured with maximum gain (x9876) and 100ms
integration time. The resulting values for *c<sub>R</sub>* are
similar to the ones shown in the table, but not identical.

Note that the raw readings only approximately scale with gain and
integration time as expected. The following table shows readings for
the two channels averaged over 100 measurements (obtained with the
[tsl2591gains example sketch](../../examples/tsl2591gains/)). The last
two columns show the respective ratios relative to the 100ms
setting. Idealy they should be exactly 1, 2, 3, 4, 5, and 6:

| time  | gain | C0DATA | C1DATA | scale0 | scale1 |
| ----- | ---- | ------ | ------ | ------ | ------ |
| 100ms | MED  |  848.0 |  149.5 |   1.00 |   1.00 |
| 200ms | MED  | 1672.7 |  294.7 |   1.97 |   1.97 |
| 300ms | MED  | 2497.4 |  439.8 |   2.95 |   2.94 |
| 400ms | MED  | 3322.9 |  584.9 |   3.92 |   3.91 |
| 500ms | MED  | 4141.8 |  729.2 |   4.88 |   4.88 |
| 600ms | MED  | 4960.6 |  873.2 |   5.85 |   5.84 |

The situation is worse for the gains, they differ between the
channels:

| time  | gain | C0DATA  | C1DATA  | scale0  | scale1  |
| ----- | ---- | ------- | ------- | ------- | ------- |
| 200ms | LOW  |    70.0 |    13.0 |    1.00 |    1.00 |
| 200ms | MED  |  1675.0 |   295.4 |   23.93 |   22.72 |
| 200ms | HIGH | 28889.6 |  4975.8 |  412.71 |  382.75 |

Maybe the integration time, and in particular the gain scaling
factors, should also be calibrated for a given chip and setting.


## Illuminance (Lux)

Illuminance *E* quantifies light energy in a photometric way to get a
measure matching our perception of light intensity. This is the
quantity the TSL2591 aims at. For this we need to appropriatley
combine and scale the counts obtained from the two sensor channels.

Central for the computation of illuminace is the cpl factor (counts
per lux):

![cpl](images/cpl.svg)

In the nominator we have the gain *g* and the integration time &tau;
measured in milliseconds. The denominator is the glass and device
factor, GDF = GA DF, a product of the device factor, DF=53, and the
glass attenuation factor, GA. The latter incorporates attenuation by
spectrally neutral filters. Both the [Adafruit TSL2591
Library](https://github.com/adafruit/Adafruit_TSL2591_Library) and the
[BlueDot TSL2591
Library](https://github.com/BlueDot-Arduino/BlueDot_BME280_TSL2591)
use GDF=408 - sources unknown.

### Specific lux equations

According to the application note [Using the Lux
equation](https://ams.com/documents/20143/36005/AmbientLightSensors_AN000170_2-00.pdf),
the illumination *E* can be estimated via a "lux equation":

![lux1](images/lux1.svg)

This is supposed to cover fluorescent and incandescent light. This
  equation is used by the [BlueDot TSL2591
  Library](https://github.com/BlueDot-Arduino/BlueDot_BME280_TSL2591).
  In the application note [developing a custom lux
  equation](https://ams.com/documents/20143/36005/AmbientLightSensors_AN000173_2-00.pdf),
  however, a second lux equation is given for dimmed incandescent
  light source:

![lux2](images/lux2.svg)

As a final illuminance reading, the maximum of these two values,
*E<sub>1</sub>* and *E<sub>2</sub>*, should be taken.


### General lux equation

In general, each of the lux equations have two unknown factors, that
weigh the counts obtained from the two channels and scale the overall
result.

A general parameterization of two lux equations is as follows:

![general](images/luxgeneral.svg)

With four free parameters, GDF, *c<sub>B</sub>*, *c<sub>C</sub>*, and
*c<sub>D</sub>*, that have to be calibrated from measurements with
different light sources.

An earlier version of the [Adafruit TSL2591
  Library](https://github.com/adafruit/Adafruit_TSL2591_Library) used
  this approach (it is still contained as a comment in the code), with
  *c<sub>B</sub>* = 1.64, *c<sub>C</sub>* = 0.59, and *c<sub>D</sub>*
  = 0.86. A source for these numbers is not given. And they dropped
  this approach (see below).


### General lux equation for fixed light source

For a given light source, the IR ratio (see above) is approximately
constant. Rearranging the general equation for *E<sub>2</sub>* we get

![Erearranged](images/lux2rearranges.svg)

Since the IR ratio `C1DATA/C0DATA` is approximately constant we can
summarize all factors into a single parameter &alpha; and get

![Esimple](images/luxsimple.svg)

For a given light source, illuminance is simply proportional to the
raw count reading.  The factor &alpha; could be calibrated by a single
measurement!

This is the approach taken by the
[TSL2591MI](https://bitbucket.org/christandlg/tsl2591mi/) library.


### Other lux equations

The [Adafruit TSL2591
  Library](https://github.com/adafruit/Adafruit_TSL2591_Library) uses
  an equation suggested by this
  [issue](https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14). This
  equation differs in structure from the ones explained above and has
  the additional problem of a possible division by zero. The argument
  in the issue does not provide any source, and this equation should
  be backed up by some calibrarion data. In particular, if that
  equation is multiplied out then one gets a term with the IR count
  *squared*. This cannot be right.


## Auto gain

By means of the different gain settings, the TSL2591 can extend its
16bit resolution by another 13bit (maximum gain of 9200).

Whenever one of the counts falls below some small value, let's say 64,
one can increase the gain by one level (corresponding to x16 or x24).
When the count gets larger than a quarter of the maximum possible count,
then the gain is reduced by one level.


## Hardware

- [Adafruit TSL2591 High Dynamic Range Digital Light Sensor](https://learn.adafruit.com/adafruit-tsl2591)

  <img src="https://cdn-shop.adafruit.com/970x728/1980-07.jpg" width=50%>

- [BlueDot BME280+TSL2591 Advanced Weather Station](https://www.bluedot.space/products/bme280-tsl2591/)

  <img src="https://image.jimcdn.com/app/cms/image/transf/dimension=697x10000:format=png/path/s9044904ce8b43c5c/image/ic78b28bd0a4d9113/version/1525169534/i2c-wiring-for-bme280-tsl2591-board.png" width=60%>

  

## Library survey

For our purpose (non blocking acquisition), the following library by
[Gregor Christandl](https://bitbucket.org/christandlg/) is most suitable:

- [TSL2591MI](https://bitbucket.org/christandlg/tsl2591mi/): flexible
  bus interface, enable/disable, power up/down, non-blocking reads,
  temperature compensation, counts, irradiance, brightness conversion,
  from visual or IR channel. Some calibration data provided, but
  without explanation.

A search via the library manager of the Arduino IDE also results in
(April 2022):

- [Adafruit TSL2591
  Library](https://github.com/adafruit/Adafruit_TSL2591_Library):
  flexible bus interface, blocking reads only, no tempertature
  compensation, weired lux equation.

- [BlueDot TSL2591
  Library](https://github.com/BlueDot-Arduino/BlueDot_BME280_TSL2591):
  fixed interface, blocking reads only, no tempertature compensation.

- [UncleRus ESP-IDF Components
  library](https://github.com/UncleRus/esp-idf-lib): blocking data
  request, temperature compensation.
