diff --git a/README.md b/README.md index b5d3fb6..74bebda 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -SparkFun APDS9960 RGB and Gesture Sensor Arduino Library +SparkFun APDS9960 RGB and Gesture Sensor Arduino Library (non blocking fork) ========================================================= ![Avago APDS-9960 Breakout Board - SEN-12787 ](https://cdn.sparkfun.com/r/92-92/assets/parts/9/6/0/3/12787-01.jpg) [*Avago APDS-9960 Breakout Board (SEN-12787)*](https://www.sparkfun.com/products/12787) +This version has added apds.checkGesture() which, when repeatedly called, returns DIR_NONE most of the time but when a completed gesture is seen it returns that gesture. Updates were made with the ESP32 in mind, so the I2C uses a semaphore to prevent problems when e.g. calling apds.getProximity() from a different thread. +If this isn't required, or you're using an Arduino, the semaphore code is ifdef'd out, but that's not tested. + Getting Started --------------- diff --git a/examples/Non-blocking-gesture-test/main.cpp b/examples/Non-blocking-gesture-test/main.cpp new file mode 100644 index 0000000..f4c67c3 --- /dev/null +++ b/examples/Non-blocking-gesture-test/main.cpp @@ -0,0 +1,67 @@ +#include +#include "SparkFun_APDS9960.h" + +SparkFun_APDS9960 apds = SparkFun_APDS9960(); + +#define CHECK(f, msg) \ + ar = f; \ + Serial.print(msg); \ + Serial.println((ar) ? " OK" : " failed") + +const char *gestureName[] = {"Left", "Right", "Up", "Down"}; + +void setup() { + // Initialize Serial port + Serial.begin(115200); + Serial.println(); + Serial.println(F("-----------------------")); + Serial.println(F("APDS-9960 - GestureTest")); + Serial.println(F("-----------------------")); + + // Initialize APDS-9960 (configure I2C and initial values) + if (apds.init()) { + Serial.println(F("APDS-9960 initialization complete")); + } else { + Serial.println(F("Something went wrong during APDS-9960 init!")); + } + + // Start running the APDS-9960 gesture sensor engine + if (apds.enableGestureSensor(true)) { + Serial.println(F("Gesture sensor is now running")); + } else { + Serial.println(F("Something went wrong during gesture sensor init!")); + } + + bool ar; + CHECK(apds.setGestureLEDDrive(2), "gesture LED drive"); + CHECK(apds.setGestureIntEnable(0), "int en"); + CHECK(apds.setGestureGain(GGAIN_4X), "gesture gain"); + CHECK(apds.enableProximitySensor(false), "Prox sensor"); + CHECK(apds.setProximityGain(PGAIN_4X), "Prox gain"); + // CHECK(apds.setGestureTimeout(3000), "Gesture timeout"); +} + +void loop() { + Gesture gesture = apds.checkGesture(); + + switch (gesture) { + case DIR_UP: + case DIR_DOWN: + case DIR_LEFT: + case DIR_RIGHT: + Serial.println(gestureName[gesture - 1]); + break; + case DIR_NEAR: + Serial.println("NEAR"); + break; + case DIR_FAR: + Serial.println("FAR"); + break; + case DIR_PENDING: + Serial.print("..."); + default: + // Don't print, we'll be here a LOT! + // Serial.print("/"); + break; + } +} \ No newline at end of file diff --git a/src/SparkFun_APDS9960.cpp b/src/SparkFun_APDS9960.cpp index 530c1f6..6bc1c9c 100644 --- a/src/SparkFun_APDS9960.cpp +++ b/src/SparkFun_APDS9960.cpp @@ -37,6 +37,10 @@ SparkFun_APDS9960::SparkFun_APDS9960() gesture_state_ = 0; gesture_motion_ = DIR_NONE; + +#ifdef ESP32 + vSemaphoreCreateBinary(xSemaphore); +#endif } /** @@ -162,7 +166,12 @@ bool SparkFun_APDS9960::init() if( !setGestureIntEnable(DEFAULT_GIEN) ) { return false; } - + + // MarcFinns - + if (!setGestureTimeout(DEFAULT_GWAIT_FOREVER)) { + return false; + } + #if 0 /* Gesture config register dump */ uint8_t reg; @@ -251,7 +260,7 @@ bool SparkFun_APDS9960::setMode(uint8_t mode, uint8_t enable) /* Change bit(s) in ENABLE register */ enable = enable & 0x01; - if( mode >= 0 && mode <= 6 ) { + if (mode <= 6) { if (enable) { reg_val |= (1 << mode); } else { @@ -473,108 +482,83 @@ bool SparkFun_APDS9960::isGestureAvailable() } } +void SparkFun_APDS9960::copyFromFifo(uint8_t bytes_read, uint8_t *fifo_data) { + for (uint8_t i = 0; i < bytes_read; i += 4) { + gesture_data_.u_data[gesture_data_.index] = fifo_data[i + 0]; + gesture_data_.d_data[gesture_data_.index] = fifo_data[i + 1]; + gesture_data_.l_data[gesture_data_.index] = fifo_data[i + 2]; + gesture_data_.r_data[gesture_data_.index] = fifo_data[i + 3]; + gesture_data_.index++; + gesture_data_.total_gestures++; + } +} + +void SparkFun_APDS9960::dbg_fifoDump(uint8_t bytes_read, uint8_t *fifo_data) { +#if DEBUG + Serial.print("FIFO Dump: "); + for (uint8_t i = 0; i < bytes_read; i++) { + Serial.print(fifo_data[i]); + Serial.print(" "); + } + Serial.println(); +#endif +} +void SparkFun_APDS9960::dbg_upDump() { +#if DEBUG + Serial.print("Up Data: "); + for (uint8_t i = 0; i < gesture_data_.total_gestures; i++) { + Serial.print(gesture_data_.u_data[i]); + Serial.print(" "); + } + Serial.println(); +#endif +} + +void SparkFun_APDS9960::dbg_flDump(uint8_t lc, uint8_t fifo_level) { +#if DEBUG + Serial.print(":"); + Serial.print(lc); + Serial.print(": FIFO Level: "); + Serial.println(fifo_level); +#endif +} + /** - * @brief Processes a gesture event and returns best guessed gesture - * - * @return Number corresponding to gesture. -1 on error. - */ -int SparkFun_APDS9960::readGesture() -{ - uint8_t fifo_level = 0; - uint8_t bytes_read = 0; - uint8_t fifo_data[128]; + @brief Processes a gesture event and returns best guessed gesture + + @return Number corresponding to gesture. -1 on error. +*/ +Gesture SparkFun_APDS9960::readGesture() { uint8_t gstatus; int motion; - int i; - + errno = GSERR_OK; + state = GS_IDLE; /* Make sure that power and gesture is on and data is valid */ - if( !isGestureAvailable() || !(getMode() & 0b01000001) ) { + if (!isGestureAvailable() || !(getMode() & 0b01000001)) { return DIR_NONE; } - + + state = GS_GESTURE_IN_PROGRESS; /* Keep looping as long as gesture data is valid */ - while(1) { - + // MarcFinns - keep looping only for some time, until timeout + long startTime = millis(); + lc = 0; + while ((state == GS_GESTURE_IN_PROGRESS) && ((gesture_timeout == DEFAULT_GWAIT_FOREVER) || (millis() - startTime < gesture_timeout))) { + lc++; /* Wait some time to collect next batch of FIFO data */ delay(FIFO_PAUSE_TIME); - - /* Get the contents of the STATUS register. Is data still valid? */ - if( !wireReadDataByte(APDS9960_GSTATUS, gstatus) ) { - return ERROR; - } - - /* If we have valid data, read in FIFO */ - if( (gstatus & APDS9960_GVALID) == APDS9960_GVALID ) { - - /* Read the current FIFO level */ - if( !wireReadDataByte(APDS9960_GFLVL, fifo_level) ) { - return ERROR; - } - -#if DEBUG - Serial.print("FIFO Level: "); - Serial.println(fifo_level); -#endif - - /* If there's stuff in the FIFO, read it into our data block */ - if( fifo_level > 0) { - bytes_read = wireReadDataBlock( APDS9960_GFIFO_U, - (uint8_t*)fifo_data, - (fifo_level * 4) ); - if( bytes_read == -1 ) { - return ERROR; - } -#if DEBUG - Serial.print("FIFO Dump: "); - for ( i = 0; i < bytes_read; i++ ) { - Serial.print(fifo_data[i]); - Serial.print(" "); - } - Serial.println(); -#endif - /* If at least 1 set of data, sort the data into U/D/L/R */ - if( bytes_read >= 4 ) { - for( i = 0; i < bytes_read; i += 4 ) { - gesture_data_.u_data[gesture_data_.index] = \ - fifo_data[i + 0]; - gesture_data_.d_data[gesture_data_.index] = \ - fifo_data[i + 1]; - gesture_data_.l_data[gesture_data_.index] = \ - fifo_data[i + 2]; - gesture_data_.r_data[gesture_data_.index] = \ - fifo_data[i + 3]; - gesture_data_.index++; - gesture_data_.total_gestures++; - } - -#if DEBUG - Serial.print("Up Data: "); - for ( i = 0; i < gesture_data_.total_gestures; i++ ) { - Serial.print(gesture_data_.u_data[i]); - Serial.print(" "); - } - Serial.println(); -#endif + /* Get the contents of the STATUS register. Is data still valid? */ + wireReadDataByte(APDS9960_GSTATUS, gstatus); + if (errno) return DIR_ERROR; - /* Filter and process gesture data. Decode near/far state */ - if( processGestureData() ) { - if( decodeGesture() ) { - //***TODO: U-Turn Gestures -#if DEBUG - //Serial.println(gesture_motion_); -#endif - } - } - - /* Reset data */ - gesture_data_.index = 0; - gesture_data_.total_gestures = 0; - } - } + /* If we have valid data, read in FIFO */ + if ((gstatus & APDS9960_GVALID) == APDS9960_GVALID) { + processFifo(); + if (errno) return DIR_ERROR; } else { - /* Determine best guessed gesture and clean up */ + state = GS_GESTURE_READY; delay(FIFO_PAUSE_TIME); decodeGesture(); motion = gesture_motion_; @@ -583,9 +567,12 @@ int SparkFun_APDS9960::readGesture() Serial.println(gesture_motion_); #endif resetGestureParameters(); - return motion; + return (Gesture)motion; } } + // MarcFinns - if exiting because of timeout, cleanup and return with no gesture + resetGestureParameters(); + return DIR_NONE; } /** @@ -2108,6 +2095,15 @@ bool SparkFun_APDS9960::setGestureMode(uint8_t mode) return true; } +// MarcFinns - Set the time the driver will wait for a gesture to complete +bool SparkFun_APDS9960::setGestureTimeout(uint16_t timeout) { + gesture_timeout = timeout; + return true; +} + +// MarcFinns - Reads the time the driver will wait for a gesture to complete +uint16_t SparkFun_APDS9960::getGestureTimeout() { return gesture_timeout; } + /******************************************************************************* * Raw I2C Reads and Writes ******************************************************************************/ @@ -2175,27 +2171,33 @@ bool SparkFun_APDS9960::wireWriteDataBlock( uint8_t reg, } /** - * @brief Reads a single byte from the I2C device and specified register - * - * @param[in] reg the register to read from - * @param[out] the value returned from the register - * @return True if successful read operation. False otherwise. - */ -bool SparkFun_APDS9960::wireReadDataByte(uint8_t reg, uint8_t &val) -{ - - /* Indicate which register we want to read from */ - if (!wireWriteByte(reg)) { - return false; - } - - /* Read from register */ - Wire.requestFrom(APDS9960_I2C_ADDR, 1); - while (Wire.available()) { - val = Wire.read(); - } + @brief Reads a single byte from the I2C device and specified register - return true; + @param[in] reg the register to read from + @param[out] the value returned from the register + @return True if successful read operation. False otherwise. +*/ +bool SparkFun_APDS9960::wireReadDataByte(uint8_t reg, uint8_t &val) { + bool good = false; +#ifdef ESP32 + if (xSemaphoreTake(xSemaphore, (TickType_t)5) == pdTRUE) { +#endif + /* Indicate which register we want to read from */ + if (!wireWriteByte(reg)) { + errno = GSERR_WIRE_READ; + } else { + /* Read from register */ + Wire.requestFrom(APDS9960_I2C_ADDR, 1); + while (Wire.available()) { + val = Wire.read(); + } + good = true; + } +#ifdef ESP32 + xSemaphoreGive(xSemaphore); + } +#endif + return good; } /** @@ -2211,21 +2213,32 @@ int SparkFun_APDS9960::wireReadDataBlock( uint8_t reg, unsigned int len) { unsigned char i = 0; - - /* Indicate which register we want to read from */ - if (!wireWriteByte(reg)) { - return -1; - } - - /* Read block data */ - Wire.requestFrom(APDS9960_I2C_ADDR, len); - while (Wire.available()) { - if (i >= len) { - return -1; + bool bail = false; +#ifdef ESP32 + if (xSemaphoreTake(xSemaphore, (TickType_t)5) == pdTRUE) { +#endif + /* Indicate which register we want to read from */ + if (!wireWriteByte(reg)) { + errno = GSERR_BLOCK_READ; + bail = true; + } else { + /* Read block data */ + Wire.requestFrom(APDS9960_I2C_ADDR, len); + while (!bail && Wire.available()) { + if (i >= len) { + errno = GSERR_BLOCK_READ; + bail = true; + } + val[i] = Wire.read(); + i++; + } } - val[i] = Wire.read(); - i++; +#ifdef ESP32 + xSemaphoreGive(xSemaphore); + } +#endif + if (bail) { + i = -1; } - return i; } \ No newline at end of file diff --git a/src/SparkFun_APDS9960.h b/src/SparkFun_APDS9960.h index 8266b45..e8be4fb 100644 --- a/src/SparkFun_APDS9960.h +++ b/src/SparkFun_APDS9960.h @@ -11,8 +11,7 @@ * APDS9960 object, call init(), and call the appropriate functions. */ -#ifndef SparkFun_APDS9960_H -#define SparkFun_APDS9960_H +#pragma once #include @@ -28,7 +27,12 @@ #define GESTURE_SENSITIVITY_2 20 /* Error code for returned values */ -#define ERROR 0xFF +#define ERROR 0xFF + +#define APDS_CHECK(expr) \ + if (!expr) { \ + return DIR_ERROR; \ + } /* Acceptable device IDs */ #define APDS9960_ID_1 0xAB @@ -189,25 +193,20 @@ #define DEFAULT_GCONF3 0 // All photodiodes active during gesture #define DEFAULT_GIEN 0 // Disable gesture interrupts +// MarcFinns +#define DEFAULT_GWAIT_FOREVER -1 // No timeout on waiting for gesture + +#define GSERR_OK 0 +#define GSERR_WIRE_READ 1 +#define GSERR_WIRE_WRITE 2 +#define GSERR_BLOCK_READ 3 + /* Direction definitions */ -enum { - DIR_NONE, - DIR_LEFT, - DIR_RIGHT, - DIR_UP, - DIR_DOWN, - DIR_NEAR, - DIR_FAR, - DIR_ALL -}; +enum Gesture { DIR_NONE, DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN, DIR_NEAR, DIR_FAR, DIR_ALL, DIR_PENDING, DIR_ERROR = ERROR }; /* State definitions */ -enum { - NA_STATE, - NEAR_STATE, - FAR_STATE, - ALL_STATE -}; +enum { NA_STATE, NEAR_STATE, FAR_STATE, ALL_STATE }; +enum GS_State { GS_IDLE, GS_GESTURE_IN_PROGRESS, GS_GESTURE_READY }; /* Container for gesture data */ typedef struct gesture_data_type { @@ -294,14 +293,107 @@ class SparkFun_APDS9960 { /* Gesture methods */ bool isGestureAvailable(); - int readGesture(); - -private: + Gesture readGesture(); + + // MarcFinns - Time the driver will wait for a gesture to complete + bool setGestureTimeout(uint16_t timeout); + uint16_t getGestureTimeout(); + Gesture checkGesture() { + uint8_t gstatus; + errno = GSERR_OK; + switch (state) { + case GS_IDLE: + if (!isGestureAvailable() || !(getMode() & 0b01000001)) { + return DIR_NONE; + } + resetGestureParameters(); + state = GS_GESTURE_IN_PROGRESS; + lc = 0; + break; + case GS_GESTURE_IN_PROGRESS: + delay(FIFO_PAUSE_TIME); + wireReadDataByte(APDS9960_GSTATUS, gstatus); + if (errno) return DIR_ERROR; + if ((gstatus & APDS9960_GVALID) == APDS9960_GVALID) { + processFifo(); + if (errno) return DIR_ERROR; + } else { + state = GS_GESTURE_READY; + delay(FIFO_PAUSE_TIME); + decodeGesture(); + int motion = gesture_motion_; +#if DEBUG + Serial.print("END: "); + Serial.println(gesture_motion_); +#endif + return (Gesture)motion; + } + break; + case GS_GESTURE_READY: + state = GS_IDLE; + break; + default: + state = GS_IDLE; + break; + } + return DIR_NONE; + } + + GS_State getState() { return state; } + private: /* Gesture processing */ void resetGestureParameters(); bool processGestureData(); bool decodeGesture(); + void copyFromFifo(uint8_t bytes_read, uint8_t *fifo_data); + void dbg_fifoDump(uint8_t bytes_read, uint8_t *fifo_data); + void dbg_upDump(); + void dbg_flDump(uint8_t lc, uint8_t fifo_level); + + /** + * @brief Read and process the FIFO + * + * @return true no read errors + * @return false some read error + */ + bool processFifo() { /* Read the current FIFO level */ + uint8_t fifo_data[128]; + fifo_level = 0; + uint8_t bytes_read = 0; + + wireReadDataByte(APDS9960_GFLVL, fifo_level); + + if (errno) return false; + + dbg_flDump(lc, fifo_level); + /* If there's stuff in the FIFO, read it into our data block */ + if (fifo_level > 0) { + bytes_read = wireReadDataBlock(APDS9960_GFIFO_U, (uint8_t *)fifo_data, (fifo_level * 4)); + if (bytes_read == 0xFF) { + errno = GSERR_BLOCK_READ; + return false; + } + dbg_fifoDump(bytes_read, fifo_data); + + /* If at least 1 set of data, sort the data into U/D/L/R */ + if (bytes_read >= 4) { + copyFromFifo(bytes_read, fifo_data); + dbg_upDump(); + + /* Filter and process gesture data. Decode near/far state */ + bool ok = processGestureData(); + if (ok) { + decodeGesture(); + } + + /* Reset data */ + gesture_data_.index = 0; + gesture_data_.total_gestures = 0; + } + } + return true; + } /* Proximity Interrupt Threshold */ uint8_t getProxIntLowThresh(); @@ -350,6 +442,13 @@ class SparkFun_APDS9960 { int gesture_far_count_; int gesture_state_; int gesture_motion_; + int lc; + uint8_t fifo_level; + uint8_t errno; + GS_State state; +#ifdef ESP32 + SemaphoreHandle_t xSemaphore = NULL; +#endif + // MarcFinns + uint16_t gesture_timeout; }; - -#endif \ No newline at end of file