adafruit / Adafruit_CircuitPython_APDS9960

Adafruit Bundle driver for APSD9960 Gesture breakout board
MIT License
10 stars 17 forks source link

gesture_proximity_threshold default causes prox value to lock at 51 #23

Closed kattni closed 2 years ago

kattni commented 4 years ago

From @dastels in #16: Related: gestures being triggered (prox > gesture_proximity_threshold) freeze the returned proximity value. E.g. if the threshold is at the default 50, the returned proximity value to 'lock in' when it reaches 51. Setting the threshold to 255 allows proximity to work property throughout it's full 0-255 range.

Stalkii commented 3 years ago

I do have this bug as well. How do I set the threshold to work around it?

jerryneedell commented 3 years ago

I tried to look into this, but I don't think I understand was the issue is. Can someone provide an example of what is wrong?

Stalkii commented 3 years ago

The proximity reading is good as long as the detected distance is below the value of 50. Meaning it always returns back to 0 if the object (e.g. hand) is removed from the sensor. Once the value gets above 50 it gets "stuck" there, so does not return to 0. Basically it doesn't change at all (neither higher nor lower values). It doesn't matter if you only activate proximity reading or have anything else (gesture, color, prox_int) activated as well.

jerryneedell commented 3 years ago

FYI - I have reproduced this. What I am seeing is that if I only enable proximity, then the setting of gesture_proximity_threshold does not matter. That is, the issue only occurs if I also enable gesture. If gesture is enabled, then once the gesture_proximity_threshold is exceeded, proximity readings are all at the value of gesture_proximity_threshold + 1. Gesture detection continues to function, but proximity readings are useless.

I have not found a workaround at this time... still looking.

joeblubaugh commented 3 years ago

I'm also experiencing this problem on a QT Py RP2040. I took a look at the datasheet: https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf

There are a number of relevant registers, I'm going to focus on ENABLE and GCONFIG4

Here's the enable_gesture code. _gesture_enable lives in the ENABLE register and _gesture_mode lives in GCONFIG4.

 ## GESTURE DETECTION
    @property
    def enable_gesture(self):
        """Gesture detection enable flag. True to enable, False to disable.
        Note that when disabled, gesture mode is turned off"""
        return self._gesture_enable

    @enable_gesture.setter
    def enable_gesture(self, enable_flag):
        if not enable_flag:
            self._gesture_mode = False
        self._gesture_enable = enable_flag

If you set _gesture_mode = False (that's GCONFIG4, bit 0), you'll drop out of the gesture state machine. I wonder if we need to clear the _gesture_mode flag after the gesture value is read ...

The gesture state machine is on page 15 of the datasheet, and it implies that if _gesture_mode isn't cleared, the gesture state machine will loop forever

fivesixzero commented 2 years ago

I've seen this issue as well during my recent tesing.

The use of the GCONFIG4<GMODE> (gesture mode) bit in the current code for gesture_enable is a bit weird here.

Here's the doc describing the GMODE flag from the section covering the GCONF4 (oxAB) register on page 32:

Gesture Mode. Reading this bit reports if the gesture state machine is actively running, 1 = Gesture, 0= ALS, Proximity, Color. Writing a 1 to this bit causes immediate entry in to the gesture state machine (as if GPENTH had been exceeded). Writing a 0 to this bit causes exit of gesture when current analog conversion has finished (as if GEXTH had been exceeded).

This only tells part of the story though. The Gesture Engine description includes a bit more detail on how GMODE is used internally.

During operation, the Gesture engine is entered when its enable bit, GEN, and the operating mode bit, GMODE, are both set. GMODE can be set/reset manually, via I²C, or becomes set when proximity results, PDATA, is greater or equal to the gesture proximity entry threshold, GPENTH. Exit of the gesture engine will not occur until GMODE is reset to zero. During normal operation, GMODE is reset when all 4-bytes of a gesture dataset fall below the exit threshold, GEXTH, for GEXPERS times. This exit condition is also influenced by the gesture exit mask, GEXMSK, which includes all non-masked datum (i.e. singular 1-byte U, D, L, R points). To prevent premature exit, a persistence filter is also included; exit will only occur if a consecutive number of below-threshold results is greater or equal to the persistence value, GEXPERS. Each dataset result that is above-threshold will reset the persistence count. False or incomplete gestures (engine entry and exit without GVALID transitioning high) will not generate a gesture interrupt, GINT, and FIFO data will automatically be purged.

Given how complicated the entry/exit of the gesture machine is it'll take some iterative testing to find the best defaults and how to expose the relevant settings effectively for things like GPENTH, GEXPERS, GEXMSK, and GWTIME. Other things like GINT, GVALID, and GMODE may be useful to read during gesture operations to determine flow in the driver, particularly since it doesn't look like there's any guarantee that every gesture engine run will result in valid data becoming available.

But I don't think we should be writing to GMODE based on this info - GEN being set to true should be enough to enter the gesture state machine. Getting the gesture state machine to reliably return useful data though will depend on making sure that other config registers are configured with values that will work for the majority of use cases.

Once I've worked through quirks in the proximity system (which has some config items that are closely related to gesture) I'm hoping to get this figured out more definitively with some extensive testing.

fivesixzero commented 2 years ago

Spent a bunch of time today getting my head around the state machines and config registers in the APDS-9960. The datasheet is thorough enough but it takes a lot of reading, re-reading and tinkering to get it all figured out. This definitely not an intuitive sensor to work with, particularly the gesture engine. 😢

The root cause of this issue is the fact that the gesture engine gets stuck in an infinite loop. With this nested state machine looping, the overarching device-wide state machine never progresses to the next steps.

That's why our PDATA looks "frozen" - the device hasn't run another proximity measurement yet. And it won't. Ever. Even after a reset, unless that reset included a power cycle.

This infinite looping happens because we haven't set GPEXTH . When that register has a value of 0, the gesture engine will loop indefinitely until the host de-asserts GMODE or the power gets pulled.

There are a few potential fixes for this, like just setting a GPEXTH on init or de-asserting GMODE at the end of our gesture() call. I've tried a few of these types of things but I'm seeing very random-looking results coming from gesture() though.

Thankfully I've got plenty of time this week to dive in. If all goes well I should have a PR up in a few days. I'm hoping to find an elegant fix but in the end a more substantial rewrite/modernization of gesture() may be in order.

fivesixzero commented 2 years ago

Managed to get a good fix on options for making gesture() do its thing much more effectively. Going to just link the notes before I get some sleep. :joy:

FoamyGuy commented 2 years ago

This has been resolved by #39