Arduino and the Taos TSL230R Light Sensor: Getting Started

Posted in Projects | Robots | Time-lapse Video | Uncategorized 91

The TSL230R light sensor IC is an interesting package: a light sensing circuit wrapped up in a clear plastic casing. This neat little device will convert irradiance (the light energy on the surface of the sensor) into frequency. Working with a simple input concept like a frequency means that we won’t have to build any extra circuitry to get the full range of information from the circuit, and having an accurate measure of radiance means that we’ll be able to convert easily over to illuminance, which is how the light looks to us. Obviously, once we can answer the question about how light looks, we can use this information to control other things. (Some great examples are: camera exposure, dimming displays, machine vision, etc.)

This guide is intended to walk you through the basics of interfacing the TSL230 series of chips from Taos with your friendly Arduino microcontroller. The specifics of the chip’s operation may not be painfully obvious the first read over the datasheet, but this guide expects you’ve at least read the important parts: which pins are which, that you can change its sensitivity, and that you can change the scale of the output.

SETUP


The only things we’ll require besides the TSL230R and an arduino is a breadboard, a 0.1uF ceramic capacitor, and some jumper wires. Wire up the pins on the TSL230R as so:

  • S0-S3: wire each of these to an arduino input, avoid digital in 2, as we’ll need that
  • OE: The output enable pin should be brought low by connecting it to GND. This pin enables or disables the output frequency. Bringing the pin HIGH will result in disabling output, so for our purposes, we’ll hard-wire it to GND, enabling output and saving an I/O line on the arduino.
  • Vcc: Connect Vcc to +5V from the arduino. On the hole nearest this pin on the breadboard, connect one leg of your 0.1uF capacitor, and the other to GND. The capacitor is necessary to help filter the power to prevent fluctuations in the output of the chip.
  • GND: connect to GND
  • OUT: Connect this pin to digital in 2 on the arduino. We’re going to use an interrupt, so we need to hook up to either in 2 or 3.

This would be a good time to define the pins in our code:

#define TSL_FREQ_PIN 2 // output use digital pin2 for interrupt
#define TSL_S0	     5
#define TSL_S1	     6
#define TSL_S2	     7
#define TSL_S3       8

OUTPUT BASICS


So, now that we’ve got everything hooked up, let’s get down to work. The TSL230 has two types of output: a pulse train, and a square wave. The pulse train is a series of high pulses that give us the exact frequency being recorded at that time. Unfortunately, as the pulse high times are in the nano-seconds, they’re gone before we can effectively measure them. The square wave output, on the other hand, has a 50% duty-cycle, meaning the high pulses and low pulses are exactly the same length – 1/2 the frequency. This allows us to register the high pulses in Arduino’s micro-second world.

Pins S2 and S3 control the output scaling, the divide-by types (2, 10, and 100) produce the square wave we want. For now, we’ll choose divide by 100, which has both S2 and S3 high.

The sensitivity controls how many receptors are active on the chip at once. When its set to higher sensitivity, the chip can register smaller and smaller amounts of light, but loses the ability to register higher levels of light. For now, we’ll stick with 1x sensitivity, meaning S0 will be high and S1 will be low.

To read the square wave, you’ve probably seen examples of using pulseIn() – this has a couple of disadvantages, the first being that you’re only measuring one cycle of the frequency (it can change often throughout a second) and the second and most glaring, being that pulseIn() requires a delay while trying to read the input pin. That means for very low light levels (1 cycle per second) you could be waiting up to an entire second for one measurement! A much better way is to take a measure of how many pulses there are per second, which will average out any fluctuations, and if we use an interrupt, we can do anything else we like while we count the pulses.

Let’s go ahead and set everything up, including our interrupt.


unsigned long pulse_cnt = 0;

void setup() {

  // attach interrupt to pin2, send output pin of TSL230R to arduino 2
  // call handler on each rising pulse

 attachInterrupt(0, add_pulse, RISING);

 pinMode(TSL_FREQ_PIN, INPUT);
 pinMode(TSL_S0, OUTPUT);
 pinMode(TSL_S1, OUTPUT);
 pinMode(TSL_S2, OUTPUT);
 pinMode(TSL_S3, OUTPUT);

 digitalWrite(TSL_S0, HIGH);
 digitalWrite(TSL_S1, LOW);
 digitalWrite(TSL_S2, HIGH);
 digitalWrite(TSL_S3, HIGH);
}

void loop() {

}

void add_pulse() {

  // increase pulse count
 pulse_cnt++;
 return;
}

Notice that our interrupt just increases a counter, that’s all we need it to do. We’re going to check the frequency and re-set it every second in our code. Note that the interrupt is given the argument RISING, this means that it’ll only trigger when the signal changes from LOW to HIGH. We only need to count the pulses per second.


 // 1000ms = 1s
#define READ_TM 1000 

...

 // two variables used to track time
unsigned long cur_tm = millis();
unsigned long pre_tm = cur_tm;

 // we'll need to access the amount
 // of time passed
unsigned int tm_diff = 0;
...

void loop() {

  // check the value of the light sensor every READ_TM ms

    // calculate how much time has passed

 pre_tm   = cur_tm;
 cur_tm   = millis();

 if( cur_tm > pre_tm ) {
	tm_diff += cur_tm - pre_tm;
 }
 else if( cur_tm < pre_tm ) {
           // handle overflow and rollover (Arduino 011)
	tm_diff += ( cur_tm + ( 34359737 - pre_tm ));
 } 

   // if enough time has passed to do a new reading...

 if( tm_diff >= READ_TM ) {

       // re-set the ms counter
   tm_diff = 0;

      // get our current frequency reading
   unsigned long frequency = get_tsl_freq();

 }

}

unsigned long get_tsl_freq() {
    // copy pulse counter and multiply.
    // the multiplication is necessary for the current
    // frequency scaling level.  Please see the
    // OUTPUT SCALING section below for more info

  unsigned long freq = pulse_cnt * 100;

   // re-set pulse counter
  pulse_cnt = 0;

  return(freq);
}

CONVERTING FREQUENCY TO ENERGY


In the above code, we’ve gotten the frequency by counting the number of high pulses per second. This will work fine for our purposes, for the time being. The frequency sent to us by the chip correlates to a particular amount of radiant energy being received by it. We need to convert it to a more useful representation, and our target is a measurement of micro-watts (uW) per centimeter squared (cm2). The datasheet shows us a graph (figure 1) on page 4 that shows us that at a wavelength of 640nm, and 1x sensitivity, the relationship between uW/cm2 and the frequency is a ratio of 1:10. Or, the energy is 1/10th the frequency — uW/cm2 = Freq / 10.

An important point, saved for the last page of the datasheet, is that the actual sensor has an area of about 0.92mm2. This is about 0.0092cm2, whereas the application note found here indicates a size of 0.0136cm2 – I prefer this number. The size of the sensor is important, as we need to figure out how much energy is landing on a square centimeter of space, but we’re much smaller than that. We’ll need to multiply up the conversion to figure how much radiant energy would be landing in the square centimeter that surrounds us.

Also note that the sensor is more or less sensitive to other wavelengths than 640nm, so we’ll work straight from the graph in the datasheet figure 1, and stick with 640nm. Here’s a function to convert the frequency into uW/cm2:

float calc_uwatt_cm2(unsigned long freq) {

  // get uW observed - assume 640nm wavelength 

    // note the divide-by factor of ten,
    // maps to a sensitivity of 1x

  float uw_cm2 = (float) freq / (float) 10;

    // extrapolate into entire cm2 area

  uw_cm2       *= ( (float) 1 / (float) 0.0136 );

  return(uw_cm2);

}

Now we have a measure of not only the uW observed, but we would likely observe, were we to have a sensor that was actually 1cm2.

INCREASING SENSITIVITY

So, the room goes dark, and your frequency lingers around, erm, 2. The TSL230R has a more sensitivity than you might imagine, but you’re going to give up some of your bright-end for it. You’ll be able to go from 0.1 uW to 0.001, at maximum sensitivity. The ramification of this, is that you’re going to have to change the divide-by factor for converting frequency to energy.

If your readings are pretty low, and you’ve done everything else right, it’s time to up the sensitivity a bit.


  // need to measure what to divide freq by
  // 1x sensitivity = 10,
  // 10x sens       = 100,
  // 100x sens      = 1000

int calc_sensitivity = 10;

...

void sensitivity( bool dir ) {

  // adjust sensitivity in 3 steps of 10x either direction

  int pin_0;
  int pin_1;

  if( dir == true ) {

      // increasing sensitivity

      // -- already as high as we can get
    if( calc_sensitivity == 1000 )
      return;

    if( calc_sensitivity == 100 ) {
        // move up to max sensitivity
      pin_0 = true;
      pin_1 = true;
    }
    else {
        // move up to med. sesitivity
      pin_0 = false;
      pin_1 = true;
    }

      // increase sensitivity divider
    calc_sensitivity *= 10;
  }
  else {
      // reducing sensitivity

      // already at lowest setting

    if( calc_sensitivity == 10 )
      return;

    if( calc_sensitivity == 100 ) {
        // move to lowest setting
      pin_0 = true;
      pin_1 = false;
    }
    else {
        // move to medium sensitivity
      pin_0 = false;
      pin_1 = true;
    }

      // reduce sensitivity divider
    calc_sensitivity = calc_sensitivity / 10;
  }

    // make any necessary changes to pin states

 digitalWrite(TSL_S0, pin_0);
 digitalWrite(TSL_S1, pin_1);

 return;
}

float calc_uwatt_cm2(unsigned long freq) {

  // get uW observed - assume 640nm wavelength
  // calc_sensitivity is our divide-by to map to a given signal strength
  // for a given sensitivity (each level of greater sensitivity reduces the signal
  // (uW) by a factor of 10)

  float uw_cm2 = (float) freq / (float) calc_sensitivity;

    // extrapolate into entire cm2 area

  uw_cm2       *= ( (float) 1 / (float) 0.0136 );

  return(uw_cm2);

}

Now we have a function, that we can call with HIGH to increase sensitivity, LOW to decrease sensitivity, and our frequency conversion to energy scales for us.

In short – the higher sensitivities allow you to differentiate very low amounts of light. Use them in darker settings, and 1x sensitivity out in bright or daylight settings.

OUTPUT SCALING

Edit: Some days you eat pie and some days you eat crow. After imploring dear readers, that you pay attention to the datasheet and which pins are which, I went and had mine all mixed up while working on the scaling portion of this tutorial. You guessed it, I was all wrong. The following text has been changed as of 11/25/08, and now stands as correct instructions.

A topic we haven’t touched on yet is the impact of output scaling. Output frequency scaling changes the way the chip reports frequency to you. Like sensitivity, frequency scaling allows you to measure different ranges of frequency. The divide by 2 method provides the lowest range of frequency, but with the most amount of resolution, divide by ten covers the mid-range of frequency with a moderate amount of
resolution, and divide by 100 covers the high range of frequency with low resolution.

Frequency scaling says that the chip will report one value representing the average value read for x periods – where x is 2, 10, or 100. Simply stated: multiply the frequency you read by the scaling level you have set. For example, in divide-by-2, you multiply your reading by two, and divide-by-100, you multiply your reading by one hundred.

Here’s some code to handle changing the output scaling:


  // set our frequency multiplier to a default of 1
  // which maps to output frequency scaling of 100x

int freq_mult = 100;

...

void set_scaling ( int what ) {

  // set output frequency scaling
  // adjust frequency multiplier and set proper pin values
  // e.g.:
  // scale = 2 == freq_mult = 2
  // scale = 10 == freq_mult = 10
  // scale = 100 == freq_mult = 100

  int pin_2 = HIGH;
  int pin_3 = HIGH;

  switch( what ) {
    case 2:
      pin_3     = LOW;
      freq_mult = 2;
      break;
    case 10:
      pin_2     = LOW;
      freq_mult = 10;
      break;
    case 100:
      freq_mult = 100;
      break;
    default:
        // don't do anything with levels
        // we don't recognize
      return;
  }

    // set the pins to their appropriate levels

  digitalWrite(TSL_S2, pin_2);
  digitalWrite(TSL_S3, pin_3);

  return;
}

unsigned long get_tsl_freq() {

    // we have to scale out the frequency --
    // Scaling on the TSL230R requires us to multiply by a factor
    // to get actual frequency

  unsigned long freq = pulse_cnt * freq_mult;

    // reset the pulse counter

  pulse_cnt = 0;

  return(freq);
}

Now, we can change the output scaling at whim, and since we modified the frequency function, we don’t have to change any other code.

Use a lower output scaling for moderate levels of light or to tell minute differences in levels, or use higher scaling for brighter areas, but with less ability to tell the difference between small amounts. For most photographic applications, you’ll prefer divide-by-10 or divide-by-100, to prevent constant jitter, whereas live luminance measurement applications will prefer divide-by-2.

CONVERTING TO WHAT WE SEE

You’ve probably heard the term Lux before, as in “lux meter” or, this light produces this many “lux”. Lux is, essentially, the measure of perceived brightness falling on, or coming from an amount of surface. We’ll be using a surface size of square meters, m2, in our examples. Now, note that we’re talking about “perceived”, as in perceived with our eyes. To our eyes, different wavelengths of light register as brighter or less bright. This is called photometry, what we’ve been dealing with up to this point is measuring actual energy, which is radiometry.

To go from the radiometric (actual) data to photometric (perceived) data, we’ll need to convert the energy into lumens per square meter (lux) from micro-watts per square centimeter. In the process, we’ll need to determine how bright the light appears to us. We’ll use the luminous efficiency function to do this. Now, this function is dependent on wavelength – our eyes perceive different wavelengths more or less brightly. To keep matters simple, we’ll pretend like our light source is monochromatic — that is consists of only a single wavelength. We’ll pick the same wavelength we have been using, 640nm.

To perform this conversion, we’ll need to find the luminous efficiency of 640nm. Fortunately for us, this is empirical data, so we can just look it up in the CIE table. We’ll use CIE Vm(l) from 1978, and find that 640nm has an efficiency of 0.175.

...

void loop() {

  // check the value of the light sensor every READ_TM ms

    // calculate how much time has passed

 pre_tm   = cur_tm;
 cur_tm   = millis();

 if( cur_tm > pre_tm ) {
	tm_diff += cur_tm - pre_tm;
 }
 else if( cur_tm < pre_tm ) {
           // handle overflow and rollover (Arduino 011)
	tm_diff += ( cur_tm + ( 34359737 - pre_tm ));
 } 

   // if enough time has passed to do a new reading...

 if( tm_diff >= READ_TM ) {

       // re-set the ms counter
   tm_diff = 0;

      // get our current frequency reading
   unsigned long frequency = get_tsl_freq();

      // calculate radiant energy
   float uw_cm2 = calc_uwcm2( frequency );

      // calculate illuminance
   float lux    = calc_lux_single( uw_cm2, 0.175 );

 }

}

...

float calc_lux_single(float uw_cm2, float efficiency) {

    // calculate lux (lm/m^2), using standard formula:
    // Xv = Xl * V(l) * Km
    // Xl is W/m^2 (calculate actual receied uW/cm^2, extrapolate from sensor size (0.0136cm^2)
    // to whole cm size, then convert uW to W)
    // V(l) = efficiency function (provided via argument)
    // Km = constant, lm/W @ 555nm = 683 (555nm has efficiency function of nearly 1.0)
    //
    // Only a single wavelength is calculated - you'd better make sure that your
    // source is of a single wavelength...  Otherwise, you should be using
    // calc_lux_gauss() for multiple wavelengths

      // convert to w_m2
  float w_m2 = (u_cm2 / (float) 1000000) * (float) 100;

      // calculate lux
  float lux  = w_m2 * efficiency * (float) 683;

  return(lux);
}

And, there you have it – you can now calculate the Lux for a single wavelength. Now, obviously most light sources we deal with produce light in many wavelengths at once, but we’ll save the more complex calculations for our next post on the subject. In the next one, we’ll cover calculating lux for multiple wavelengths, converting to Exposure Value (EV), calculating exposure time from EV/aperture/ISO, and other conversions.


  1. Thanks for this! I was wondering how I could make use of this chip, now it all makes sense!

    -Bur.gr

  2. According to the TI website this IC is no longer made. Too bad.

  3. Strike that last comment. Some websites refer to the TI website for information on this part, but TI does not make it. However, all of TIs similar products have been discontinued.

  4. Hi Phil, the TSL230 series is under license to TAOS (Texas Advanced Optical Systems) along with a number of other optical devices originally produced by TI. I would suggest not using the TI datasheets as the design reference has changed since the licensing – specifically: size of the sensor, wavelength power response curve, and reference levels for a given frequency.

    You can find an accurate datasheet linked in the second paragraph of this post.

    Thx!

  5. This is great stuff. Can’t wait for the EV numbers calculations, always wanted to make my own light meter.

  6. Thanks Eric!

    I expect that I’ll have a chance to do the next write-up early next week. I have all of the code done and tested (well, did before I wrote this one – had to verify my calculations were correct by evaluating camera exposure to what the arduino reported) – just need to find the time to write it up!

    !c

  7. Anon Y Mous - November 23, 2008

    Epic Tutorial

  8. What font and size are you using for the code shown above? I really like it.

    Please post or email me the info.

    Thanks.

  9. Harit, in wordpress i use the tag: [sourcecode language=’cpp’] which auto-generates the templates you see.

    !c

  10. Thank you. I think wordpress uses stylehighlter which appears to use Consola.

  11. Eric, the next in the series is posted, covering EV, aperture, exposure time, and multiple wavelength lux calculations here: http://roamingdrone.wordpress.com/2008/11/28/arduino-and-the-tsl230r-photographic-conversions/

    !c

  12. Hey, I like the article … very new to micros. I’m thinking about making this my first Arduino project. Do you know what the max lux you could record with the sensor is? I am not certain on how to obtain this info from the datasheet. Just curious how good this sensor is in that sense.

    Also, do you have a good recommendation on testing the sensor once it is built? I notice that bulbs come with a lumens rating. Is an acceptable way to verify results to buy a new bulb and see if the sensor lives up to the info on the package? I am always curious how you verify that what you’ve built is giving you accurate results.

  13. Chris – I don’t know what the maximum lux you can read out of it would be (that would be determined by the formula which you use to calculate lux) – remember that the chip only gives you light power (uw/cm2) received, that light may be more or less visible to your eyes.

    The absolute maximum frequency you could get out of the chip in the divide-by-two mode is 500KHz. (The maximum frequency in pulse-train mode is 1MHz, that would be one pulse every uSec. Divide-by two has a fifty percent duty-cycle, which would make it 500KHz.) At 500KHz, in divide-by-two and 1x sensitivity, that would work out to about 735 W/m2, or 87,800 lux @ 430nm. Obviously, that’s theoretical, and much higher than you would read in the real world. I have read > 10,000 lx using the guassian formula in the second article, outside during sunlight.

    As for testing the sensor – it’s kind of a chicken and egg issue. Does that light bulb _actually_ produce the amount of light it claims? For how long will it produce that amount of light? What wavelengths of light does it produce? The amount of equipment you’d have to have just to answer these questions would present an issue – and each of them would have to be calibrated themselves. I used a simple, real-world test for mine. I created an algorithm based around Pentax reflective metering. (See the photographic conversions article.) Then, I took readings from the sensor, and exposed at the indicated levels – if everything was working correctly, I would find that the majority of light would wind up in the mid-gray brightness band. (I.e. “mid-gray exposure” as metered by a camera light meter — this should result in most of the light being stacked in the middle of the histogram on a digital camera.) I found that shooting the subject at the indicated values from the algorithm produced a consistent mid-gray exposure at least 90% of the time.

    !c

  14. Thank you for this great tutorial!
    It was super helpful. I would prefer to not have to check the value every 1000ms though. So I made changes and added that code to the interrupt. That way you can just use ‘freq’ whenever you need it.

    The modified code is on my blog if anyone is interested at
    http://www.teampaulc.org/2009/01/tsl230r-to-arduino-interface.html

    and again, thanks for the tutorial!

  15. Bill M - August 4, 2009

    Just another “Thanks!!” It is thru the efforts and sense of sharing that the Arduino is getting big, Really Big.

    • Bill – thanks for the great feedback! I’m always glad to share information when I have it. There’s nothing more frustrating than getting some great new component and then finding out you have no idea on how to use it!

  16. Just wanted to say thanks.. I’m working on my first project as an Electrical Engineer in College – Party lights ;) Im kind of obsessed with them now, and made a second version that has 8 channels and can be dimmed via an AVR (Triac-based Phase Control). However, dimming isn’t linear at all, and this will help me dial into the right curve that I need.

    Thanks again!
    Andrew Ortman

  17. Hi,

    GREAT tutorial. I just wonder if there would be a way to get from the sensor, the X,Y position of a light beam ??

    Thanks

    Seb

    • Sebf, it would require multiple sensors and/or motion to calculate the directionality of light. As the sensor is only a single sensor, it has no frame of reference to understand whether the light is stronger at its current position, or another. You can do a simple experiment to understand this: pick up the circuit with the sensor, and watch the values as you move it around – pointing it directly at, and away from the light source.

      !c

  18. Hi – Great tutorial! I’m a student at the University of Sydney in Interaction Design and was wondering about doing something similar for a project. I’m measuring UV rays (seperate sensor) and would like to see the difference (with this type of sensor) of when a person is wearing or not wearing sunscreen. This looks like it may be the way to go. Any insight or suggestions would be great. Thanks in Advance, DG

    • Hi Dave, looking at the datasheet for the TSL230R, we find that it’s responsivity drops to near zero at 300nm. While you could filter out everything above 400nm, using a special filter (Wood’s Glass, maybe?), you’d be restricted to UVB and UVA measurements, and you’d have to adjust readings according to the spectral response curve from the datasheet. The combination of the filter and the reduced responsivity will greatly reduce the resolution (by 90-95% or more, hip-shot estimate) of any UV measurements made with the TSL230R.

      So, with that in mind, I think a purpose-designed diode or sensor would probably work better. However, I’m not clear if you’re intending to measure the amount of UV, or another type of light – you mention having a separate sensor already for UV. Are you intending to measure how much light, of a longer wavelength, is being received?

      !c

  19. phishinphree - October 2, 2009

    Hi
    I’m just trying to get a feel for how much light .001uW/cm2 is. I’m working on an exposure meter for a large pinhole camera. I want to measure cumulative exposure over a period of several mins to several hours to account for varying light levels that would be encountered on, for example, a partly cloudy day. Basically I just need to know if this sensor is sensitive enough. I’m not sure of the exact f-stop of the pinhole, im guessing the hole is in the neighborhood of 1-2mm and the photopaper is on a back plate anywhere from a few inches to several feet. Also, I was curious how important it would be to filter the ir or near ir light? Awesome writeup! Thanks for the help

    • Hi phishingphree

      .001 uW/cm2 is very little light. Of course, “how visible” that light is depends on the source of light (daylight, fluorescent, etc.). The f/stop of your camera will, umm, vary quite wildly given those dimensions (1mm at 1 inch would be about f/24, whereas at 1 foot would be close to f/288). Filtering near IR light won’t generally be necessary, but it can help quite a bit – I haven’t tested at exposure lengths greater than a few minutes, but it’s (it being the thing I’m about to point you to =) within 1% accuracy for a 2 minute exposure. Now, I suggest you have a look at a couple of other posts I’ve made, the first being the second tutorial in this series that covers how to calculate photographic exposure amounts, presuming you’ve gone through this tutorial. The link is here: http://roamingdrone.wordpress.com/2008/11/28/arduino-and-the-tsl230r-photographic-conversions/

      The second tutorial, is a complete device that is pretty much exactly what you’re looking for. It calculates and fires a camera – now, it sounds like you have a home-made camera, so automatic exposing will likely be out of the question (without a linear actuator or something to trigger your shutter), but you can use it to show a running count of how much time is left in your shot, the link is here: http://roamingdrone.wordpress.com/2009/03/25/lightrails-dynamic-external-exposure-control-for-time-lapse/

      For the complete device, and to suit your purposes, you’d likely have to make two changes:
      1: Add a calculation for reciprocity failure for your given film type
      2: Adjust the software so that it displays a remaining exposure time left. You will also want to account for the change in light, so what you’d do is figure out what percentage of the total exposure you’ve already actually taken, and what the remaining time should be given the current reading.

      The sensor is perfectly suited for this task, but to make a point – the sensor should be _outside_ of the body of the camera (not measuring the light inside of the camera, it won’t provide you any benefit there, and won’t be sensitive enough), and if you make it through the second tutorial, you’ll see that you just need to know the camera’s parameters, and not so much have to have the light meter inside of the camera. You don’t need to get it perfect at first, experience will tell you how to tune your algorithm. BTW, I designed lightrails (the second link I provided) to eventually be used for digital pinhole photography, as I used to do a lot of that as well. It just lacks the running exposure re-calculation ability, which would be extremely simple to add.

      !c

      • phishinphree - October 13, 2009

        Chris,
        Thanks for the help and tips. I am now about 75% done with the project. I’m using a pic 16f916 (8k) but now that I’m implementing the calc_lux_gauss function, I realize I won’t have enough program space to finish. I know there isn’t any shortcut for the calculus however due to the setting this will be used in, there are two assumptions that can be made. One, only b&w paper is being exposed, which, if i understand correctly, is sensitive only to blue and yellow(?) light. And two, The camera is only used in daylight. But I suppose the spectrum of that light depends on what it is reflecting off. Not sure if this information can be used to simplify the lux calculation or not. My backup plan is moving to an arm processor as I only used the pic because of its low power consumption and the simplicity of having one power rail for the lcd, pic and sensor (which cuts my part list in half). And its breadboard friendly.

        Also, I too noticed the 7.7hz/(uW/cm2) figure mentioned in the data sheet and was wondering if there is any reason for using 10 instead?

        Thanks again. I look forward to showing off this project in the near future.

      • You can remove the elements of the arrays that are unnecessary for you, but alas, I’m afraid you cannot eliminate lists and algorithmically determine the values, as the CIE tables are empirical. You might be able to save a LOT of space, however, by avoiding floating point math altogether. It’s an old trick, but highly effective (especially since neither the PIC nor the Atmega have an FPU) by working in higher scales and then bringing your result back down at a final stage in calculation.

        Please stop by and drop a link to your project when you’re done – I’d love to see it!

        !c

      • phishinphree - October 17, 2009

        Hi again
        I ust wanted to ask about the uw/cm2 to w/m2 calculation. Maybe I’m missing something in the code but aren’t there 10000 square cm in a square m? Changing the value from 100 to 10000 in the code seems to be producing ballpark lx values for me. For instance with these conditions, raw freq = 16000 hz, scaling set to /2 and at 100x sensitivity, I get around 80-90 lx depending on where its pointed (using a white paper to diffuse). This seems very reasonable according to http://en.wikipedia.org/wiki/Lux Im in a smaller spare bedroom home office with 2 60w lights. Maybe I’ve got a bug elsewhere but so far this is where I’ve tracked it. Without the change, I’m seeing 1 or less lx and that just doesn’t seem right to me. Its not that dark in here.

        As always, I greatly appreciate your suggestions.

      • There are 100 cm in 1 m. Therefore, there are 100 sq cm in 1 sq m. This can be proven through basic reduction of redundant terms.

        That is to say, if 1m == 100cm, then 1m^2 == 100cm^2. Remember that there are 1,000,000 uW in a W.

        The problem is unlikely to be in your w_m2 calculation, especially if you are using the method I provided, straight (without modification). Don’t presume the problem lies in your watt calculus if you’re measuring by lux output. De-complicate the issue by first validating wattage, and then validate lux.

        So, if you use the calc_uwatt_cm2() function I provide (with my 10x factor), you should get an output of ~1176.47. (Remember that you have to scale the area you actually read to the area of a sqaure cm.)

        If you changed to 7.7 as the factor, then you should get ~1527.87.

        Given the 1527.87 uW/cm2, you then check your conversion to W/cm2, by dividing by 1M — 0.00152787 W/cm2, then extrapolate that to the same reading over an area of m2, or 0.152787 W/m2.

        The conversion to lx is probably where you’re messing up. Can you show me your modified conversion tables for the gaussian function?

        Also, remember, that the spot you’re reading the reflection from may not truly be representative of the most amount of light. You have a sensor of 0.0136 cm2, and are extrapolating that out to the same reading across an area of m2. If your formulas are all right, then attempt to move the area you’re reading off and see what changes. Remember that 16kHz at 100x sensitivity and 2x scaling is kinda low. =)

      • phishinphree - October 22, 2009

        Hey, I only have a minute right now but I wanted to point out that there are, in fact, 10,000 cm^2 in 1 m^2. Your conversion is correct for linear measurements as, there are 100 cm in 1 meter. However 1 square meter is broken down to 1m*1m. or 100cm*100cm. which is 1m^2 or 10000 cm^2. I must admit that I am surprised at your error here as the conversion from radiometric to photometric units requires much more complex mathematics.

        Anyway, I’ll start a thread somewhere soon documenting my work so far. The comments section here probably isn’t the best place for it.

      • You are absolutely right. I can blame my error today on the fact that I’m responding between dealing with more complex issues with my day job here, and my error then that I was more focused on the photometric conversions =) Or maybe just that I, like many, have a tendency to think gloss over something and never come back to it!

        This does draw into question how I can start with an incorrect assumption, and end up with a correct answer. In fact, that’s the 8k lb gorilla in the room. This more than likely leads me to an error further down the chain, I’m presuming its going to be in the APEX conversions, as I spent quite a bit of time on gaussian function, and can’t claim credit to it (I stole it, alas =).

        I would like to thank you and Andrew for pointing out these errors, I will use them to correct this tutorial and the others in the near future.

        !c

      • phishinphree - October 22, 2009

        I’ve also done the same thing many times myself, no worries. Your right tho, there has to be some error elsewhere that compensates for it. If I have time over the next few days, I’ll debug some more. Otherwise, it’ll wait till I’m back in town.

      • Phishingphree, hey I forgot to mention it, but since you’re working on a photo-related project, if you’re looking for somewhere to document your project work – might I suggest my site over at OpenMoco.org? It’s intended to be a repository of information related to DIY photography/timelapse/photo motion control – and we could definitely use the content. It’s to be a community-driven site, but as of yet, we haven’t gotten a lot of extra input outside of what I’ve been writing there.

        There are a lot of contributors working on things, but I’d love to have your work and notes as well – I plan to get some more work on pinhole-related subjects there done as I complete some hardware prototypes for the timelapse gear I’m working with.

        !c

      • phishinphree - October 25, 2009

        hi again
        Ive looked things over a bit. Wanted to ask why your using the sensor size to convert the readings to uw/cm^2. Maybe I’m missing something but the impression I get from the datasheet is that the sensor reports in uw/cm^2. Meaning that the sensor already takes its size into consideration. Another thing I noticed is that the part I received from sparkfun has a sensor size much closer to .9mm2 than 1.3. I’ve modified the calc_uwatt_cm2() function to return freq*2/sensitivity. Also, I’m using .77, 7.7, and 77 as the sensitivity powers as this matches with the datasheet as well. Curious why yours are an order of magnitude larger and not 1, 10, and 100. With these changes, I’m able to get reasonable shutter speed approximation.. like 40ms vs 1/30th. still off, but probably within the 20% for this part number.

      • The indication that you have to extrapolate the size to get an accurate reading comes from Taos directly.

        Here’s the quote from the Taos application note: “The thick middle line is the one to use here. And that line tells us that 10 kHz on the graph corresponds to around 100 mW/cm2. So the power incident on the array is (100 mW/cm2)*(.0136_cm2)=1.36 mW of power.”

        Here’s the link to the application note: http://www.taosinc.com/getfile.aspx?type=press&file=taosdn5.pdf

        Incidentally, I likely picked up the bad 10uW/Hz reading from this application note. Also incidental, .9mm is what my calipers read. (I didn’t have a good set of calipers when I wrote the tutorial, so I experimented with both values – and found 1.37mm to result in a closer reading to my pentax spot meter – in fact, it was ‘spot on’ for readings < 1 second.)

        As to why the values are 10, 100, and 1000 vs 1, 10, 100. At this point, a year later – I couldn't tell ya why. I see the same thing you see in this case.

        !c

  20. Andrew - October 3, 2009

    I’ve enjoyed your articles immensely and in the process of building a light meter for myself using your project as a background. Minor differences: I’m using the hardware counter to measure pulses and only dealing with the overflow interrupt. At 1x scaling, the time it takes servicing a pin change interrupt caused my program to temporarily lock up when I shined a bright light on the sensor.

    Anyways, I wonder if your calc_uwatt_cm2 function might not be right. By my reading of the data sheet, the typical responsivity is 7.7 Hz/(uW/cm2), not 10. The returned value from calc_uwatt_cm2 should be about 1.3x higher.

    Does this make a difference or is it all taken into account when I calibrate the meter?

    • Andrew, I do see the point you’re indicating. In fact, it’s been long enough now that I can’t properly recall where I got 10 from, but my poor recollection at this phase says it likely came from one of the application examples from Taos for the PIC. You’re right that it does in fact say 7.7.

      You should probably choose 7.7 for the most accurate number, however, I’ve found very little deviation from other light meters, in spot mode (such as the one in my K10D) when I was calibrating. It is highly likely that floating point precision (or lack thereof) ends up modifying the difference throughout the full chain of processing.

      It strikes me as interesting that it locks up for you at 1x sensitivity with a flashlight? I have been effectively using a device in the field (see my “LightRails” post) at 1x sensitivity in full sunlight (much more light than a flashlight can produce) using the same implementation here with no issues. The only time I ever had a lockup was when the wiring was incorrect.

      Edit: In fact, I had 100% rate of lockup when using a flashlight, just like you mention, when the wiring was incorrect. That determination led to the edit in the post its self where I commented about the original version using the wrong pinouts and causing the code to be incorrect.

      !c

      • Andrew - October 16, 2009

        Thanks for the reply. I haven’t found time to finish building this or put a camera in the loop yet, but it’s still in the works.

        The lockup problem was something stupid on my part. I originally wanted the scale and sensitivity values to be dynamic, based on the calculated frequency. i.e. if the frequency went too low, try decreasing the scaling, if scaling is at minimum, increase sensitivity and so on. The problem with code like this is that if the IC is at 100x sensitivity and 1x scaling and I shine a 3W LED into the chip from a couple centimeters away, the ISR doesn’t finish before the next pulse comes in. Add another TSL230R into the mix and it gets worse.

        This was all solved by using the onboard timer/counter hardware and enabling an interrupt on overflow. Every sample period I add the counter register to the overflow count multiplied by the maximum value of that timer register. Now I can read two sensors at 1x scale in sunlight with plenty of time to do other things on the AVR.

  21. You are reading the TAOS application note wrong. In the datasheet (graph in Figure 1) it is clearly said that the output frequency is directly converted to uW/cm^2, there’s no need for scaling with the sensor size. That’s what the 1/cm^2 means, it is already scaled to a unit area. The application note just says that you can get the incident _power_ by multiplying with the sensor size.

    Now, in the code you divide with the sensors size. If you do this, the unit would be (uW/cm^2) / cm^2 = uW/cm^4, which isn’t anything relevant.

    As my application only needs irradiance (uW/cm^2), I haven’t followed through what you do to get reasonable exposures with the camera, but there’s some fishy anyway, based on the incorrect starting point..

    • Hi Panu,

      You are correct, phishingphree and I have come to that conclusion via email. Unfortunately, it was a little too difficult to try and have that full conversation in the comment area here.

      There are numerous reasons as to “why it works” in the field, even when the core assumptions are wrong, but namely, two factors come into play: the lack of an FPU in the atmega chips results in the very small calculations falling within the margin of error on the CPU (it is highly unlikely that it comes anywhere near exact – in fact, this is why I didn’t model blackbody radiators in the second tutorial), and the fact that using it as a “middle-gray” light meter forces EV correction for proper exposure (like any other meter), which largely compensates for any errors. Especially as those errors are largely consistent. (See first point.)

      At any rate, I’ll be updating all of the tutorials as soon as I have a chance to build another working model to test on (I’m very busy with the OpenMoco project ATM), as I don’t want to put untested code in here. For the time being, the comment area will serve as a record for these corrections.

      !c

  22. Hi… I’ve been reading this tutorial and find it fasinating and decided to dig out an arduino/light sensor and give it a go.

    I went through and started pasting the various sections of code – but it dawned on me that it seems incomplete. The subroutine calc_uwcm2 is missing… Can you include it or send it to me.. Or better yet, provide a complete program… I would really like to dig into this more and learn as much as I can… Thanks.

  23. Fred, there’s a typo – “calc_uwcm2″ should be “calc_uwatt_cm2″ – seems I missed one location when updating the names to longer, easier to read versions for the post. For a complete implementation, see: http://roamingdrone.wordpress.com/2009/03/25/lightrails-dynamic-external-exposure-control-for-time-lapse/

    !c

  24. Thanks for getting back to me so quickly… I was suspecting that, but wasn’t sure… I’ll check it out…

  25. Excellent article. I am new to the Arduino but an “old” hand at microprocessors. I recently found the TS light sensor in a drawer and your article should make its hookup and implementation quite easy. My needs are more limited than photography – just a light detector to measure sunlight so that I can rotate a solar collector (I’ll probably make a cheap quad-cell using a slowly rotating aperture and 4 light fibers. I’ll also use the light information to have the Arduino decide whether to close some insulating panels.

  26. Hi. Is there a complett downloadable file wich contains the schematic, and source codes?

  27. What Arduino board would you recommend for this chip?

    • Dave,

      Any arduino board will work. If it’s your first arduino project, I’d suggest starting with the Duemilanove.

  28. Thanks very much. I’ll get that one so!

  29. Ok, I’ve copy and pasted the code word for word into the compiler and I get an error saying:

    “In function ‘void loop()’:
    error: redefinition of ‘void loop()”

    It’s in reference to the void loop() in the second section of code from the OUTPUT BASICS in this tutorial. Anyone have any idea what the problem is?

    Also, the compiler is having problems with “…” in the code. Maybe I’m just being stupid but is there supposed to be something where the “…” is that I’m missing?

    • Well, “…” generally indicates “some stuff here”, and you shouldn’t define the same functions twice. If you look at the code and follow the sections, you might notice I’m indicating where I’ve changed an earlier body of code. And where earlier code might reside. This is tutorial that’s supposed to walk you through building a new body of code yourself, so break each out part into its own code section, much of which requires other stuff to be present to actually work.

      For all of the code shown here, put in one copy-and-paste location, look at the beginning of part 2: http://roamingdrone.wordpress.com/2008/11/28/arduino-and-the-tsl230r-photographic-conversions/#more-105

      !c

  30. Matthew - April 1, 2010

    Hi, I’m new at this and could use some help. I’m using a Arduio Duenmilanove 328P and keep geting the error (‘add_pulse’ was not declared in this scope In function ‘void loop()’:
    In function ‘float calc_lux_single(float, float)’:) when compiled. Any advice? Thank you

  31. Aizen - April 5, 2010

    Hi

    Thks for the great info!

    I read from the datasheet that the output pin of the TSL230R can be driven to abt 12 inches max. Have your application been longer than that, without using a buffer or a line driver? I am thinking of hooking the TSL230R at abt 60 inches from the microcontroller. Would that be a problem, based on your experience?

    Thks!

    • You might have issues at 60 inches. I’ve run about 24 inches distance, but not much longer. I would suggest either buffering, or using a very small arduino (like the nano or pro mini) closer to the sensor to handle communication with a remote arduino.

      !c

  32. Aizen - April 8, 2010

    yes,u r right! Tried that and the pulse count stopped at 2655 using x100 and div10 measuring morning light by the window. Will look into a driver.

  33. Hi

    Thanks for the great tutorial! Just a quick question:

    The uW/cm2 value obtained in this code is at 640nm (obtained by dividing the freq by 10 and using fig 1 in the datasheet). If I wanted to find the uW/cm2 at different wavelengths e.g. uW/cm2 at 400nm only, how would I get this new value (I can’t use illuminance since I am measuring reflectivity and I need wavelengths from 350 to 1000)?

    Is there another datasheet to get the direct relationship between frequency and wavelength?

    Thanks

  34. Ahkarhul - November 11, 2010

    Thanks for the awesome tutorial. I am following along with my arduino (currently just before converting frequency to energy) I am using Serial.println in my main loop to report the frequency each second. I have noticed that as the frequency gets up to about 70Khz the interrupt takes over, my main loop hangs and I stop getting serial data. Once the sensor is exposed to less light, I get a huge value dumped to serial and then the program continues as normal. Any insight on how I can make the interrupt less pushy? Let me know if you want me to post code.

    • Ahkarhul,

      Thanks for the great feedback! For info on how to solve the lockup issue, see Andrew and mine’s discussion near the top of the comments, we both experienced it in different ways.

      !c

  35. Hi,
    Great tutorial, thanks for your efforts.
    “An important point, saved for the last page of the datasheet, is that the actual sensor has an area of about 0.92mm2. This is about 0.0092cm2, whereas the application note found here indicates a size of 0.0136cm2″
    I also noticed that the datasheet says 0.0014 in2 which sounds like an approximation of 0,0136in2, don’t you think :)
    Thanks

  36. Great tutorial! Quick question for you: I’m looking to use your code as the basis for a much simpler sensor (I hope). I have a bunch of motorized blinds on the west side of my house and am trying to build a sensor that can essentially return two values to my home automation system: “sunny” and “not sunny”. “Sunny” would equal when it’s bright enough outside such that the solar radiation pounding through the glass would overheat the house. Not-sunny would equal everything else (e.g. cloudy, night time, etc).

    Am I right in that to perform this calcuation best, I would actually use the initial irradiance value and *not* an illuminance value? I’m guessing so because it’s the actual irradiance that produces the unwanted heat and not the illuminance. And if so, is the frequency sufficient for that or am I going to want to use the uw_cm2 value?

    Many thanks in advance… I bow to your genius.

  37. Kinata - June 7, 2011

    Great article; thank you so much for documenting your work in this area. Mostly interested in building a pulse oximeter at present. However, might you know of similar sensors of other than IR wavelengths to implement a colorimeter?

    Best regards,
    Kinata

  38. David Findlay - June 29, 2011

    Hi,

    I’m trying to construct an pyranometer using this chip. As such I’m trying to calibrate it such as to report an equivalent of w/m^2 of full output. Has anyone else done this and have some calibration information I can use?

    Also I’d like to run two of these on the Arduino Uno, how do I get started at that? Thanks,

    David

  39. Rhonda - July 11, 2011

    Hey,
    Great tutorial. There’s one thing I can’t figure out. Is the sensor reading the full spectrum, or just 640nm? I think I have my setup working, but my readings drop off considerably in the shade, much more than I expect. I’m wondering if it’s related to the wavelength.

  40. hello,can this tutorial measure irradiance of the solar panel? and also how do i combine it with voltage detector circuit.
    i think by using arduino can help more to do it.

  41. secretagent - October 5, 2011

    It still locks up when you shine a strong light on it, in my case 2x3W LEDs from a few centimeters away in full brightness. Have you updated the code?

  42. Hi, I am student and was trying to implement the TSL230R with an arduino but the problem is that i have background in micro-controllers. I have a few questions for you

    1) What coding environment did you use? was it processing?

    2) I can’t figure out the circuit that i need to make, will it be possible for you to provide me with a picture of your circuit?

    3) once you have everything hooked up, the code written, how do i see the results?

    im sorry if i sound stupid but i have no prior knowledge of this and i really want to learn

    john

  43. calichemistry - February 8, 2012

    This is great tutorial! I have one question, however. I believe the values for irradiance off.

    I know this is the case, because in a dark room there should be less than a microwatt of light hitting the sensor, but the sensor detects 5 mW :x ouch.

  44. Wael - March 5, 2012

    hi can someone please send me the complete code to get a reading in lux because i couldnt really put the above codes together im getting errors id really appreciate it and thanks whoever uploaded this the best tutorial i ever seen, id appreciate anyone who can send me asap to waelmahlous123@hotmail.com

  45. Angeline - March 12, 2012

    hi,thank you for this. i want to convert the output from digital to analog, shall i use DAC0800?

  46. Your TSL230R tutorials are wonderfully detailed and helpful. However, I am having a hard time figuring out how to reassembly the code bits you have into a single sketch. Do you have a fully-defined example somewhere that I could view? It is difficult for a newbie to figure out how to combine the examples above and get it to compile correctly. Thanks! This is great work.

  47. peter - May 9, 2012

    Hi,

    Is any one experienced this problem. I am using this sensor to measure the sunlight. When the sun light is very strong, the system stop working. I checked the frequency before I am using 1x sensitivity and 1x division. Is this because when the frequency is too high, the interruption function will fail arduino?

    Cheers

    • Peter,

      Adjust your scaling – you’re firing the interrupt too often, and blocking up processing. Increasing the scaling will reduce the interrupt count. You should scale automatically as the rate you’re reading goes up and down.

  48. Sixto Rojas - May 25, 2013

    dear Church,
    I’m working with a Taos230, and like to read colors, ie, convert the frequency that gives the their corresponding RGB sensor, I’m new to this, but I want to make a colorimeter.
    best regards

    Sixto

    • Hi Sixto,

      If you want to measure the color of light received, you’d need to put color filters (one at a time) in front of the sensor, and then sample each color range separately. Alternatively, if you want to read the color reflected off of a surface (i.e. material color) or transmissive (passed through), you can use an RGB LED. Taking into account, of course, the spectral sensitivity of the chip.

      I can’t quite make out the hardware they’re using here, I presume it’s in the TSL230 family, but here is one such (transmissive) project: http://www.iorodeo.com/colorimeter

  49. Dave - June 28, 2013

    I see the reference & dereference pointers in your code where you do the conversions. WHERE can I learn more about the use of these? The Arduino reference site simply mentions their existence, but nothing more. Tips on how your code structure works here:

    float uw_cm2 = (float) freq / (float) 10;

    // extrapolate into entire cm2 area

    uw_cm2 *= ( (float) 1 / (float) 0.0136 );

    I’m disappointed that there’s not better calibration information (or formulae) in the datasheets for getting proper illuminance values…sigh! Have to find a calibration source next!

    Thx again for the tutorial!

  50. lemorlenny - July 31, 2013

    HI, great tutorial,
    I would like to measure an TSL235 array (almost 12 sensor), on Arduino the IRQ pins are only 2, what you suggest the best approach to retrieve a most accurate measure from each sensor of my sensors array, without use IRQ?

    Thanks

    Regards

    Lenny

    • Hi Lenny,

      Off the top of my head, you could use something like the 74HC4020 or the SN74LV8154N, with shift registers to handle multiple of them. Would have the added benefit of letting you poll the values on your own time, rather than having to use an ISR that triggers often.

      • lemorlenny - July 31, 2013

        HI, thanks for reply,
        I also thought to use a shift register but I’m thinking if use anyway the IRQ port or timing myself the scanning loop of sensor. The problem is that for a correct reading I need to complete the scan of all sensor almost 20 times per second, I dont’ if this can be enough for a good reading of each sensor value.

        Thanks

        Lenny

Leave a Reply