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 :
/*Pro_TSL201RViewer.pde 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 Wiewer"; 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(180); // 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 font; 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(){ 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; font = createFont("Arial",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(font,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(font,36); fill(titleColor); text(heading,width/2,topMargin/2); //Draw graph title. textAlign(RIGHT,BOTTOM); textFont(font,24); fill(200); 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... } }
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)…
Will be continued…
(in twitter you can follow my progress before it be posted here ): @arduining
