MMA7361 – Beschleunigungssensor

Über den Beitrag

Nachdem ich im letzten Beitrag den 6-Achsen I2C Sensor MPU6050 vorgestellt habe, möchte ich mich in diesem Beitrag dem einfacheren, analogen 3-Achsen Beschleunigungssensor MMA7361 widmen. Dabei behandele ich folgende Themen:

Technische Daten / Eigenschaften des MMA7361

MMA7361 Modul
MMA7361 Modul

Wie Beschleunigungssensoren funktionieren, habe ich im letzten Beitrag erläutert.

Der MMA7361 ist ein reiner Beschleunigungssensor. Die Messdaten für die Beschleunigung in x-, y- und z-Richtung werden an den Pins X, Y und Z als analoge Werte zur Verfügung gestellt. Wo sich die Achsen x, y und z befinden, ist auf dem Modul aufgedruckt.

Das in diesem Beitrag verwendete MMA7361 Modul wird im Gegensatz zum „nackten“ MMA7361 IC bei 3,3 oder 5 Volt betrieben. Der Messbereich kann auf +/-1.5 g („Erdbeschleunigungs – g“, nicht Gramm!) oder +/-6 g eingestellt werden. Darüber hinaus hat der MMA7361 eine Selbsttestfunktion und einen 0g Interrupt (Free-Fall Erkennung). Die Auswertung der Rohdaten müssen geeignete Sketche übernehmen. Ein Datenblatt gibt es zum Beispiel hier.

Hier die wichtigsten Daten auf einen Blick:

  • Spannungsversorgung: 3.3 oder 5 V
  • Strombedarf:
    • IC: ~0.4 mA (Normalbetrieb) / ~3 µA (Sleep-Mode)
    • Modul: ~1.9 mA – bedingt durch die Board LED, die ihr vorsichtig entfernen könnt
  • 2 Ranges: +/- 1.5 g (GS = LOW) / +/- 6 g (GS = HIGH)
  • Empfindlichkeit:
    • +/- 1.5 g Range: 800 mV/g
    • +/- 6 g Range: 206 mV/g
  • 0G Erkennung (active-high)
  • Sleep Mode (SL = LOW)
  • Self Test (ST = HIGH): dazu schaut bitte ins Datenblatt

Auslesen der Rohdaten des MMA7361

Das Auslesen der Rohdaten ist einfach und erfolgt über die analogen Eingänge eures Arduino – oder welchen Microcontroller ihr auch immer verwendet. Der A/D-Wandler des Arduino UNO schwankt recht stark mit +/- 2 oder 3 Einheiten. Ich gleiche das im Sketch durch 50-maliges Lesen aus. Alternativ könntet ihr über den Einsatz eines A/D-Wandlers wie den ADS1115 nachdenken.

Ein echtes Ärgernis sind die Abmessungen des Moduls und die Tatsache, dass die Pinleisten schon fertig angelötet sind. Ihr könnt das Board in ein Breadboard stecken, aber dann ist nur noch auf einer Seite Platz für Steckbrückenkabel. Ich habe eine Pinleiste entfernt und auf die Oberseite gelötet:

MMA7361 Modul, links: original, rechts: umgelötete Pinleiste
MMA7361 Modul, links: original, rechts: umgelötete Pinleiste

Die Schaltung

Die Schaltung ist keine große Überraschung:

MMA7361, Schaltung für alle Beispiele
MMA7361, Schaltung für alle Beispiele

Der Sketch zum Auslesen der Rohdaten

Auch zum Sketch gibt es nicht viel zu sagen. Außer vielleicht, dass ich mich entschieden habe, vorwiegend mit Integervariablen zu arbeiten. Die Schwankungen der Messwerte sind so hoch, dass Fließkommazahlen eine Genauigkeit vorgaukeln würden, die einfach nicht da ist.

int xPin = A0;
int yPin = A1;
int zPin = A2;
int selfTestPin = 8;
int gSelectPin = 9;
int sleepPin = 10;

void setup(){
  Serial.begin(9600);
  pinMode(selfTestPin, OUTPUT);
  pinMode(gSelectPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(selfTestPin, LOW);
  digitalWrite(gSelectPin, LOW); 
  Serial.println("MMA7361 - Raw Data");
}

void loop(){
  Serial.print("x-Axis: "); 
  Serial.print(readMMA7361(xPin));
  Serial.print("     ");
  Serial.print("y-Axis: "); 
  Serial.print(readMMA7361(yPin));
  Serial.print("     ");
  Serial.print("z-Axis: "); 
  Serial.println(readMMA7361(zPin));
  delay(1000);
}

int readMMA7361(int channel){
  long sum = 0;
  int result = 0;
  for(int i=0; i<50; i++){
    sum += analogRead(channel);
  }
  result = sum / 50;
  return result;
}

 

Das Ergebnis

Ausgabe von MMA7361_raw_data.ino
Ausgabe von MMA7361_raw_data.ino

Der MMA7361 lag für die obigen Werte flach, also mit seiner x/y-Ebene, auf dem Tisch. Das bedeutet, dass 0 g auf die x- und y-Achse wirken und 1 g auf die z-Achse. Man würde entsprechend gleiche Messwerte für x und y erwarten. Das ist aber nicht der Fall. Ich habe mehrere Module getestet und die Werte lagen meist zwischen 350 und 380 Einheiten, sprich bei ca. 1,7 bis 1,9 Volt. Allerdings war der Startwert für ein gegebenes Modul und eine gegebene Achse recht gut reproduzierbar.

Die Angabe zur Empfindlichkeit für die Einstellung +/-1.5 g, nämlich 800 mV/g, konnte ich bei Tests bestätigen. Umgerechnet in „analogRead-Einheiten“ gilt:

5000\; [\text{mV}]\stackrel{\wedge}= 1023   \;\; \rightarrow\;\;800\;[\text{mV}]\stackrel{\wedge}=163.7

D.h. wenn ich den MMA7361 um 90° gedreht habe, ging der Messwert der betroffenen Achse um ca. 163 Einheiten (im Sketch: unitsPerG) nach oben oder unten.

Berechnung der Beschleunigung und der Winkel

Nullpunkte der MMA7361 Achsen

Bevor wir Beschleunigungen und Winkel berechnen, müssen wir erst einmal die Nullpunkte (im weiteren Verlauf auch „Startwerte“ genannt) ermitteln. Prinzipiell gibt es zwei Möglichkeiten:

  1. Ihr ermittelt durch langsames Drehen den Maximal- und Minimalwert und verwendet den Mittelwert als Nullpunkt. Die Nullpunkte werden als Konstanten gespeichert.
  2. Ihr richtet den MMA7361 flach aus. Die Messwerte für die x- und die y-Achse entsprechen den Nullpunkten. Der Messwert für die z-Achse entspricht 1 g. Das heißt der Nullpunkt für die z-Achse ist bei Verwendung der Range +/-1.5 g der Messwert minus 800 mV (= 163 Einheiten) bzw. Messwert minus 206 mV  bei  +/- 6 g.

Ich habe mich für Methode 2 entschieden. In meinen Sketchen werden bei jedem Reset die Nullpunkte neu ermittelt.

Berechnung der Beschleunigung

Die Berechnung der Beschleunigung a aus dem Nullpunkt startVal, dem Messwert rawVal und der Steigung unitsPerG ist einfach:

a = startV\!al+(rawV\!al-startVal)\cdot unitsPerG

Berechnung der Winkel

Wenn der MMA7361 nicht bewegt wird, lässt sich über die Verteilung der Erdbeschleunigung  auf die Achsen eine Aussage über deren Lage im Raum treffen. Das folgende Schema soll das am Beispiel der x-Achse verdeutlichen:

Winkelberechnung aus g-Werten des MMA7361
Winkelberechnung aus g-Werten (Perpendicular = Senkrechte)

Dabei ist geff die effektiv auf die Achse wirkende Schwerkraft. Es gilt für den Neigewinkel α:

sin(\alpha)=\frac{g_{ef\!f}}{g} \;\;\;\;\rightarrow\;\;\;\;\alpha=arcsin \left( \frac{g_{ef\!f}}{g}\right)

Es gibt allerdings zwei Einschränkungen:

  • Die Winkel sind nicht eindeutig. Der geff/g-Wert ist für einen Winkel α und den Winkel 180°-α identisch.
  • Die Berechnung ist, je näher man den 90° kommt, zunehmend fehleranfällig.

Bezüglich der zweiten Einschränkung hier eine Beispielrechnung:

arcsin(0) = 0°\;\;\;\leftrightarrow\;\;\;arcsin(1)=90°
arcsin(0.01) = 0.57°\;\;\;\leftrightarrow\;\;\;arcsin(0.99)=81.89°

Eine Messwerteschwankung wirkt sich also um 90° herum also ziemlich drastisch aus. Bei kleineren Neigewinkeln ist die Methode aber recht brauchbar.

Beispielsketch „Alle Werte“

Hier nun der Sketch, der die Beschleunigungen und Winkel aus den Rohdaten berechnet. Ein paar Anmerkungen:

  • Die Werte sind jeweils in einem Array gespeichert (x,y,z).
  • Die asin() Funktion liefert die Winkel in Rad. Ich rechne die Ergebnisse in Grad um (mal 360/2π).
  • Ich habe versucht, die Ausgabe mit sprintf schön zu formatieren. Bei den g-Werten (gVal) wurde es recht komplex. Ich hatte es hinbekommen, aber nur mit recht viel Code und habe es dann doch weggelassen, damit der Blick auf das Wesentliche erhalten bleibt.
  • An einigen Stellen multipliziere ich mit 1.0. Das hat den Sinn zu verhindern, dass mir der Compiler Integerwerte liefert, wo ich Fließkommazahlen haben möchte.
  • Um Fehler durch geff/g-Werte größer 1 abzufangen (ungültig für die asin Funktion), schneide ich die Werte einfach ab, falls es dazu kommt.

 

const int unitsPerG = 163; // depends on your ADC and the range (1.5g vs 6g)
int axisPin[] = {A0, A1, A2};
int selfTestPin = 8;
int gSelectPin = 9;
int sleepPin = 10;
int startVal[3] = {0};

char result[8]; // for format of the output

void setup(){
  Serial.begin(9600);
  pinMode(selfTestPin, OUTPUT);
  pinMode(gSelectPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(selfTestPin, LOW);
  digitalWrite(gSelectPin, LOW);
  getStartVals(); 
}

void loop(){
  int angles[3] = {0};
  int raw[3] = {0};
  float gVal[3] = {0.0};

  getRawVals(raw);
  getAngles(angles,raw);
  getGValues(gVal, raw); 
  Serial.print("x-Raw:  "); sprintf(result,"%6d", raw[0]); // sprintf for format
  Serial.print(result);
  Serial.print("     ");
  Serial.print("y-Raw:  "); sprintf(result,"%6d", raw[1]); 
  Serial.print(result);
  Serial.print("     ");
  Serial.print("z-Raw:  "); sprintf(result,"%6d", raw[2]); 
  Serial.println(result);
  
  Serial.print("x-Angle:  "); sprintf(result,"%4d", angles[0]);
  Serial.print(result);
  Serial.print("     ");
  Serial.print("y-Angle:  "); sprintf(result,"%4d", angles[1]);
  Serial.print(result);
  Serial.print("     ");
  Serial.print("z-Angle:  "); sprintf(result,"%4d", angles[2]);
  Serial.println(result);

  Serial.print("x-g:      "); 
  Serial.print(gVal[0]); 
  Serial.print("     ");
  Serial.print("y-g:      "); 
  Serial.print(gVal[1]);
  Serial.print("     ");
  Serial.print("z-g:      ");
  Serial.println(gVal[2]);
  Serial.println();
  
  delay(1000);
}

void getGValues(float gValueArray[], int rawArray[]){
  for(int i=0; i<3; i++){
    int diff = rawArray[i] - startVal[i];
    gValueArray[i] = diff*1.0/unitsPerG;
  }
}

void getRawVals(int rawVals[]){
  for(int i=0; i<3; i++){
    rawVals[i] = readMMA7361(axisPin[i]);
  }
}

void getAngles(int angleArray[], int rawArray[]){
  bool positive = true;
  for(int i=0; i<3; i++){
    int diff = rawArray[i] - startVal[i];
    positive = (diff>=0);
    diff = abs(diff);
    if(diff > unitsPerG){
      diff = unitsPerG;
    }
    angleArray[i] = int((asin(diff*1.0/unitsPerG))*57.296);
    if(positive==false){
      angleArray[i] = -angleArray[i];
    }
  } 
}

int readMMA7361(int pin){
  long sum = 0;
  int result = 0;
  for(int i=0; i<50; i++){  // mean value of 50 measurements
    sum += analogRead(pin);
  }
  result = sum / 50;
  return result;
}

void getStartVals(){
  Serial.println("Your MMA71361 should now be placed flat on the ground, i.e.:");
  Serial.println("x/y-axis = 0 degrees, z-axis = 90 degrees ");
  Serial.print("Wait....");
  delay(2000); // to have enough time to position the MMA7361
  for(int i=0; i<3; i++){
    startVal[i] = readMMA7361(axisPin[i]);
  }
  startVal[2] -= unitsPerG; // Z-axis is at 90° in start position! 
  Serial.println("ready!");
}

 

Das Ergebnis

So kann dann ein Ergebnis aussehen:

Ausgabe von MMA7361_all_data.ino
Ausgabe von MMA7361_all_data.ino

Man sieht ganz gut, wie ich den MMA7361 zwischen dem zweiten und dritten Messblock um 90° gedreht habe.

Free Fall (0 g) Interrupt

Der Pin 0G ist im Normalzustand LOW. Wird an allen Achsen gleichzeitig 0 g gemessen (laut Datenblatt -0,4 g bis + 0,4 g) geht der Pin 0G auf HIGH. Der 0 g Zustand wird im freien Fall erreicht oder bei Vibrationen.

So könntet Ihr das Signal nutzen:

int selfTestPin = 8;
int gSelectPin = 9;
int sleepPin = 10;
int zeroGPin = 2;

volatile bool fall = false;

void setup(){
  Serial.begin(9600);
  pinMode(selfTestPin, OUTPUT);
  pinMode(gSelectPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  pinMode(zeroGPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(zeroGPin), zeroG, RISING);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(selfTestPin, LOW);
  digitalWrite(gSelectPin, LOW);
  Serial.println("Free Fall Interrupt for MMA7261 active...");
  delay(100);
  fall = false;
}

void loop(){
  if(fall==true){
    Serial.println("Aaaaaaaaah!!!!! - I'm faaaaallllling!");
    delay(1000);
  }
  fall = false;
}

void zeroG(){
  fall = true;
}

Der Free-Fall Interrupt ist relativ empfindlich. Er kann schon ausgelöst werden, wenn ihr etwas kräftiger auf den Tisch haut, auf dem der MMA7361 liegt. Um zu verhindern, dass er schon bei einer kurzen Vibration auslöst, könnt ihr einen kleinen Trick anwenden.

Zunächst wechselt ihr die Bedingung für den Interrupt von RISING auf HIGH. Wenn ein Interrupt ausgelöst wird, dann prüft ihr nach einer kurzen Zeit (ich habe im Beispielsketch 100 ms gewählt) für eine weitere kurze Zeit (z.B. 50 ms) ob der 0G Pin immer noch aktiv ist.

int selfTestPin = 8;
int gSelectPin = 9;
int sleepPin = 10;
int zeroGPin = 2;

volatile bool fall = false;

void setup(){
  Serial.begin(9600);
  pinMode(selfTestPin, OUTPUT);
  pinMode(gSelectPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  pinMode(zeroGPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(zeroGPin), zeroG, HIGH);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(selfTestPin, LOW);
  digitalWrite(gSelectPin, LOW);
  Serial.println("Free Fall Interrupt for MMA7261 active...");
  delay(100);
  fall = false;
}

void loop(){
  if(fall==true){
    delay(100);
    fall = false;
    delay(50);
      if(fall==true){
        Serial.println("Aaaaaaaaah!!!!! - I'm faaaaallllling!");
        delay(1000);
      }
    fall = false;
  }
}

void zeroG(){
  fall = true;
}

 

Spielt einfach ein wenig mit den delay Zeiten herum, um die Interrupt Funktion euren Bedürfnissen anzupassen.

Der MMA7361 als Lagesensor

Im letzten Abschnitt möchte ich euch noch zeigen, wie ihr den MMA7361 als Lagesensor nutzen könnt. Damit meine ich die Bestimmung der 6 verschiedenen Ausrichtungen im Raum, die ich folgendermaßen bezeichne (Blick von der Seite):

Die sechs Ausrichtungen des MMA7361 (von der Seite betrachtet)
Die sechs Ausrichtungen des MMA7361 (von der Seite betrachtet)

Im Prinzip ist das einfach. Zunächst werden die Winkel bestimmt. Die Achsen mit einem Winkel(-betrag) kleiner 45° gelten als horizontal, die Achse mit einem Winkel(-betrag) größer oder gleich 45° als vertikal. Je nachdem, ob die vertikale Achse einen positiven oder negativen Winkel hat, zeigt sie nach oben oder unten. Und das fragt der nachfolgende Sketch einfach systematisch ab. Den Spezialfall, dass zwei Achsen genau 45° haben, habe ich nicht berücksichtigt.

const int unitsPerG = 163;
int axisPin[] = {A0, A1, A2};
int selfTestPin = 8;
int gSelectPin = 9;
int sleepPin = 10;
int startVal[3] = {0};

enum MMA7361_ORIENTATION{
  FLAT, FLAT_1, XY, XY_1, YX, YX_1
} orientation;

void setup(){
  Serial.begin(9600);
  pinMode(selfTestPin, OUTPUT);
  pinMode(gSelectPin, OUTPUT);
  pinMode(sleepPin, OUTPUT);
  digitalWrite(sleepPin, HIGH);
  digitalWrite(selfTestPin, LOW);
  digitalWrite(gSelectPin, LOW);
  getStartVals(); 
}

void loop(){
  int angles[3] = {0};
  int raw[3] = {0};
 
  getRawVals(raw);
  getAngles(angles,raw);
  getOrientation(angles);
  printOrientation();

  delay(1000);
}

void getRawVals(int rawVals[]){
  for(int i=0; i<3; i++){
    rawVals[i] = readMMA7361(axisPin[i]);
  }
}

void getAngles(int angleArray[], int rawArray[]){
  bool positive = true;
  for(int i=0; i<3; i++){
    int diff = rawArray[i] - startVal[i];
    positive = (diff>=0);
    diff = abs(diff);
    if(diff > unitsPerG){
      diff = unitsPerG;
    }
    angleArray[i] = int((asin(diff*1.0/unitsPerG))*57.296);
    if(positive==false){
      angleArray[i] = -angleArray[i];
    }
  } 
}

int readMMA7361(int pin){
  long sum = 0;
  int result = 0;
  for(int i=0; i<50; i++){
    sum += analogRead(pin);
  }
  result = sum / 50;
  return result;
}

void getStartVals(){
  Serial.println("Your MMA71361 should now be placed flat on the ground, i.e.:");
  Serial.println("x/y-axis = 0 degrees, z-axis = 90 degrees ");
  Serial.print("Wait....");
  delay(2000);
  for(int i=0; i<3; i++){
    startVal[i] = readMMA7361(axisPin[i]);
  }
  startVal[2] -= unitsPerG; // Z-axis is at 90° start position! 
  Serial.println("ready!");
}

void getOrientation(int angleArray[]){
  if(abs(angleArray[0])<45){      // |x| < 45
    if(abs(angleArray[1])<45){    // |y| < 45
      if(angleArray[2]>0){        //  z  > 0
        orientation = FLAT;
      }
      else{                       //  z  < 0
        orientation = FLAT_1;
      }
    }
    else{                         // |y| > 45 
      if(angleArray[1]>0){        //  y  > 0
        orientation = XY;
      }
      else{                       //  y  < 0
        orientation = XY_1;   
      }
    }
  }
  else{                           // |x| >= 45
    if(angleArray[0]>0){          //  x  >  0
      orientation = YX;       
      }
      else{                       //  x  <  0
        orientation = YX_1;
      }
  }
}

void printOrientation(){
  Serial.print("Orientierung: ");
  
  switch(orientation){
    case FLAT:
      Serial.println("Flat");
      break;
    case FLAT_1:
      Serial.println("Flat - upside down");
      break;
    case XY:
      Serial.println("XY");
      break;
    case XY_1:
      Serial.println("XY - upside down");
      break;
    case YX:
      Serial.println("YX");
      break;
    case YX_1:
      Serial.println("YX - upside down");
      break;
  }
}

 

Danksagung

Die Rakete im Beitragsbild stammt von Satheesh Sankaran auf Pixabay.

2 thoughts on “MMA7361 – Beschleunigungssensor

  1. Hallo,

    wir haben ein-zwei Probleme.
    Ich habe Patent auf Elektronisches Mülltonnen Schloss der auf Mülltonnen Deckel befestigt wird.
    Dafür brauche ich Beschleunigung INFO vom Heber der auf LKW ist.
    Weil aber immer andere LKW,S oder Hebe Vorrichtung ist, ist Schwer das sich Tonne immer Öffnet.
    Was können Sie mir Vorschlagen? dass sich Tonne IMMER Entleert bei End-Prozess .
    MfG
    Nikic

    eMail: ivan.nikic1@gmail.com

    1. Hallo, ich weiß nicht, ob ich das Problem richtig verstanden habe. Gibt es dazu vielleicht eine Zeichnung oder andere Unterlagen? Die können Sie mir an wolfgang.ewald@wolles-elektronikkiste.de senden. Oder auch einfach nur eine genauere Erklärung. Muss denn der Beschleunigungssensor auf dem Heber sitzen? Kann man nicht einfach den Sensor mit auf die Mülltonne setzen? Und wenn die Mülltonne dann beschleunigt wird oder in einem bestimmten Winkel geneigt ist, dann ist das das Signal zum Öffnen. Ansonsten müsste jeder Heber mit einem Sensor ausgestattet werden. Und er müsste dann per Funk (433Mhz oder Bluetooth dem Mülltonnenschloss sagen, dass es aufgehen soll. Aber wie gesagt, vielleicht habe ich das Problem auch noch nicht verstanden. VG, Wolfgang

Schreibe einen Kommentar

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