Go Down

Topic: How to: Light to Frequency Converter - TSL235R? (Read 23735 times) previous topic - next topic


Jul 19, 2010, 11:06 am Last Edit: Jul 19, 2010, 02:04 pm by madepablo Reason: 1
I tested both sensors, TSL230R and the TSL235R, and the results are not comparable at all.

So, i assume that the previous code that i posted here for the TSL235R is not correct. Let me study in detail the code and rewrite it... soon.

[edit]Just only one question:

In the datasheet of both TSL235R and TSL230R, Figure 1 shows that 10KHz is about (not exactly, but just for discussion is enough) 10uW/cm2. So now it let me think... those 10uW/s are acorrected to 1cm2 sensing area?

What i mean is... if i obtain a measurement of 10kHz just only measuring the pulses and dividing by the time in seconds, does it means that i have 10uW/cm2? or i need to correct the 10Khz to about 108KHz assuming a 0.0092cm2 sensing area?

Any idea? Thanks![/edit]

Here are the datasheets:

TSL230R http://www.sparkfun.com/datasheets/Sensors/TSL230R-LF-e3.pdf
TSL235R http://www.sparkfun.com/datasheets/Sensors/Imaging/TSL235R-LF.pdf


Jul 20, 2010, 09:22 am Last Edit: Jul 20, 2010, 09:23 am by madepablo Reason: 1

I made a new version of the code to read the TSL235R:

Code: [Select]
/* Example: TSL235R
  Measurement area: 0.92mm2
  TSL235R    Arduino pins
  GND        GND
  Vcc        +5V
  Out        Digital pin 2
  Wire a 0.1uF capacitator between Vcc and GND close to the sensor
  Wire a led between pin 13 and GND

// Pin definitions
# define TSL235R 2                   // Pin to TSL235R output
# define LEDpin 13                   // Led to show when the sensors is measuring

// Constants
int period = 2000;                   // measurement period, in miliseconds
int ScalingFactor = 1;               // Scaling factor of this sensor
float area = 0.0092;                 // Sensing area of TSL25R device, in cm2

// Variables
unsigned long counter = 0;           // Counter of measurements
volatile unsigned long pulses = 0;   // Counter pf pulses in each measurement period
unsigned long frequency;             // Store the frequency calculated
float irradiance;                    // Store the irradiance calculated
unsigned long startTime;             // Stablish the time when measurement starts
unsigned long currentTime;           // Used to measure the time during measurement

void setup(){
 Serial.begin(9600);    // Starts the serial port
 pinMode(TSL235R, INPUT);                    // Declare the pin such as an input of data
 pinMode(LEDpin, OUTPUT);
 Serial.println("Testing a TSL235R sensor:");  // Splash screen
 Serial.println(" v0.2      20100720");  

void loop(){
 digitalWrite(LEDpin, HIGH);
 Serial.print("   ");
 startTime = millis();
 currentTime= millis();
 while (currentTime - startTime <= period){
   attachInterrupt(0, count_pulses, RISING);
   //detachInterrupt(0);                          // Stop to sensing pulses from TSL235R sensor
 digitalWrite(LEDpin, LOW);
 Serial.print("pulses: ");
 Serial.print(";  ");
 Serial.print("frequency: ");
 Serial.print(getfrequency(), DEC);
 Serial.print("  Hz;  ");
 Serial.print("irradiance: ");
 Serial.print(getirradiance(), DEC);
 Serial.println("  uW/cm2");
 pulses = 0;                                   // Reset the pulses counter for the next measurement
 delay(5000);                                  // Wait 5 seconds until next measurement

void count_pulses()

unsigned long getfrequency(){
frequency = pulses/(period/1000);

long getirradiance() {
 irradiance = frequency / area;                // Calculate Irradiance (uW/cm2)
 return (irradiance);

it is 4210 bytes.

and here is and example of the results:
Testing a TSL235R sensor:
v0.2      20100720

1   pulses: 4800;  frequency: 2423  Hz;  irradiance: 263369  uW/cm2
2   pulses: 16850;  frequency: 8448  Hz;  irradiance: 918260  uW/cm2
3   pulses: 16698;  frequency: 8373  Hz;  irradiance: 910108  uW/cm2
4   pulses: 16709;  frequency: 8378  Hz;  irradiance: 910652  uW/cm2
5   pulses: 16807;  frequency: 8427  Hz;  irradiance: 915978  uW/cm2
6   pulses: 17101;  frequency: 8574  Hz;  irradiance: 931956  uW/cm2
7   pulses: 16581;  frequency: 8314  Hz;  irradiance: 903695  uW/cm2
8   pulses: 16592;  frequency: 8319  Hz;  irradiance: 904239  uW/cm2
9   pulses: 16530;  frequency: 8285  Hz;  irradiance: 900543  uW/cm2
10   pulses: 15568;  frequency: 7807  Hz;  irradiance: 848586  uW/cm2
11   pulses: 16450;  frequency: 8248  Hz;  irradiance: 896521  uW/cm2
12   pulses: 25021;  frequency: 12535  Hz;  irradiance: 1362500  uW/cm2
13   pulses: 1145288;  frequency: 572674  Hz;  irradiance: 62247172  uW/cm2
14   pulses: 24702;  frequency: 12378  Hz;  irradiance: 1345434  uW/cm2
15   pulses: 229165;  frequency: 114604  Hz;  irradiance: 12456956  uW/cm2
16   pulses: 855675;  frequency: 427856  Hz;  irradiance: 46506084  uW/cm2
17   pulses: 1901801;  frequency: 950916  Hz;  irradiance: 103360432  uW/cm2
18   pulses: 11109;  frequency: 5571  Hz;  irradiance: 605543  uW/cm2
19   pulses: 12433;  frequency: 6233  Hz;  irradiance: 677500  uW/cm2

it seems to run (although i still didn´t check with TSL230R). However, there is one problem. When the light is intense (i tested taking so near a red led), the lecture take infinite time.... it does not stop to measure until to move a little bit the led far from the sensor... any idea? it is a problem of the code i assume.... but i don´t know where.

Your ideas are welcome. Thanks!


Aug 02, 2010, 10:21 pm Last Edit: Aug 02, 2010, 10:23 pm by BetterSense Reason: 1
IF you are hooking the pin up to the interrupt, when the chip is put in a bright area the frequency may become so great that it trips the interrupt fast enough to freeze the rest of your code. I have several light meters made with these chips and I created a routine to adjust the sensitivity and frequency division to keep the interrupt frequency between 300Hz and about 7kHz. When I have tried running without the auto-scaling code, it would definitely freeze up the rest of my code when I took the chip outside. I could get you numbers, but I would guess the chip goes into MHz range frequencies if you leave it on max sensitivity and no frequency division.

This is why the new, smaller version on sparkfun is probably not very useful for use as a light meter that must cover a very wide range of brighnesses. I like the small package, but I think the frequency division and sensitivity adjustment is probably necessary for the most part.


Thanks Bettersense,

I see the problem.. i also love this small device TSL235R, because the TSL230R (what is really complete and more accurate) needs a lot of pins in my arduino duemilanove... what i don´t have. May i could solve it by the use of shift registers, but i don´t know.

In any case, thanks so much for your clear explanation.



I've gotten this to work pretty well, but unfortunately not for my purposes, yet.

I adopted someone elses code to do the pulse counting in the interrupt & poll it periodically to get the counts, or freq, etc.

I'm graphing in Processing, again adopting someone elses code, and outputting the various values such as pulse count, time elapsed, etc.

The light levels are fine, although I'm working indoors. I need it accurate at a temporal resolution of about 10 or 20 msec & for fairly subtle light level changes. It's pretty clean, but I get these odd spikes semi-periodically. I'm trying to clean it up, by creating a buffer of prior samples & trying to throw out the bad ones.

I'm using an infrared led on it, powered by the board.

The brighter the light, the bigger the spikes, so it seems to be inherent noise that gets amplified. Although the spiking seems different, depending on the light source.

The strange thing is, that the noise spikes seem to increase whenever the light level stays constant, which is bad for my project.

I wanted to post a couple of pics, but it said I couldn't on my first post, so hopefully the pics will follow this post.


This one is the result of 4 sample points (at 2msec intervals)

This is the graph of the individual 4 sample points. Each color is a different element in the 4 sample buffer.


Sorry, didn't explain those pics very well.

The top one is over 5 or 6 seconds and is the total of the 4 samples in the buffer.

The bottom one is the same time period but the differences of each of the 4 samples from the prior sample. Each of the colored lines represent one of the 4 samples. As you can see, the spike seems to show up as an aberration from the prior sample.


Hi Oscarcar,

Great to know that it works for you. Could you post the code here to see how it runs?

On the other hand, do you have similar results than my example? (i mean about constant result, not the same values, of course!).

may be you could try to measure and obtain a value for longer periods of time. I mean, one sample per second or two seconds.
Other option is to obtain average values from different samples and see if the mean value is more constant under constant light conditions...

On the other hand, What is the vertical axis?? frequency? %?... Did it occurs also under different light conditions (but constant in each one of them)? because may be this big "error", is not so important if at the end is only 1% or less... (is an  ideal umber, i don´t have idea of the error of this sensor). Do you know what i mean?

Try at different light conditions as constant at possible en each one of them, to see if the results are similar.

I am involve in other project now, and i didn`t go back to the TSL235R, but it couldbe great if we could advance in this sensor.

But this in only my oppinion, that i am really noob on electronics and also in this sensor.



Unfortunately it's not entirely working my project but that's because I'm demanding a lot from it. I can get it to work fine for most other purposes that people would use it for.

I'll post my code in about a week or so. Right now it's pretty unintelligible cause I'm trying so many different things.

The graph is of pulse counts, in other words how many times the interrupt got triggered.



Aug 11, 2010, 09:41 am Last Edit: Aug 11, 2010, 09:42 am by madepablo Reason: 1
Ok, thanks Oscarcar,

so, vertical axis is pulses in measurements every 2us, and horizontal axis is time in us....

I don´t know if it is enough interesting for your project, but try to measure for longer time periods 500us or 1000 us. just to see if you have more stable readings. In that case, you could reduce gradually the time period in order to see when it start to be more random. Of course, under the same and constant light conditions.... it is just only an idea, because i don´t know where could be the problem.

Good luck!


I've been referring to milliseconds, but your notation seems more like microseconds in your last post.

In the top graph, each sample point represents 8 msec (the addition of 4 sample points at 1 sample every 2msec).

The bottom graph is each individual sample at 1 per 2msec, but graphing the difference from the prior sample.

What you suggest is kind of what I'm trying to do, but you have a good point. I could probably code it to sample at different sample periods and it would probably make the noise more apparent. I've been just trying to do it by trial-and-error.

I guess this is kind of the problem with using a signal-to-frequency sensor. It does a good job of eliminating noise in the circuitry but also hides it in the output. If I could see the voltage representation I would be able to see the pattern of noise more easily.

Which leads me to think that maybe I should get a light-to-voltage sensor for analysis. I think the unit below is identical except the circuitry to output the voltage.

Taos TSL14S

I see them for just over a buck at mouser.

The TSL257 also seems possible, and the taos page has an
interesting paper on noise in the sensor. Some of that might be applicable to the Light-to-Frequency sensor too.



You are right Oscarcar,

I mean ms not us... but it was only a notation problem, because i had on min miliseconds.

I have a TSL230R that seems to work perfectly with codes that you could see on internet. for example:

it is also a Light-to-Frequency sensor, but well tested. So may be i could try both at the same time to see what happens.

Thanks for the links. I will take a look as soon as i could have time for this project.


Well, happy to report it's going fairly well.

I adapted some code from the digitalSmoothing() function on this site,
so that it throws out the highest & lowest values in the buffer.

Currently I'm sampling every 2 msec and place in a buffer the last 5 samples. I then take the average of the 3 remaining samples and compute the total pulse counts as if it was 5 samples.

Here's a pic of me waving an infrared LED in front of the sensor and then moving it around slowly. It's about 12 seconds of data.


Tell me this, is the sensor facing towards a light, laptop IR port, or other things that may emit light periodically? If you have a cheap webcam, point towards your laptop IR port, you can see it pulse every so often. Could be the source of the spikes. Put labels on your axes.

No, the spikes happen even in an enclosed space that blocks out other light.

I'll put labels on future graphs so that it's easier to see.

For this test, the sensor is staying put and I'm waving an LED around so that is point at it, or slightly askew, or farther away.


Here's where I'm at with the code right now. This is my first arduino project so I'm still trying to get the variable scope correct & such.

There still seems to be something fishy when I change the READ_TM and the NUM_BUFFER values around, in showing noise. But I think it's only with the LED so it may be something with the PWM on the LED.

Please post back to let me know how it works for you.

* reads TLS230R module
* uses interrupt for counting pulses
* loop() periodically takes snapshots & sends out
* either as ascii or binary for graphing real-time over serial
* smooths out spikes inherenet in signal
* lights an LED for the sensor to pick up
* author: Oscar Carrillo. Adapted from others' code
* Please see here:
* http://home.teampaulc.org/arduino/tsl230r-to-arduino-interface
* and here:
* http://roamingdrone.wordpress.com/2008/11/13/arduino-and-the-taos-tsl230r-light-sensor-getting-started/
* If put in binary mode, you can see it graphed in Real-Time with this using Processing:
* Realtime Graphing of Accelerometer/Gyroscope Data
* http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1246274143
* portions of digitalSmoothing used:
* http://www.arduino.cc/playground/Main/DigitalSmooth

// setup the TLS230R to Arduion Mega mapping
#define TSL_FREQ_PIN 21 // output use digital pin2 for interrupt
#define TSL_FREQ_INT 2 //pin 18=int5, 21=int2, 2=int0
#define READ_TM 2 // milliseconds between pulse count tabulation on the interrupt
#define NUM_BUFFER 5 // number of samples in the buffer for smoothing function
#define PERCENTILE 15 //cut off for throwing out outliers: bottom/top percentile
#define OUTPUT_MODE 1 //This will output ascii over serial or binary for graphing on Processing (0=ascii, 1=binary)

//I'm not really sure what is the best way to do this
//I want the interrupt to act as a continuous sampling engine
//I want the loop to get a sample (free of spikes) whenever I want
//I don't want to get close to actual temporal resolution of interrupt tabulation periods
//but I don't want to get repeats too often (buffer not updated)

int ledPin = 12; //led pin #
int calcSensitivity; //only for sensor with sensitivity setting (230R)
unsigned long pulseCount = 0; // pulseCount is always in use
unsigned long currentTime = millis();
unsigned long startTime = currentTime;
//I think I need to rethink this hex value for something more appropriate
unsigned int startTag = 0xDEAD;  // Analog port maxes at 1023 so this is a safe termination value
int holder=0; //a dummy value for sending over serial ports on unused signals
unsigned int bufferCounter = 0;
volatile unsigned int buffer[NUM_BUFFER];
unsigned int bufferSnapshot[NUM_BUFFER];
unsigned int nSmoothSample = 0;

// freq will be modified by the interrupt handler so needs to be volatile
// freq holds the latest frequency calculation
unsigned long frequency;
float uWattCm2;
volatile unsigned int curPulseCount;
volatile unsigned int windowCount;
unsigned int windowCountSnapshot;
volatile unsigned int timeElapsed;
volatile unsigned int timeElapsedSample;
unsigned int timeElapsedSnapshot;
unsigned int count=0, i=0;
unsigned int scale;   // holds the TSL scale value, see below
int ledLevel = 0;

void setup() {
 //led stuff, if you aren't powering an LED you don't need this
 pinMode(ledPin, OUTPUT);
 ledLevel = map(120, 0, 500, 0, 255);
 analogWrite(ledPin, ledLevel);

 //initialize arrays
 for (i=0;i<NUM_BUFFER;i++) {
   buffer = 0;
   bufferSnapshot = 0;


 // attach interrupt to pin2, send output pin of TSL235R to arduino 2
 // call handler on each rising pulse
 //something weird may be going on with interrupts with Arduino Mega

 //pin 2, doesn't work on Mega, dunno why
 // attachInterrupt(0, add_pulse, RISING);
 attachInterrupt(2, addPulse, RISING); //interrupt 2, matches pin 21
 scale = 1; // set this to match TSL_S2 and TSL_S3 for 230R, not needed for 235R

void loop() {
 nSmoothSample = getSmoothedSample(); //Get smoothed out sample every go round

 if (OUTPUT_MODE == 0) {
   // this is just for debugging
   // it shows that you can sample freq whenever you need it
   // even though it is calculated 100x a second
   // note that the first time that freq is calculated, it is bogus
   Serial.print("winCount: ");
   Serial.print(" count: " );
   Serial.print(" smooth: ");
   //    Serial.print(" freq: ");
   //    Serial.println(getFrequency(nSmoothSample), DEC);
   //might be possible to figure this out for 235R, but this is intended for 230R
   //Serial.print("\tuW/cm: ");
   //Serial.print(getUwattCm2(), DEC);
 else {
   //This has to start with startTag, and then there must be six values
   //passed for the RTG Processing app to display correctly
   Serial.write( (unsigned byte*)&startTag, 2);
   Serial.write((unsigned byte*)&curPulseCount, 2);
   Serial.write((unsigned byte*)&nSmoothSample, 2);
   //   Serial.write((unsigned byte*)&timeElapsed, 2);
   Serial.write((unsigned byte*)&holder, 2);
   //     Serial.write((unsigned byte*)&windowCount, 2);
   Serial.write((unsigned byte*)&holder, 2);
   Serial.write((unsigned byte*)&holder, 2);
   Serial.write((unsigned byte*)&holder, 2);

 delay((READ_TM * NUM_BUFFER)); //delay for msec btwn tabulations * (# of running samples) + 1 to make sure we don't get a repeat

void addPulse() {
 // DON'T calculate anything or smooth the data every READ_TM ms
 // just store the pulse count to be used outside of the interrupt
 pulseCount++; // increase pulse count
 currentTime = millis();
 timeElapsed = currentTime - startTime;

 //Tabulate pulses if the time elapsed surpasses READ_TM
 if( timeElapsed >= READ_TM ) {
   curPulseCount = pulseCount;  // use curPulseCount for calculating freq/uW
   buffer[bufferCounter] = curPulseCount;

   //increment and roll over if necessary
   if (bufferCounter == (NUM_BUFFER-1)) {
     bufferCounter = 0; // roll over
     windowCount++; //keep track of windows computed
     timeElapsedSample = timeElapsed; //could poll this if you want to see how close this is to READ_TM
   else {
     bufferCounter++; //increment

   pulseCount = 0;
   startTime = millis();

* This gets the frequency (counts/sec)
* getFrequency(unsigned int)
double getFrequency(unsigned int sample) {
 return (sample*(1000.0/timeElapsedSnapshot));

* This returns the irradiance of light based on things
* known about the 230R sensor. Could be adapted to 235R possibly.
long getUwattCm2() {
 // copy pulse counter and multiply.
 // the multiplication is necessary for the current
 // frequency scaling level.
 frequency = curPulseCount * scale;

 // 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) frequency / (float) calcSensitivity;

 // extrapolate into entire cm2 area
 uWattCm2  = uw_cm2  * ( (float) 1 / (float) 0.0136 );


* Gets rid of spikes in buffer and returns a smoothed
* value. It also takes a snapshot of the timeElapsed
* for this particular snapshot of the buffer
* so it can be used elsewhere
unsigned int getSmoothedSample() {
 static unsigned int sorted[NUM_BUFFER];
 unsigned int intAvg=0, mod=0;
 unsigned int j=0, nValid=0, temp=0, top=0, bottom=0, total=0;    
 boolean done;

 //This is probably overkill: copy, copy, sort
 //duplicate samples in the buffer at this point in time
 for (i=0;i<NUM_BUFFER;i++) {
   bufferSnapshot = buffer;

 //copy the value for use within loop
 timeElapsedSnapshot = timeElapsedSample;
 windowCountSnapshot = windowCount;

 //copy the data into yet another array for sorting
 for (i=0;i<NUM_BUFFER;i++) {
   sorted = bufferSnapshot;

 // simple swap sort, sorts numbers from lowest to highest
 for (done=0;done!=1;) {
   done = 1;
   for (j = 0; j < (NUM_BUFFER - 1); j++){
     if (sorted[j] > sorted[j + 1]){     // numbers are out of order - swap
       temp = sorted[j + 1];
       sorted [j+1] =  sorted[j] ;
       sorted [j] = temp;
       done = 0;

 // throw out top and bottom PERCENTILE of samples - limit to throw out at least one from top and bottom
 bottom = max(((NUM_BUFFER * PERCENTILE)  / 100), 1);
 top = min((((NUM_BUFFER * (100-PERCENTILE)) / 100) + 1  ), (NUM_BUFFER - 1));   // the + 1 is to make up for asymmetry caused by integer rounding

 for (j=bottom; j<top; j++){
   total += sorted[j];  // total remaining indices
   nValid++; // # of good values

 intAvg = total/nValid;
 mod = total%nValid;

 //this is simulating floating point math, a bit more accurate than just rounding
 //takes the divisor, multiples the modulus by # of samples and divides it by how
 //many samples it is based on
 return ((intAvg * NUM_BUFFER) + ((mod * NUM_BUFFER)/(nValid)));

Go Up