Introduction: NonBlocking APDS9960 Gesture Sensor Implementation


This Instructable details how to create a non-blocking implementation of the APDS9960 Gesture Sensor using the SparkFun_APDS-9960_Sensor_Arduino_Library.


So you’re probably asking yourself what is non-blocking? Or even blocking for that matter?

More importantly why is it important to have a non-blocking anything right?

Ok, so when a microprocessor runs a programme it sequentially executes lines of code and in doing so makes calls to and returns from functions according to the order in which you wrote them.

A blocking call is just a call to any kind of functionality that causes a halting of execution, meaning a function call where the caller will not resume execution until the called function is finished executing.

So why is this important?

In the case where you have written some code which needs to regularly execute lots of functions sequentially such as read a temperature, read a button and update a display, should the code to update the display be a blocking call, your system will be unresponsive to button presses and changes in temperature, as the processor will be spending all of it's time waiting for the display to update and not reading the button status or latest temperature.

For my part I want to create an MQTT over WiFi capable IoT desktop device which reads both local and remote temp/humidity values, ambient light levels, barometric pressure, keeps track of time, display all these parameters on an LCD, log to a uSD card in real time, read button inputs, write to output LEDs and monitor gestures to control things in my IoT infrastructure and all of which to be controlled by an ESP8266-12.

Unfortunately the only two sources of APDS9960 library I could find were the SparkFun and AdaFruit libraries, both of which ripped from application code from Avago (the ADPS9960 manufacturer) and possess a call named ‘readGesture’ which contains a while(1){}; loop which when used in the project above causes the ESP8266-12E to reset whenever the ADPS9960 sensor became saturated (ie. when an object remained in close proximity, or there was another IR source illuminating the sensor).

Consequently to resolve this behaviour I chose to move the processing of the Gestures to a second processor whereby the ESP8266-12E became the master microcontroller and this system the slave, as depicted in Pics 1 & 2 above, the System Overview and System Composition diagrams respectively. Pic 3 shows the prototype circuit.

In order to limit the changes I needed to make to my existing code I also wrote a wrapper class/library imaginatively named ‘APDS9960_NonBlocking’.


What follows is a detailed explanation of the non-blocking solution.


What Parts Do I Need?

If you want to construct the I2C solution which works with the APDS9960_NonBlocking library you will require the following parts.

  1. 1 off ATMega328P here
  2. 1 off PCF8574P here
  3. 6 off 10K Resistors here
  4. 4 off 1K Resistors here
  5. 1 off 1N914 Diode here
  6. 1 off PN2222 NPN Transistor here
  7. 1 off 16MHz crystal here
  8. 2 off 0.1uF Capacitors here
  9. 1 off 1000uF Electrolytic Capacitor here
  10. 1 off 10uF Electrolytic Capacitor here
  11. 2 off 22pF Capacitors here


If you want to read the gesture sensor output via the parallel interface then you can drop the PCF8574P and three off 10K resistors.


What software do I need?

  • Arduino IDE 1.6.9


What skills do I need?

To set the system up, use the source code (provided) and create the necessary circuitry you will need the following;


Topics covered

    • Brief overview of the circuit
    • Brief overview of the software
    • Testing the APDS9960 Gesture Sensing Device
    • Conclusion
    • References

    Step 1: Circuit Overview

    The circuit is divided into two sections;

    • The first is the serial I2C to parallel conversion accomplished via resistors R8...10 and IC1. Here R8...R10 set the I2C address for the 8 bit I/O expander chip IC1 an NXP PCF8574A. Valid address ranges for this device are 0x38 ... 0x3F respectively. In the I2C software example provided 'I2C_APDS9960_TEST.ino' '#define GESTURE_SENSOR_I2C_ADDRESS' would need to be altered to suit this address range.
    • All other components form a slave embedded Arduino Uno and have the following functions;
      • R1, T1, R2 and D1 provide a slave device reset input. Here an active high pulse on IC1 - P7 will force U1 to reset.
      • R3, R4, are current limiting resistors for the embedded device programming TX/RX lines.
      • C5 and R7 allow the Arduino IDE to automatically programme U1 via a pulse on the DTR line of an attached FTDI device.
      • R5 and R6 are I2C pull up resistors for the APDS9960 with C6 providing local supply rail decoupling.
      • U1, C1, C2 and Q1 form the embedded Arduino Uno and it's clock respectively.
      • Finally C3 and C4 provide local supply rail decoupling for U1.

    Step 2: Software Overview


    To successfully compile this source code you will need the following extra libraries to programme the embedded Arduino Uno U1;






    See the following Instructable on how to programme an embedded Arduino Uno (ATMega328P) microcontroller if you are not familiar with how to achieve this;



    Functional Overview

    The ATMega328P embedded slave microcontroller polls the INT line from the ADPS9960. When this line goes low the microcontroller reads the ADPS9960 registers and determines if there has been a valid gesture sensed. If a valid gesture has been detected, the code for this gesture 0x0...0x6,0xF is placed on Port B and 'nGestureAvailable' is asserted low.

    When the Master device sees 'nGestureAvailable' active, it reads the value on Port B then pulses 'nGestureClear' low temporarily to acknowledge receipt of the data.

    The slave device then de-asserts 'nGestureAvailable' high and clears the data on Port B. Pic 5 above shows a screen grab taken from a logic analyser during a full detect/read cycle.


    Code Overview

    Pic 1 above details how the software in U1 the embedded slave Arduino Uno functions, along with Pic 2 how the two background/foreground tasks interact. Pic 3 is a code segment outlining how to use the APDS9960_NonBlockinglibrary. Pic 4 gives a mapping between Arduino Uno Digital Pins and actual hardware pins on the ATMega328P.

    After reset the embedded slave microcontroller initialises the APDS9960 allowing gesture detection to trigger its INT output and configures it's I/O, attaching interrupt service routine (ISR) 'GESTURE_CLEAR()' to interrupt vector INT0 (Digital pin 2, Hardware IC pin 4), configuring it for a falling edge trigger. This forms the nGestureClear input from the master device.

    The Interrupt output pin 'INT' from the APDS9960 is connected to Digital Pin 4, Hardware IC Pin 6 which is configured as an input to U1.

    The 'nGestureAvailable' signal line on Digital pin 7, Hardware IC pin 13 is configured as an output and set high, inactive (de-asserted).

    Finally Port B bits 0...3 respectively are configured as outputs and set low. These form the data nibble which represents the various detected gesture types; None = 0x0, Error = 0xF, Up = 0x1, Down = 0x2, Left = 0x3, Right = 0x4, Near = 0x5 and Far = 0x6.

    The background task 'Loop' is scheduled which continually polls the APDS9960 Interrupt output INT via reading Digital Pin 4. When the INT output from the APDS9960 becomes active low indicating the sensor has been triggered the microcontroller tries to interpret any gesture by calling 'readGesture()' with it's while (1) {}; endless loop.

    If a valid gesture has been detected, this value is written to Port B, the 'nGestureAvailable' output is asserted and the boolean semaphore 'bGestureAvailable' is set, preventing any further gestures from being logged.

    Once the master detects the active 'nGestureAvailable' output it reads this new value and pulses 'nGestureClear' active low. This falling edge triggers the foreground task 'ISR GESTURE_CLEAR()' to be scheduled suspending execution of the background task 'Loop', clearing Port B, 'bGestureAvailable' semaphore and 'nGestureAvailable' output.

    The foreground task 'GESTURE_CLEAR()' is now suspended and background task 'Loop' re-scheduled. Further gestures from the APDS9960 can now be sensed.


    By using interrupt triggered foreground/background tasks in this way the potential infinite loop in 'readGesture()' of the slave device will not affect the master device from operating and will not impede execution of the slave device either. This forms the basis of a very simple real time operating system (RTOS).


    Note: The prefix 'n' means active low or asserted as in 'nGestureAvailable'

    Step 3: Testing the NonBlocking APDS9960 Gesture Sensing Device


    Even though the APDS9960 module is supplied with +5v it uses an on-board +3v3 regulator meaning it's I2C lines are +3v3 compliant and not +5v. This is why I chose to use the +3v3 compliant Arduino Due as the test micro controller, to obviate the need for level shifters.

    If however, you wish to use an actual Arduino Uno then you would need to level shift the I2C lines to U1. See the following Instructable where I have attached a useful slide set (I2C_LCD_With_Arduino) which gives lots of practical tips on using I2C.

    I2C Interface Testing

    Pics 1 and 2 above show how to set up and programme the system for the I2C interface. You will need to download and install the APDS9960_NonBlocking library first. here

    Parallel Interface Testing

    Pics 3 and 4 detail the same for the Parallel interface

    Step 4: Conclusion


    The code works well and detects gestures responsively without any false positives. It's been up and running for a few weeks now as a slave device in my next project. I have tried many different failure modes (and so has the inquisitive Quinn household moggie) which previously resulted in a ESP8266-12 reset, with no negative effect.

    Possible improvements

    • The obvious. Re-write the APDS9960 Gesture Sensor library to be non-blocking.
      • Actually I did contact Broadcom who put me on to a local distributor who promptly ignored my request for support, I'm just not a SparkFun or AdaFruit, I guess. So this will probably have to wait a while.
    • Port the code to a smaller slave microcontroller. Using an ATMega328P for one task is a bit of an overkill. Though I did initially look at the ATTiny84, stopped short of using one as I felt the compiled size of the code was a border line fit. With the added overhead of having to modify the APDS9960 library to work with a different I2C library.

    Step 5: References

    Required to programme the embedded arduino (ATMega328P - U1)


    Required to embed this non-blocking functionality into your arduino code and give worked examples


    Real Time Operating System

    APDS9960 Datasheet

    PCF8574A datasheet

    Arduno Uno Pinout diagram