Lab: Sensor Change Detection

Introduction

Microcontrollers can sense what’s going on in the physical world using digital and analog sensors, but a single sensor reading doesn’t tell you much. In order to tell when something significant happens, you need to know when that reading changes. For example, when a digital input changes from LOW to HIGH or the reverse, you can tell that a person closed or opened  a switch. When a force-sensing resistor reaches a peak reading, you know that something has hit the sensor. In this lab, you’ll learn how to program your microcontroller to look for three common changes in sensor readings that give you information about events in the physical world: state change detection on digital sensors, and threshold crossing and peak detection on analog sensors. You’ll use these three techniques all the time when you’re designing to read users’ actions.

What You’ll Need to Know

To get the most out of this lab, you should be familiar with the following concepts. You can check how to do so in the links below:

Things You’ll Need

A short solderless breadboard with two rows of holes along each side. There are no components mounted on the board. The board is oriented sideways so that the long rows of holes are on the top and bottom of the image.
A short solderless breadboard.
Three 22AWG solid core hookup wires. Each is about 6cm long. The top one is black; the middle one is red; the bottom one is blue. All three have stripped ends, approximately 4 to 5mm on each end.
22AWG solid core hookup wires.
An Arduino Uno. The USB connector is facing to the left, so that the digital pins are on the top of the image, and the analog pins are on the bottom.
An Arduino Uno.
Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.
Resistors. Shown here are 220-ohm resistors. You can tell this because they have two red and one brown band, followed by a gold band.  For this exercise, you’ll need both 220-ohm resistors and 10-kilohm resistors (10K resistors are brown-black-orange. For more, see this resistor color calculator)

 

 

Understanding How Your Sensor Changes

Before you start trying to detect specific sensor change events, you should know what your sensor’s changes look like over time. You might want to start by viewing the change on an oscilloscope, or by using a graphing program like the one shown in the first Serial Lab to understand how your sensors change.

Graphing a sensor in Processing. The drawing shows a graph in which the line is alternately rising and falling on a relatively smooth curve.
Graphing a sensor in Processing

Sensor changes are described in terms of the change in voltage output over time. The most important cases to consider for sensor change are the rising and falling edges of a digital or binary sensor, and the rising and falling edges and the peak of an analog sensor. The following graphs of sensor voltage over time illustrate these conditions:

Graph of a digital sensor's rising and falling edge. The graph shows a flat line near zero, followed by a vertical rise to the top of the vertical axis. This change is labeled "Rising". Then there is another flat line for a short distance, then a vertical drop back to near zero. This change is labeled "Falling".
Digital sensors change from high voltage to low and vice versa. The change from low voltage to high is called the rising edge, and the change from high voltage to low is called the falling edge
Graph of an analog sensor. Graph depicts the sensor rising, peaking, and falling over time. It also depicts a threshold value below the rising, peak, and falling points.
The three general states of an analog sensor are when it’s rising (current state > previous state), when it’s falling (current state < previous state), and when it’s at a peak.

Prepare the breadboard

Connect power and ground on the breadboard to power and ground from the microcontroller. On the Arduino module, use the 5V and any of the ground connections:

An Arduino Uno on the left connected to a solderless breadboard, right. The Uno's 5V output hole is connected to the red column of holes on the far left side of the breadboard. The Uno's ground hole is connected to the blue column on the left of the board. The red and blue columns on the left of the breadboard are connected to the red and blue columns on the right side of the breadboard with red and black wires, respectively. These columns on the side of a breadboard are commonly called the buses. The red line is the voltage bus, and the black or blue line is the ground bus.
An Arduino Uno on the left connected to a solderless breadboard, right.

 

Add a pushbutton

Attach a pushbutton to digital pin 2. Connect one side of the pushbutton to 5 volts, and the other side of the pushbutton to a 10-kilohm resistor. Connect the other end of the resistor to ground. Connect the junction where the pushbutton and the resistor meet to digital pin 2. (For more on this digital input circuit, see the Digital Input Lab)

Schematic view of an Arduino connected to a pushbutton. One side of the pushbutton is connected to digital pin 2 of the Arduino. A 10-kilohm resistor is connected from digital pin 2 to ground as well. The other side of the pushbutton is attached to +5V.
Schematic view of an Arduino connected to a pushbutton on digital pin 2
Breadboard view of an Arduino connected to a pushbutton. The pushbutton is mounted across the middle divide of the solderless breadboard. A 10-kilohm resistor connects from the same row as pushbutton's bottom left pin to the ground bus on the breadboard. There is a wire connecting to digital pin 2 from the same row that connects the resistor and the pushbutton. The top left pin of the pushbutton is connected to +5V.
Breadboard view of an Arduino connected to a pushbutton on digital pin 2.

Program the microcontroller to read the pushbutton’s state change

In the Digital Lab you learned how to read a pushbutton using the digitalRead() command. To tell when a pushbutton is pushed, you need to determine when the button’s state changes from off to on. With the button wired as you have it here, the button’s state will change from 0 to 1. In order to know that, you need to know not only what the current state of the button is, but you also need to remember the state of the button the previous time you read it. This is called state change detection. To do this, set up a global variable to store the button’s previous state. Initialize the button in your program’s setup() function using the pinMode() command. Then, in the loop() function, write a block of code that reads the button and compares its state to the previous state variable. To do this, you need to read the button, check the current button state against the last state, then save the current state of the button in a variable for the next time through the loop like so:

int lastButtonState = LOW;    // state of the button last time you checked

void setup() {
  // make pin 2 an input:
  pinMode(2, INPUT);
}

void loop() {
  // read the pushbutton:
  int buttonState = digitalRead(2);

  // check if the current button state is different than the last state:
  if (buttonState != lastButtonState) {
     // do stuff if it is different here
  }

  // save button state for next comparison:
  lastButtonState = buttonState;
}

If buttonState is not equal to lastButtonState, then the button has changed. Then you want to check if the current state is HIGH. If it is, then you know the button has changed from low to high. That means your user pressed it. Print out a message to that effect.

int lastButtonState = LOW;   // state of the button last time you checked

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // make pin 2 an input:
  pinMode(2, INPUT);
}

void loop() {
  // read the pushbutton:
  int buttonState = digitalRead(2);

  // if it's changed and it's high, toggle the mouse state:
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      Serial.println("Button was just pressed.");
    }
  }
  // save button state for next comparison:
  lastButtonState = buttonState;
}

Your code should only print out a message when the button changes state. For every button press, you should get one line of code. You can use this technique any time you need to tell when a digital input changes state.

Count Button Presses

One of the many things you can do with state change detection is to count the number of button presses. Each time the button changes from off to on, you know it’s been pressed once. By adding another global variable and incrementing it when you detect the button press, you can count the number of button presses.  Add a global variable at the top of your program like so:

int lastButtonState = LOW;   // state of the button last time you checked
int buttonPresses = 0;  // count of button presses

Then in the if statement that detects the button press, add one to the button press:

    if (buttonState == HIGH) {
      buttonPresses++;
      Serial.print("Button has been pressed ");
      Serial.print(buttonPresses);
      Serial.println(" times.");
    }

The key to detecting state change is the use of a variable to save the current state for comparison the next time through the loop. This is a pattern you’ll see below as well:

int lastState = LOW;   // sensor's previous state
...

void loop() {
  // read the sensor:
  int sensorState = digitalRead(2);

  // if it's changed:
  if (buttonState != lastButtonState) {
    // take action or run a more detailed check
  }
  // save button state for next comparison:
  lastButtonState = buttonState;
}

Analog Sensor Threshold Detection

When you’re using analog sensors, binary state change detection like you saw above is not usually effective, because your sensors can have multiple states. Remember, an analog sensor on an Arduino can have up to 1024 possible states. The simplest form of analog state change detection is to look for the sensor to rise above a given threshold in order to take action. However, if you want the action be triggered only once when your sensor passes the threshold, you need to keep track of both its current state and previous state.

Change the Breadboard

To build this example, you’ll need an analog sensor attached to your microcontroller, as shown in the Analog Input lab:

Schematic of Arduino connected to an FSR on pin 2. One leg of the FSR connects to +5V. The other leg simultaneously connects to the Arduino's digital pin 2 and one end of a 10-kilohm resistor. The other end of the resistor connects to ground.
Schematic of Arduino connected to an FSR on pin 2
Breadboard view of Arduino connected to an FSR on pin 2. One leg of the FSR connects to +5V. The other leg simultaneously connects to the Arduino's digital pin 2 and one end of a 10-kilohm resistor. The other end of the resistor connects to ground.
Breadboard view of Arduino connected to an FSR on pin 2

 

Program the Microcontroller to Read a Sensor Threshold Crossing

This example is very similar to the one above:

int lastSensorState = LOW;   // sensor's previous state
int threshold = 512;   // an arbitrary threshold value

void setup() {
  Serial.begin(9600);
}

void loop() {
  // read the sensor:
  int sensorState = analogRead(A0);

  // if it's above the threshold:
  if (sensorState >= threshold) {
    // check that the previous value was below the threshold:
     if (lastSensorState < threshold) {
        // the sensor just crossed the threshold
        Serial.println("Sensor crossed the threshold");
     }
  }
  // save button state for next comparison:
  lastSensorState = sensorState;
}

This program will give you an alert only when the sensor value crosses the threshold when it’s rising. You won’t get any reading when it crosses the threshold when it’s falling, and you’ll only get one message when it crosses the threshold. It is possible to sense a threshold crossing when the sensor is falling, by reversing the greater than and less than signs in the example above. The threshold you set depends on your application. For example, if you’re using a light sensor to detect when it’s dark enough to turn on artificial lighting, you’d use the example above, and turn on the light when the threshold crossing happens. But you might also need to check for the falling threshold crossing to turn off the light.

Detecting a Peak

There are times when you need to know when an analog sensor reaches its highest value in a given time period. This is called a peak. To detect a peak, you first set an initial peak value at zero.  Pick a threshold below which you don’t care about peak values. Any time the sensor value rises above the peak value, you set the peak value equal to the sensor value. When the sensor value starts to fall, the peak will remain with the highest value:

int peakValue = 0;
int threshold = 50;   //set your own value based on your sensors

void setup() {
  Serial.begin(9600);
}

void loop() {
  //read sensor on pin A0:
  int sensorValue = analogRead(A0);
  // check if it's higher than the current peak:
  if (sensorValue > peakValue) {
    peakValue = sensorValue;
  }
}

You only really know you have a peak when you’ve passed it, however. When the current sensor value is less than the last reading you saved as the peak value, you know that last value was a peak. When the sensor value falls past below threshold after you have a peak, but your peak value is above the threshold, then you know you’ve got a significant peak value. after you use that peak value, you need to reset the variable to 0 to detect other peaks :

int peakValue = 0;
int threshold = 50;   //set your own value based on your sensors

void setup() {
  Serial.begin(9600);
}

void loop() {
  //read sensor on pin A0:
  int sensorValue = analogRead(A0);
  // check if it's higher than the current peak:
  if (sensorValue > peakValue) {
    peakValue = sensorValue;
  }
  if (sensorValue <= threshold) {
    if (peakValue > threshold) {
      // you have a peak value:
      Serial.println(peakValue);
      // reset the peak variable:
      peakValue = 0;
    }
  }
}

Dealing with Noise

Quite often, you get noise from sensor readings that can interfere with peak readings. Instead of a simple curve, you get a jagged rising edge filled with many local peaks:

Graph of a sensor rising above a constant threshold, peaking, falling below that peak but still above the threshold, then rising to its highest point and falling below the threshold
Graph of local peaks

You can smooth out the noise and ignore some of these local peaks by adding in a noise variable and checking to see if the sensor’s change is different than the previous reading and the noise combined, like so:

int peakValue = 0;
int threshold = 50;   //set your own value based on your sensors
int noise = 5;        //set a noise value based on your particular sensor

void setup() {
  Serial.begin(9600);
}

void loop() {
  //read sensor on pin A0:
  int sensorValue = analogRead(A0);
  // check if it's higher than the current peak:
  if (sensorValue > peakValue) {
    peakValue = sensorValue;
  }
  if (sensorValue <= threshold - noise ) {
    if (peakValue > threshold + noise) {
      // you have a peak value:
      Serial.println(peakValue);
      // reset the peak value:
      peakValue = 0;
    }
  }
}

Most sensor change cases can be described using a combination of state change detection, threshold crossing, and peak detection. When you start to write sensor change detection routines, make sure you understand these three basic techniques, and make sure you have a good idea what your sensor’s readings look like over time. With that combination, you should be able to detect most simple sensor change events.

Originally written on September 4, 2015 by Tom Igoe
Last modified on August 28, 2018 by David Rios