ADXL345 – der universelle Beschleunigungssensor – Teil 1

Über den Beitrag

In meinen letzten beiden Beiträgen hatte ich über den 6-Achsen Sensor MPU6050 und den eher schlichten Beschleunigungssensor MMA7361 mit analogem Ausgang berichtet. In diesem Beitrag widme ich mich nun dem 3-Achsen Beschleunigungssensor ADXL345. Mit seinen vielen Funktionen ist er für mich das Schweizer Taschenmesser unter den Beschleunigungssensoren.

Ich habe eine ganze Reihe von Bibliotheken für den ADXL345 gefunden. Ich empfand sie aber alle für entweder nicht ganz vollständig oder für so geschrieben, dass man immer noch tief in das Datenblatt einsteigen muss, um sie zu verstehen. Das hat mich dazu motiviert, eine eigene Bibliothek zu schreiben, die – zumindest aus meiner Sicht – bequem zu bedienen ist. Um auch die Nutzung komplexerer Funktionen wie z.B. FIFO einfach zu machen, habe ich der Bibliothek 14 Beispielsketche spendiert. Aufgrund dieses großen Umfanges musste ich den Beitrag zweiteilen (hier geht es zum Teil 2).

Eigenschaften / technische Daten des ADXL345

ADXL345 Modul

Das Funktionsprinzip eines Beschleunigungssensors hatte ich in meinem Beitrag über den MPU6050 schon recht ausführlich beschrieben.

Wie bei anderen Sensoren habe ich auch dieses Mal der Einfachheit halber nicht zum blanken ADXL345 IC, sondern zu einem Modul gegriffen. Diese haben in der Regel:

  • Pull-Up Widerstände für die I2C-Leitungen.
  • Einen Kondensator zur Spannungsstabilisierung.
  • Einen Spannungskonverter, damit man das Modul mit 3-5 Volt betreiben kann.

Der Nachteil an dem Modul gegenüber dem IC ist, dass der Stromverbrauch etwas höher ist. Im normalen Betriebsmodus bei Messfrequenzen unter 10 Hz sollte der Verbrauch des eigentlichen Sensors bei 30 µA liegen. Ich habe bei 5 V Versorgungsspannung 100 µA gemessen, bei 3,3 V waren es 50 µA.

Die wichtigsten Daten auf einen Blick

  • Messbereiche: +/-2, +/-4, +/-8, +/-16 g.
  • Auflösung: 13 Bit im maximalen Messbereich (typ. 4 mg / LSB)
    • Regelbar auf 10 Bit für alle Messbereiche.
  • Kommunikation über I2C oder SPI.
  • Single und Double Tap Erkennung, heißt: Erkennung von kurzen Beschleunigungen verursacht durch Erschütterungen; Tap lässt sich vielleicht mit Klopfen am besten übersetzen.
  • FIFO (first in, first out) Pufferspeicher für 32 Messwerte.
  • 8 Interrupts, unter anderem für freien Fall („free fall“), Single und Double Tap, Aktivität und Inaktivität, FIFO Überlauf.
    • Interrupts sind zwei Ausgängen zuweisbar.
  • Spannungsversorgung: 3 – 5 Volt (für das Modul).
  • Stromverbrauch (nach meinen Messungen):
    • von 23 µA (Stand-by Modus, 3.3 Volt Betriebsspannung)
    • bis 220 µA (Normaler Modus, 3200 Hz Messfrequenz, 5 Volt Betriebsspannung).
  • Diverse Energiesparmodi.

Im Detail lassen sich die Funktionen des ADXL345 am besten mithilfe der Beispielsketche erklären.

Verkabelung des ADXL345 mit dem Arduino

Anschluss des ADXL345 an einen Arduino UNO
Anschluss des ADXL345 an einen Arduino UNO

Das oben abgebildete Anschlussschema gilt für alle Beispielsketche.

  • VCC/GND: 3 – 5 Volt.
  • 3V3 ist ein spezielles Feature des Adafruit Moduls: dort werden 3.3 Volt bereitgestellt.
  • CS (Chip Select): bei Anschluss an GND ist das Modul nicht mehr über I2C ansprechbar; hängt ihn also an VCC. Oder an einen I/O Pin, dann könnt ihr z.B. mehr als zwei ADXL345 betreiben.
  • SDA/SCL: I2C Anschlüsse; Pull-Ups sollten auf dem Modul vorhanden sein.
  • SDO: Wahl der I2C Adresse. Für 0x53 lasst den Pin unverbunden oder hängt ihn an GND, für 0x1D verbindet ihr ihn mit VCC.
  • INT1/INT2: Interrupt Pins; ihr könnt die Interrupts diesen Pins zuordnen. Außerdem lassen sich die Interrupts active-high oder active-low einstellen.

Betrieb mit der Bibliothek ADXL345_WE

Die Bibliothek ADXL345_WE könnt ihr hier von Github herunterladen. Zur Installation entpackt die ZIP Datei im Arduino Library Ordner. Alternativ könnt ihr sie auch über die Bibliotheksverwaltung der Arduino IDE installieren. 

Wie eingangs erwähnt, habe ich versucht, die Bibliothek benutzerfreundlich zu gestalten. Trotzdem ist sie doch sehr umfangreich geworden. Das ist schlicht den vielen Funktionen geschuldet, die der ADXL345 besitzt.

Ich führe euch nun anhand der Beispielsketche durch die Bibliothek. Die Sketche lassen sich in die folgenden Themengebiete gliedern:

  • Basisfunktionen
  • Interrupts
  • Activity, Inactivity Funktionen
  • Single und Double Tap Detektion
  • FIFO Funktionen

Ich habe alle Sketche sehr ausführlich kommentiert und für die Funktionen Parameterlisten eingefügt. Da das aber auch vom Wesentlichen ablenken kann, drucke ich hier nur den ersten Sketch vollständig ab. Bei den anderen nehme ich viele Kommentare heraus. Wenn ihr die Sketche ausprobieren oder darauf aufbauen wollt, nehmt lieber die ausführlichen Versionen, die ihr mit der Bibliothek herunterladet. 

Der Beitrag ist recht länglich. Ungeduldige können auch einfach die Beispielsketche ausprobieren und hierher zurückkehren, wenn etwas unklar ist.

Basisfunktionen des ADXL345

Der ADXL345 speichert die ermittelten die Beschleunigungswerte in Form von Rohdaten, die in den Datenregistern für die x, y und z-Achse bereitgestellt werden. Ihr bekommt dort die jeweils neuesten Werte. Wie alt diese maximal sind, hängt von der Messfrequenz (Data Rate) ab. Eine hohe Frequenz führt zu erhöhtem Rauschen und höherem Stromverbrauch. Übertreibt es also nicht unnötig.

Die Rohwerte werden von der Bibliothek in Beschleunigungswerte in g umgerechnet. In erster Näherung ist der Umrechnungsfaktor in der vollen Auflösung 3.9 mg (Milli-g, nicht Milligramm!) pro LSB (= Least Significant Bit). Anders ausgedrückt:

\text{Beschleunigung in g} = \text{Rohwert}\cdot3.9 

Für die Ergebnisdaten, wie z.B. die Rohdaten, g-Werte oder Winkel habe ich die Struktur (struct) xyzFloat definiert. Ein xyzFloat besteht aus drei float Variablen. Beispielsweise gehören zu den Rohdaten raw die Werte raw.x, raw.y und raw.z. 

Beispielsketch 1: ADXL345_basic_data.ino

Fangen wir an. Im ersten Beispielsketch lernt ihr die folgenden Funktionen kennen:

  • ADXL345_WE myAcc(ADXL345_I2CADDR) erzeugt das Objekt myAcc (für „My Accelerometer“).
  • init() initialisiert den ADXL345 mit einigen Voreinstellungen. Die Funktion liefert „true“ zurück, wenn der ADXL345 wie erwartet antwortet.
  • setDataRate(range) legt die Messfrequenz fest. Wählt aus der Parameterliste.
  • setRange(range) bestimmt den g-Bereich fest. Wählt aus der Parameterliste.
  • setFullRange(true/false) legt die volle Auflösung (10-13 Bit) oder die reduzierte Auflösung (10 Bit) fest. Die volle Auflösung ist voreingestellt. Ich sehe eigentlich keinen Grund, das zu ändern.
  • getDataRateAsString() liefert die eingestellte Messfrequenz als String zurück.
  • getRangeAsString() liefert den g Bereich als String.
  • setLowPower(true/false) stellt den Low Power Mode ein. Er bringt Stromersparnis für bestimmte Datenraten zum Preis etwas höheren Rauschens. 

Als Alternative zu den „get as string“ Funktionen könnt ihr auch die folgenden Funktionen verwenden:

  • getRange() / getDataRate()

Dann bekommt ihr allerdings nur Zahlenwerte zurückgeliefert, die ihr „übersetzen“ müsst. Schaut in die Bibliotheksdatei ADXL345_WE.h für die Definition der entsprechenden enum Konstruktionen.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53

ADXL345_WE myAcc(ADXL345_I2CADDR);
// ADXL345_WE myAcc = ADXL345_WE(); // Alternative: sets default address 0x53

void setup(){
  Wire.begin();
  Serial.begin(9600);
  Serial.println("ADXL345_Sketch - Basic Data");
  Serial.println();
  if(!myAcc.init()){
    Serial.println("ADXL345 not connected!");
  }
   
/* Choose the data rate         Hz
    ADXL345_DATA_RATE_3200    3200
    ADXL345_DATA_RATE_1600    1600
    ADXL345_DATA_RATE_800      800
    ADXL345_DATA_RATE_400      400
    ADXL345_DATA_RATE_200      200
    ADXL345_DATA_RATE_100      100
    ADXL345_DATA_RATE_50        50
    ADXL345_DATA_RATE_25        25
    ADXL345_DATA_RATE_12_5      12.5  
    ADXL345_DATA_RATE_6_25       6.25
    ADXL345_DATA_RATE_3_13       3.13
    ADXL345_DATA_RATE_1_56       1.56
    ADXL345_DATA_RATE_0_78       0.78
    ADXL345_DATA_RATE_0_39       0.39
    ADXL345_DATA_RATE_0_20       0.20
    ADXL345_DATA_RATE_0_10       0.10
*/
  myAcc.setDataRate(ADXL345_DATA_RATE_50);
  Serial.print("Data rate: ");
  Serial.print(myAcc.getDataRateAsString());

/* In full resolution raw values the size of the raw values depend 
    on the range: 2g = 10 bit; 4g = 11 bit; 8g = 12 bit; 16g =13 bit;
    uncomment to change to 10 bit for all ranges. 
 */
  // myAcc.setFullRes(false);

/* Choose the measurement range
    ADXL345_RANGE_16G    16g     
    ADXL345_RANGE_8G      8g     
    ADXL345_RANGE_4G      4g   
    ADXL345_RANGE_2G      2g
*/ 
  myAcc.setRange(ADXL345_RANGE_4G);
  Serial.print("  /  g-Range: ");
  Serial.println(myAcc.getRangeAsString());
  Serial.println();

/* Uncomment to enable Low Power Mode. It saves power but slightly
    increases noise. LowPower only affetcs Data Rates 12.5 Hz to 400 Hz.
*/
  // myAcc.setLowPower(true);
}

/* The LSB of the Data registers is 3.9 mg (milli-g, not milligramm).
    This value is used calculating g from raw. However, this is an ideal
    value which you might want to calibrate. 
*/

void loop() {
  xyzFloat raw = myAcc.getRawValues();
  xyzFloat g = myAcc.getGValues();
  
  Serial.print("Raw-x = ");
  Serial.print(raw.x);
  Serial.print("  |  Raw-y = ");
  Serial.print(raw.y);
  Serial.print("  |  Raw-z = ");
  Serial.println(raw.z);

  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();
  
  delay(1000);
  
}

 

Ausgabe von ADXL345_basic_data.ino

Für die folgende Ausgabe habe ich versucht, den ADXL345 flach zu positionieren. Die Roh- und g-Werte für die x- und y-Achse sollten also Null sein. Der g-Wert der z-Achse sollte 1 sein.

ADXL345 Sketch: Ausgabe von ADXL345_basic_data.ino
Ausgabe von ADXL345_basic_data.ino

Ihr erkennt kleinere Abweichungen für die x- und y-Werte und eine nicht unerhebliche Abweichung für den z-Wert. Die Abweichungen schwanken von Modul zu Modul. Es handelt sich beim Beschleunigungssensor um ein mikromechanisches Bauteil mit gewissen Toleranzen.

Euch ist vielleicht aufgefallen, dass der erste Satz an Werten stärker abweicht. Es ist ratsam diese ersten Werte bei Programmstart, bei Rückkehr aus dem Stand-by Modus und einigen anderen Zuständen zu verwerfen. In diesem Fall könntet ihr hier einfach ein kleines delay am Ende des Setups einfügen.

Beispielsketch 2: ADXL345_calibration.ino

Dieser Sketch ist eine Anleitung für die Kalibrierung des ADXL345. Startet den Sketch und verändert nicht die Auflösung. Dreht den ADXL345 langsam und versucht die maximalen und minimalen Rohwerte für die Achsen zu finden. Stützt dabei am besten die Arme auf, da Zittern die Werte verfälscht. Es kommt nicht auf ein oder zwei Einheiten an. Die Werte notiert ihr euch und könnt sie dann in den weiteren Sketchen verwenden. Wie ihr das macht, seht ihr dann im Beispielsketch 3.

Die Maximal- und Minimalwerte entsprechen jeweils +1 g bzw. -1 g (sofern ihr die Kalibrierung auf unserem Heimatplaneten vornehmt). Für die Nullpunkte gilt nach dieser Methode:

\text{Rohwert}_0 = \frac{\text{Rohwert}_{\text{min}}+\text{Rohwert}_{\text{max}}}{2}

Entsprechend wird daraus ein Offset errechnet. Für die Steigung gilt:

\text{Steigung}\;[\text{g-Wert/Rohwert}] = \frac{|\text{Rohwert}_{\text{min}}|+\text{Rohwert}_{\text{max}}}{2}
#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53

ADXL345_WE myAcc(ADXL345_I2CADDR);
// ADXL345_WE myAcc = ADXL345_WE(); // Alternative: sets default address 0x53

void setup(){
  Wire.begin();
  Serial.begin(9600);
  Serial.println("ADXL345_Sketch - Calibration");
  Serial.println();
  if(!myAcc.init()){
    Serial.println("ADXL345 not connected!");
  }
  Serial.println("Calibration procedure:");
  Serial.println(" - stay in full resolution");
  Serial.println(" - supply voltage has influence (at least for the modules)");
  Serial.println("        -> choose the same voltage you will use in your project!"); 
  Serial.println(" - turn your ADXL slowly (!) to find the min and max raw x,y and z values");
  Serial.println(" - deviations of one or two units don't matter much");
  Serial.println(" - the calibration changes the slope of g vs raw and assumes zero is (min+max)/2 ");
  Serial.println(" - write down the six values ");
  Serial.println(" - you can try the calibration values in ADXL345_angles_tilt_orientation.ino example sketch");
  Serial.println(" - ready to go? Then type in any key and send. ");
  while(!Serial.available());
  Serial.read();
  Serial.println(); Serial.println(); Serial.println();  
}

void loop() {
  xyzFloat raw = myAcc.getRawValues();
  Serial.print("Raw-x = ");
  Serial.print(raw.x);
  Serial.print("  |  Raw-y = ");
  Serial.print(raw.y);
  Serial.print("  |  Raw-z = ");
  Serial.println(raw.z);
  
  delay(1000);
  
}

 

Die von mir ermittelten Werte waren: xmin = -266.0, xmax = 285.0, ymin =  -268.0, ymax = 278.0, zmin = -291.0, zmax = 214.0.

Trotz der Kalibrierung werdet ihr gewisse Abweichungen sehen, denn es ist nicht unbedingt gesagt, dass der Nullpunkt wirklich in der Mitte zwischen den Min- und Maxwerten liegt.

Andere schlagen vor, den ADXL345 auf einem würfel- oder quaderförmigen Körper zu befestigen, um ihn für die Kalibrierung besser ausrichten zu können. Das setzt allerdings voraus, dass der ADXL345 wirklich flach auf das Modul gelötet wurde und dass ihr das Modul ebenso flach auf dem Hilfskörper befestigt.

Beispielsketch 3: ADXL345_angles_orientation.ino

Hier könnt ihr nun die Kalibrierung anwenden, indem ihr die eben ermittelten Werte in die folgende Funktion eintragt:

  • setCorrFactors(xmin, xmax, ymin, ymax, zmin, zmax)

Die Funktion getRawValues() liefert immer noch die unkorrigierten Rohdaten. getGValues() hingegen verwendet die Korrekturfaktoren. Das ist auch in allen anderen Sketchen der Fall.

Der Sketch berechnet zusätzlich die Winkel der Achsen zur Horizontalen aus den g-Werten. Bis zu Winkeln von 60° funktioniert das gut für x und y. Im nächsten Abschnitt lernt ihr dann eine andere Funktion kennen, die höhere Winkel abdeckt. Hier wird aber zunächst die folgende, einfache Funktion verwendet:

Winkel = \arcsin(\text{g-Wert})

Warum das so ist und warum Werte in der Nähe von 90° besonders fehlerbehaftet sind, habe ich in meinem letzten Beitrag erklärt. Ihr erhaltet die Winkel über die Funktion getAngles(). Da die arcsin Funktion nicht für Werte größer 1 definiert ist, schneide ich g-Werte größer 1 einfach ab.

Wenn ihr Winkel bis 60° ziemlich exakt vermessen wollt und dafür bei 0° beginnen möchtet, könnt ihr den trotz Kalibrierung immer noch vorhandenen Offset herausrechnen lassen. Dafür positioniert ihr den ADXL345 möglichst flach und ruhig und führt die Funktion measureAngleOffsets() aus. Danach erhaltet ihr über getCorrAngles() die zusätzlich korrigierten Werte.

Als letzte Funktion möchte ich euch getOrientationAsString() vorstellen. Diese ermittelt, welche Achse am weitesten nach oben gerichtet ist. Je nach Ausrichtung liefert sie: x up, x down, y up, y down, z up oder z down als String zurück.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53

ADXL345_WE myAcc(ADXL345_I2CADDR);

void setup(){
  Wire.begin();
  Serial.begin(9600);
  Serial.println("ADXL345_Sketch");
  Serial.println();
  if(!myAcc.init()){
    Serial.println("ADXL345 not connected!");
  }
  
  myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);

/* In this next step the offset for angles is corrected to get quite precise corrected 
 *  angles for x and y up to ~60°. The additional offsetCorrection is only used for 
 *  corrected angles measurements.The procedure just ensures to start at 0°.
*/
  Serial.println("Position your ADXL345 flat and don't move it");
  delay(2000);
  myAcc.measureAngleOffsets();
  Serial.println("....done");
   
  myAcc.setDataRate(ADXL345_DATA_RATE_25);
  myAcc.setRange(ADXL345_RANGE_2G);

}

void loop() {
  xyzFloat raw = myAcc.getRawValues();
  xyzFloat g = myAcc.getGValues();
  xyzFloat angle = myAcc.getAngles();
  xyzFloat corrAngles = myAcc.getCorrAngles();
  
/* Still the uncorrected raw values!! */  
  Serial.print("Raw-x    = ");
  Serial.print(raw.x);
  Serial.print("  |  Raw-y    = ");
  Serial.print(raw.y);
  Serial.print("  |  Raw-z    = ");
  Serial.println(raw.z);

/* g values use the corrected raws */
  Serial.print("g-x     = ");
  Serial.print(g.x);
  Serial.print("  |  g-y     = ");
  Serial.print(g.y);
  Serial.print("  |  g-z     = ");
  Serial.println(g.z);

/* Angles use the corrected raws. Angles are simply calculated by
    angle = arcsin(g Value) */
  Serial.print("Angle x  = ");
  Serial.print(angle.x);
  Serial.print("  |  Angle y  = ");
  Serial.print(angle.y);
  Serial.print("  |  Angle z  = ");
  Serial.println(angle.z);

/* Corrected angles use corrected raws and an extra angle 
    offsets to ensure they start at 0° 
*/
  Serial.print("cAngle x = ");
  Serial.print(corrAngles.x);
  Serial.print("  |  cAngle y   = ");
  Serial.print(corrAngles.y);
  Serial.print("  |  cAngle z   = ");
  Serial.println(corrAngles.z);

  Serial.print("Orientation of the module: ");
  Serial.println(myAcc.getOrientationAsString());

  Serial.println();
  
  delay(1000);
  
}

 

 

Ausgabe von ADXL_angles_orientation

ADXL345 Sketch: Ausgabe von ADXL_angles_orientation.ino
Ausgabe von ADXL_angles_orientation.ino

Ihr erkennt, dass die korrigierten g-Werte für die z-Achse nun erheblich besser aussehen. Die Winkel haben wegen des verbliebenen Offsets eine gewisse Abweichung. Die korrigierten Winkel hingegen schwanken nahe um den Nullpunkt. 

Beispielsketch 4: ADXL345_pitch_roll_corrected_angles.ino

Die hier beschriebene Methode zur Winkelberechnung habe ich anderen Bibliotheken entnommen, wie z.B. der MPU6050 light oder der Arduino-ADXL345. Der Vorteil dieser Methode ist, dass sie mehrere Achsen mit einbezieht und so Fehler ausgleicht. Um die Methode abzugrenzen, habe ich mich der Nomenklatur anderer bedient und den Neigungswinkel der x-Achse als „pitch“ und den der y-Achse als „roll“ bezeichnet.

pitch\; angle= \arctan \left(\frac{g_x}{\sqrt{g_x\cdot g_y +g_z^2}}\right)
roll\;angle = \arctan\left( \frac{g_y}{g_z} \right)

Auch hier gilt natürlich, dass die Winkelbestimmung nur im statischen Zustand gültig ist. Zusätzliche Beschleunigungen verfälschen das Ergebnis.

Es folgt nun der Beispielsketch, genauer gesagt seine wesentlichen Bestandteile. Zur Erinnerung: die originalen Beispielsketche sind mit wesentlich mehr Kommentaren versehen.

Zum Vergleich kommt zusätzlich die Methode aus dem vorherigen Sketch zur Anwendung.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53

ADXL345_WE myAcc(ADXL345_I2CADDR);

void setup(){
  Wire.begin();
  Serial.begin(9600);
  Serial.println("ADXL345 Sketch - Pitch and Roll vs. Corrected Angles");
  Serial.println();
  if(!myAcc.init()){
    Serial.println("ADXL345 not connected!");
  }
  
  myAcc.setCorrFactors(-266.0, 285.0, -268.0, 278.0, -291.0, 214.0);

  Serial.println("Position your ADXL345 flat and don't move it");
  delay(2000);
  myAcc.measureAngleOffsets();
  Serial.println("....done");
   
  myAcc.setDataRate(ADXL345_DATA_RATE_50);
  myAcc.setRange(ADXL345_RANGE_2G);
}

void loop() {
  xyzFloat corrAngles = myAcc.getCorrAngles();
  
  Serial.print("Angle x = ");
  Serial.print(corrAngles.x);
  Serial.print("  |  Angle y = ");
  Serial.print(corrAngles.y);
  Serial.print("  |  Angle z = ");
  Serial.println(corrAngles.z);

  float pitch = myAcc.getPitch();
  float roll  = myAcc.getRoll();
  
  Serial.print("Pitch   = "); 
  Serial.print(pitch); 
  Serial.print("  |  Roll    = "); 
  Serial.println(roll); 
  
  Serial.println();
  
  delay(1000);
  
}

 

Ausgabe von ADXL345_pitch_roll_corrected_angles.ino

ADXL345 Sketch: Ausgabe von ADXL345_pitch_roll_corrected_angles.ino
Ausgabe von ADXL345_pitch_roll_corrected_angles.ino

Für die obige Ausgabe habe ich den ADXL345 langsam um die y-Achse gedreht, d.h. den x-Winkel variiert. Mit einfachen Mitteln (Brett und Geodreieck) habe ich die Werte nachgemessen (auch für roll). Bei kleinen bis mittleren Winkeln sind die Werte für Angle realitätsnäher, bei großen Winkeln ist die pitch / roll Methode besser.

Beispielsketch 5: ADXL345_sleep

Um Strom zu sparen, könnt ihr den ADXL345 in den Schlaf schicken. Die Funktion dazu (und zum Aufwecken) lautet:

  • setSleep(true/false)

Der Schlaf wird von kurzen Wachphasen unterbrochen. Das lässt sich auch nicht abstellen. Aber ihr könnt die Aufwachfrequenz festlegen, und zwar auf 1, 2, 4 oder 8 Hz. Um den Schlafmodus zusammen der Aufwachfrequenz zu steuern, könnt ihr die Funktion auch mit zwei Parametern aufrufen:

  • setSleep(true/false, frequency)

Wenn ihr nicht zu häufig neue Daten abruft, werdet ihr keinen Unterschied zum Dauerwachmodus feststellen. Erst wenn die Frequenz eurer Messwerteabrufe größer als die Aufwachfrequenz ist, seht ihr den Effekt.

Im Beispielsketch werden je 10 Messwerte im Wach- und im Schlafmodus abgerufen. Die Aufwachfrequenz habe ich auf eine Sekunde eingestellt, die Messwerte werden im Abstand von 300 Millisekunden angefordert. Hier der (verkürzte) Sketch:

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53

ADXL345_WE myAcc(ADXL345_I2CADDR);
// ADXL345_WE myAcc = ADXL345_WE(); // Alternative: sets default address 0x53

void setup(){
  Wire.begin();
  Serial.begin(9600);
  Serial.println("ADXL345_Sketch - Sleep");
  Serial.println();
  if(!myAcc.init()){
    Serial.println("ADXL345 not connected!");
  }

  myAcc.setDataRate(ADXL345_DATA_RATE_50);
  myAcc.setRange(ADXL345_RANGE_2G);
}

void loop(){
  myAcc.setSleep(true, ADXL345_WUP_FQ_1);
  Serial.println("Measure in Sleep Mode:");
  doMeasurements();
  
  myAcc.setSleep(false);
  Serial.println("Measure in Normal Mode:");
  doMeasurements();
}
  

void doMeasurements(){
  for(int i=0; i<10; 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);
    
    delay(300);
  }
}

 

Ausgabe von ADXL345_sleep.ino

Ausgabe ADXL345_sleep.ino
Ausgabe ADXL345_sleep.ino

Für die obige Ausgabe habe ich den ADXL345 permanent bewegt. Ihr erkennt, dass die Daten im normalen Modus bei jeder Abfrage unterschiedlich sind. Im Sleep Mode sind – wie erwartet – jeweils 3 bis 4 Werte identisch.

Eigentlich würde es sich anbieten, als Nächstes den Auto Sleep Modus vorzustellen. Dafür muss ich allerdings erst das Thema Interrupts behandeln. Ich komme dann etwas später auf den Auto Sleep Modus zurück.

Interrupt Funktionen des ADXL345

Allgemeines über die Interrupt Funktionen

Der ADXL345 unterscheidet acht Interrupts:

  • Overrun (Überlauf): wird ausgelöst, wenn ungelesene Daten überschrieben werden, d.h. wenn die Messfrequenz (Data Rate) größer ist als die Frequenz des Datenabrufs.
  • Watermark: wenn die Anzahl der Messwerte im FIFO Puffer dem im FIFO Kontrollregister definierten Wert entspricht (wird später klarer).
  • Free Fall: dieser wird ausgelöst, wenn die Beschleunigungswerte an allen Achsen einen bestimmten Wert für eine bestimmte Zeit unterschreiten.
  • Inactivity: liegt vor, wenn an festgelegten Achsen ein Grenzwert der Beschleunigung für eine festgelegte Zeit unterschritten wird.
  • Activity: liegt vor, wenn an festgelegten Achsen ein Grenzwert der Beschleunigung überschritten wird.
  • Single Tap („einfaches Klopfen“): ein Beschleunigungspeak bestimmter maximaler zeitlicher Ausdehnung, der oberhalb eines bestimmten Grenzwertes liegt.
  • Double Tap („doppeltes Klopfen“): zwei Peaks, die beide die Single Tap Bedingungen erfüllen und zusätzlich einen bestimmten zeitlichen Abstand zueinander haben.
  • Data Ready: es liegen ungelesene Daten vor.

Die Interrupts aktiviert ihr mit der Funktion setInterrupt(type, pin1/pin2). Der erste Parameter ist der Interrupt Typ, mit dem zweiten Parameter legt ihr fest, an welchem Pin der Interrupt ausgegeben wird. Wie die Parameter genau aussehen, steht in den Beispielsketchen.

Es ist wichtig zu wissen, dass die Interrupts für Overrun, Watermark und Data Ready immer aktiviert sind. Ihr könnt sie also nicht deaktivieren, sondern lediglich den Ausgabepin ändern. Voreingestellt ist INT1. Alle anderen Interrupts müsst ihr erst aktivieren. Deaktivieren könnt ihr sie mit deleteInterrupt(type).

Im Interrupt Register wird im Falle eines Interrupts hinterlegt, welcher Interrupt Typ vorliegt. Das Lesen des Registers löscht den Interrupt, sodass ein neuer Interrupt ausgelöst werden kann. Dafür gibt es die Funktion readAndClearInterrupts(). Sie liefert den Interrupt Typ als Byte zurück. Wie ihr das „übersetzt“, findet ihr in der Bibliotheksdatei ADXL345_WE.h. Alternativ prüft ihr mit checkInterrupt(source, type) auf einen bestimmten Typ.

Bleibt noch eine weitere, allgemeine Funktion, nämlich setInterruptPolarity(polarity). Damit stellt ihr ein, ob die Interrupt Pins active-low oder active-high (Voreinstellung) sind.

Beispielsketch 6: ADXL345_free_fall_interrupt.ino

Als Erstes schauen wir uns den Free Fall Interrupt an. Im Beispielsketch wird er mit setInterrupt(ADXL345_FREEFALL, INT_PIN_2) aktiviert und auf INT2 gelegt.

Mit setFreeFallThresholds (0.4, 100) wird das Beschleunigungslimit auf 0.4 g und die Mindestdauer für dessen Unterschreitung auf 100 Millisekunden festgelegt. Zulässige und empfohlene Werte findet ihr im Beispielsketch. Spielt am besten ein bisschen mit den Parametern herum, um diese Funktion besser kennenzulernen.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53
const int int2Pin = 2;
volatile bool freeFall = false;

ADXL345_WE myAcc(ADXL345_I2CADDR);

void setup(){
  Wire.begin();
  Serial.begin(9600);
  pinMode(int2Pin, INPUT);
  Serial.println("ADXL345_Sketch - Free Fall 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_25);
  myAcc.setRange(ADXL345_RANGE_2G);
  
/* The parameters of the setFreeFallThresholds function are:  
     - g threshold - do not choose a parameter which is too low. 0.3 - 0.6 g is fine.
     - time threshold in ms, maximum is 1275. Recommended is 100 - 350;
    If time threshold is too low, vibrations can be detected as free fall. 
 */
  myAcc.setFreeFallThresholds(0.4, 100);
  
/* 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_FREEFALL, INT_PIN_2);
  
  attachInterrupt(digitalPinToInterrupt(int2Pin), freeFallISR, RISING); 
  freeFall=false; 
}

void loop() {
  xyzFloat raw = myAcc.getRawValues();
  xyzFloat g = myAcc.getGValues();
     
  Serial.print("Raw-x = ");
  Serial.print(raw.x);
  Serial.print("  |  Raw-y = ");
  Serial.print(raw.y);
  Serial.print("  |  Raw-z = ");
  Serial.println(raw.z);

  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();
 
  if(freeFall==true){
    Serial.println("Aaaaaaaaah!!!!! - I'm faaaaallllling!");
    delay(1000);
    freeFall = false;
    /* by reading the interrupt register the interrupt is cleared */
    myAcc.readAndClearInterrupts();
    
    /* if you expect also other interrupts you can check the type. For this comment the previous line, 
    and replace by the following four lines: */
    //byte intType = myAcc.readAndClearInterrupts();
    //if(myAcc.checkInterrupt(intType, ADXL345_FREEFALL)){
    //  Serial.println("FREEFALL confirmed");
    //}
  }

  delay(500);
}

void freeFallISR(){
  freeFall = true;
}

 

Beispielsketch 7: ADXL345_data_ready_interrupt.ino

So, einen Beispielsketch habe ich noch für den Teil 1, dann ist Halbzeit. Hier nutzen wir den Data Ready Interrupt, um die Datenausgabe zu steuern. Zunächst habe ich dazu die Messfrequenz mit setDataRate(ADXL345_DATA_RATE_0_20) auf langsame 0.2 Hz festgelegt.

Der Data Ready Interrupt ist immer aktiv. Aber damit er nicht mit anderen „Immer-an“ Interrupts ins Gehege kommt, habe ich ihn mittels setInterrupt(ADXL345_DATA_READY, INT_PIN_2) auf den Pin INT2 gelegt.

Der Rest ist einfach. Der Data Ready Interrupt löst einen Interrupt am Arduino Pin 2 aus. Das ist das Zeichen zum Lesen der Messwerte. Danach wird der Interrupt gelöscht und der nächste Interrupt wird erwartet.

#include<Wire.h>
#include<ADXL345_WE.h>
#define ADXL345_I2CADDR 0x53
const int int2Pin = 2;  // interrupt pin
volatile bool dataReady = false;

ADXL345_WE myAcc(ADXL345_I2CADDR);

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

  myAcc.setDataRate(ADXL345_DATA_RATE_0_20);
  myAcc.setRange(ADXL345_RANGE_2G);
    
  attachInterrupt(digitalPinToInterrupt(int2Pin), dataReadyISR, RISING);

/* Default Interrupt Pin Polarity is active high. Change if you like */  
  // myAcc.setInterruptPolarity(ADXL345_ACT_LOW);

  myAcc.setInterrupt(ADXL345_DATA_READY, INT_PIN_2); 
}

/* In the following loop there is only one interrupt type that can occur on INT2.
    In cases where you expect more candidates, you can check as follows:
        uint8_t intType = myAcc.readAndClearInterrupts();
        if(myAcc.checkInterrupt(intType, ADXL345_DATA_READY)){
          Serial.println("DATA READY confirmed");
        }
*/
void loop() {
  // you see here is no delay to control the output rate
  if(dataReady==true){
    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);
  
    dataReady = false;
  }  
}

void dataReadyISR(){
  dataReady = true;
}

 

Ausblick

Im nächsten Beitrag kommen wir zu den Beispielsketchen für

  • Activity / Inactivity
  • Auto Sleep Mode
  • Single / Double Tap
  • Verschiedene FIFO Modes

Danksagung

Die Utensilien, die ich dem ADXL345 Modul im Beitragsbild spendiert habe, entstammen dem Bild eines Schweizer Taschenmessers von Clker-Free-Vector-Images auf Pixabay.

Der Firma Adafruit danke ich, dass sie das Fritzing Schema ihres ADXL345 Moduls veröffentlicht haben.

 

 

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

  1. Ich würde sagen, probiere es am besten aus. Wenn du nur ein Alarmsystem brauchst, das auf Bewegung reagieren soll, dann ist ein Erschütterungssensor unter Umständen die angemessene Lösung. Allerdings muss man natürlich darauf achten, dass der Alarm nicht gleich bei jeder Vibration losgeht. Da kann man mit einem Beschleunigungssensor zielgerichteter arbeiten. Ich hoffe ich habe die Frage richtig verstanden…

  2. Toller Beitrag. Mit dem habe ich auch mal angefangen in Zusammenhang mit dem ESP8266. Bin aber wegen des zu hohen Ruhestromverbrauchs später beim LIS3DH gelandet. Der schien damals unschlagbar mit 8 uA. Habe aber von einem Kollegen, der das Projekt später in einem anderen Projekt weiterführte, gehört, dass es noch mit weniger Strom geht, die Ohren aufzuhalten und auf Erschütterungen zu reagieren, um den Prozessor hochzufahren.

    Ich denke aber, jeder fängt erst mal mit dem ADXL an 🙂

    1. Hallo, vielen Dank für den Hinweis. Vom Funktionsumfang her sieht der LIS3DH recht ähnlich aus. Ein noch geringerer Stromverbrauch ist natürlich schön. Ich muss ihn mir mal genauer anschauen.

      Auf jeden Fall sollten diejenigen, für die der Stromverbrauch wichtig ist und den ADXL345 einsetzen wollen eher den blanken IC einsetzen und die Stromsparoptionen wählen.

    2. Wenn es denn nur um das Aufwachen geht könnte man einen Piezo Lautsprecher nehmen mit angeklebtem Gewicht? Bei Beschleunigung bekommt man so auch ein „Signal“

      Hatte letzten ein Fahrrad Lampe mit Alarm zerlegt, innen war eine Glas Phiole mit einer Feder, im inneren der Feder ein haar feiner Kupfernadel. so eine Art mechanischer Beschleunigung Sensor

      Was war den letztlich die ultimative Lösung?

Schreibe einen Kommentar

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