Sharp IR - Linearizing Range Output

Hi,

I have a 20-150cm Sharp IR sensor, which I would like to interface to a SVP324. Interfacing was pretty straight forward and time delay between
readings was understood to be 36ms from the data sheet. I used 50ms. What is the best way to linearise the distance calculated with respect to the
analog voltage output. The non-linearity in the data sheet can be broken down into two quadratic curves (i am trying to get this sensor to read 10cm
by using math and simple case statement - to try and achieve 10-150cm detection range). Using two parameterised quatratic curves I believe I can do this.
I really do not want to have two sharp IR sensors ( a 10-80 & a 20-150cm) to do the job. I need analog inputs for other sensors on my robot.

My questions are: Can be the SVP handle floating numbers or decimals - resulting for linearized curve output? The reason I ask:
The LCD.h library did not have a print_decimal function? (C language is not my strong point yet)Can I print say “5.5cm” on the LCD display or do I have to use a integer type cast followed by print_long()? If it has to be converted to integer, does the routine round up or down ?

I have seen the straight line approximation code on Arduino_playground and other websites, but I believe quadratic is more accurate. I am aiming for 0.5cm resolution if possible. The math is easy to do. Is this a realistic expectation from such a sensor?

And lastly, this is a sampling issue for the 3 axis accelerometers you are selling. What is the recommended sampling rate (10 times per second? higher? lower? Currently i have a 50 ms delay between each reading, not including the inherent delay for other tasks like writing to the LCD.

Your advice/feedback would be much appreciated.

Thanks, Carl

First of all, what are you doing with this linearized output? The approach you take is definitely going to depend on your application.

Anyway, yes, the Orangutan can handle 4-byte floats. Link in the math library and you will have access to all of the functions described here. Linking in the math library also includes optimized code that will make normal floating point calculations more efficient, even if you do not use the functions listed in the library documentaiton.

Floating-point routines are going to be relatively slow compared to integer routines on an AVR, so if you care about speed at all you should consider choosing a way to turn your numbers into integers, such as representing 5.34 as 534 - this is called “fixed point” arithmetic.

There are a bunch of rounding routines in the math library, so you can generally do whatever you want, but casting directly to an int without calling a special function will just throw away the fractional part.

For debugging, the easiest way to print a float is to multiply it by 10, 100, 1000, or whatever, depending on your application. It is also pretty easy to do something like this:

print_long(x);
print(".");
print_long(((int)round(x*100))%100);

(That prints 2 digits after the decimal point.) Beyond that, you can use printf with the AVR if you pay attention to the instructions here, which is someitmes nice, but it will make your code a lot bigger. Search for “float” in there to see how to compile in floating point support.

I do not know whether that sensor has any hope of doing 0.5 cm.

As for the accelerometers, the more you sample, the lower your noise will be, so if you have to choose between delaying and sampling, you might as well sample some more. According to the data sheet, the MMA7260QT has a bandwidth of a few hundred Hz - that means it is effectively updating its output hundreds of times per second. Of course, whether that data is interesting to you depends on your application.

-Paul

Carl,

Did you see the section at the bottom of the Sharp distance sensor product page on linearizing the outputs? You talk about representing the curves as quadratics, but the relationship is basically linear between the voltage and the inverse of the distance.

- Ben

Ben,

Yes, I did see that linearising section. And I also found another website whereby they show you how
to calculate the Y=MX+B constants for almost any Sharp IR sensor using those graphs. I then put these data points on an excel spreadsheet and compared
both linear and quadratic forms (fitting a trendline), and found there was a big enough difference to want to consider the quadratic.

A second opinion is always a good though.

I might have to abandom the idea if I found that quadratic was burdening the processor ?
Or perhaps put in all the data points from the curve, and using a interpolate function (lesss MCU intensive) to try and get a better estimate ? feasible?

Is a resolution of 1cm a reasonable expectation?

Any comments would be appreciated.

Carl,

I haven’t tried to do anything that precise with the Sharp distance sensors, so I really can’t give you too much advice, especially because I think the answer will depend a lot on your particular setup. I suggest you just try it out yourself, and initially don’t even worry about linearizing the output. Just display the sensor output as an analog voltage measured by the Orangutan SVP. Move the target by increments of 1cm and see if the voltage changes by a reliable, measurable amount. This test can also give you a sense of how much noise you’ll have to deal with. If you can see increments of 1cm using raw voltage, you should be able to see increments of 1cm in terms of distance if you get the conversion right. Note that if your readings are noisy, you might want to try putting a capacitor (>10uF) between power and ground somewhere close to your sensor.

Please let us know what kind of results you get!

- Ben

Jan,

Thanks for your advice. I will be sure to use print decimal trick. Perhaps I can write a function that could be used for other measurements if I could
ever get them that precise. The more I look into, a 1 inch or 2.5cm resolution with these sharp IRs is realistic.

After some curve fitting exercises on the sensor data, the exponential method was far more accurate and consistent in its output as a function. Its looks promising - but is it MCU intensive ?? (please advise).

voltage(in Volts) = 40 x Dist(cm)^-0.896. (it reads 40 times DistanceMeasured to the power of -0.896). It was only 5% out for values from 25cm to 160cm.

The only function in the <math.h> library that appeared to be the solution was: ldexp() or possibly exp2() ?? Other suggestions?

There was also an inverse graph in the specs sheet,where if you put in voltage, it gave you the distance. Very practical in theory. However, the data points were too close together and cluttered. Is there any way of finding out what the actual plotted values were?? Perhaps the algebraic way might
be the go in the absence of such data.

I am taking Ben’s advice and will measure the raw voltage measured at 1cm intervals to determine whether a change is noticable enough with these sensors(in 10 Bit mode). Will try this for the 20-150 and 10-80cm sensors. A 100MHz analog oscilloscope should provide an idea of how noisy the signal is.
I am assuming you are suggesting a 10uF electrolytic capacitor should be mounted close to the JST connector. Is that right?

Devising a test rig to cater for 1cm increments is going to be interesting while keeping the supply voltage constant!

Cheers.

Carl,

What are you planning to do with the measurements?

I am a bit confused - you first said you want 0.5cm accuracy, yet you seem very happy with a formula that gives you only 5%, which would be 5cm or more. It would be helpful to know what you are using the measurements for!

Are you asking how to compute the inverse of the function y = 40*x^(-0.896)? You can do that using x = pow(y/40,-1/0.896); pow() is a perfectly good function available in the avr math library. You mentioned “exp2”, which is not in the avr math library - did you follow my link or are you looking at something else?

I have no idea how fast pow() is, but I bet that it is going to take a small fraction of the time that will be available to your AVR.

As for your test “rig”, I think that all you need is a ruler and a target for the sensor. What were you thinking you would have to do?

-Paul

Carl,

The closer the capacitor is to the sensor module itself, the better, though if the sensor cable is short, it might be sufficient to put it in a more convenient spot at the end of the cable that doesn’t have the JST connector. I recommend you use your oscilloscope to see what the output looks like with the capacitor not there and in various places; then you can judge for yourself how beneficial it is.

Like paul, I cannot say how slow computing a fractional power will be, but I assume it will be rather processor-intensive. An easy way to find out is to try it and use one of the hardware timers on the AVR to see how long it takes. Alternatively, you can drive an I/O line high right before the calculation and low as soon as the calculation is done and time the resulting pulse with your oscilloscope. You can then determine the percentage of your main loop this duration will represent and from there decide if that percentage is acceptable. It’s possible you’ll have plenty of time to compute powers since the Sharp distance sensor only updates at around 25 Hz, but I’m not sure what kind of other things you’re going to want to do in your main loop that the power computation might delay.

I guess what it comes down to again and again for me is: try things out and then share with us what you find out. You have all the tools there to determine how long various approaches take, how accurately you can determine distance, etc. And if your distance-computation approach doesn’t work, maybe you can create your own emperically derived table relating voltages to distance and interpolate between the points for intermediate values.

- Ben

Ben,

As a contingency thought only for computing power of floating numbers, I have an idea which I would like check its feasibility. And this is just an idea!

I have a retired SV-168 which can potentially be used to assist with computing power. Is it a relatively straight forward exercise to connect the SVP and SV-168 via their serial ports??? What tricks might an unsuspecting hobbyist be up against when attempting this?
Timing issues is the only thing that comes to my mind!

The 3pi library has a master and slave example project files with serial commands for communication. I have not looked at these files in great detail yet, but it seemed possible at least between same platforms (3pi to 3pi).

Regards,
Carl

Carl,

It is fairly easy to connect two Orangutans via their serial ports, and the Pololu AVR library provides functions for serial communication. The master-slave program really is specific to controlling the 3pi, but you could adapt it for your own Orangutan-slave program. There are a few things to consider when communicating between two devices serially:

  1. Come up with a protocol that makes it hard for noise or erroneous bytes from causing the two devices from getting out of sync. For example, you can make the most significant bit of the byte determine if it is a command byte or a data byte, which lets the two devices resynchronize on command bytes. If you have no way of telling apart a command byte from a data byte, a dropped byte could cause the two devices from permanently getting out of sync.

  2. Make sure your main loop on both devices is fast enough to read all bytes that are received at whatever baud rate you’re using, otherwise you’ll lose data and miss commands.

Serial communication isn’t all that fast, relatively speaking, so it really only makes sense if you can offload a truly independent and time-consuming task onto your slave device. It doesn’t make sense if a lot of back-and-forth communication must take place between the two devices in order for the task to be carried out.

- Ben

Ben,

Thanks for the advice. Your explanation and reasoning makes good sense.

Regards,
Carl

Hi,

Is there any reason why the print_long() routine does not print to the LCD on the Orangutan SVP324?

I am using the latest WINAVR and Orangutan Library versions. It is not printing that Sharp IR distance measurement
with the suggested printing of decimals method. eg. “20.5cm”. The screen just goes blank. It works with
print() though. ??? Any help would be appreciated.

Regards,
Carl

Can you post the snippet of code that obtains the sensor value and tries to print it? Note that the print_long() function only works with integer data types (char, short, int, and long), not floats or doubles.

- Ben

Ben,

As per my recent email, please find attached code. It uses/references the autoscale function code, that can be found on
Arduino Playground site.

void read_sharpIR()
{
  	set_analog_mode(MODE_10_BIT);
	
	long int reading1 = analog_read_millivolts(4);
	delay_ms(33);
	long int reading2 = analog_read_millivolts(4);
	delay_ms(33);
	long int reading3 = analog_read_millivolts(4);
	
	IR_dist_measure_mv = (int)((reading1+reading2+reading3/*+reading4+reading5*/)/3);

	int corr_cm[16]=  {20  ,30  ,40  ,50  ,60  ,70 ,80 ,90 ,100,110,120,130,140,150,160,170};
	int corr_mv[16] = {2500,2000,1550,1250,1050,900,800,715,650,600,550,500,475,450,425,400}; 
	
		
	if(IR_dist_measure_mv >= corr_mv[0])
	{
		distance_calculated_in_cm = corr_cm[0]; 
		IR_dist_measure_mv        = corr_mv[0];
	}
	else if((IR_dist_measure_mv >= corr_mv[1]) && (IR_dist_measure_mv <= corr_mv[0]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[1], corr_mv[0],corr_cm[1],corr_cm[0], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[2]) && (IR_dist_measure_mv <= corr_mv[1]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[2], corr_mv[1],corr_cm[2],corr_cm[1], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[3]) && (IR_dist_measure_mv <= corr_mv[2]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[3], corr_mv[3],corr_cm[3],corr_cm[2], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[4]) && (IR_dist_measure_mv <= corr_mv[3]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[4], corr_mv[3],corr_cm[4],corr_cm[3], IR_dist_measure_mv);
	}	   	   
	else if((IR_dist_measure_mv >= corr_mv[5]) && (IR_dist_measure_mv <= corr_mv[4]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[5], corr_mv[4],corr_cm[5],corr_cm[4], IR_dist_measure_mv);
	}	   	   
	else if((IR_dist_measure_mv >= corr_mv[6]) && (IR_dist_measure_mv <= corr_mv[5]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[6], corr_mv[5],corr_cm[6],corr_cm[5], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[7]) && (IR_dist_measure_mv <= corr_mv[6]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[7], corr_mv[6],corr_cm[7],corr_cm[6], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[8]) && (IR_dist_measure_mv <= corr_mv[7]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[8], corr_mv[7],corr_cm[8],corr_cm[7], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[9]) && (IR_dist_measure_mv <= corr_mv[8]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[9], corr_mv[8],corr_cm[9],corr_cm[8], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[10]) && (IR_dist_measure_mv <= corr_mv[9]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[10], corr_mv[9],corr_cm[10],corr_cm[9], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[11]) && (IR_dist_measure_mv <= corr_mv[10]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[11], corr_mv[10],corr_cm[11],corr_cm[10], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[12]) && (IR_dist_measure_mv <= corr_mv[11]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[12], corr_mv[11],corr_cm[12],corr_cm[11], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[13]) && (IR_dist_measure_mv <= corr_mv[12]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[13], corr_mv[12],corr_cm[13],corr_cm[12], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[14]) && (IR_dist_measure_mv <= corr_mv[13]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[14], corr_mv[13],corr_cm[14],corr_cm[13], IR_dist_measure_mv);
	}
	else if((IR_dist_measure_mv >= corr_mv[15]) && (IR_dist_measure_mv <= corr_mv[14]))
	{
		distance_calculated_in_cm = autoscale(corr_mv[15], corr_mv[14],corr_cm[15],corr_cm[14], IR_dist_measure_mv);
	}
	else if(IR_dist_measure_mv <= corr_mv[15])
	{
		distance_calculated_in_cm = corr_cm[15]; 
		IR_dist_measure_mv        = corr_mv[15];
	}		
};

The function works well. It seems to be giving me the 1cm resolution I had hoped for, at least upto 1m range.
I have to do more test for more than one meter. However, the manufacturer curve data is very accurate. This is
confirmed with a laser dot pointer and a tape measure to confirm measurements.
Above the 1 meter mark, you can get an error of 2cm or more. The biggest factor seems to be the angle of the object being detected.
That is, I used a hard cover text book standing at 90ºC, then slightly slanted to the horizontal. This seemed to effect the reading.
I am using a blue and red surface. Although when I test it, I will try with different surfaces. A mirror surface, a black surface, a red and while ceraminc
tile. the laser pointer comes in handy becuase you can tell exactly where the reflection is taking place.

The power(x,y) method in the math.h library took two long to be feasible. Lienar interpolation was the least MCU intensive. After some trial error, I found that sampling every 100ms is about the right rate for the RP5 speed. At 40ms, it is the best for a single reading.

With the code, I have averaged out three readings before conditionally testing the millivolt output. However, 15 conditional test statements
appears inefficient in terms of coding style. Perhaps if you had a look at the code, you may be able to suggest ways to improve it/shorten it.
I am happy to share this code with others. I can provide photos and other relevant data. I am getting upto 170cm (down to 400mV) at the moment.

Is there anyway I can turn off the ADC (ie port A), for the purpose of saving battery power?

Regards,
Carl

Hello Carl,

You can turn of the ADC by clearing the ADEN bit of ADCSRA. I haven’t tested it but something like

ADCSRA &= ~(1<<ADEN); should work.

More details are available on page 258 of the ATmega324 datasheet (~25mb pdf)

- Ryan

Ryan,

Thank you. I will look up the manual and try this.

The reason I ask that is : if you have say 6-8 analog sensors on your MCU all using 10 bit ADCs, that might
drain the battery more quickly than desired. Especially ultrasonics sensors.

Just trying to find a way to save power switch on/off sensors when required.

Carl,

There are several ways to locate an array element without resorting to an if-statement for every possible element in your array. If your array is ordered, you can locate any element in log(N) time (where N is the number of elements in the array) using a binary search. This can save quite a bit of time if you’re dealing with long arrays (for example log2(100,000) = 16.6, which means you can find the element you’re looking for in 16 steps rather than 100,000). For short arrays, it’s usually easiest to just loop through the array until you find what you’re looking for (log2(16) = 4, which means using a binary search might save you at best a few microseconds).

I can show you how to perform a binary search if you want, but I don’t think the improved efficiency is worth it for you and the code is more complicated.

The simplest way is to loop through your array until you find what you’re looking for:

unsigned char i;
if (IR_dist_measure_mv >= corr_mv[0])
{
  distance_calculated_in_cm = corr_cm[0]; 
  IR_dist_measure_mv = corr_mv[0];
}
else if (IR_dist_measure_mv <= corr_mv[15])
{
  distance_calculated_in_cm = corr_cm[15]; 
  IR_dist_measure_mv = corr_mv[15];
}
else
{
  for (i = 1; i < 16; i++)
  {
    if (IR_dist_measure_mv >= corr_mv[i])
    {
      distance_calculated_in_cm = autoscale(corr_mv[i], corr_mv[i-1],corr_cm[i],corr_cm[i-1], IR_dist_measure_mv);
      break;
    }
  }
}

Does this code make sense?

As for your question about turning off the ADC, you can disable the AVR’s ADC module, but I think the power savings will be somewhat minimal since I don’t think the ADC module uses much power. You can disable the ADC module by clearing the ADEN bit of the ADCSRA register:

ADCSRA &= ~(1 << ADEN); // disable ADC module

You will need to set this bit if you want to use the the ADC again:

ADCSRA |= 1 << ADEN; // enable ADC module

You can also save power by using the DIDR0 register to disable the digital input buffers of any PORTA pins that you intend to use only as analog inputs. From the ADC section of the ATmega324PA datasheet:

You can disable the digital inputs of all of the PORTA pins by using the line:

DIDR0 = 0xFF;

I think the main power drain will come from other sources, such as the LCD and your sensors themselves. If you really want to save power, make it possible to turn off the LCD backlight (or avoid using it) and connect your sensors in a way that you have control on whether they are receiving power or not (or, if they have enable pins, use those to put them to sleep when they are not needed). For example, you might want to control power to your sharp distance sensor using a MOSFET gated by an SVP I/O line. In general, if power is an issue, you should read through the ATmega324PA datasheet to see what else you can do to decrease the power used by the MCU itself.

- Ben

Carl,

It sounds like you don’t quite understand how your system is working. The ADC module on the AVR is not powering your sensors, and turning it off will not turn off your sensors. The ADC module is just converting the voltage output of your sensors into digital values that you can then read. The sensors will continue to drain power whether you read their output voltage or not. You can potentially save power by disabling the ADC module, but the power you are saving comes in the form of decreased consumption by the MCU itself, not from the sensors.

- Ben

Ben.

Thank you. Your time in explaining is much appreciated. I will try your code and let you know how it works.

The reason for my asking if the ADC converter can be switched ON /OFF, is the following:
Most sensor data sheets like the Sharp IR provides two current consumption figures - for example 3mA steady state and 15mA
active sensing (wrong terminology but hopefully you get what I mean). My aim was to keep the sensors on, but I thought what if I could
switch on / off the converter instead of the power it save on IO pins. If the ADC converter was not activated, then the sensor
would still be powered in a steady state mode(ie 3mA). And when you call analog_read(), that is when ADC will be requested
to start converting and consume 15mA say. The sharp IR isn’t really the problem because 15mA is not much.

However, when you come sensors such as some ultrasonic sensors that peak at 0.5W (100mA @5V) it may be worth asking the question then,
which was my original aim.

I accept that my understanding may not fully be correct, however I am trying to understand more in depth so that I can make
best use of the MCU and the IO pins available, while keeping minimising battery consumption. Isnt this every robot designers challenge?

Cheers,
Carl

Carl,

Sensors that output an analog voltage operate the same way whether they are connected to a microcontroller or not. The microcontroller isn’t requesting a measurement from the sensor, it is just sampling an output that is there regardless of whether it is sampled or not. Turning off the ADC does not keep the Sharp distance sensor in the steady state (3 mA) mode. It may be possible to use your sonar sensor in a mode that takes readings on command, but this would require a more complicated interaction between your SVP and the sensor. If the sensor is just configured to be in free-run mode and you are obtaining the output as an analog voltage, turning off the ADC won’t do anything to decrease the current draw of the sensor.

I suggest you make sure your LCD backlight is turned off because that can draw quite a bit of power (usually this is negligible compared to the current draw of motors and servos, but it can dominate your power consumption if all you have connected are sensors).

- Ben