EFA | Processing Input Signals: Analog

Simple Threshold

Often the simplest way to work with analog values is to check if the signal has risen above a certain threshold value. This can be done with a simple if statement.

int lightValue;
int threshold = 320;
// trigger variable is used to visualize the output on the plotter
int trigger = 0;

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

void loop() {
  lightValue = analogRead(A0);
  if (lightValue > threshold) {
    digitalWrite(9, HIGH);
    trigger = 1023;
  } else {
    digitalWrite(9, LOW);
    trigger = 0;
  }

  // 0 and 1023 are printed to make sure the plotter doesn't autoscale
  Serial.print(0);
  Serial.print(" ");
  Serial.print(1023);
  Serial.print(" ");
  Serial.print(lightValue);
  Serial.print(" ");
  Serial.print(threshold);
  Serial.print(" ");
  Serial.println(trigger);
  delay(10);
}

Just like we already did with the digital signals, we can also make a small change to the code to have the output triggered only once when the input goes above or below the threshold.

Threshold With Rising Signal

int lightValue;
int prevLightValue;
int threshold = 320
// trigger variable is used to visualize the output on the plotter
int trigger = 0;

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

void loop() {
  lightValue = analogRead(A0);
  if (lightValue > threshold) {
    // check if the previous value was below threshold
    if (prevLightValue < threshold) {
      // do something once (blink LED)
      digitalWrite(9, HIGH);
      delay(100);
      digitalWrite(9, LOW);
    }
  }
  prevLightValue = lightValue;

  // 0 and 1023 are printed to make sure the plotter doesn't autoscale
  Serial.print(0);
  Serial.print(" ");
  Serial.print(1023);
  Serial.print(" ");
  Serial.print(lightValue);
  Serial.print(" ");
  Serial.println(threshold);
  delay(10);
}

Threshold With Falling Signal

int lightValue;
int prevLightValue;
int threshold = 320;
// trigger variable is used to visualize the output on the plotter
int trigger = 0;

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

void loop() {
  lightValue = analogRead(A0);
  if (lightValue < threshold) {
    // check if the previous value was above threshold
    if (prevLightValue > threshold) {
      // do something once
      digitalWrite(9, HIGH);
      delay(100);
      digitalWrite(9, LOW);
    }
  }
  prevLightValue = lightValue;

  // 0 and 1023 are printed to make sure the plotter doesn't autoscale
  Serial.print(0);
  Serial.print(" ");
  Serial.print(1023);
  Serial.print(" ");
  Serial.print(lightValue);
  Serial.print(" ");
  Serial.println(threshold);
  delay(10);
}

Hysteresis

Hysteresis is a phenomenon in which the value of a physical property lags behind changes in the effect causing it, as for instance when magnetic induction lags behind the magnetizing force. In electronics this could mean the lag between an input value and a corresponding output.

Sometimes you might want to try to minimize hysteresis in your circuits to get rid of any lag. However, when working with interactive systems and sensors (inputs) you often want to add hysteresis in your system to prevent unwanted flickering between two different states due to noisy signals. We saw this happening in the examples above when using just a simple threshold.

You can add hysteresis to your input signal by using two threshold values:

  • High threshold – the input value needs to go above this value to turn on the output
  • Low threshold – the input value needs to go below this value to turn off the output
int lightValue;
int hThreshold = 320;
int lThreshold = 250;
int trigger = 0;

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

void loop() {
  lightValue = analogRead(A0);
  if(lightValue > hThreshold){
    trigger = 1023;
    digitalWrite(9,HIGH);
  }else if(lightValue < lThreshold){
    trigger = 0;
    digitalWrite(9,LOW);
  }
  
  // 0 and 1023 are printed to make sure the plotter doesn't autoscale
  Serial.print(0);
  Serial.print(" ");
  Serial.print(1023);
  Serial.print(" ");
  Serial.print(lightValue);
  Serial.print(" ");
  Serial.print(hThreshold);
  Serial.print(" ");
  Serial.print(lThreshold);
  Serial.print(" ");
  Serial.println(trigger);
  delay(10);
}

Mapping

Arduino has a a very useful map() function that might be familiar to you from Processing.

int sensorValue;
int mappedValue;

sensorValue = analogRead(A0);
mappedValue = map(sensorValue, 0, 1023, 0, 255);

Constrain

The map() function only scales the values from one range to another, it does not clamp or constrain the values to the range that you have set. Fortunately, there is also constrain() that we can use. Just add it after your map() like in the example below.

int sensorValue;
int mappedValue;

sensorValue = analogRead(A0);
mappedValue = map(sensorValue, 0, 1023, 0, 255);
mappedValue = constrain(mappedValue, 0, 255);

Filtering and Smoothing Values

Quite often the readings you get from sensors or other inputs are noisy. The noise might be due to electrical noise in the circuit, faulty sensors, or just from how the physical world works (shaky hands, unstable movements, wind etc.)

Average

A simple way to filter out noise is to take multiple readings and take an average of them.

Add together a number of measurements and then divide the total by the number of measurements you added together. Here is a simple example.

int lightValue;
int sum;
int readings = 16;
int averageValue;

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

void loop() {
  for (int i = 0; i < readings; i++) {
    lightValue = analogRead(A0);
    sum += lightValue;
    delay(1);
  }
  averageValue = sum / readings;
  Serial.println(averageValue);
}

Moving Average

The normal average makes the program somewhat unresponsive and slow as it has to read through many values in the same loop(). A moving average speeds up the reading as it only reads one value to an array each loop.

There is an Arduino tutorial showing how to do this: https://www.arduino.cc/en/Tutorial/BuiltInExamples/Smoothing

Exponential Moving Average

The orange line shows the filtered signal, green is the raw values from analogRead()

Exponential moving average (or weighted average) reduces memory usage since it only stores the previous smoothed value in memory. However, it requires the use of floats which increases memory use.

It also has the advantage that it can react to new changes more quickly.

This is based on this algorithm:

smoothedValue = weight * sensorValue + (1 - weight) * prevSmoothedValue;

Here is an example of how to use it in your code:

int lightValue;
int threshold = 320;
// trigger variable is used to visualize the output on the plotter
int trigger = 0;

float smoothedValue = 0.0;
float prevSmoothedValue = 0.0;
// weight is the smoothing factor, in range [0,1].
// Higher the value - less smoothing (higher the latest reading impact)
float weight = 0.1; 

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

void loop() {
  lightValue = analogRead(A0);
  smoothedValue = filter(lightValue, weight, prevSmoothedValue);
  prevSmoothedValue = smoothedValue;
  if (smoothedValue > threshold) {
    digitalWrite(9, HIGH);
    trigger = 1023;
  } else {
    digitalWrite(9, LOW);
    trigger = 0;
  }

  // 0 and 1023 are printed to make sure the plotter doesn't autoscale
  Serial.print(0);
  Serial.print(" ");
  Serial.print(1023);
  Serial.print(" ");
  Serial.print(lightValue);
  Serial.print(" ");
  Serial.print(smoothedValue);
  Serial.print(" ");
  Serial.print(threshold);
  Serial.print(" ");
  Serial.println(trigger);
  delay(10);
}

float filter (float rawValue, float w, float prevValue) {
  float result = w * rawValue + (1.0 - w) * prevValue;
  return result;
}