IR Sensor ADC trigger, Median Filter, Analysis

Have you thought of how to filter the data coming from an analog sensor?
Why do you even want to filter that data? Should you use arithmetic mean or
median? Why can’t you just grab a sample from Arduino::analogRead() and be
done with it?

Those are the questions I had on my mind. After playing with the data I got
from my infrared (and ultrasonic) sensor, the answers became more obvious.

In this post, I’ll describe:

Problem with sensor measurements

Way to auto-trigger analog data collection

Median and mean filter application

Analysis of sample data

I also touch on why my adventure in using ROS on Arduino is put on hold.

On YouTube

Problem

In the following snapshot, the ladder-like trace, shows a signal from Sharp
10-80 centimetre infrared sensor. I was waving my hand in front of it:

As the trace shows, the sensor spends about 40mS to take a measurement. The
data sheet for the sensor specifies 38.3mS +/- 9.6mS, which is about the same.

If my code reads analog value from the sensor once every 50mS, the reading
may come from one of the spikes. So, when that value is converted into a
distance, the distance representation will not be accurate. As is the case with
the first spike in the image, the sample will represent the distance from two
measurements ago, which is bad.

A common approach to improve the accuracy is to take multiple measurements and
compute the average. But which one, mean or median? Later, I’ll show you the
comparison between all three when measuring the distance to a fixed point.

Auto-Triggering Data Collection

To read an analog value, one can use Arduino’s analogRead() function.
When calling it from a main loop, the CPU is tied up while collecting
the samples necessary to compute the average. This may pose a problem
because other important tasks are put on hold while this read-and-wait
cycle executes:

A better approach is to collect the samples in ADC completion interrupt, which
ADC calls when it completes converting voltage level to a digital value:

1
2
3
4
5
6
7
8
9

ISR(ADC_vect){// ADC conversion is complete, store the ADC value.g_AdcChannels[g_AdcChannelIdx].push(ADC);// Poll the next channel. Make sure the timer for// ADC trigger fires up with greater interval than it takes// to poll all analog channels.pollNextAdcChannel();}

ADC can operate in free-running mode, which is ok if I have a single analog
sensor. With multiple sensors, the timing of reading data from different
sensors can be challenging to code properly. Therefore, I chose to trigger the
ADC using a timer:

volatileboolg_Update=false;voidinitTimers(){// Clear and set timer to CTC mode. When TOP is at OCRnA// use WGMn2, mode 4 (See p.145)TCCR2A=0;TCCR2B=0;TCCR2A|=(1<<WGM21);// Set prescaler 64 (p.185)TCCR2B|=(1<<CS22);OCR2A=250;// Enable interruptTIMSK2|=(1<<OCIE2A);}ISR(TIMER2_COMPA_vect){// This interrupt is called every 1mS. Do actions according// to the plan:// 1. Poll ADC channels every 10mS// 2. Send updates every 50mS//staticuint32_tcounter=0;if((counter%ADC_FREQ)==0){pollNextAdcChannel(0);}// Increment the counter here because there is nothing to// update with at "time" 0.++counter;if((counter%UPDATE_FREQ)==0){g_Update=true;}}

Since IR distance measurement takes 50mS, and I want to average over
5 samples, the timer interrupt routine is configured to initiate ADC
conversion every 10mS (ADC_FREQ = 10).

At this point, I’m set up with a single analog sensor. But when I add more in
the future, the function below will instruct ADC to sample from all configured
sensors in turn:

ROS and Arduino

In my previous post (“Integration of ROS and Arduino”)
I showed how to use ROS to publish “hello world” messages from the Arduino.
My plan was to use ROS in this example as well. The plan hit a snag.

First, the idea was to publish an array of sensor values. It seemed like a
simple task, but to create an array is anything but simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

UInt16MultiArrayg_SensorVals;// The following code may cause "lost sync with device...".// See https://answers.ros.org/question/10988/use-multiarray-in-rosserial/////g_SensorVals.layout.dim_length = 1;g_SensorVals.layout.dim=(std_msgs::MultiArrayDimension*)malloc(sizeof(std_msgs::MultiArrayDimension));g_SensorVals.layout.dim[0].label="";g_SensorVals.layout.dim[0].size=SENSOR_COUNT;g_SensorVals.layout.dim[0].stride=sizeof(uint16_t);g_SensorVals.layout.data_offset=0;g_SensorVals.data_length=SENSOR_COUNT;g_SensorVals.data=(uint16_t*)malloc(sizeof(uint16_t)*SENSOR_COUNT);

The code didn’t work at first go. While waiting for ROS message board to come
out of maintenance, I had time to take a look at the design of ROS2. That
changed my plans of using ROS on Arduino.

In ROS2, the messaging will be replaced with the Data Distribution Service (DDS).
As far as I know, there is no DDS software for AVR hardware.

From my experience, DDS is quite rich in features and provides everything one
could ever want from a middleware. For more capable hardware, DDS is awesome unless I have to pay an arm and a leg for those features.

Another change ROS2 plans to make is to move from catkin build system to
something called “ament”.

With all of that, more time investment in ROS1 is not as appealing. But, I look
forward to all the goodness in ROS2.

You may also like

After connecting an infrared sensor to my Arduino, I noticed that an ultrasonic sensor, which is connected to the same board, started reporting inconsistent measurements. The investigation took me a few hours, but now I know better to pay attention to the power requirements for electronic components.

ROS has good messaging system that uses publisher/subscriber model. My project requires an Arduino to talk to a ROS network. Rosserial for Arduino exists to enable such communication. My previous attempt to use rosserial on Atmega168 was not successful due to 1 kilobyte SRAM limit on the Atmega. This time, I will use Atmega2560 with 8 kilobytes of SRAM.

After spending your time on learning advanced “hello world” tutorial of some third-party
library, and trying to code something more useful, you hit your first problem. Scavenging the
internet for help on seemingly simple issues burns more of your precious time with little
return. You think - there has to be a better way to move your project along.