Skip to Content

APDS-9960 Gesture and Color Sensor with Arduino

APDS-9960 Gesture and Color Sensor with Arduino

In this tutorial you will learn how to use the APDS-9960 Gesture and RGB Color Sensor with an Arduino or other common microcontrollers such as the ESP32 or ESP8266.

The APDS-9960 is a very small sensor that uses IR light and photodiodes to recognize four different gestures (left to right, right to left, top to bottom, bottom to top). It also has a built-in detectors for ambient light, color measurements (red, green, blue) and proximity.

Required Parts

For this project you will need an APDS-9960 sensor and a microcontroller. I used an Arduino Uno but any other Arduino or any ESP32/ESP8266 will work as well, as long as it provides a 3.3V power output. One of the examples also uses a servo, but this is not an essential part for this project.

APDS-9960 Sensor

Arduino

Arduino Uno

USB Data Sync cable Arduino

USB Cable for Arduino UNO

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

Features of the APDS-9960 Gesture and RGB Color Sensor

The APDS-9960 is a tiny chip (3.94×2.36×1.35 mm) that features advanced Gesture detection, Proximity detection, Digital Ambient Light Sensing (ALS) and Color Sensing (RGBC). The picture below shows the APDS-9960 chip:

APDS-9960 chip
APDS-9960 chip

Gesture detection is achieved by four directional photodiodes that sense reflected IR light (sourced by the integrated LED), which is then converted to digital motion information (i.e. velocity, direction and distance). Simple UP-DOWN-RIGHT-LEFT gestures or more complex gestures can be sensed.

The Color and ALS detection feature of the APDS-9960 provides red, green, blue and clear light intensity data. Each of the R, G, B, C channels have a UV and IR blocking filter and a dedicated data converter producing 16-bit data simultaneously.

Functional Block Diagram of APDS-9960

The picture below shows the Functional Block Diagram of the APDS-9960. You can easily identify the IR Emitter diode LED, the four photodiodes for Color Sensing (Clear, Red Green, Blue), and the four photodiodes for Gesture detection (Up, Down, Left, Right).

Functional Block Diagram of APDS-9960 (source)

The APDS-9960 communicates via an I2C interface (SCL, SDA) and has an additional interrupt output (INT) that is activated when a programmable threshold is exceeded (for instance, proximity).

Working voltage of the APDS-9960 is 2.4V-3.6V, the measuring range for gestures is 10-20cm and the I2C address is 0x39. For more detailed information have a look at the datasheet linked below:

Breakout board for APDS-9960

The APDS-9960 chip is too small to connect it to an Arduino directly. Typically, you will need a breakout board as shown below:

Front and Back of breakout board for APDS-9960
Front and Back of breakout board for APDS-9960

Most breakout boards for the APDS-9960 have the following six pins:

  • VCC: Power supply (2.4 – 3.6V)
  • GND: Ground
  • VL: IR LED power
  • SDA: I2C data signal
  • SCL: I2C clock signal
  • INT: Interrupt pin

Typically you will need only the power supply pins (VCC, GND), the pins for I2C communication (SCL, SDA) and the interrupt (INT) pin. VL allows you to provide external power to the IR LED, which you typically don’t need.

Note that the APDS-9960 runs on 3.3V and that most breakout boards usually don’t have a voltage regulator.

Connecting the APDS-9960 to Arduino

Thanks to the I2C interface of the APDS-9960, connecting it to an Arduino is simple. First, connect the SCL pin of the APDS-9960 breakout board to A5 of the Arduino. Similarly, connect SDA to A4 of the Arduino. Next, connect GND to GND and 3.3V to VCC of the APDS-9960.

Connecting APDS-9960 to Arduino
Connecting APDS-9960 to Arduino

Make sure that you use 3.3V as power supply. The APDS-9960 sensor is not rated for 5V and breakout boards for the APDS-9960 typically do not have a voltage regulator.

Install Library for APDS-9960

There are two main Arduino libraries for the APDS-9960. The Adafruit_APDS9960 Library and the SparkFun_APDS-9960_Sensor_Arduino_Library. I will use the Adafruit_APDS9960 Library in this project.

To install the Adafruit_APDS9960 Library, open the Library Manager, search for “APDS9960”, find the library by Adafruit and install it, as shown below:

Installing APDS9960 Library by Adafruit via Library Manager
Installing APDS9960 Library by Adafruit via Library Manager

The installer may ask you to install library dependencies. That is fine. Just press “INSTALL ALL”

Installing dependencies of  APDS9960 Library by Adafruit
Installing dependencies of APDS9960 Library by Adafruit

With the library installed, let’s write some test code to try the sensor out. Upload the following code and wave your hand in front of the sensor in a distance of about 5cm.

#include "Adafruit_APDS9960.h"

Adafruit_APDS9960 sensor;

void setup() {
  Serial.begin(9600);
  if (!sensor.begin()) {
    Serial.println("failed to initialize device!");
  }
  sensor.enableProximity(true);
  sensor.enableGesture(true);
}


void loop() {
  if(sensor.readGesture()) {
    Serial.println("Movement detected");
  }  
}

If you see the message “Movement detected” printed to your Serial Monitor all is good. However, if you see “failed to initialize device!” things are getting a bit more difficult and you need to read the next section. If all is good you can skip it.

Adafruit_APDS9960: failed to initialize device!

First, check that the wiring is correct (SCL->A5, SDA->A4, VCC->3V3, GND->GND). If the wiring is okay and the sensor is not defect than there are two main reasons why sensor.begin() may fail. A different I2C address or a different chip ID. Let’s check the I2C address first.

Different I2C address of APDS9960

The usual I2C address of the APDS9960 is 0x39 and the Adafruit_APDS9960 Library expects this I2C address. To verify this install and run the following I2C scanner code:

#include "I2CScanner.h"

I2CScanner scanner;

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

void loop() {
  scanner.Scan();
  delay(5000);
}

The I2C scanner should find an I2C device with address 0x39 and print this to the Serial Monitor.

I2C device found at address 0x39  !
--- Scan finished ---

If you are seeing a different I2C address, then you have to change the code in the setup function as follows and replace “addr” by the I2C address you are seeing.

void setup() {
  ...
  if (!sensor.begin(10, APDS9960_AGAIN_4X, addr, &Wire)) {
    Serial.println("failed to initialize device!");
  }
  ...
}

If your I2C address is 0x39 or you have a changed the I2C address as described above but sensor.begin() still fails, then the next candidate is a different chip ID. If the I2C scanner cannot find any device, then your sensor is most likely broken (or the wiring is still wrong).

Different chip ID of APDS9960

If you look into the code for the begin() function in the Adafruit_APDS9960 Library you will see that the code first uses the I2C address addr. It then checks the chip ID and expects it to be 0xAB.

boolean Adafruit_APDS9960::begin(uint16_t iTimeMS, apds9960AGain_t aGain,
                                 uint8_t addr, TwoWire *theWire) {

  if (i2c_dev)
    delete i2c_dev;
  i2c_dev = new Adafruit_I2CDevice(addr, theWire);
  if (!i2c_dev->begin()) {
    return false;
  }

  /* Make sure we're actually connected */
  uint8_t x = read8(APDS9960_ID);
  if (x != 0xAB) {
    return false;
  }

However, I have an APDS9960 with the chip ID 0xA8 (not 0xAB) and the SparkFun_APDS-9960_Sensor_Arduino_Library lists another chip ID, which is 0x9C.

Since, the chip ID is hard-coded we have to change the code. The easiest way to do this, is to download two files Adafruit_APDS9960.h and Adafruit_APDS9960.cpp into your project folder, next to your sketch, e.g. MySketch.ino:


Adafruit library files copied into sketch folder
Adafruit library files copied into sketch folder

You will then see these two new files appear as tabs in your Arduino IDE:

Adafruit library files in Arduino IDE

Now open the tab with the Adafruit_APDS9960.cpp file, find the begin function and simply remove or comment out the block annotated with /* Make sure we're actually connected */ as shown below:

boolean Adafruit_APDS9960::begin(uint16_t iTimeMS, apds9960AGain_t aGain,
                                 uint8_t addr, TwoWire *theWire) {

  if (i2c_dev)
    delete i2c_dev;
  i2c_dev = new Adafruit_I2CDevice(addr, theWire);
  if (!i2c_dev->begin()) {
    return false;
  }

  /* Make sure we're actually connected */
  // uint8_t x = read8(APDS9960_ID);
  // if (x != 0xAB) {
  //  return false;
  // }

Alternatively, you could also print out “x” to find out ID of you chip and change the test if (x != 0xAB), accordingly.

If you don’t want to change the code yourself, I created a modified version of the two file, which you can download here. You still have to unpack the files and copy them into your project though. With this fix, the test code above should now run.

Recognizing Gestures with APDS-9960

The following code is a slight extension of the test code above. As before, in the setup function we initialize the sensor with sensor.begin() and then enable proximity and gesture detection:

#include "Adafruit_APDS9960.h"

Adafruit_APDS9960 sensor;

void setup() {
  Serial.begin(9600);
  if (!sensor.begin()) {
    Serial.println("failed to initialize device!");
  }
  sensor.enableProximity(true);
  sensor.enableGesture(true);
}


void loop() {
  uint8_t gesture = sensor.readGesture();
  if (gesture == APDS9960_DOWN) Serial.println("DOWN");
  if (gesture == APDS9960_UP) Serial.println("UP");
  if (gesture == APDS9960_LEFT) Serial.println("LEFT");
  if (gesture == APDS9960_RIGHT) Serial.println("RIGHT");
}

In the loop function we call sensor.readGesture() and depending on the return value print the detected gesture to the Serial Monitor.

Now, wave your hand across the sensor in a distance of about 5-10 cm and depending on the direction you should see the detected gesture printed to the Serial Monitor as shown below:

Detected Gestures on Serial Monitor
Detected Gestures on Serial Monitor

Instead of printing to the Serial Monitor you could now change the code to switch LEDs, control servos or cause any kind of other action you like, depending on the gesture. The following sections contains a simple example on how to control a servo with gestures.

Control servo with APDS-9960 Gesture Sensor

First we need to connect the servo to the Arduino. If you have one of those little SG90 Micro Servos then you can connect them directly to an Arduino (no need for a separate power supply). Just connect the red wire (middle pin) of the servo to 5V, the brown wire to ground (GND) and the orange/yellow wire to pin 13, as shown below.

Connecting Servo to Arduino
Connecting Servo to Arduino

If you need more info on how to connect and use these servos have a look at the How to control servo motors with Arduino tutorial.

The code for controlling the servo with a gesture is a simple variation of the previous code for the gesture detection.

#include "Adafruit_APDS9960.h"
#include "Servo.h"

Adafruit_APDS9960 sensor;
Servo servo;

const int servoPin = 13;

void setup() {
  if (!sensor.begin()) {
    Serial.println("failed to initialize device!");
  }
  sensor.enableProximity(true);
  sensor.enableGesture(true);
  servo.attach(servoPin);
  servo.write(90);
}

void loop() {
  uint8_t gesture = sensor.readGesture();
  if (gesture == APDS9960_DOWN) servo.write(90);
  if (gesture == APDS9960_UP) servo.write(90);
  if (gesture == APDS9960_LEFT) servo.write(10);
  if (gesture == APDS9960_RIGHT) servo.write(170);
}

First, we include the standard Servo library, (no need to install a library). Then we create the servo object and define the pin the servo is connected to (servoPin).

In the setup function we attach the servo to the servo pin and initially orient the servo to 90°. In the loop function we replace the print function calls by write commands to the servo. In case of an up or down gesture, we move the servo to the 90° position. A left gesture moves the servo to a 10° position and a right gestures moves it to a 170° position. The short video clip below demonstrates the code in action:

Controlling Servo with Gestures and APDS-9960
Controlling Servo with Gestures and APDS-9960

With that you now have a simple gesture control for a small servo, which you could use, for instance, to open or close a box without touch. In the next code example we try out the color sensor within the APDS-9960

Measuring Colors with APDS-9960

The code below uses the color sensor in the APDS-9960 to measure the color values for red, green, blue and clear, and prints them to the Serial Monitor.

We start by including the Adafruit library and creating the sensor object. In the setup function we initialize the sensor as usual with sensor.begin(), and then call sensor.enableColor(true) to enable the color sensor.

#include "Adafruit_APDS9960.h"

Adafruit_APDS9960 sensor;

void setup() {
  Serial.begin(9600);
  if (!sensor.begin()) {
    Serial.println("failed to initialize device!.");
  }
  sensor.enableColor(true);
}

void loop() {
  uint16_t r, g, b, c, tmp, lux;

  while (!sensor.colorDataReady()) {
    delay(5);
  }

  sensor.getColorData(&r, &g, &b, &c);
  Serial.print("red:");
  Serial.println(r);

  Serial.print("green:");
  Serial.println(g);

  Serial.print("blue:");
  Serial.println(b);

  Serial.print("clear:");
  Serial.println(c);

  tmp = sensor.calculateColorTemperature(r, g, b);
  Serial.print("tmp:");
  Serial.println(tmp);

  lux = sensor.calculateLux(r, g, b);
  Serial.print("lux:");
  Serial.println(lux);

  Serial.println();

  delay(1000);
}

In the loop function, we call sensor.colorDataReady() to retrieve the color values for red (r), green (g), blue (b) and clear (c), and then print them to the Serial Monitor.

The Adafruit_APDS9960 Library has two additional functions that allow you to compute the color temperature and the brightness in lux based on the measured color values. We print those to the Serial Monitor as well.

If you change the brightness of the ambient light or place LEDs with different colors in front of the sensor, you will see that the color values are changing.

Testing the color detection of the APDS-9960

To test the color detection of the APDS-9960, I used red, green and blue LEDs. The values below are what I measured when I placed a red LED close to the sensor:

red:329
green:26
blue:47
clear:342
tmp:24902
lux:65436

As expected the red value reads much higher than the green and blue values. Also note that the values for color temperature and lux brightness are crazily high. It seems the red LED light confuses these measurements.

Next, I used a green LED. The green LED was comparatively weak and the green value increased only by bit. Also the red value went up. You can see at the lux value that the brightness of my green LED was low.

red:15
green:16
blue:6
clear:42
tmp:3045
lux:15

Finally, I tired a blue LED and got the color values shown below. The blue value was very high but there was also an increase in the green component.

red:40
green:561
blue:2507
clear:2751
tmp:1754
lux:64574

The response of the color sensor obviously depends on the brightness and the wavelength of the colored light. My LEDs were not necessarily in the most responsive range, which could explain the weak reaction to the blue LED. But you can also the see from the Spectral Response Graph below, that the APDS-9960 generally has a much lower sensitivity for blue light (B).

Spectral Response of APDS-9960 Color Sensor
Spectral Response of APDS-9960 Color Sensor (source)

Reacting to Interrupts with APDS-9960

As a last example, I want to show you how to use the interrupt pin of the APDS-9960. For that we need to connect the INT output pin of the APDS-9960 with an interrupt capable pin of the Arduino. In case of an Arduino Uno only pins 2 and 3 are interrupt pins. For interrupt pins of other microprocessor boards see here.

In the wiring diagram below I am connecting the INT output of the APDS-9960 with pin 3 of the Arduino.

Connecting Interrupt Pin of APDS-9960 with Arduino
Connecting Interrupt Pin of APDS-9960 with Arduino

The following code fires an interrupt signal if an object gets close to the sensor and then prints out the proximity value.

#include "Adafruit_APDS9960.h"

Adafruit_APDS9960 sensor;
const int intPin = 3;

void setup() {
  Serial.begin(9600);
  if (!sensor.begin()) {
    Serial.println("failed to initialize device!");
  }
  sensor.enableProximity(true);
  sensor.setProximityInterruptThreshold(0, 100);
  sensor.enableProximityInterrupt();
  pinMode(intPin, INPUT_PULLUP);
}

void loop() {
  if (!digitalRead(intPin)) {
    Serial.println(sensor.readProximity());
    sensor.clearInterrupt();
  }
}

We start the code by including the library, creating the sensor object and defining the interrupt pin. In the setup function we enable proximity detection, setting the proximity thresholds, and enabling the proximity interrupt. We also need to switch the interrupt pin intPin to INPUT mode.

In the loop function we are polling the state of the intPin. iIf its state goes low it means an interrupt was fired and we read the proximity value and print it out.

Proximity values range from 255 (closest) to 0 (furthest). Since we set a proximity threshold of 100, the code starts printing proximity values if an object gets closer than 100, which corresponds to a distance of about 10mm.

Dimming an LED proportional to Proximity

You could use this code to control the brightness of an LED depending on the proximity of an object. For that we add an LED to our circuit. Connect the Cathode (short leg) of the LED with GND and the Anode (long leg) via a 220Ω resistor to pin 11. Any other PWM pin will work as well.

Connecting an LED with Arduino
Connecting an LED with Arduino

Next, we extend the code a bit. We define the ledPin and set it to OUTPUT mode in the setup function. In the loop function we read the proximity prox, and since it is a value between 0 and 255, we can use it directly to control the brightness of the LED be calling analogWrite(ledPin, prox):

#include "Adafruit_APDS9960.h"

Adafruit_APDS9960 sensor;
const int intPin = 3;
const int ledPin = 11;

void setup() {
  Serial.begin(9600);
  if (!sensor.begin()) {
    Serial.println("failed to initialize device!");
  }
  sensor.enableProximity(true);
  sensor.setProximityInterruptThreshold(0, 0);
  sensor.enableProximityInterrupt();
  pinMode(intPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  if (!digitalRead(intPin)) {
    uint8_t prox = sensor.readProximity();
    analogWrite(ledPin, prox);
    sensor.clearInterrupt();
  } 
}

The short video clip below demonstrates how the brightness of the LED changes depending on the proximity of the hand to the sensor:

Dimming LED with APDS-9960 depending on Proximity

And that’s it for today! I hope you had fun playing with the APDS-9960 Sensor.

Conclusions

In this tutorial you learned how to use the APDS-9960 Gesture and Color Sensor with an Arduino to detect gestures and measure color and brightness values.

In comparison to the APDS-9930 Proximity Sensor, the APDS-9960 can detect four built-in gestures (up, down, left, right), and has an RGB color sensor, while the APDS-9930 only has a proximity and an ambient light sensor. Both sensors are specifically designed for mobile phone or tablet application to activate functions (like switching on speakers, microphones, displays) and to control the brightness of the display.

If you need more gestures have a look at the PAJ7620U2, which can detect up to 13 gestures and its smaller brother, the PAJ7620 still can detect 9 gestures.

The range of the proximity sensor in the APDS-9930 and APDS-9960 is on purpose very limited. If you want to accurately measure longer distances to objects, e.g. for robotics applications, you are better off with infrared distance sensors such as the GP2Y0A710K0F that use triangulation to determine the distance to an object. Or Time-of-Flight (ToF) laser range distance sensors such as the TOF10120 or the VL53L1X library.

If you have any questions feel free to leave them in the comment section.

Happy Tinkering ; )