ADXL345 – der universelle Beschleunigungssensor – Teil 2

Über den Beitrag

Das ist nun der zweite Teil meines Beitrages über den ADXL345 Beschleunigungssensor und meine zugehörige Bibliothek. Er baut auf dem ersten Teil, in dem ich auf die allgemeinen Eigenschaften und die ersten sieben Sketche eingegangen bin, auf.

In diesem Teil liegt der Schwerpunkt auf:

  • Activity / Inactivity
    • hier hole ich auch die Auto Sleep Funktion nach
  • Single und Double Tap
  • FIFO Funktionen

Weitere Hinweise:

  • Die für die Beispielsketche verwendete Schaltung entspricht der des ersten Teils.
  • Auch hier findet ihr nur die verkürzten Beispielsketche. Die Originalsketche bekommt ihr mit der Bibliothek. Sie enthalten erheblich mehr Kommentare, vor allem betrifft das die Optionen für die Funktionsparameter.
  • Ich habe nach Erstellung des ersten Teils noch ein paar Bugs gefunden. Installiert die Updates über die Arduino IDE oder haltet Ausschau nach der neuesten Version auf GitHub!

Activity – Inactivity – Auto Sleep

Der Activity Interrupt wird ausgelöst, wenn an den festgelegten Achsen ein bestimmter Beschleunigungswert überschritten wird. Der Inactivity Interrupt wird ausgelöst, wenn an den teilnehmenden Achsen ein bestimmter Beschleunigungswert für eine bestimmte Zeit unterschritten wird.

Beide Interrupts können unabhängig voneinander eingerichtet werden. Wegen ihrer Verwandtschaft behandele ich beide in einem Beispielsketch. Außerdem lässt sich so die Wirkung des Link Bits am besten zeigen. 

Die Interrupts des ADXL345 im Allgemeinen habe ich im letzten Beitrag behandelt.

Beispielsketch 8: ADXL345_activity_inactivity_interrupt.ino

Hier verwende ich die folgenden neuen Funktionen:

  • setActivityParameters(mode, axes, g-threshold)
    • mode: ist DC oder AC
      • DC: das Limit (threshold) gilt absolut.
      • AC: das Limit ist das Delta zum Startwert.
    • axes: die beteiligten Achsen.
    • g-threshold: das Limit in g.
  • setInactivityParameters(mode, axes, g-threshold, time-threshold)
    • mode, axes, g-threshold: wie oben.
    • time-threshold: das zeitliche Limit in Sekunden (max. 255).
  • setLinkBit(true/false)
    • Erkläre ich weiter unten.
  • getActTapStatusAsString()
    • Liefert als String zurück, welche Achse(n) für den Activity Interrupt verantwortlich war(en). Wird auch für die Tap Funktionen verwendet.
  • getActTapStatus()
    • Liefert den Inhalt des Act Tap Status Registers als byte zurück. Die Bedeutung des zurückgelieferten Wertes erschließt sich aus dem Datenblatt und den Bibliotheksdateien. Für Fortgeschrittene.

Im Sketch findet ihr aufgelistet, wie die Parameter im Einzelnen anzugeben sind. Zusätzlich müsst ihr die Interrupts noch mit setInterrupt() aktivieren.

Für den Activity und Inactivity Interrupt stelle ich den DC Modus ein, wähle die x- und y-Achse aus, 0,5 g als  g-Limit und nehme 10 Sekunden als Zeitlimit für die Inaktivität. 

Da ein Interrupt sowohl ein Activity wie auch ein Inactivity Interrupt sein kann, wird mit intSource = readAndClearInterrupt() der Inhalt des Interrupt Registers abgefragt und dann mit checkInterrupt(intSource, interruptType) geprüft, ob interruptType der Auslöser war.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53
const int int2Pin = 2;
volatile bool in_activity = false; // in_activity: either activity or inactivity interrupt occured

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Activity and Inactivity Interrupts");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);


  myAcc.setDataRate(ADXL345_DATA_RATE_25);
  myAcc.setRange(ADXL345_RANGE_4G);
  
  attachInterrupt(digitalPinToInterrupt(int2Pin), in_activityISR, RISING);

/* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.5);
  
/* Four parameters have to be set for in activity:
    1. DC / AC Mode:
        see activity parameters
    2. Axes, that are considered:
        see activity parameters
    3. Threshold in g
    4. Inactivity period threshold in seconds (max 255)
*/  
  myAcc.setInactivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.5, 10.0);
  
/* Setting the link bit only makes sense if both activity and inactivity are used
 *  If link bit is not set:  
 *   - activity interrupt can be triggered any time and multiple times
 *   - inactivity can be triggered if inactivity param. are met, independent of activity param. (if used) 
 *  If link bit is set:
 *   - activity interrupt can only be triggered after inactivity interrupt 
 *   - only one activity interrupt can be triggered until the next inactivity interrupt occurs
 */
   // myAcc.setLinkBit(true);

/* You can choose the following interrupts:
     Variable name:             Triggered, if:
    ADXL345_OVERRUN      -   new data replaces unread data
    ADXL345_WATERMARK    -   the number of samples in FIFO equals the number defined in FIFO_CTL
    ADXL345_FREEFALL     -   acceleration values of all axes are below the thresold defined in THRESH_FF 
    ADXL345_INACTIVITY   -   acc. value of all included axes are < THRESH_INACT for period > TIME_INACT
    ADXL345_ACTIVITY     -   acc. value of included axes are > THRESH_ACT
    ADXL345_DOUBLE_TAP   -   double tap detected on one incl. axis and various defined conditions are met
    ADXL345_SINGLE_TAP   -   single tap detected on one incl. axis and various defined conditions are met
    ADXL345_DATA_READY   -   new data available

    Assign the interrupts to INT1 (INT_PIN_2) or INT2 (INT_PIN_2). Data ready, watermark and overrun are 
    always enabled. You can only change the assignment of these which is INT1 by default.

    You can delete interrupts with deleteInterrupt(type);
*/
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);
  myAcc.setInterrupt(ADXL345_INACTIVITY, INT_PIN_2);
  
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are responsible activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); return the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/

void loop() {
  if ((millis() % 1000) == 1) {
    xyzFloat g = myAcc.getGValues();
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }

  if(in_activity == true) {
      //byte actTapSource = myAcc.getActTapStatus();
      //Serial.println(actTapSource, BIN);
      String axes = myAcc.getActTapStatusAsString();
      byte intSource = myAcc.readAndClearInterrupts();
      if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
        Serial.print("Activity at: ");
        Serial.println(axes);
      }
     
     if(myAcc.checkInterrupt(intSource, ADXL345_INACTIVITY)){
        Serial.println("Inactivity!");
      }
      
    delay(1000);
    myAcc.readAndClearInterrupts();
    in_activity = false;
  }
}

void in_activityISR() {
  in_activity = true;
}

 

Ausgabe des Activity / Inactivity Sketches

Wird das Kriterium für einen Aktivitätsinterrupt erfüllt, wird dieser ausgelöst. Da ich die Grenzen für Activity und Inactivity gleich gesetzt habe, setzt ein Aktivitätsinterrupt den Zeitnehmer für den Inaktivitätsinterrupt wieder auf null.

ADXL345 Beispielsketch:
Ausgabe von ADXL345_activity_inactivity.ino
Ausgabe von ADXL345 activity_inactivity.ino

Das Link Bit

Und nun entkommentiert mal die Zeile mit der Funktion setLinkBit(true) und startet den Sketch neu. Activity und Inactivity sind nun miteinander gekoppelt. Ein Activity Interrupt – und zwar immer nur einer – kann nur dann ausgelöst werden, wenn zuvor ein Inactivity Interrupt ausgelöst wurde. Probiert es am besten aus.

Beispielsketch 9: ADXL345_auto_sleep.ino

Hier holen wir jetzt den Auto Sleep Modus nach. Ist dieser aktiviert, geht der ADXL345 in den Sleep Mode, wenn ein Inactivity Interrupt ausgelöst worden ist. Ein Activity Interrupt weckt den ADXL345 wieder auf. Wie im normalen Sleep Mode wacht der ADXL345 in einer wählbaren Frequenz von 1 bis 8 Hz auch ohne Activity Interrupt zwischenzeitlich kurz auf.

Für den Auto Sleep Modus muss das Link Bit gesetzt sein. Das macht die Bibliothek im Hintergrund automatisch. Entsprechend verhalten sich der Actitvity und der Inactivity Interrupt voneinander abhängig.

Folgende Funktionen sind neu:

  • setAutoSleep(true/false, frequency)
    • true/false ist der An-/Ausschalter.
    • frequency legt die Aufwachfrequenz fest.
  • setAutoSleep(true/false)
    • Ein-/Ausschalten der Auto Sleep Funktion ohne Änderung der Aufwachfrequenz.
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53 // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool in_activity = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Auto Sleep");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }
/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);
  
  myAcc.setDataRate(ADXL345_DATA_RATE_25);
  myAcc.setRange(ADXL345_RANGE_4G);
 
  attachInterrupt(digitalPinToInterrupt(int2Pin), in_activityISR, RISING);

/* The following settings are similar to the settings in ADXL345_activity_inactivity_interrupt.ino */

/* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold 
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/  
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.8);

/* Four parameters have to be set for in activity:
    1. DC / AC Mode:
        see activity parameters
    2. Axes, that are considered:
        see activity parameters
    3. Threshold in g
    4. Inactivity period threshold in seconds (max 255)
*/    
  myAcc.setInactivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.8, 10);
  
/* You can choose the following interrupts:
     Variable name:             Triggered, if:
    ADXL345_OVERRUN      -   new data replaces unread data
    ADXL345_WATERMARK    -   the number of samples in FIFO equals the number defined in FIFO_CTL
    ADXL345_FREEFALL     -   acceleration values of all axes are below the threshold defined in THRESH_FF 
    ADXL345_INACTIVITY   -   acc. value of all included axes are < THRESH_INACT for period > TIME_INACT
    ADXL345_ACTIVITY     -   acc. value of included axes are > THRESH_ACT
    ADXL345_DOUBLE_TAP   -   double tap detected on one incl. axis and various defined conditions are met
    ADXL345_SINGLE_TAP   -   single tap detected on one incl. axis and various defined conditions are met
    ADXL345_DATA_READY   -   new data available

    Assign the interrupts to INT1 (INT_PIN_1) or INT2 (INT_PIN_2). Data ready, watermark and overrun are 
    always enabled. You can only change the assignment of these which is INT1 by default.

    You can delete interrupts with deleteInterrupt(type);
*/ 
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);
  myAcc.setInterrupt(ADXL345_INACTIVITY, INT_PIN_2);
 
/* Auto sleep is connected with activity and inactivity. The device goes in sleep when inactivity is 
    detected. The link bit must be set, if you want to use auto sleep. The library sets the link bit 
    automatically. When the ADXL345 goes into sleep mode it wakes up periodically (default is 8 Hz).
    
    Choose the wake up frequency:
    ADXL345_WUP_FQ_1  =  1 Hz
    ADXL345_WUP_FQ_2  =  2 Hz
    ADXL345_WUP_FQ_4  =  4 Hz 
    ADXL345_WUP_FQ_8  =  8 Hz
    
*/
  myAcc.setAutoSleep(true, ADXL345_WUP_FQ_1);
  // alternative: myAcc.setAutoSleep(true/false) without changing the wake up frequency.
}

void loop() {
  if ((millis() % 300) == 1) {
    xyzFloat g = myAcc.getGValues();
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }

  if(in_activity == true) {
    byte intSource = myAcc.readAndClearInterrupts();
    if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
      Serial.println("Activity!");
      if(!myAcc.isAsleep()){        //check the asleep bit
        Serial.println("I am awake!");
      }
    }
     
    if(myAcc.checkInterrupt(intSource, ADXL345_INACTIVITY)){
      Serial.println("Inactivity!");
      if(myAcc.isAsleep()){
        Serial.println("I am sleeping...");
      }
    }
      
    myAcc.readAndClearInterrupts();
    in_activity = false;
  }
}

void in_activityISR() {
  in_activity = true;
}

 

Ausgabe des Auto Sleep Sketches

Unten seht ihr den Ausschnitt einer Ausgabe. Dabei habe ich den ADXL345 unterhalb des Activity Limits bewegt. Dass der ADXL345 wirklich schläft, erkennt ihr daran, dass der Messwert im Schlafmodus nur jede Sekunde aktualisiert wird (wie beim Sleep Beispielsketch). Da die Abfrage alle 300 ms erfolgt, sind jeweils 3-4 Werte identisch.

ADXL345 Beispielsketch:
Ausgabe von ADXL345_auto_sleep.ino
Ausgabe von ADXL345 auto_sleep.ino

Single und Double Tap

Ein Single Tap (= Schlag, Klaps, Klopfen) Ereignis ist im Grunde ein Activity Ereignis, aber mit einer definierten maximalen Dauer (DUR). Ist das Ereignis länger als DUR, wird es nicht als Single Tap gewertet. Damit lässt sich beispielsweise eine Erschütterung von einer längeren Beschleunigung unterscheiden. Der nächste Tap kann erst nach Ablauf einer Latenz (latent) detektiert werden. Die Latenz beginnt, nachdem das Limit (Threshold) unterschritten wurde.

Ein Double Tap besteht aus zwei Single Taps mit jeweils identischen Bedingungen. Dabei gilt die zusätzliche Bedingung, dass der zweite Tap in ein Zeitfenster (Window) fallen muss, das nach Ablauf der Latenz des ersten Taps beginnt. Klingt kompliziert, ist es aber nicht, wenn man es sich grafisch dargestellt anschaut:

ADXL345 Single und Double Tap Detektion
ADXL345 Single und Double Tap Detektion

Für die Single und Double Tap Detektion braucht ihr eher hohe Messfrequenzen. Es wäre beispielsweise sinnlos, einen Wert für DUR im Millisekundenbereich anzugeben, aber Messwerte im Bereich von Sekunden aufzunehmen. Das muss zusammenpassen.

Beispielsketch 10: ADXL345_single_tap.ino

Für den Single und den Double Tap könnt ihr eine Reihe von Parametern festlegen. Die Parameter, die sowohl für den Single, wie auch für den Double Tap gelten, werden mit der folgenden Funktion definiert:

  • setGeneralTapParameters(axes, g-threshold, duration, latent)
    • axes sind wieder die beteiligten Achsen.
    • g-threshold ist das Limit in g (diesmal ohne DC/AC Modus).
      • Erschütterungen haben meist hohe Beschleunigungen und verursachen „Echos“, das Datenblatt empfiehlt deshalb 3 g als Startwert.
    • duration ist die maximale Dauer des Taps in Millisekunden. Das Datenblatt empfiehlt Werte größer 10 ms. Das Maximum ist 159 ms.
    • latent ist die Latenz in Millisekunden. Sie sollte größer als 20 ms sein. Das Maximum ist 318 ms.

Die Funktion getActTapStatusAsString() kennt ihr schon aus ADXL345_activity_inactivity.ino. Hier liefert sie nun die für den Tap Interrupt verantwortliche Achse zurück oder gegebenenfalls mehrere.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53 // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool tap = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Single Tap Interrupt");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);
  
  myAcc.setDataRate(ADXL345_DATA_RATE_200);
  myAcc.setRange(ADXL345_RANGE_8G);

  attachInterrupt(digitalPinToInterrupt(int2Pin), tapISR, RISING);
  
/* The following four parameters have to be set for tap application (single and double):
    1. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    2. Threshold in g
        It is recommended to not choose the value to low. 3g is a good starting point. 
    3. Duration in milliseconds (max 159 ms): 
        maximum time that the acceleration must be over g threshold to be regarded as a single tap. If 
        the acceleration drops below the g threshold before the duration is exceeded an interrupt will be 
        triggered. If also double tap is active an interrupt will only be triggered after the double tap 
        conditions have been checked. Duration should be greater than 10. 
    4. Latency time in milliseconds (maximum: 318 ms): minimum time before the next tap can be detected.
        Starts at the end of duration or when the interrupt was triggered. Should be greater than 20 ms.  
*/
  myAcc.setGeneralTapParameters(ADXL345_XY0, 3.0, 30, 100.0);
  myAcc.setInterrupt(ADXL345_SINGLE_TAP, INT_PIN_2); 
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are resposible activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); return the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/
void loop() {
  if ((millis() % 1000) == 1) {
    xyzFloat g = myAcc.getGValues();
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }

  if(tap == true) {
      //byte actTapSource = myAcc.getActTapStatus();
      //Serial.println(actTapSource, BIN);
      String axes = myAcc.getActTapStatusAsString();
      byte intSource = myAcc.readAndClearInterrupts();
      if(myAcc.checkInterrupt(intSource, ADXL345_SINGLE_TAP)){
        Serial.print("TAP at: ");
        Serial.println(axes);
      }
    
    delay(1000);
    myAcc.readAndClearInterrupts();
    tap = false;
  }
}

void tapISR() {
  tap = true;
}

 

Ausgabe des Single Tap Sketches

Für die Ausgabe unten habe ich zuerst mehrfach in Richtung der x-Achse auf das Modul geklopft und dann in Richtung der y-Achse. Die Messwerteausgabe hat übrigens keine tiefere Bedeutung. Das ist nur damit es zwischendurch nicht langweilig wird.

ADXL345 Beispielsketch:
Ausgabe von ADXL345_single_tap.ino
Ausgabe von ADXL345 single_tap.ino

Beispielsketch 11: ADXL345_double_tap.ino

Für die Double Tap Detektion definiert ihr zunächst die allgemeinen Parameter wie für die Singles Taps. Dann legt ihr zwei weitere Parameter für die Double Taps fest:

  • setAdditionalDoubleTapParameters(suppress, window)
    • suppress ist true oder false und legt fest, ob das Suppress Bit gesetzt wird. Wenn das Suppress Bit gesetzt ist, dann invalidiert ein zweiter Tap innerhalb der Latenz des ersten Taps die Double Tap Bedingung. Alles klar? Falls nicht, dann geht ins Datenblatt. Dort gibt es eine Grafik dazu auf Seite 29. 
    • window setzt das Zeitfenster in dem der zweite Tap stattfinden muss, um als Double Tap gewertet zu werden. Die Angabe erfolgt in Millisekunden, das Maximum ist 318 ms.

Im Beispielsketch habe ich sowohl den Single, wie auch die Double Tap Interrupt aktiviert. Da also zwei unterschiedliche Interrupts vorliegen können, muss wieder geprüft werden, welcher Interrupt vorliegt.

Der Sinn der Single und Double Tap Detektion liegt darin, nach sehr charakteristischen Erschütterungen Ausschau zu halten. Dafür ist allerdings viel Ausprobieren nötig. Spielt einfach ein bisschen mit den Parametern herum.

Ausgabe des Double Tap Sketches

So könnte z.B. eine Ausgabe aussehen:

ADXL345 Beispielsketch:
Ausgabe von ADXL345_double_tap.ino
Ausgabe von ADXL345 double_tap.ino

Für dieses Beispiel habe ich den ADXL345 mit kurzen Doppelschlägen in Richtung der x-Achse traktiert.

FIFO Funktionen

Der FIFO Pufferspeicher ist eine Art Aufnahmegerät für Messwerte. Damit könnt ihr z.B. sehr schnelle Ereignisse bei 3200 Hz Messfrequenz verfolgen. Oder ihr analysiert, was um ein bestimmtes Ereignis herum (Aktivität oder Tap) passiert.

Im FIFO haben bis zu 32 Messwerte Platz und zwar volle x,y,z-Datensätze. Ihr könnt die Anzahl auswählen. FIFO steht für „first in, first out“, d.h. die Messwerte, die zuerst gespeichert werden, werden später auch zuerst ausgelesen. Das Auslesen funktioniert wie das normale Lesen von Messwerten. Nur werden die Datenregister nicht mit frischen Messwerten versorgt, sondern aus dem FIFO.

Es gibt vier Betriebsarten:

  • Bypass: FIFO ist deaktiviert, was der Standard ist. Die Messwerte werden im Datenregisterm bereitgestellt und neue Messwerte überschreiben die alten.
  • FIFO: ihr gebt den Startschuss zum Aufnehmen der Werte. Wenn der Puffer die von euch definierte Anzahl an Messwerten enthält, endet die Aufnahme. Der ADXL345 löst den Watermark Interrupt aus. Ein bisschen verwirrend, dass einer der FIFO Modi auch FIFO heißt.
  • Stream: wie der Name es vermuten lässt, werden ständig Werte in den Pufferspeicher übernommen. Wenn der FIFO voll ist, werden weiterhin Werte hineingeschoben und die jeweils ältesten fallen heraus. Ihr bestimmt das Ende der Messwerteaufnahme.
  • Trigger: Messwerte werden wie im Stream Modus permanent aufgenommen. Bei einem von euch gewählten Ereignis (= Trigger, Interrupt) verbleibt nur die von euch gewählte Anzahl an Werten im FIFO. Dann allerdings wird der FIFO mit laufenden Messwerten bis 32 aufgefüllt. Dann endet der Prozess. Ihr könnt hier also z.B. die Mitte einer Messwerteaufnahme festlegen.

Beispielsketch 12: ADXL345_fifo_fifo.ino

Folgende neue Funktionen kommen zum Einsatz:

  • setFifoParameters(TriggerIntPin, number)
    • Der TriggerIntPin kommt erst beim Trigger Modus zum Tragen. In diesem Beispiel ist unwichtig, welchen (der erlaubten) Parameter ihr wählt.
    • number legt die maximale Anzahl an Messwerten im FIFO fest.
  • setFifoModes(mode)
    • Mit mode legt ihr den Modus fest.

Die Einstellung des Fifo Modus ist also sehr einfach. Etwas komplexer sind die anderen Einstellungen, damit das ganze richtig funktioniert. Insbesondere Interrupts bieten viele Möglichkeiten für Fehler. Es ist wichtig, die richtige Abfolge einzuhalten.

Ein paar Anmerkungen zum Beispielsketch:

  • Die Messfrequenz habe ich mit 6,25 Hz recht niedrig angesetzt. Bei 32 Messwerten dauert es ca. 5 Sekunden bis der FIFO voll ist. Genug Zeit um den ADXL345 beispielsweise langsam zu kippen.
  • Die Messwerteermittlung habe ich bis zum Beginn der „FIFO-Aufzeichnung“ deaktiviert.
  • Die Messungen werden mit einem Countdown gestartet. Ihr könnt natürlich auch etwas anderes als Startschuss nehmen, zum Beispiel einen Interrupt.
  • Der Watermark Interrupt liegt auf den Pin INT2 und signalisiert das Ende der Messung.
  • Am Ende der Messung gebe ich 34 x,y,z-Wertetripel aus. Das ist mehr als der FIFO Puffer fasst. Ich möchte damit nur zeigen, dass nach dem 32sten Wertripel nichts mehr passiert.
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53 // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool event = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - FIFO - Fifo Mode");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

/* Insert your data from ADXL345_calibration.ino and uncomment for more precise results */
  // myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);
  
  myAcc.setDataRate(ADXL345_DATA_RATE_6_25);
  myAcc.setRange(ADXL345_RANGE_4G);

/* Stop the measure mode - no new data will be obtained */
  myAcc.setMeasureMode(false);
  attachInterrupt(digitalPinToInterrupt(int2Pin), eventISR, RISING);

/* You can choose the following interrupts:
     Variable name:             Triggered, if:
    ADXL345_OVERRUN      -   new data replaces unread data
    ADXL345_WATERMARK    -   the number of samples in FIFO equals the number defined in FIFO_CTL
    ADXL345_FREEFALL     -   acceleration values of all axes are below the threshold defined in THRESH_FF 
    ADXL345_INACTIVITY   -   acc. value of all included axes are < THRESH_INACT for period > TIME_INACT
    ADXL345_ACTIVITY     -   acc. value of included axes are > THRESH_ACT
    ADXL345_DOUBLE_TAP   -   double tap detected on one incl. axis and various defined conditions are met
    ADXL345_SINGLE_TAP   -   single tap detected on one incl. axis and various defined conditions are met
    ADXL345_DATA_READY   -   new data available

    Assign the interrupts to INT1 (INT_PIN_1) or INT2 (INT_PIN_2). Data ready, watermark and overrun are 
    always enabled. You can only change the assignment of these which is INT1 by default.

    You can delete interrupts with deleteInterrupt(type);
*/ 
  myAcc.setInterrupt(ADXL345_WATERMARK, INT_PIN_2); // Interrupt when FIFO is full
  
/* The following two FIFO parameters need to be set:
    1. Trigger Bit: not relevant for this FIFO mode.
    2. FIFO samples (max 32). Defines the size of the FIFO. One sample is a set of an x,y,z triple.
*/
  myAcc.setFifoParameters(ADXL345_TRIGGER_INT_1, 32);

/* You can choose the following FIFO modes:
    ADXL345_FIFO     -  you choose the start, ends when FIFO is full (at defined limit)
    ADXL345_STREAM   -  FIFO always filled with new data, old data replaced if FIFO is full; you choose the stop
    ADXL345_TRIGGER  -  FIFO always filled up to 32 samples; when the trigger event occurs only defined number of samples
                        is kept in the FIFO and further samples are taken after the event until FIFO is full again. 
    ADXL345_BYPASS   -  no FIFO
*/   
  myAcc.setFifoMode(ADXL345_FIFO);
}

void loop() {
  event = false; 
  
  countDown(); // as an alternative you could define any other event to start to fill the FIFO
  myAcc.readAndClearInterrupts();
  myAcc.setMeasureMode(true); // this is the actual start
  while(!event){}  // event = true means WATERMARK interrupt triggered -> means FIFO is full
  myAcc.setMeasureMode(false);
  Serial.println("FiFo full");
  
  for(int i=0; i<34; i++){ // this is > 32 samples, but I want to show that the values do not change when FIFO is full
    xyzFloat g = myAcc.getGValues();
    
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }
  Serial.println("For another series of measurements, enter any key and send");
  
  while(!(Serial.available())){}
  Serial.read();
  Serial.println();
}

void eventISR() {
  event = true;
}

void countDown(){
  Serial.println("Move your ADXL345 to obtain interesting data");
  Serial.println();
  delay(1000);
  Serial.print("Fifo collection begins in 3, "); 
  delay(1000);
  Serial.print("2, "); 
  delay(1000);
  Serial.print("1, "); 
  delay(1000);
  Serial.println("Now!");
}

 

Ausgabe des FIFO FIFO Sketches

Für die unten abgebildete Ausgabe habe ich den ADXL345 langsam um die y-Achse gedreht (also die x- und z-Werte variiert).

Ausgabe von ADXL345 fifo_fifo.ino
Ausgabe von ADXL345 fifo_fifo.ino

Bespielsketch 13: ADXL345_fifo_stream.ino

Der Beispielsketch verwendet keine neuen Funktionen. Ein paar Anmerkungen:

  • Mit setFifoMode(ADXL345_STREAM) wird der Stream Modus eingestellt.
  • Als Ereignis, das die Messwertaufnahme beendet, habe ich einen Activity Interrupt ausgewählt: 0,6 g an der x- oder y-Achse.
  • Die 32 Messwerte vor Eintreten des Ereignisses werden im FIFO gespeichert.
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53  // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool event = false;

/* There are several ways to create your ADXL345 object:
 * ADXL345_WE myAcc = ADXL345_WE()                -> uses Wire / I2C Address = 0x53
 * ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR) -> uses Wire / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2)          -> uses the TwoWire object wire2 / ADXL345_I2CADDR
 * ADXL345_WE myAcc = ADXL345_WE(&wire2, ADXL345_I2CADDR) -> all together
 */
ADXL345_WE myAcc = ADXL345_WE(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - FIFO - Stream Mode");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

  myAcc.setDataRate(ADXL345_DATA_RATE_12_5);
  myAcc.setRange(ADXL345_RANGE_4G);

  attachInterrupt(digitalPinToInterrupt(int2Pin), eventISR, RISING);

 /* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.6);
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);

/* The following two FIFO parameters need to be set:
    1. Trigger Bit: not relevant for this FIFO mode.
    2. FIFO samples (max 32). Defines the size of the FIFO. One sample is a set of an x,y,z triple.
*/
  myAcc.setFifoParameters(ADXL345_TRIGGER_INT_1, 32);
  myAcc.setFifoMode(ADXL345_STREAM);

  Serial.println("Waiting for Activity Event - I propose to slowly turn the ADXL345");
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are responsible for activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); returns the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/

void loop() {
  if(event){
    myAcc.setMeasureMode(false);
    //byte actTapSource = myAcc.getActTapStatus();
    //Serial.println(actTapSource, BIN);
    String axes = myAcc.getActTapStatusAsString();
    byte intSource = myAcc.readAndClearInterrupts();
    if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
        Serial.print("ACT at: ");
        Serial.println(axes);
    }
    printFifo();
    Serial.println("For another series of measurements, enter any key and send");
    while(!(Serial.available())){}
    Serial.read();
    Serial.println();
    Serial.println("Waiting for Activity Event");
  
    event = false;
    myAcc.readAndClearInterrupts();
    myAcc.setMeasureMode(true);
  }
}

void eventISR() {
  event = true;
}

void printFifo(){
  for(int i=0; i<32; i++){
    xyzFloat g = myAcc.getGValues();
    
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }
}

 

Ausgabe des FIFO Stream Sketches

Auch hier habe ich wieder den ADXL345 langsam um die y-Achse gedreht.

Ausgabe von ADXL345 fifo_stream.ino
Ausgabe von ADXL345 fifo_stream.ino

Beispielsketch 14: ADXL345_fifo_trigger.ino

Und nun zum letzten Beispielsketch. Hier wird die Aufnahme der FIFO Daten über einen Trigger gesteuert. Dazu komme ich noch mal auf die folgende Funktion zurück:

  • setFifoParameters(TriggerIntPin, number)
    • TriggerIntPin stellt ihr auf INT1 oder INT2 ein. Ein Interrupt an dem ausgewählten Pin ist der Trigger für die FIFO Aufnahme. Nun müsst ihr noch einen Interrupt auswählen, den ihr diesem Pin zuordnet. Dafür habe ich wieder Activity gewählt.
    • number sollte irgendwo zwischen 1 und 32 liegen. Würdet ihr 32 wählen, entspräche das dem Stream Modus. Bei 1 könntet ihr genauso gut den FIFO FIFO Modus wählen. Für das Beispiel habe ich 15 gewählt.

Stellt sich noch die Frage, wie man das Ende der Messungen detektiert. Im Prinzip ginge das mit dem Watermark Interrupt. Wenn wir diesen auf INT2 legen, dann kommen wir unter Umständen mit dem Activity Interrupt durcheinander. Legen wir ihn auf INT1, dann gibt es unter Umständen Probleme mit dem Data Ready Interrupt, der sich ja dummerweise nicht abstellen lässt. Eine schlichte Methode wäre ein einfaches delay, das der Messfrequenz angepasst werden müsste. Glücklicherweise lässt sich die Anzahl der im FIFO vorhandenen Messwerte abfragen. Die Funktion getFifoStatus() liefert den Inhalt des FIFO Status Registers. Seine Bits 0 bis 5 enthalten die Anzahl der Werte im FIFO. Ist das Bit 5 gesetzt, befinden sich 25 = 32 Messwerte im FIFO. Damit ergibt sich:

FIFO ist voll, wenn (getFifoStatus() & 32) == true.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53  // 0x1D if SDO = HIGH
const int int2Pin = 2;
volatile bool event = false;

ADXL345_WE myAcc(ADXL345_I2CADDR);

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - FIFO - Trigger Mode");
  Serial.println();
  if (!myAcc.init()) {
    Serial.println("ADXL345 not connected!");
  }

  myAcc.setDataRate(ADXL345_DATA_RATE_12_5);
  myAcc.setRange(ADXL345_RANGE_4G);
 
  attachInterrupt(digitalPinToInterrupt(int2Pin), eventISR, RISING);
  
/* Three parameters have to be set for activity:
    1. DC / AC Mode:
        ADXL345_DC_MODE - Threshold is the defined one (parameter 3)
        ADXL345_AC_MODE - Threshold = starting acceleration + defined threshold
    2. Axes, that are considered:
        ADXL345_000  -  no axis (which makes no sense)
        ADXL345_00Z  -  z 
        ADXL345_0Y0  -  y
        ADXL345_0YZ  -  y,z
        ADXL345_X00  -  x
        ADXL345_X0Z  -  x,z
        ADXL345_XY0  -  x,y
        ADXL345_XYZ  -  all axes
    3. Threshold in g
*/
  myAcc.setActivityParameters(ADXL345_DC_MODE, ADXL345_XY0, 0.6);
  myAcc.setInterrupt(ADXL345_ACTIVITY, INT_PIN_2);

/* The following two FIFO parameters need to be set:
    1. Trigger Bit: the trigger is an interrupt at INT1 or INT2
        ADXL345_TRIGGER_INT_1 - Trigger is an interrupt at INT1
        ADXL345_TRIGGER_INT_2 - Trigger is an interrupt at INT2
    2. FIFO samples (max 32). Defines the size of the FIFO. One sample is a set of an x,y,z triple.
*/
  myAcc.setFifoParameters(ADXL345_TRIGGER_INT_2, 15);
  myAcc.setFifoMode(ADXL345_TRIGGER);

  myAcc.readAndClearInterrupts();
  event = false;

  Serial.println("Waiting for Activity Event");
}

/* In the main loop some checks are done:
    getActTapStatus() returns which axes are responsible for activity interrupt as byte (code in library)
    getActTapStatusAsString() returns the axes that caused the interrupt as string
    readAndClearInterrupts(); returns the interrupt type as byte (code in library) 
    checkInterrupt(intSource, type) returns if intSource is type as bool  
*/
void loop() {
  
  if(event){
    String axes = myAcc.getActTapStatusAsString();
    byte intSource = myAcc.readAndClearInterrupts();
    if(myAcc.checkInterrupt(intSource, ADXL345_ACTIVITY)){
        Serial.print("Activity at: ");
        Serial.println(axes);
    }
    while(!(myAcc.getFifoStatus() & 32)){}
    printFifo();
    
    Serial.println("For another series of measurements, enter any key and send");
    while(!(Serial.available())){}
    Serial.read();
    Serial.println();
    myAcc.readAndClearInterrupts();
    myAcc.resetTrigger();
    Serial.println("Waiting for Activity Event");
    event = false;
  }
}

void eventISR() {
  event = true;
}

void printFifo(){
  for(int i=0; i<32; i++){
    xyzFloat g = myAcc.getGValues();
    
    Serial.print("g-x   = ");
    Serial.print(g.x);
    Serial.print("  |  g-y   = ");
    Serial.print(g.y);
    Serial.print("  |  g-z   = ");
    Serial.println(g.z);
  }
}

 

Ausgabe des FIFO Trigger Sketches

Für die Ausgabe unten habe ich den ADXL345 um die y-Achse gedreht, bis der Activity Interrupt ausgelöst hat und habe ihn dann wieder zurückgedreht. Ihr erkennt, dass der Messwert, der für den Activity Interrupt verantwortlich ist, mitten in den FIFO Werten liegt.

Ausgabe von ADXL345 fifo_trigger.ino
Ausgabe von ADXL345 fifo_trigger.ino

Bei dieser Messfrequenz ist das Spielerei. Interessant wird es, wenn ihr zu hohen Messfrequenzen geht und z.B. auf diesem Wege ein Tap Event genauer analysiert.  

Abschließende Worte

Die Erstellung der Bibliothek und der Beispielsketche war diesmal aufgrund der Vielseitigkeit dieses Bauteils ein recht hartes Stück Arbeit. Ich hoffe, es hat sich gelohnt und der eine oder andere setzt die Bibliothek ein. Fehler sind bei so einem Projekt nicht nur nicht ausgeschlossen, sondern fast unvermeidlich. Wenn ihr etwas entdeckt oder wenn ihr Funktionen vermisst, meldet euch bitte. 

4 thoughts on “ADXL345 – der universelle Beschleunigungssensor – Teil 2

  1. Super erklärt, ganz herzlichen Dank. Besonders die Kommentare, die die einzelnen Befehlszeilen näher erläutern sind extrem sinnvoll und hilfreich. Auch wenn ich nicht genau das selbe Projekt nachbaue, helfen die Befehle und die anschließende Dokumentation sehr gut, um teile des Codes zu verstehen und dann ggf. selber in anderen Projekten zu verwenden. Danke

  2. Vielen Dank für das Feedback, das freut mich!

    readAndClearInterrupt() liefert übrigens den Inhalt des INT_SOURCE Registers zurück. Und das kann man dann in der Tat weiter verarbeiten. Ich persönlich aber „lasse lieber liefern“ als dass ich ich selbst „abhole“.

  3. Danke für dieses ausführliche und komplexe Tutorial. Ich habe nicht komplett deine Bibliothek übernommen. Aber sie, und dieses Tutorial, hat sehr viel geholfen, überhaupt alles zu verstehen.

    Was vielleicht fehlt. Man kann die Interrupts nicht nur über die Int0 und Int1 auslesen. Vor allem wenn man einen kleinen µC wie z.B. den Attiny85 nutzt, sind die Pins rar, bzw. müsste man den/die Pins umstellen.

    Aus dem Register 0x30—INT_SOURCE, kann man einfach die Interrupts auslesen und einer Variable zuweisen und diese im Sketch verarbeiten. Natürlich muss der Sketch dann so aufgebaut sein, das kein Interrupt nötig ist um den Sketch zu unterbrechen, sondern der Sketch zeitnah durchlaufen wird. Ein Auslesen des Registers nullt ihn wieder.

    Auf jeden Fall Riesen Dank für deine Tutorial und deine Bibliotheken.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.