APDS9960: Gesten erfassen und graphisch darstellen

Hier werden einzelne Projekte mit MicroPython vorgestellt
Antworten
Heinrichs
Beiträge: 173
Registriert: Do 21. Okt 2010, 18:31

APDS9960: Gesten erfassen und graphisch darstellen

Beitrag von Heinrichs » Do 30. Mär 2023, 15:20

Mit Hilfe seiner zahlreichen Photodioden kann der APDS9960-Baustein auch Gesten detektieren. Eine Geste besteht darin, dass eine Hand (Handfläche oder Faust) oder auch ein Papierstreifen in einigen Zentimetern Abstand über den Sensor bewegt wird. Folgende Gesten können erkannt werden (vgl. Abb. 1):

Bewegungsrichtung A:
  • Right (nach Rechts)
  • Left (nach Links)
Bewegungsrichtung B:
  • Up (Vorwärts)
  • Down (Rückwärts)
Gesten3_klein.jpg
Abb. 1
Gesten3_klein.jpg (12.77 KiB) 7903 mal betrachtet



1. Das Prinzip der Gestenerkennung

Im Prinzip funktioniert die Gestenerkennung beim APSD9960 folgendermaßen: Eine IR-LED (in der Abbildung durch einen braunen Punkt gekennzeichnet) sendet Infrarot-Licht aus. Wenn sich ein Gegenstand oberhalb der Sensor-Matrix befindet, reflektiert er dieses Licht. Ein Teil davon gelangt auf die Fotozellen in der Sensor-Matrix. Je nach Position des Gegenstandes erhalten die einzelnen Fotozellen unterschiedlich viel Licht. Der APDS9960 bildet nun fortlaufend 4 Messwerte: Die Messwerte U(p) und D(own) kennzeichnen die vertikale Bewegung, die Messwerte L(eft) und R(ight) die horizontale Bewegung.

gesture_8_left_to_right_klein.jpg
Abb. 2
gesture_8_left_to_right_klein.jpg (39.26 KiB) 7901 mal betrachtet

In der Abb.2 sind die Messwerte für U, D, L und R für eine Geste von Links nach Rechts dargestellt. Man erkennt deutlich, wie zunächst sämtlich Messwerte ansteigen, wenn das Objekt über den Sensor bewegt wird. In diesem Fall ist dabei der R-Wert jeweils größer als der L-Wert. Wenn das Objekt nun weiterbewegt wird, dann werden die Messwerte irgendwann wieder kleiner - ein Zeichen dafür, dass das Objekt sich wieder vom Sensor entfernt und dementsprechend weniger Licht reflektiert wird. Nun sehen wir, dass in diesem Bereich der R-Wert jeweils kleiner als der L-Wert ist. Zudem stellen wir fest, dass in der Anfangs- und Endphase der Geste der Abstand zwischen den jeweiligen L- und R-Werten größer ist als der Abstand zwischen den jeweiligen U- und D-Werten. Diese Phänomene sind auf die unterschiedlichen Positionen der entsprechenden Photodioden zurückzuführen.

Untersucht man auf dieselbe Weise auch die anderen Gesten, kommt man zu folgenden Beobachtungen:

gestentabelle_2.jpg
gestentabelle_2.jpg (36.75 KiB) 7691 mal betrachtet

Um aus den Messdaten den Gesten-Typ abzuleiten, können wir deswegen folgendermaßen vorgehen:

Wir betrachten z. B. nur die letzten Werte für U, D, L und R und vergleichen jeweils |L-R| mit |U-D|. Ist z. B. |L-R| < |U-D|, dann wissen wir, dass es sich um eine Geste in Bewegungsrichtung B handelt. Nun schauen wir die jeweiligen U- und D-Werte an: Wenn jeweils D > U, d. h. U-D < 0 ist, liegt eine Vorwärts-Bewegung vor. Auf die gleiche Weise können mit Hilfe der Tabelle auch die anderen Gesten identifiziert werden.

Es bietet sich an, die Differenzen L-R und U-D nicht einzeln zu betrachten, sondern deren Mittelwerte oder Summen; damit können Messungenauigkeiten ausgeglichen werden. Außerdem sind dann nur noch zwei Vergleiche erforderlich.

Auf ähnliche Weise könn(t)en wir auch die ersten Werte für U, D, L und R auswerten. Beide Auswertungsergebnisse sollten hinsichtlich der Geste natürlich übereinstimmen. Wer weniger Aufwand betreiben möchte, wird sich vielleicht mit einer einzigen Auswertungsweise begnügen; wer mehr Sicherheit bei der Gestenerkennung erzielen möchte, sollte beide Auswertungsweisen benutzen und die Ergebnisse vergleichen.


2. Gewinnung der Messwert-Reihen für die UDLR-Sets

Im folgende mache ich Gebrauch von den Informationen der Webseite Constructing the gesture controller. Allerdings werde ich nicht direkt auf die Register des APDS9960 zugreifen, sondern Methoden und Properties benutzen, welche in dem Modul APDS9960.py (s. Anhang) zur Verfügung stelle. Dieses Modul stellt eine Erweiterung des Moduls APDS9960_ALS_PROX.py dar: Die beiden Klassen ALS und PROX wurden um die Klasse GESTURE ergänzt. Diese stellt folgende Attribute, Low-Level-Methoden und -Properties zur Verfügung (KA=Klassen-Attribut, P=Property, M=Methode):

Klassen-Attribute:
gesture_FORWARD [KA]
gesture_BACKWARD [KA]
gesture_TO_LEFT [KA]
gesture_TO_RIGHT = [KA]
gesture_DATA_LIST_SIZE_MAX [KA]

Low-Level-Properties und -Methoden:
mode [P]
dimension [P]
ledCurrent [P]
gain [P]
GFLVL [P]
GFIFO [P]
enableInterrupt [P]
GValid [P]
GExit [P]
waitTime [P]
gestureDataClear [M] (clear FIFO and reset GFLVL)

High-Level-Methoden:
detect [M] (detects a gesture)
gestureDataProcess [M] (used by detect in order to interpret the data [*])

Bei der Instanziierung werden eine Reihe von Objekt-Attributen mit Startwerten festgelegt:

Code: Alles auswählen

def __init__(self, i2c):
        super().__init__(i2c,_APDS9960_ADDR) # initiate I2CEX with APDS9960_ADDR
        self.gestureData = []
        self.gestureDataCount = 0
        self.timeSinceLastGestureData = utime.ticks_ms()
        self.timeNow = utime.ticks_ms()
        self.gestureInProgress = False
        self.gesturePresent = 0
        self.gestureCount = 0


Im Folgenden werde ich von den Attributen und den Low-Level-Properties und -Methoden reichlich Gebrauch machen: Mit ihrer Hilfe wird das Programm für die Gewinnung der Messwert-Reihen für die UDLR-Sets übersichtlicher. Bei der Erstellung des Programms orientieren wir uns an dem schon in dem Beitrag Der APDS9960-Sensor: Ein Blick hinter die Kulissen vorgestellten Zustandsdiagramm (Abb. 2). Dabei werden wir von der Gesture Engine und zusätzlich auch von der Proximity-Engine Gebrauch machen.

Jetzt fragt man sich vielleicht: Wozu eigentlich soll hier auch die Proximity Engine eingesetzt werden? Nun, die Gesture Engine soll nur dann in Betrieb sein, wenn sich unsere Hand (oder ein anderer Gegenstand) über dem Sensor befindet. Und genau das kann mit der Proximity Engine kontrolliert werden. Nach der Instanziierung

Code: Alles auswählen

import machine
from time import sleep_ms
import APDS9960
import utime

i2c = machine.I2C(1, sda=machine.Pin(22), scl=machine.Pin(21))
apds9960=APDS9960.APDS9960(i2c)      # apds9960 instanziieren

g = apds9960.gesture
p = apds9960.prox
aktivieren wir deswegen nicht nur den Gesture-Sensor, sondern auch den Proximity-Sensor:

Code: Alles auswählen

g.enableSensor() # GEN-Bit setzen
p.enableSensor() # PEN-Bit setzen
p.eProximityGain = 3 # maximale Verstärkung
Nun legen wir Schwellenwerte (thresholds) für das Ein- und Ausschalten des Gesture-Modes fest:

Code: Alles auswählen

PROXIMITY_THRESHOLD_COUNT = 40
GESTURE_EXIT_THRESHOLD_COUNT = 30
g.GEntry = PROXIMITY_THRESHOLD_COUNT
g.GExit = GESTURE_EXIT_THRESHOLD_COUNT
Dadurch wird das GMODE-Bit während des ganzen Programms automatisch immer dann gesetzt (zurückgesetzt), wenn der Proximity-Wert 40 überschritten (30 unterschritten) wird. Erinnern wir uns daran, dass der Proximity-Wert um so größer ist, je näher sich die Hand am Sensor befindet!

Jetzt sorgen wir noch mit der Anweisung

g.waitTime = 0

dafür, dass es keine zusätzliche Wartezeit zwischen den einzelnen Messungen für die UDLR-Sets gibt.

Die folgende Funktion detect() kontrolliert nun, ob Messwerte vorliegen. Wenn dies der Fall ist, werden sie in Form einer Liste von UDRL-Sets zurückgegeben, andernfalls ist der Rückgabewert None. Die UDLR-Sets bestehen ihrerseits jeweils aus einer Liste mit den Werten für U, D, L und R.Die Funktionsweise der Funktion detect() ergibt sich größtenteils aus den Erläuterungen des Quelltexts. An dieser Stelle sind nur zwei Vorbemerkungen wichtig:
  • Der APDS9960 legt die Messwerte für U, D, L, R in einem FIFO ab. Genauer gesagt handelt es sich eigentlich um 4 einzelne FIFO-Speicher. Es ist aber einfacher, sich einen einzigen FIFO vorzustellen, der in seinen Zellen UDRL-Sets speichert. In der Abb. 4 wurden das Set UDLR0 zuerst gemessen, das Set UDLR5 zuletzt. Demnach liegen 6 UDLR-Sets vor; diese Anzahl kann mit g.GFLVL ermittelt werden. Mit dem Property g.GFIFO wird jeweils die linke Speicherzelle gelesen; in unserem Fall verschwindet damit UDLR0 aus dem FIFO und die restlichen UDLR-Sets rücken entsprechend automatisch nach. (Das macht die Gesture-Engine automatisch!) Man beachte: g.GFIFO liefert das UDLR-Set in Form eines ByteStrings.

    GFIFO.jpg
    Abb. 3
    GFIFO.jpg (12.31 KiB) 7708 mal betrachtet
  • Die einzelnen UDLR-Sets werden in einer Liste g.Data gesammelt. Wenn (über einen längeren Zeitraum, z. B. 100 ms) keine neuen UDLR-Sets registriert werden können, sehen wir dies als ein Zeichen dafür an, dass die Geste abgeschlossen ist. In diesem Fall soll die detect-Funktion das Sammeln der Gesten-Daten beenden und die Liste g.Data als Rückgabewert ausgeben. Um eine solche Zeitüberschreitung feststellen zu können, wird mit g.timeSinceLastGestureData = utime.ticks_ms() bei jeder Gewinnung eines UDLR-Sets ein “Zeitstempel” gespeichert.


Code: Alles auswählen

# Geste erkennen und diese als Liste von UDLR-Sets zurückgeben
# Dabei besteht jedes Set seinerseits aus einer Liste mit den Werten für U, D, L und R
def detect():
    g.Data = [] # Start mit leerer Liste
    g.gestureDataCount = 0 
    g.gesturePresent = g.GValid # Das GVALID-Bit gibt an, ob ein aktuelles (valides) UDLR-Set vorliegt
    if g.gesturePresent == 1 :
        g.gestureInProgress = True # zeigt an, dass gerade eine Gesten-Messung durchgeführt wird
        g.gestureCount = g.GFLVL # Anzahl der UDLR-Sets im FIFO 
        print('Geste liegt vor')
        while g.gestureCount > 0 : 
            g.gestureData.append(g.GFIFO) # fügt die 4 Bytes für U-D-L-R (UDLR-Set) als Bytestring an die bestehende Liste (zu Beginn []) an.
            g.gestureDataCount += 1 # Zähler für die gesammelten UDLR-Sets 
            g.gestureCount = g.GFLVL # Anzahl der UDLR-Sets im FIFO
            # print('gestureDataCount =', g.gestureDataCount)
            if g.gestureDataCount > g.gesture_DATA_LIST_SIZE_MAX: # Wenn Liste der UDLR-Sets zu lang, dann gesammte Liste löschen, um Speicher-Probleme zu vermeiden
                g.gestureDataClear()
        # print(g.gestureData)
        if g.gestureDataCount > 0 : # Hier geht es weiter, wenn FIFO leer ist (d. h. alle Gesten-Daten ausgelesen worden sind)
            # Ggf.Vorletztes UDLR-Set ausgeben
            # print('GestureData = ', g.gestureData[g.gestureDataCount-1][0], g.gestureData[g.gestureDataCount-1][1], g.gestureData[g.gestureDataCount-1][2], g.gestureData[g.gestureDataCount-1][3])
            g.timeSinceLastGestureData = utime.ticks_ms()        
    else:
        if g.gestureInProgress == False :
            g.timeSinceLastGestureData = utime.ticks_ms()

    GESTURE_PROCESS_TIMEOUT = 100
    g.timeNow = utime.ticks_ms()
    if (g.timeNow - g.timeSinceLastGestureData) > GESTURE_PROCESS_TIMEOUT :
        g.gestureInProgress = False
        print('Gesture Process ended')
        result = (g.gestureData, g.gestureDataCount)
        return result
Die ermittelten Gesten-Daten können wir mit dem folgenden Haupt-Programm auf dem Terminal ausgeben:

Code: Alles auswählen

g.gestureDataClear() # FIFO vorsichtshalber löschen
r = detect()
while r == None: # solange keine Geste gefunden wurde, immer wieder aufs Neue probieren
    r = detect()

data, datacount = r
print('datacount=', datacount) # Anzahl der UDLR-Sets
# UDLR-Daten (in einem csv-Format) auf dem Terminal ausgeben
print('U;D;L;R')
for d in data:
    print(d[0], end = '')
    print(';', end ='')
    print(d[1], end = '')
    print(';', end ='')
    print(d[2], end = '')
    print(';', end ='')
    print(d[3])
Um die Daten wie in Abb. 2 mit EXCEL graphisch darzustellen, gehen Sie folgendermaßen vor:
  • Kopieren Sie die ausgegebenen Daten - beginnend mit der Zeile “U; D; L; R” - über die Zwischenablage in einen Texteditor wie Notepad.
  • Speichern Sie mit dem Texteditor die Datei (mit der Standard-Extension txt) ab.
  • Starten Sie nun EXCEL und öffnen Sie diese Datei mit Datei - Öffnen. Achten Sie dabei darauf, dass Sie die Option “Textdateien” (rechts neben dem Dateinamen) wählen! In dem nun erscheinenden Fenster betätigen Sie zunächst die Schaltfläche “Weiter” und wählen danach als Trennzeichen (Delimiter) das Semikolon. Wenn Sie anschließend die Schaltfläche “Fertig stellen” betätigen, werden die Messwerte als Tabelle mit den Spaltenüberschriften U, D, L und R angezeigt.
  • Zur graphischen Darstellung markieren Sie nun einfach den Bereich mit den Daten (inklusive der UDRL-Beschriftungszeile). Mit Einfügen - Linie erhalten Sie dann ein zugehöriges Liniendiagramm.
.
Dateianhänge
APDS9960.zip
APDS9960-Modul
(6.64 KiB) 505-mal heruntergeladen

Antworten