We have previously detailed how to read in an LM34 temperature sensor, an ultrasonic rangefinder, and a light color sensor. This guide will show how to interface with a TSL230 light-to-frequency converter.
First, we’ll need to wire it up. Here is a diagram of our setup:
This looks something like this when breadboarded:
Note that we’ve used a 0.1 microfarad capacitor to decouple the power supply lines and reduce noise introduced by the power supply. The LM34 temperature sensor was easy to read because it is an analog sensor whose voltage varies linearly with temperature . The TSL230 is more tricky to read because of its output behavior. It outputs a square wave with a frequency proportional to irradiance. The greater the irradiance, the higher the frequency.
Here we can see a functional diagram of the TSL230 (from the datasheet):
S0 and S1 are pins on the TSL230 that control the sensitivity of the TSL230. Pins S2 and S3 control a divide-by factor for the output. Here we can see how frequency varies with irradiance (also from the datasheet):
In this guide, we will not consider responsivity varying as a function of wavelength, and we will assume a monochromic 640 [nm] light source. A calibrated source with a known spectral power distribution along with the spectral responsivity for this particular sensor would be needed to calibrate it. The expected responsivity curve for a TSL230 looks like this: (also from the datasheet):
This curve is similar to any silicon-based detector. As this guide focuses on interfacing the TSL230 to Sensorpedia and not on data acquisition, calibration of the will be left as an exercise to the reader.
To do more logic-intensive operations like initialization of the sensor and unit adjustments, we will need to program the Arduino. Since the TSL230 outputs a square wave with frequency proportional to the intensity of incident light, we will need to find a way to read this frequency. From the Arduino’s perspective, this square wave looks like a series of binary digits, with +5VDC high being 1, and 0VDC low being 0. By counting the number of high states in a known time period, we can find the frequency.
The Arduino supports setting interrupts, so we will be using an interrupt-driven program. As each pulse from the TSL230 comes in, the program running on the Arduino will halt and a function specific to our interrupt trigger (in this case, the rising edge of each pulse) will fire, incrementing a pulse count. After the interrupt function has finished, the program continues on as it normally would. We will use a timer to output the pulse count at a known time interval, in our case every 1000 [ms].
Knowing the pulse count, we can use the known scaling factor to convert this into a frequency, which can then in turn be converted into values with useful units attached, watts/meter^2.
Here we see the code for the Arduino.
// Pin Mappings // ______________________________ // |__TSL230_Pin__|__Arduino_Pin__| // | 6 | 2 | // | 1 | 7 | // | 2 | 6 | // | 7 | 4 | // | 8 | 5 | // ------------------------------ #define TSL_OUTPUT_PIN 2 #define TSL_S0 8 #define TSL_S1 6 #define TSL_S2 4 #define TSL_S3 5 #define SAMPLE_DURATION 1000 //1 second is 1000 ms unsigned long pulseCount = 0; unsigned long currentSampleTime = millis(); unsigned long previousSampleTime = currentSampleTime; unsigned int sampleTimeDifference = 0; unsigned int tsl230scalingFactor = 1; unsigned int tsl230sensitivity = 1; void setup() { Serial.begin(9600);//start serial communication attachInterrupt(0, incrementPulseCount, RISING);// add an intterupt on pin 2, trigger on rising pulse (0->5V rising edge) pinMode(TSL_OUTPUT_PIN, INPUT); pinMode(TSL_S2, OUTPUT); pinMode(TSL_S3, OUTPUT); pinMode(TSL_S0, OUTPUT); pinMode(TSL_S1, OUTPUT); // Set sensitivity and scaling factor setTsl230Sensitivity(1); //valid values are 0,1,10,100 setTsl230Scaling(10); //valid values are 1,2,10,100 } void setTsl230Sensitivity(unsigned int sensitivity){ // Selectable Options: Sensitivity // _____________________________ // |__S1__|__S0__|__Sensitivity__| // | L | L | Power down | // | L | H | 1x | // | H | L | 10x | // | H | H | 100x | // ----------------------------- //set initial state, also serves as a defualt if the parameters are out of range digitalWrite(TSL_S0, HIGH); digitalWrite(TSL_S1, LOW); tsl230sensitivity = 1; switch(sensitivity){ case 0: digitalWrite(TSL_S0, LOW); //this is the powered-down state digitalWrite(TSL_S1, LOW); tsl230sensitivity = 0; break; case 1: digitalWrite(TSL_S0, HIGH); digitalWrite(TSL_S1, LOW); tsl230sensitivity = 1; break; case 10: digitalWrite(TSL_S0, LOW); digitalWrite(TSL_S1, HIGH); tsl230sensitivity = 10; break; case 100: digitalWrite(TSL_S0, HIGH); digitalWrite(TSL_S1, HIGH); tsl230sensitivity = 100; break; } } void setTsl230Scaling(unsigned int scaling){ // Selectable Options: Scaling // _________________________________________ // |__S3__|__S2__|__f_O_Scaling_(divide_by)__| // | L | L | 1 | // | L | H | 2 | // | H | L | 10 | // | H | H | 100 | // ----------------------------------------- //set initial state, also serves as a defualt if the parameters are out of range digitalWrite(TSL_S2, LOW); digitalWrite(TSL_S3, LOW); tsl230scalingFactor = 1; switch(scaling){ case 1: digitalWrite(TSL_S2, LOW); digitalWrite(TSL_S3, LOW); tsl230scalingFactor = 1; break; case 2: digitalWrite(TSL_S2, HIGH); digitalWrite(TSL_S3, LOW); tsl230scalingFactor = 2; break; case 10: digitalWrite(TSL_S2, LOW); digitalWrite(TSL_S3, HIGH); tsl230scalingFactor = 10; break; case 100: digitalWrite(TSL_S2, HIGH); digitalWrite(TSL_S3, HIGH); tsl230scalingFactor = 100; break; } } void loop() { // output the current irradiance every SAMPLE_PERIOD ms previousSampleTime = currentSampleTime; //set the previous time value currentSampleTime = millis(); //get the current time value if( currentSampleTime > previousSampleTime ) { // calculate how much time has passed. if the current time value is later longer than the last one, add the add the difference sampleTimeDifference += currentSampleTime - previousSampleTime; } else if( currentSampleTime < previousSampleTime ) { //otherwise, it has rolled over, so subtract from the maximum sampleTimeDifference += ( currentSampleTime + ( 34359737 - previousSampleTime )); //since millis() rolls over after ~9.5 hours, 34359737 } if( sampleTimeDifference >= SAMPLE_DURATION ) {// if SAMPLE_DURATION ms have passed, output a reading sampleTimeDifference = 0; // re-set the sample time counter unsigned long frequency = getTsl230Frequency(); // get the current frequency reading int irradiance = calculateWattsPerMeterSquared(frequency); //calculate irradiance Serial.println(irradiance); //output irradiance } } void incrementPulseCount() { pulseCount++;// increment the pulse count return; } unsigned long getTsl230Frequency() { unsigned long frequencyValue = pulseCount * tsl230scalingFactor;// copy the pulse count value and multiply by the scaling factor. pulseCount = 0;// re-set pulse counter return(frequencyValue); //return frequency value } float calculateWattsPerMeterSquared(unsigned long frequency) { // return approx irradiance, assume monochromatic 640 [nm] source as per datasheet graph of Output Frequency vs Irradiance float wattsPerMeterSquared = ( (float) frequency / ((float) 10 * (float)tsl230sensitivity)) * 0.01; //equation as per the datasheet, factor of 0.01 to go from uW/cm^2 to W/m^2 return(wattsPerMeterSquared); }
We will be adapting the Atom server from our last guide to serve an Atom feed of data from our TSL230:
//We'll need a few things using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using System.IO; using System.IO.Ports; using System.Net; using System.Xml; using System.Xml.Serialization; using System.Runtime.InteropServices; using System.Threading; using WebFeeds; using WebFeeds.Feeds; using WebFeeds.Feeds.Atom; namespace LightSensorAtomServer{ class MainProgram { // Create the serial port with basic settings private SerialPort port = new SerialPort("COM17", 9600, Parity.None, 8, StopBits.One); //the serial port we will use to read in data. note that the COM port must be that which our data is coming in on. something like COM5 for windows, or /dev/ttyUSB0 for POSIX static void Main(string[] args){ new MainProgram(); //instantiate the program } MainProgram(){ SerialPortStart(); //initialize the serial port if (port.IsOpen) startServer(); //start up our HTTP server, which will in turn be responsible for calling methods to read in data, generate an Atom feed, and serve it else Console.WriteLine("An open serial port is needed to read data. Make sure the port settings are correct."); } public void startServer(){ HttpListener listener = new HttpListener(); //create a new HTTP listener listener.Prefixes.Add("http://*:80/"); //make it listen to all requests on the standard web port, 80 listener.Start(); //start listening Console.WriteLine("Listening..."); for (; ; ) //run the server forever { HttpListenerContext context = listener.GetContext(); //gives us access to information about requests new Thread(new Worker(context, writeFeed(ReadData())).ProcessRequest).Start(); //make a new thread to respond, pass it the context AND the feed we created. Note that we are reading in data and then writing a feed. Effect: We wait for the latest data before serving } } class Worker{ private HttpListenerContext context; //a ListenerContext local to this class and thus thread private byte[] byteFeedArray; //this will hold our feed output as a byte array public Worker(HttpListenerContext context, byte[] byteFeedArray){//set our instance variables according to the parameters passed in this.context = context; //set our HTTPListenerContext this.byteFeedArray = byteFeedArray; //set our data feed to the output generated above } public void ProcessRequest(){ //answer our web request string msg = context.Request.HttpMethod + " " + context.Request.Url; Console.WriteLine(msg); //output the request to the console so we can spot strange requests if (context.Request.HttpMethod == "GET") { //if they are requesting the data with a GET request context.Response.ContentType = "application/xhtml+xml"; //since we are outputting Atom, set the MIME type of the data output to XML context.Response.ContentLength64 = byteFeedArray.Length; //set our content length context.Response.OutputStream.Write(byteFeedArray, 0, byteFeedArray.Length); //write out our entire feed byte array context.Response.OutputStream.Close(); //close the request, we're done serving to this request } else {} //we're not allowing other HTTP verbs } } public byte[] writeFeed(string data) { //this function is responsible for actually creating and outputting the byte array of our feed from data and stored variables byte[] byteFeedArray; //a byte array to store our finished feed array int streamPosition; //We will store the output of WebFeeds in a MemoryStream, and use this streamPosition variable to help us keep track of where we are in reading it out AtomFeed sensorFeed = new AtomFeed(); //This AtomFeed object is the real deal. It will be the one serialized and outputted to our final byte array string feedTitle = "TSL230 Irradiance Data Feed"; //set the title for our feed string feedSubtitle = "A series of irradiance values [W/m^2]."; //set the subtitle for our feed string feedAuthorName = "Chris Tomkins-Tinch"; //set the feed author name string feedAuthorUri = "http://www.christomkintinch.com";//set the feed author uri double feedLat = 35.9302; //set the feed lat double feedLon = -84.3112; //...and lon string entryTitle = "Irradiance Data"; //set the entry title (we'll only have one for now) string entryContent = data; //set the data to that passed in as a parameter double entryLat = 35.9302; //set the entry lat double entryLon = -84.3112;//...and lon sensorFeed = generateFeed(System.Guid.NewGuid().ToString(), feedTitle, feedSubtitle, feedAuthorName, feedAuthorUri, feedLat.ToString(), feedLon.ToString());//pass all of the above local variables to the function that will output a new AtomFeed with all of the needed metadata, set our local sensorFeed to this output XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();//make a Serializer so we can add some namespaces to our feed namespaces.Add("georss", "http://www.georss.org/georss"); //and the georss namespace sensorFeed.AddNamespaces(namespaces); //add these namespaces to the sensor feed sensorFeed.Entries.Add(generateEntry(System.Guid.NewGuid().ToString(), entryTitle, entryContent, entryLat.ToString(), entryLon.ToString()));//pass all of the above local variables to the function that will output a new AtomEntry with all of the needed elements and added to the feed we made above MemoryStream memoryStream = new MemoryStream(); //make a new MemoryStream to hold the output of the XML Serializer FeedSerializer.SerializeXml(sensorFeed, memoryStream, null, true); //serialize our AtomFeed (sensorFeed) and put the output into our memory stream memoryStream.Seek(0, SeekOrigin.Begin);// prepare to start navigating and outputting from our memory stream. set the position to the beginning of the stream. byteFeedArray = new byte[memoryStream.Length]; //initialize our feed byte array to be the same length as our memory stream streamPosition = memoryStream.Read(byteFeedArray, 0, 20);// Read the first 20 bytes from the stream. while (streamPosition < memoryStream.Length) {// Read the remaining bytes, byte by byte. byteFeedArray[streamPosition++] = Convert.ToByte(memoryStream.ReadByte()); } return byteFeedArray; //output the final byte array. this contains the serialized xml of our AtomFeed which has been converted from its memory stream location to a byte array. now we can serve it. } public AtomFeed generateFeed(string feedUuid, string feedTitle, string feedSubtitle, string feedAuthorName, string feedAuthorUri, string feedLat, string feedLon){ AtomFeed feed = new AtomFeed(); //an AtomFeed from the JSONFX WebFeeds lib XmlDocument xmlDoc = new XmlDocument(); //an XmlDocument so we can make XML elements feed.Updated = new AtomDate(DateTime.UtcNow); //set the feed updated time to the current time feed.Title = feedTitle; //set the feed title to that specified by the parameter feed.ID = feedUuid; //set the feed universally unique identifier to that specified by the parameter AtomPerson author = new AtomPerson(); //make a new AtomPerson construct author.Name = feedAuthorName; //add the author name specified in the parameter to the author construct author.Uri = feedAuthorUri; //add the author URL specified in the parameter to the author construct feed.Authors.Add(author); //add the author construct to the feed. this will generate the author element and child elements of name, url, etc. AtomLink feedLink = new AtomLink("http://www.example.com/sensor/feed/url");//add a link construct to our feed. this represents where we can go to retrieve this feed feed.Links.Add(feedLink); //add the feed link to our feed feed.Categories.Add(new AtomCategory("light")); //add a category construct feed.Categories.Add(new AtomCategory("irradiance")); // and another feed.Categories.Add(new AtomCategory("arduino"));// and one more XmlElement georssPoint = xmlDoc.CreateElement("georss", "point", "http://www.georss.org/georss"); //make a new XML element using the XmlDocument we defined above. specify its namespace as georss, and add the georss namespace to that link. WebFeeds will take care of consolidating like namespaces and adding them to the feed level of the outputted document georssPoint.InnerText = feedLat + " " + feedLon; //make a space delimited string to represent our location, per the GeoRSS simple spec feed.ElementExtensions.Add(georssPoint); //add our newly minted GeoRSS XmlElement to our feed return feed; //return our feed. we aren't done with it yet, but now we have added all of the feed-level metadata } public AtomEntry generateEntry(string entryUuid, string entryTitle, string entryContent, string entryLat, string entryLon) {//Make and return a simple Atom entry AtomEntry entry = new AtomEntry(); //an AtomEntry from the JSONFX WebFeeds lib XmlDocument xmlDoc = new XmlDocument(); //an XmlDocument so we can make XML elements entry.Updated = new AtomDate(DateTime.UtcNow); //set the entry updated time to the current time entry.Title = entryTitle;//set the entry title to that specified by the parameter entry.ID = entryUuid;//set the feed universally unique identifier to that specified by the parameter entry.Content = entryContent;//set the feed content to that specified by the parameter, in this case our data XmlElement georssPoint = xmlDoc.CreateElement("georss", "point", "http://www.georss.org/georss");//make a new XML element using the XmlDocument we defined above. specify its namespace as georss, and add the georss namespace to that link. WebFeeds will take care of consolidating like namespaces and adding them to the feed level of the outputted document georssPoint.InnerText = entryLat + " " + entryLon;//make a space delimited string to represent our location, per the GeoRSS simple spec entry.ElementExtensions.Add(georssPoint);//add our newly minted GeoRSS XmlElement to our entry return entry;//return our entry object. now it can be added to a feed } public void SerialPortStart() { try{ port.Open();//begin communication } catch (Exception e){ Console.WriteLine(e.Message); throw; } } public string ReadData() { char tempChar; //a local byte variable to hold output from the serial buffer string serialOutputString = ""; //a local variable to hold the string we will buid from the serial output port.DiscardInBuffer();//wipe the serial buffer so we know this is a new reading while (serialOutputString.IndexOf("n") == -1){ //wait for an end-of-line character, so we know our next reading is complete tempChar = (char)port.ReadChar(); //read in a char serialOutputString = serialOutputString + tempChar; //and add the byte to our string Console.WriteLine(serialOutputString); } serialOutputString = port.ReadLine(); serialOutputString = serialOutputString.Replace("n", "").Replace("r", ""); //once we're here we have reached an end-of-line and are now our of the loop. strip the newline characters Console.WriteLine(serialOutputString); //write the values to the console since they're nice to see. return serialOutputString; //return the serial output. we're finished reading from the serial port for now } } }
This is the resulting XML:
<?xml version="1.0" encoding="utf-8"?> <feed xmlns:georss="http://www.georss.org/georss" xmlns="http://www.w3.org/2005/Atom"> <georss:point>35.9302 -84.3112</georss:point> <author> <name>Chris Tomkins-Tinch</name> <uri>http://www.christomkintinch.com/</uri> </author> <category term="light" /> <category term="irradiance" /> <category term="arduino" /> <link href="http://www.example.com/sensor/feed/url" /> <id>afa7c0c7-0baa-4899-a507-dc5c4569b2f1</id> <title>TSL230 Irradiance Data Feed</title> <updated>2009-02-26T00:09:58Z</updated> <entry> <georss:point>35.9302 -84.3112</georss:point> <id>7f815f6e-7ac7-4ebd-9a53-2aaaa892732d</id> <title>Irradiance Data</title> <updated>2009-02-26T00:09:58Z</updated> <content>12</content> </entry> </feed>














March 11th, 2009 at 12:28 am
Thank you for your good teaching.
March 23rd, 2009 at 9:56 am
[...] Interface an irradiance sensor [...]
June 19th, 2009 at 9:42 am
[...] How To: Interface an irradiance sensor with Sensorpedia [...]