linearizing sharp IR sensor graph

Hello,

I have a Sharp IR sensor, and as you can see here, the relationship between the voltage produced and the distance sensed is logarithmic: http://www.danshope.com/blog/images/irSensor1.png
I want to make this relationship linear, i.e., the further the object is, the lower the voltage.
I've been looking at this page here: Linearizing Sharp Ranger Data | Acroname
I've also been playing around with code and wiring, trying to figure things out, but to no avail thus far. Here is what I have so far. Any suggestions?

int sensorvar = 0;
int led = 9;
int outputValue = 0;
int d = 0;
double linear = 0;

void setup(){
pinMode(led, OUTPUT);
Serial.begin(9600);
}

void loop(){

sensorvar = analogRead(1);
d = map(sensorvar, 0, 1023, 20, 150);
linear = 1 / ( d + 0.42 );
outputValue = map(linear, 20, 150, 0, 255);
analogWrite(led, outputValue);
Serial.print("sensor value:");
Serial.println(linear);

delay(500);

}

I wrote a library just to handle this: - Arduino Playground - MultiMap -

my sample code - you need to update the arrays used as these are 20-150 cm, I callibrated the arrays with a measure stick (steps of 10 cm) you might do in in steps of 1 cm. but they don't need to be equidistant

// 
//    FILE: SharpDistanceSensor
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.01
// PURPOSE: Demo SHARP 2Y0A02 F 9Y - distance sensor 
//
// HISTORY: 
// 0.1.00 - 2011-01-22 initial version
// 0.1.01 - 2011-01-24 improved multimap

// Released to the public domain
//
#define RED    4
#define GREEN  5

void setup()
{
  Serial.begin(19200);
  pinMode(RED, OUTPUT);
  pinMode(GREEN, OUTPUT);
  analogReference(EXTERNAL);
  
  Serial.println("Demo SHARP 2Y0A02 F 9Y - distance sensor"); 
  Serial.println();  
  Serial.println("TIME\tRAW\tCM");
  Serial.println();
}

void loop()
{
  long raw = sharpRawAvg();
  int mm = sharp2mm(raw);

  Serial.print(millis());
  Serial.print("\t");
  Serial.print(raw);
  Serial.print("\t");
  Serial.print(mm);
  Serial.print("\t");
  for (int i=50; i<mm;i+=10) Serial.print("]");
  
  if (mm > 750) digitalWrite(GREEN, HIGH);
  else digitalWrite(GREEN, LOW);
  if (mm < 850) digitalWrite(RED, HIGH);
  else digitalWrite(RED, LOW);
//  if (cm < 40) 
//  {
//    digitalWrite(RED, HIGH);
//    delay(cm/2);
//    digitalWrite(RED, LOW);
//    delay(cm/2);
//  }
  Serial.println();
}

///////////////////////////////////////////////////////////////////////
//
// SAMPLE FUNCTIONS
// 

// multisample version
// as the sensor is not very steady in its reading 
// doing 128 samples smooths the reading.
unsigned int sharpRawAvg()
{
  unsigned long raw = 0;
  for (byte i=0; i<32; i++) 
  {
    raw += analogRead(A0);
    //delay(1);
  }
  return raw/32;
}

// running average version
// use of 1024 == 10 bit shift 
//    + configurable ~1/1000
//    + no floats
unsigned int sharpRawRA()
{
  static unsigned long value = 0;
  unsigned int a = 950;
  value = (a * value + (1024-a) * analogRead(A0)) /1024;
  return (unsigned int) value;
}


/*
TABLE OF MEASUREMENTS
CM    RAW      DELTA
150   91	91 
140   97	6 
130  105	8 
120  113	8
110  124	9
100  135	11
90   147	12
80   164	17
70   185	21
60   218	33
50   255	37
40   317	62
30   408	91
20   506	98
*/


int sharp2mm(int val)
{
  int out[] = {1500,1400,1300,1200,1100,1000,900,800,700,600,500,400,300,200};
  
  // calibration 17-01
  // internal reference 5,00 Volt
  // factor 1.0
  // range = 416
  int in1[]  = { 90, 97,105,113,124,134,147,164,185,218,255,317,408,506};
  
  // (quick) calibration 24-01 (colder?)
  // internal reference 5,00 Volt
  // factor 1.0
  // range = 396
  int in2[] = { 87, 93,100,108,116,126,139,157,177,197,231,304,384,483};
  
  // (quick) calibration 24-01 
  // external reference of ~3,07 V
  // factor 1,6243 * _in2[]
  // range = 644
  int in3[] = {141,151,162,178,193,205,229,256,287,320,375,494,624,785};
  
  return multiMap(val, in3, out, 14);
}

///////////////////////////////////////////////////////////////////////
//
// MULTILMAP FUNCTION
// 
int multiMap(int val, int* _in, int* _out, int size)
{
  val = constrain(val, _in[0], _in[size-1]);
  
  // handle first value separately
  if (val == _in[0]) return _out[0];
  // search right interval
  int pos = 0;
  while(val > _in[pos] && pos < size) pos++;
  // interpolate
  return map(val, _in[pos-1], _in[pos], _out[pos-1], _out[pos]);
}

int sharp2cm2(int val)
{
  val = constrain(val, 90, 506);
  int _in[]  = {0,  90, 97,105,113,124,134,147,164,185,218,255,317,408,506};
  int _out[] = {0, 150,140,130,120,110,100, 90, 80, 70, 60, 50, 40, 30, 20};
  int size = 15;
  int pos = 0;
  
  while(val > _in[pos] && pos < size) pos++;

  return map(val, _in[pos-1], _in[pos], _out[pos-1], _out[pos]);
}

Just for completeness: that's not a logarithmic relationship, its a reciprocal one.

Just for completeness: that's not a logarithmic relationship, its a reciprocal one.

It is not reciprocal - although the approximation formula is - I put measurements (see previous sketch) in a spreadsheet and let it determine the formula but it allways misfit somewhere in the range. That's why I used multiMap(). And yes, it is an approximation too, but by adding extra points it can decrease the maximum error.

It looks like the maximum value is near 512 which would be a voltage of about 2.5 volta. You could get about 50% more resolution by connecting the ARef pin to 3.3v and selecting analogReference(EXTERNAL). Then the max value would be closer to 775 (1024 * 2.5/3.3).

... just for completeness (;)): the way the Sharp IR-sensor is working actually leads to a reciprocal relationship.

These sensors are based on triangulation, where a tiny IR light spot is "seen" by a position sensitive device from a slightly different perspective. The perspective shift induced by this on the PSD (position sensitive device) is in fact proportional to 1/z, with z being the distance of the object seen. This can be easily derived by drawing the basic geometry of such a setup on a piece of paper and utilizing similar triangles.

Deviations from the reciprocal relationship are due to the simple sensor design. Even so, these devices are much more precise than your usual ultrasound ranger; you can even use them for 3D scanning (I have attached an example of such a scan below this post).

Another remark: even so these sensors are rated at 30mA or so, they draw much more current when "fireing" - this can lead to heavy spikes on the power supply. It's a good idea to use an appropriate capacitor as close to the sensor as possible (see Sharp Infrared Distance Sensor Test Apparatus, Page 3 - Robot Room for example).

3d_scan_04.jpg