Link Search Menu Expand Document

Detect nearby objects and motion

Table of contents

  1. Overview
  2. Install the APDS9960 Library
  3. Run the proximity_sensor.ino sketch from the Adafruit Library
  4. Run the demoProximity.ino sketch
    1. Viewing the Proximity Data
    2. Walk Through the Code
      1. Retrieve and Print Proximity Values
  5. Further Reading

Overview

This web page demonstrates how to retrieve the proximity reading from the APDS9960 sensor mounted on the Feather nRF52840 Sense. The APDS9960 can also measure ambient light levels and separately detect the red, blue and green components of the ambient light. A separate web page describes the use of the APDS9960 to measure ambient light levels.

The APDS9960 sensor can detect the presence of nearby objects by reflected infrared (IR) light emitted by an IR diode on the APDS9960. Additional detectors on the chip can be employed to sense the direction the object moves, which allows the APDS9960 to infer left-right, up-down and forward back motions as gestures.

The location of the sensor on the Feather nRF52840 Sense board is shown in the image below.

APDS9960 on Adafruit Feather Sense

The APDS9960 has its internal processing and communication hardware. Proximity, gesture, ambient light and light color readings are sent to the host Arduino program via a I2C digital interface.

This web page shows how to perform the basic operation of measuring the proximity of an object that is near the APDS9960. Although the proximity detection involves measuring the distance to an object that reflects IR light, the APDS9960 is not the best sensor for measuring distance. Think of the APDS9960 as a sensor for devices that automatically dispense paper towels or soap in a restroom.

Adafruit provides a software library and instructions with examples for using the APDS9960. The source code is available on github. APDS9960 usage is also demonstrated in the sample code provided by Adafruit for the Feather nRF52840 Sense.

Install the APDS9960 Library

To write Arduino sketches that use the APDS9960, you first need to install the Adafruit APDS9960 Library. The following steps use the Arduino Library Manager and assume that you have properly configured the board support page.

From menu at the top of the Arduino IDE, select Sketch –> Include Library –> Manage Libraries…

  1. Enter “9960” in the search box to narrow the range of choices. Or, if you prefer, scroll through the entire list of libraries
  2. Choose the “Adafruit APDS9960 Library” panel
  3. Click Install

Select and install the Adafruit APDS9960 library

Run the proximity_sensor.ino sketch from the Adafruit Library

The Adafruit APDS9960 library includes three example sketches to demonstrate features of the APDS9960 sensor. The color_sensor.ino sketch shows how to extract red, blue and green readings from the sensor. The gesture_sensor.ino sketch reads the left-right, up-down and near-far motions of objects above the sensor. The proximity_sensor.ino sketch prints the raw proximity output. We will discuss the proximity_sensor sketch and then develop a modified version that smooths out the readings with a running average.

To run the proximity_sensor sketch, open it from the Example folder. From the File menu of the Arduino IDE, select Examples –> Adafruit APDX9960 Library –> proximity_sensor. The code for the proximity_sensor.ino sketch should appear in a new text window of the Arduino IDE.

Upload the proximity_sensor sketch and open the Serial Monitor or Serial Plotter to see the continuous stream of readings from the sensor. Move your hand closer or farther from the sensor between 2 cm (1 inch) and 20 cm (8 inches) from the sensor. The output should vary between 0 and 255, in the raw units from the sensor.

Run the demoProximity.ino sketch

The demoProximity sketch is a variation on the proximity_sensor sketch from the Adafuit Library.

Download the demoProximity.ino sketch and upload it to your board. While uploading, the NeoPixel on the Feather nRF52840 glows green and then red. After the upload is complete you can open the Serial Monitor to see the distance data from the sensor. Make sure the baud rate of the Serial Monitor is the same as the baud rate in the Serial.begin() command. In the demoProximity code (shown below) the baud rate is 115200.

//  File:  demoProximity.ino
//
//  Read the built-in proximity sensor of an Adafruit Feather nRF52840 Sense
//  and print the raw sensor output to the Serial object
//
//  This sketch is a variation on the proximity_sensor.ino sketch that
//  comes with the Adafruit APDS9960 library.  See http://www.adafruit.com/products/3595
//
//  Gerald Recktenwald, gerry@pdx.edu,  created 2020-08-12, updated 2021-10-15

#include <Adafruit_APDS9960.h>   //  Use the Adafruit Library for APDS9960
Adafruit_APDS9960 apds;          //  Create an APDS9960 object
#define INT_PIN 3                //  Interrupt pin for the APDS9960

// ----------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);         //  Open Serial object for messages and data
  delay(3000);                  //  Wait for USB connection
  setupAPDSproximity();         //  Configure APDS9960 to read ambient light
}

// ----------------------------------------------------------------------------
void loop() {
  int proximity;                       //  Proximity reading

  proximity = readAPDSproximity();     //  Get proximity reading  
  Serial.println(proximity);           //  and print that value
  delay(25);                           //  Slow down the Serial Monitor output
}

// ----------------------------------------------------------------------------
//  Read the proximity sensor.  Return the raw value, or -99 if no object
//  is detected within range
//
int readAPDSproximity() {

  int reading;                          //  Output of proximity sensor
  
  // -- INT_PIN goes low when APDS9960 detects persistent object
  if ( !digitalRead(INT_PIN) ) {
    reading = apds.readProximity();     //  Get the proximity reading
    apds.clearInterrupt();              //  Reset APDS9960 to be ready for another event
  } else {
    reading = -99.0;
  }
  return(reading);
}

// ----------------------------------------------------------------------------
//  Configure the APDS9960 sensor to read proximity of objects.
//
void setupAPDSproximity() {

  pinMode(INT_PIN, INPUT_PULLUP); //  Configure nRF52840 for interrupt from APDS9960

  // -- Check to see that the sensor is connected
  if ( !apds.begin() ) {
    Serial.println("Failed to initialize device! Please check your wiring.");
  } else {
    Serial.println("Device initialized!");
  }

  apds.enableProximity(true);                   // Enable proximity mode
  apds.setProximityInterruptThreshold(0, 175);  // Set threshold to indicate a reading is made
  apds.enableProximityInterrupt();              // Enable the proximity interrupt
}

Viewing the Proximity Data

When connected to your computer the demoProximity sketch should produce a two columns of values related to the distance between the top of the APDS9960 and an object directly above the sensor. The first column is the instantaneous raw reading and the second column is the moving average of NBUFFER values. NBUFFER is equal to 250 in the demoProximity sketch, but you can change it to experiment with different amounts of smoothing of the raw data. The output from demoProximity can be viewed with the Serial Monitor or the Serial Plotter in the Arduino IDE.

Walk Through the Code

The demoProximity.ino sketch performs these main steps:

  1. Include the Adafruit_APDS9960.h library
  2. Create an Adafruit_APDS9960 object. In this code the object is called apds
  3. Configure and start the apds object
    • Enable the proximity mode
    • Set the interrupt threshold
    • Enable the proximity interrupt
  4. Repeat the loop function:
    • If a proximity interrupt occurs:
    • Obtain the reading and store it in the averaging buffer
    • Compute the running average of proximity readings
    • Print the average value

We’ll examine each step in more detail.

Include the APDS9960 Library

The first step is to include the Adafruit_APDS9960.h library, which provides support for the APDS9960 chip. You can read the code from the github repository.

#include <Adafruit_APDS9960.h>

The demoProximity.ino sketch uses only the proximity reading of the APDS9960 chip.

Create an APDS9960 Object

The next step is create or instantiate a APDS9960 object, which is the mechanism to communicate with the APDS9960 chip in the Arduino software environment.

Adafruit_APDS9960 apds;

In this example the APDS9960 object is called apds. An object is like a variable with properties for storing and retrieving data. You can use any valid variable name for the object name. The Adafruit_APDS9960 apds statement invokes the constructor for Adafruit_APDS9960 objects. In this case the constructor does not require any input parameters. Therefore, apds appears without any parenthesis.

Configure and Start the APDS9960 Object

The APDS9960 chip has internal hardware for detecting the presence of objects in its field of view. When configured for proximity sensing, the chip signals the presence of an object by setting one of its digital pins low. In mounting the APDS9960 on the Feather board, Adafruit has connected this signal line from the APDS9960 to pin 3 on the nRF5280 microcontroller.

The code in the setupAPDSproximity() function of demoProximity.ino configures the APDS9960 chip for proximity readings and sets a threshold to detect whether a proximity event has occurred.

void setupAPDSproximity() {

  pinMode(INT_PIN, INPUT_PULLUP); //  Configure nRF52840 for interrupt from APDS9960

  // -- Check to see that the sensor is connected
  if ( !apds.begin() ) {
    Serial.println("Failed to initialize device! Please check your wiring.");
  } else {
    Serial.println("Device initialized!");
  }

  apds.enableProximity(true);                   // Enable proximity mode
  apds.setProximityInterruptThreshold(0, 175);  // Set threshold to indicate a reading is made
  apds.enableProximityInterrupt();              // Enable the proximity interrupt
}

The pinMode(INT_PIN, INPUT_PULLUP) command configures pin number INT_PIN on the nRF52840 for input and ties that pin to the high logic state (3.3V) with an internal pull-up resistor. This means that without any externally applied voltage, pin digitalRead(INT_PIN) will return a value of 1 or HIGH. The APDS9960 signals that a proximity event has occurred by setting the voltage connected to INT_PIN to ground.

The next block of code, beginning with if ( !apds.begin() ) { tries to start the apds object with the .begin() method. The ! in front of apds.begin() negates the value returned by apds.begin(). Therefore, if apds.begin() returns 0, then !apds.begin() will evaluate as TRUE.

The final section of code in setupAPDSproximity() prepares the apds object for reading. The apds.enableProximity(true) statement is straightforward – it tells the apds object to set the APDS9960 into proximity sensing mode. The apds.setProximityInterruptThreshold(0, 175) statement sets a threshold value for the interrupt that indicates when an object is detected. Finally, apds.enableProximityInterrupt() it tells the apds object to set the APDS9960 into proximity sensing mode.

Retrieve and Print Proximity Values

The readAPDSproximity() function returns the raw proximity reading from the sensor. The reading indicates the number of counts of reflected light pulses. Higher counts are correlated with nearer objects.

int readAPDSproximity() {

  int reading;                          //  Output of proximity sensor
  
  // -- INT_PIN goes low when APDS9960 detects persistent object
  if ( !digitalRead(INT_PIN) ) {
    reading = apds.readProximity();     //  Get the proximity reading
    apds.clearInterrupt();              //  Reset APDS9960 to be ready for another event
  } else {
    reading = -99.0;
  }
  return(reading);
}

Measurement sequence

  1. APDS9960 hardware and firmware detects the presence of an object
  2. APDS9960 firmware sets its INT pin low
  3. nRF52840 detects the change of the INT pin
  4. nRF52840 requests the proximity value via I2C
  5. nRF52840 tells the APDS9960 to return to monitoring for presence of objects

Use the state of INT_PIN to determine whether the APDS9960 has detected an object

  // -- INT_PIN goes low when APDS9960 detects persistent object
  if ( !digitalRead(INT_PIN) ) {
    reading = apds.readProximity();     //  Get the proximity reading
    apds.clearInterrupt();              //  Reset APDS9960 to be ready for another event
  ...

Proximity Output versus Distance

The following plot is from page 18 of the APDS9960 datasheet. The horizontal axis is the distance between the sensor and a target. The vertical axis is the raw data or PS Count from the sensor. The three curves show the sensor output for two shades of gray target surface and one black surface. The 90% Kodak Card is a very light gray. The 18% Kodak Card is what photographers call middle gray and is perceptuallyc c half way pure black and pure white.

APDS9960 datasheet Figure 13a

Further Reading