Dealing with linear Sensor Arrays is not easy. After some research in the web, found some experiments with TSL1401, TSL202R and TSL201R, many of them without a happy ending.
My intent is to find out how to do simple image processing of Linear Sensor Arrays using the Arduino.
This will open the path to develop some interesting experiments and applications:
-Line Follower Robots.
-Optical Flow Sensor (to sense movement in robots and autonomous vehicles).
-Laser Range Finders.
-Imagen Scanners (Barcode, character recognition etc.)
-Position Sensors.
-Objet Detection and Objet Counting.
-Objet Shape Identification and Analysis.
-Light Spectrum Analyzers.
-Etc…
After reading the (64×1) linear Sensor Arrays TSL201R manual…
…decided to buy some DIP-8 versions (package discontinued) in Aliexpress.com, (breadboard friendly).
AMS produces the SMT version, The TSL201CL.
This is the wiring diagram:
Arduino NANO, solid wires (AWG#22) and an adequate breadboard for the job.
The Arduino is relatively slow to scan and read this sensor. To solve this limitation the integration time is increased by reducing the incoming light. A pinhole approach is implemented with a tiny black box (a relay case).
Pinhole cameras using PCB Relay’s cases:
Because the small hole, more integration time is needed.
The projected image field of view can be estimated using geometry…
In the Sketch only one pixel is readed in each scan. The image is completed after 1+64 scans.(first is discarded)
First scan is repeated to permit a complete integration period.
The Pixel integration Period is adjustable using a potentiometer in the analog channel 1.
/*TSL201R_Scanning.ino Arduining 26 MAR 2014 Linear Array Sensor TSL201R (TAOS) is now AMS: -64 x 1 Linear Sensor Array 200 DPI -Sketch used with the Arduino NANO. Data is Send at 115200 bauds (ASCII messages ending with CR and LF) One frame (64x1 image) has the followinf format: >5890 Indicates the integration period in microseconds. 843 this value is discarded. 234 value of the pixel 1 242 value of the pixel 2 . . . . 245 value of the pixel 64 -Reading only one pixel in each scan. -The image is completed after 1+64 scans.(first is discarded) -First scan is repeated to permit a complete integration period). -Pixel integration Period is adjustable (potentiometer in analog channel 1). */ #define CLK 2 #define SI 3 #define VOUT 0 //pixel intensity in the analog channel 0 #define INTVAL 1 //integration time adjust in the analog channel 1. #define PIXELS 64 int intDelay; //Integration Period = (intDelay + 535 ) microseconds. int Value; //pixel intensity value. void setup(){ pinMode(CLK, OUTPUT); pinMode(SI, OUTPUT); digitalWrite(CLK, LOW); digitalWrite(SI, LOW); // Serial.begin(9600); Serial.begin(115200); Serial.flush(); } void loop(){ intDelay=1+analogRead(INTVAL)*10; //read integration time from potentiometer. Serial.print(">"); //Mark the start of a new frame Serial.println(intDelay + 535); //Send Integration Period in microseconds. // delay(8); // used with 9600 bauds delay(2); // used with 115200 bauds readPixel(0); //the first reading will be discarded. delayMicroseconds(intDelay); for(int i=0;i<PIXELS;i++){ readPixel(i); delayMicroseconds(intDelay); //Delay added to the integration period. } } //------------------ Send the intensity of the pixel serially ----------------- void readPixel(int pixel){ digitalWrite(CLK, LOW); digitalWrite(SI, HIGH); digitalWrite(CLK, HIGH); digitalWrite(SI, LOW); for(int i=0;i<pixel; i++){ //Clock pulses before pixel reading digitalWrite(CLK, LOW); digitalWrite(CLK, HIGH); //Select next pixel and reset integrator of actual pixel. } Value = analogRead(VOUT); Serial.println(Value); // delay(8); //used with 9600 bauds delay(1); //used with 115200 bauds for(int i=0;i<=(PIXELS-pixel); i++){ //Clock pulses after pixel reading. digitalWrite(CLK, LOW); digitalWrite(CLK, HIGH); //Select next pixel and reset integrator of actual pixel. } }
The application in Progressing shows the illuminance of each pixel sent by the arduino.
Integration time and frames per second (FPS) are also presented.
Here is the Processing Code:
Pro_TSL201RViewer.pde 28 AUG 2015
Processing Viewer for the Linear Array Sensor TSL201R (AMS):
</pre> /*Pro_TSL201RViewer_01.pde Arduning.com 28 AUG 2015 Processing Viewer for the Linear Array Sensor TSL201R (AMS): 64 x 1 sensors (200 DPI) Data is received at 115200 bauds (ASCII messages ending with CR and LF) One frame (64x1 image) has the followinf format: >5890 Indicates the integration period in microseconds. 543 this value must be discarded. 234 value of the pixel 1 242 value of the pixel 2 . . . . 245 value of the pixel 64 Works with the Sketch:TSL201R_Scanning.ino in the Arduino. ------------------------------------------------------------------------------*/ //---------------- Labels ----------------------------------------------------- String heading = "TSL201R Viewer"; String credit = "Arduining.com"; //------------------------- Graph dimensions (constants) ---------------------- //System variables width and height are defined with size(width,height) in setup(). int leftMargin= 80; //from Graph to the left margin of the window int rightMargin= 80; //from Graph to the right margin of the window int topMargin= 70; //from Graph to the top margin of the window. int bottonMargin= 70; //from Graph to the botton margin of the window. //-------------------- Colors ------------------------------------------------- final color frameColor = color(0,0,255); // Main window background color.(Blue) final color graphBack = color(0,0,128); // Graphing window background color. (Dark black) final color borderColor= color(128); // Border of the graphing window.(grey) final color barColor= color(255,255,0); // Vertical bars color. (yellow) final color titleColor= color(255,255,0); // Title color. (yellow) final color textColor= color(200); // Text values color. (grey) //-------------------- Serial port library and variables ---------------------- import processing.serial.*; Serial myPort; boolean COMOPENED = false; // to flag that the COM port is opened. final char LINE_FEED=10; //mark the end of the frame. final int numRecords=3; //Number of data records per frame. String[] vals; //array to receibe data. String dataIn= null; //string received int pixelCount= 64; int lightIntensity; int Integration= 0; //Integration period in milliseconds int timeStamp; // used to calculate frames per second (FPS). int lastTimeStamp; // used to calculate frames per second (FPS). float framePeriod=0; // used to calculate frames per second (FPS). PFont fontArial; PFont boldArial; int graphBase,graphWidth,graphHeight; //-------- Variables to allocate the parameters in "settings.txt" -------------- String comPortName; float Gain= 1; //-------- Default content of "settings.txt" (one parameter per line) ---------- String[] lines = { "Port:COM4", //Serial Port "Gain:1"}; //Gain (data amplification) //------------------------------------------------------------------------------ // setup //------------------------------------------------------------------------------ void setup(){ // String[] fontList = PFont.list(); // println(fontList); // exit(); size(800,600); // setting system variables width and height. FillParams(); //Read "settings.txt" an fill the values. PortManager(); //Deal with the port selection and opening. graphBase= height-bottonMargin; graphHeight= height-bottonMargin-topMargin; graphWidth= width-leftMargin-rightMargin; fontArial = createFont("Arial",12); boldArial = createFont("Arial Bold Italic",12); timeStamp= millis(); lastTimeStamp = timeStamp; background(frameColor); // Main window background color. clearGrahp(); showLabels(); showParams(); // Display variables (usefull for debugging) } //============================================================================== // draw (main loop) //============================================================================== void draw(){ while (myPort.available() > 0) { dataIn = myPort.readStringUntil(LINE_FEED); if (dataIn != null) { print(dataIn); //show dataIn for debugging dataIn= trim(dataIn); //removes spaces, CR and LF. PlotData(); showParams(); } } } //------------------------------------------------------------------------------ //Plot the incoming data in the Graph. //------------------------------------------------------------------------------ void PlotData(){ if (dataIn.charAt(0)=='>'){ Integration= int(dataIn.substring(1,dataIn.length())); // removes '>'. timeStamp= millis(); //calculate period between frames. framePeriod= 1000/float(timeStamp - lastTimeStamp); lastTimeStamp= timeStamp; println(" (" + Integration +")"); // print Integration period. pixelCount=-2; //reset pixel counter (-2 to skip first reading). } else if((pixelCount >= 0) && (pixelCount < 64)){ lightIntensity= int(dataIn)/2; println("light= " + lightIntensity); // ------- Clear the pixel region. ------------------- noStroke(); //no border fill(graphBack); //Dark blue background rect(1+leftMargin +(graphWidth/64)*pixelCount, graphBase, (graphWidth/64)-2, 1-graphHeight); // ------- Draw pixel intensity value as a vertical bar. ------- // strokeWeight(1); // stroke(0); //black border noStroke(); fill(barColor); //Color of the vertical bars rect(1+leftMargin +graphWidth*pixelCount/64, graphBase,(graphWidth/64)-2, -constrain(lightIntensity*Gain, 0, graphHeight-1)); } pixelCount++; //pointer to draw next pixel. println("--------" + pixelCount + "-"); //show pixel for debugging } //------------------------------------------------------------------------------ //Display diferent values for debugging. //------------------------------------------------------------------------------ void showParams(){ noStroke(); //no border fill(frameColor); //Blue background rect(1,height, width-250, 1-bottonMargin); //Clean parameters region. textFont(fontArial,18); textAlign(LEFT,BOTTOM); //Horizontal and vertical alignment. fill(textColor); //---------- Show COM Port ----------------------------- if (COMOPENED){ text(lines[0],20, height-15); //Show the COM port. text("Gain: "+ Gain ,150, height-15); //Show Vertical Gain. text("Integration: " + Integration,270, height-15); //Show Integration Period. text("FPS: "+ nf(framePeriod, 1, 1) ,450, height-15); //Show frames Per Second (FPS) } else { fill(255,0,0); text(lines[0]+" not available",20, height-15); //COMM port not available. } } //------------------------------------------------------------------------------ //Clear graph area. //------------------------------------------------------------------------------ void clearGrahp(){ stroke(borderColor); //Graph border color.(gray) fill(graphBack); //Dark Blue background rect(leftMargin, graphBase,graphWidth,-graphHeight); } //------------------------------------------------------------------------------ //Write the labels. //------------------------------------------------------------------------------ void showLabels(){ textAlign(CENTER,CENTER); textFont(fontArial,36); fill(titleColor); text(heading,width/2,topMargin/2); //Draw graph title. textAlign(RIGHT,BOTTOM); textFont(boldArial,24); fill(textColor); text(credit,width-25,height-15); //Draw credits. } //------------------------------------------------------------------------------ // Look for the values in "settings.txt" and set the variables accordingly. //------------------------------------------------------------------------------ void FillParams(){ String splitLine[]; //-------- If "settings.txt" doesn't exist, create it.------------- if (loadStrings("settings.txt")== null){ saveStrings("settings.txt", lines); //each string is a line. } else{ //Reading "settings.txt" and update variables: lines = loadStrings("settings.txt"); //lines is a string array splitLine = split(lines[0], ':'); //split line[0]. comPortName= splitLine[1]; //the substring after '=' splitLine = split(lines[1], ':'); //split line[1]. Gain= Float.parseFloat(splitLine[1]); //the substring after '=' } //---------- Show the content of "settings.txt" in the the console ------------- println("there are " + lines.length + " lines in \"settings.tx\" file"); for (int i=0; i < lines.length; i++) { println(lines[i]); //Send to console. } saveStrings("settings.txt", lines); //Save the settings. } //------------------------------------------------------------------------------ //Try to open the port specified in "settings.txt". //------------------------------------------------------------------------------ void PortManager(){ //------------- List the available COM ports in this system -------------------- String[] portlist = Serial.list(); //Create a list of available ports. println("There are " + portlist.length + " COM ports:"); println(portlist); //----------- If COM port in "settings.txt" is available, open it ------------- for (int i=0; i < portlist.length; i++) { if(comPortName.equals(portlist[i]) == true){ myPort = new Serial(this, portlist[i], 115200); //open serial port. COMOPENED = true; println(portlist[i]+" opened"); } } //-------- Procedure when COM port is not available -------------------- if(!COMOPENED){ println(comPortName + " is not available"); //Anounce the problem // SelComPort(); //Procedure to select another port... } } <pre>
An example of how to use the sensor: Measure the position of a floating ball.
Another possible application, a laser ranger:
Working to increase the frames per second (FPS)…
(in twitter you can follow my progress before it be posted here ): @arduining
Hi, I awaiting for a new posts in blog from many days… loved this blog.. thanks for awesome posts..
Hello,
I’m trying to do the same montage as you… what are your raw readings ? I’ve doubts about my montage, thanks !
Hello,
what are the example of values you get from your sensor ?
I don’t know how to validate if the sensor is correctly wired.
thanks !
Hello,
I’ve tried the parallax code and adapted to this one, always ones…
http://forums.parallax.com/showthread.php/125594-TSL1401-and-Arduino
The TSL1401 works different than the TSL201.
In the TSL1401 the reset, charge and hold of sensor capacitors occurs simultaneously.
In the TSL201 it occurs sequentially.
potentiometer = ? ohm
10K was used.
Dear friend; Are you possible to share pc software.
processing code please 🙂
Processing code included.
I currently reach 300 FPS with the Arduino & TSL1401R.
Some hints:
digitalWrite is damn slow and will mess up your Exposure Time setting …
// Reset Camera
digitalWriteFast (CLKpin, LOW);
digitalWriteFast (SIpin, HIGH);
delayMicroseconds (1); // required when using port manipulation or fastwrite
digitalWriteFast (CLKpin, HIGH);
digitalWriteFast (SIpin, LOW);
delayMicroseconds (1);
for (i = 0; i < 128; i++) {
digitalWriteFast (CLKpin, LOW);
delayMicroseconds (1);
digitalWriteFast (CLKpin, HIGH);
}
I created a custom optic combined with a 3D printed case.
My main task is a fast lasertriangulation device (0-3m).
Hi Xpeace,
I’m wondering if you have a typo for your 300 fps frame rate statement.
I am using a “micro” which has an effective baud rate of about 100 kbps through the USB connection. Just sending 128 readings in binary (256 characters) takes about 14 ms which is an equivalent rate of just over 70 fps. Add clocking the detector and taking A/D samples, and its hard to imagine frame rates above 30 or 40 fps. Please help me understand.
Thanks,
Scott
Hi All and xpeace,
I am wondering if you have an error in your frame rate number 300 fps?
The reason I ask is this: I have measured the USB serial transfer rate using a Arduino “micro” to be about 100 KBAUD or about 100 us/character. Even sending sensor data in binary as 256 bytes, that’s nearly 26 ms to send the frame. That works out to be under 40 FPS. Add in clocking the detector and ADC conversions, well, you get the idea. Can you explain how you’re getting that high of a rate?
Thanks,
Scott
Oh, and I nearly forgot – you could also enable faster AD readout on the Arduino
#define FASTADC 1
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
void setup(void)
{
.
.
.
.
#if FASTADC // 1MHz ADC
// set prescaler to 16
sbi(ADCSRA,ADPS2);
cbi(ADCSRA,ADPS1);
cbi(ADCSRA,ADPS0);
#endif
.
.
.
.
Serial.begin (115200);
}
Hello are you still there?
I have a TSL1401 with arduino uno.
what is the max readout per second FPS (scanning time)?
I have an application where the frame rate needs to be 1000FPS.
Is this duable?
Thanks in advance.
Hi Roger, I don’t think the Arduino (ATMEGA328) can do that. Also take in consideration the integrating nature of the sensor, it means time and light intensity is needed to reach useful voltage values in the output. The UNO will need to take 128k samples/second (128 sensors x 1000 samples)…
Hi Xpeace, can you give me an example complete for reading the TSL1401R?
Thanks
Thanks xpeace, very useful ideas.
Posting the Processing Code (adjusted to run it in Processing 3.0)
processing code please…
Finally… the Processing code included.
Can u help me out How many 7 segments digits can this sensors scan sucessfully?
Hi rahul, I was planning to read 4 digit 7-segment led display. Because the linear array, a servomotor can be used for scanning. Usually the 7-segments led displays are multiplexed.
The problem is the multiplexing frequency and the scanning frequency implemented with the TSL201. If the reading of the display is stable (like the one in my Radon Monitor) may be possible to integrate the readings to identify the On or Off state of each display segments.
How do I connect oled display
computer software which language c# ? vb.net ?
Processing
What is this Processing ? computer software required please at me
https://processing.org/
I did not know this language, thanks…
&amp;amp;gt –> This section gives an error
Processing file save add github.
It gives error codes. Processing file save add github.
How do you calculate the maximum FPS for the sensor?
The frame rate (FPS) is calculated in the Processing code. Measuring the time betwen ‘>’ incoming characters.
The processing code is compiled together in the Arduino IDE or another ambient?
https://processing.org
In the total absence of light, value is still generated for each pixel around 13. Is this value correct under the conditions described in the post?
For me was dificult to avoid any light reaching the sensor. Because the integration time is relatively large, any minimal light coming from the sides or back of the sensor affects the reading. Special care is necessary to block all sources of unwanted light.
Is the integration period with a value of (intDelay + 535) arbitrary?
Is the integration period with a value of (intDelay + 535) arbitrary?
IntDelay is an adjustable delay to change the integration time (sensitivity), 535 usecs is the minimum integration time (when IntDelay =0).
InDelay can be adjusted using a 10K potentiometer connected to A1.
Hi!
I’m trying to read out a TSL1401R chip. it has 128×1 pixel sensor. The problem is whatever I do, I can read out only 64 data… I’ve modified the arduino code and the processing code to handle 128 lines, but no luck.
It seems that the whole sensor data is covered by these 64 values (left side to right ) The rest of the data seems to be noise with values of 30-40…
I’ve tried different solutions that I found, but always get only 64 lines of valuable data..
I have 2 TSL1401R chips, both behave the same….