ICM-20948 9-Achsensensor Teil I

Über den Beitrag

In diesem Beitrag stelle ich mit dem ICM-20948 einen weiteren 9-Achsensensor vor. Vor ein paar Monaten hatte ich schon über seinen Vorgänger, den MPU-9250, berichtet. Der MPU-9250 ist vom Hersteller als „EoL“ (End of Life) gekennzeichnet. Aber auch, wenn er sicherlich noch eine ganze Weile erhältlich sein sollte, ist es an der Zeit, sich den Nachfolger näher anzuschauen.

Zumindest nach außen sind die Unterschiede zwischen dem ICM-20948 und dem MPU-9250 nicht wirklich groß. Wenn ihr mit meiner Bibliothek zum MPU-9250 gearbeitet habt, werdet ihr deshalb auch schnell mit meiner Bibliothek zum ICM-20948 zurechtkommen.

„Unter der Haube“, also auf Registerebene, sind die Unterschiede erheblich. Auch, wenn es anders wirkt – das Erstellen der Bibliothek war deshalb mehr als eine kleine Modifikation der MPU-9250 Bibliothek.

Eigenschaften und technische Daten des ICM-20948

Der ICM-20948 heißt 9-Achsensensor, da er drei Sensoren vereinigt, die jeweils drei Achsen abdecken. Er ist:

  • Beschleunigungssensor (Akzelerometer),
  • Rotationsgeschwindigkeitssensor (Gyroskop),
  • und Magnetometer.
  • Darüber hinaus besitzt der ICM-20948 ein Thermometer.

Oft findet man für solche kombinierten Sensoren auch die Bezeichnung IMU für „Inertial Measurement Unit“.

Wie ein Gyroskop und ein Beschleunigungssensor funktionieren, habe ich schon in meinem Beitrag über den MPU6050 beschrieben. Das Magnetometer funktioniert auf Basis des Hall-Effekts. Der ist hier sehr anschaulich erklärt.

 

ICM-20948 Modul

Die wesentlichen technischen Daten des ICM-20948 sind:

  • Spannungsversorgung: 1.71 – 3.6 Volt
    • die meisten Module haben keinen Spannungsregler
  • Kommunikation über I2C, Adressen:
    • AD0 an LOW: 0x68
    • AD0 an HIGH: 0x69
  • Kommunikation über SPI (habe ich nicht implementiert)
  • Gyroskop:
    • Messbereiche: +/-250, +/-500, +/-1000, +/-2000 °/s
    • Datenrate: 4.4 Hz – 9 kHz
  • Beschleunigungssensor:
    • Messbereiche: +/- 2, +/-4, +/-8, +/-16 g
    • Datenrate: 4.5 Hz – 4.5 kHz
  • Magnetometer (AK09916):
    • Messbereich: +/- 4900 µT
    • max. Datenrate: 100 Hz
  • FIFO (first in, first out) Datenspeicher: 512 / 4096 Bytes
  • Interrupts (die von mir implementierten): FIFO Überlauf, Data Ready, FSync und „Wake-on-Motion“ (Beschleunigungsinterrupt)
  • Integriertes Thermometer

Der ICM-20948 ermöglicht es, bis zu fünf weitere Sensoren mit einer I2C Hilfsschnittstelle (Auxiliary Interface) zu steuern. Die Messwerte werden in Registern des ICM-20948 gespeichert und von dort ausgelesen.

Das Magnetometer ist nicht wirklich in den ICM-20948 integriert. Es besitzt eigene Register und eine eigene I2C Adresse. Ihr könnt es nur über die eben beschriebene Hilfsschnittstelle erreichen. Aber keine Sorge, ihr müsst euch nicht damit auseinandersetzen, denn das macht meine Bibliothek im Hintergrund.

Weitere Informationen findet ihr im technischen Datenblatt des ICM-20948 und im Datenblatt des Magnetometers AK09916. Über die Unterschiede zwischen dem MPU-9250 und dem ICM-20948 könnt ihr euch hier informieren.

Digital Motion Processor DMP

Der ICM-20948 bietet die Möglichkeit, die von ihm und weiteren externen Sensoren gemessenen Daten zu verarbeiten. Das entlastet den steuernden Mikrocontroller. Zu diesem Zweck besitzt der ICM-20948 den sogenannten Digital Motion ProcessorTM, kurz DMP. Allerdings wäre die Implementierung der DMP Funktionen ein Aufwand für mich, der meine Kapazitäten übersteigt. In die Entwicklung der Bibliothek habe auch ohne das schon ein paar dutzend Stunden gesteckt. Außerdem würden die meisten Hobbyanwender die DMP Funktionen wohl gar nicht nutzen.

Ansteuerung

Schaltung

Bei der Spannungsversorgung müsst ihr darauf achten, dass die meisten ICM-20948 Module nur bis zu 3.6 Volt vertragen. Das gilt für alle Pins. Deshalb habe ich einen Levelshifter zwischen den Arduino Nano und den ICM-20948 gesetzt. Alternativ setzt ihr Spannungsteiler ein oder – noch einfacher – ihr verwendet einen Mikrocontroller, der auf 3.3 Volt läuft.

Den Adresspin AD0 verbindet ihr mit VCC oder GND, je nachdem welche Adresse ihr verwenden wollt. In einigen Beispielsketchen verwende ich den Interruptpin INT, den ich an den Arduino Pin 2 gehängt habe.

Weitere Pins:

  • ADA und ACL sind Anschlüsse für weitere Sensoren, die über die I2C Hilfsschnittstelle angesteuert werden („A“ für auxiliary). Das habe ich nicht in allgemeiner Form implementiert, sondern nur für das Magnetometer.
  • NCS ist der Chip Select Pin für die Ansteuerung per SPI.
  • FYSYNC könnt ihr als externen Interruptpin einsetzen. Dazu habe ich später einen Beispielsketch.
Ansteuerung des ICM-20948 mit Arduino Nano und Level-Konverter
Ansteuerung des ICM-20948 mit Arduino Nano und Level-Konverter

Einführung in die ICM20948_WE Bibliothek

Ich habe einige Bibliotheken ausprobiert, konnte mich aber für keine so richtig begeistern. Deswegen habe ich meine eigene Bibliothek geschrieben, die ihr hier auf GitHub findet. Alternativ könnt ihr sie direkt über den Bibliotheksmanager der Arduino IDE installieren.

Das Problem mit dem ICM-20948 ist, dass er – bedingt durch seine 4 Sensoren plus I2C Hilfsschnittstelle – unglaublich viele Einstellmöglichkeiten hat. Und obwohl ich nicht alle Features implementiert habe, umfasst meine Bibliothek stolze neunundfünfzig öffentliche Funktionen. Um die Verwendung dieser Funktionen zu erklären, habe ich die Bibliothek mit dreizehn Beispielsketchen ausgestattet.

Ich gehe nun die Beispielsketche durch, was ziemlich trockene Lektüre ist. Vielleicht probiert ihr sie einfach aus und kommt hierher zurück, wenn ihr etwas nicht versteht.

Basisdaten mit dem ICM-20948 ermitteln

ICM20948_01_acceleration_data.ino

Auf diesen ersten Sketch gehe ich am intensivsten ein. Vieles wiederholt sich in den folgenden Sketchen.

Vorab noch eine Bemerkung zu dem Datentyp „xyzFloat“. Dabei handelt es sich um eine Struktur (struct), die aus drei Floatwerten besteht. Ich benutze xyzFloat für alle Daten, die eine x-, y- und z-Komponente haben.

#include <Wire.h>
#include <ICM20948_WE.h>
#define ICM20948_ADDR 0x68

/* There are several ways to create your ICM20948 object:
 * ICM20948_WE myIMU = ICM20948_WE()              -> uses Wire / I2C Address = 0x68
 * ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR) -> uses Wire / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2)        -> uses the TwoWire object wire2 / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2, ICM20948_ADDR) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR);

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while(!Serial) {}
  
  if(!myIMU.init()){
    Serial.println("ICM20948 does not respond");
  }
  else{
    Serial.println("ICM20948 is connected");
  }

  /*  This is a method to calibrate. You have to determine the minimum and maximum 
   *  raw acceleration values of the axes determined in the range +/- 2 g. 
   *  You call the function as follows: setAccOffsets(xMin,xMax,yMin,yMax,zMin,zMax);
   *  The parameters are floats. 
   *  The calibration changes the slope / ratio of raw acceleration vs g. The zero point 
   *  is set as (min + max)/2.
   */
  myIMU.setAccOffsets(-16330.0, 16450.0, -16600.0, 16180.0, -16640.0, 16560.0);
    
  /* The starting point, if you position the ICM20948 flat, is not necessarily 0g/0g/1g for x/y/z. 
   * The autoOffset function measures offset. It assumes your ICM20948 is positioned flat with its 
   * x,y-plane. The more you deviate from this, the less accurate will be your results.
   * It overwrites the zero points of setAccOffsets, but keeps the the correction of the slope.
   * The function also measures the offset of the gyroscope data. The gyroscope offset does not   
   * depend on the positioning.
   * This function needs to be called after setAccOffsets but before other other settings since 
   * it will overwrite settings!
   */
  Serial.println("Position your ICM20948 flat and don't move it - calibrating...");
  delay(1000);
  myIMU.autoOffsets();
  Serial.println("Done!"); 
  
  /* enables or disables the acceleration sensor, default: enabled */
  // myIMU.enableAcc(true);

  /*  ICM20948_ACC_RANGE_2G      2 g   (default)
   *  ICM20948_ACC_RANGE_4G      4 g
   *  ICM20948_ACC_RANGE_8G      8 g   
   *  ICM20948_ACC_RANGE_16G    16 g
   */
  myIMU.setAccRange(ICM20948_ACC_RANGE_2G);
  
  /*  Choose a level for the Digital Low Pass Filter or switch it off.  
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF       3dB Bandwidth [Hz]      Output Rate [Hz]
   *    0              246.0               1125/(1+ASRD) (default)
   *    1              246.0               1125/(1+ASRD)
   *    2              111.4               1125/(1+ASRD)
   *    3               50.4               1125/(1+ASRD)
   *    4               23.9               1125/(1+ASRD)
   *    5               11.5               1125/(1+ASRD)
   *    6                5.7               1125/(1+ASRD) 
   *    7              473.0               1125/(1+ASRD)
   *    OFF           1209.0               4500
   *    
   *    ASRD = Accelerometer Sample Rate Divider (0...4095)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setAccDLPF(ICM20948_DLPF_6);    
  
  /*  Acceleration sample rate divider divides the output rate of the accelerometer.
   *  Sample rate = Basic sample rate / (1 + divider) 
   *  It can only be applied if the corresponding DLPF is not off!
   *  Divider is a number 0...4095 (different range compared to gyroscope)
   *  If sample rates are set for the accelerometer and the gyroscope, the gyroscope
   *  sample rate has priority.
   */
  myIMU.setAccSampleRateDivider(10);
}

void loop() {
  myIMU.readSensor();
  xyzFloat accRaw = myIMU.getAccRawValues();
  xyzFloat corrAccRaw = myIMU.getCorrectedAccRawValues();
  xyzFloat gVal = myIMU.getGValues();
  float resultantG = myIMU.getResultantG(gVal);

   
  Serial.println("Raw acceleration values (x,y,z):");
  Serial.print(accRaw.x);
  Serial.print("   ");
  Serial.print(accRaw.y);
  Serial.print("   ");
  Serial.println(accRaw.z);

  Serial.println("Corrected raw acceleration values (x,y,z):");
  Serial.print(corrAccRaw.x);
  Serial.print("   ");
  Serial.print(corrAccRaw.y);
  Serial.print("   ");
  Serial.println(corrAccRaw.z);

  Serial.println("g-values (x,y,z):");
  Serial.print(gVal.x);
  Serial.print("   ");
  Serial.print(gVal.y);
  Serial.print("   ");
  Serial.println(gVal.z);

  Serial.print("Resultant g: ");
  Serial.println(resultantG);
  Serial.println("*************************************");
 
  delay(1000);
}

 

Initialisierung und Offsets

Ich habe mehrere Konstruktoren implementiert. Das erlaubt euch die I2C Adresse und/oder das I2C Objekt zu übergeben. Letzteres ist interessant, wenn ihr beispielsweise beide I2C Schnittstellen eines ESP32 nutzen wollt.

Die Funktion init() führt zunächst ein Reset des ICM-20948 durch und schreibt Defaultwerte in einige Register. init() liefert false zurück, wenn der ICM-20948 nicht ansprechbar sein sollte, ansonsten true.

Unbewegt wirkt nur die Erdbeschleunigung auf den ICM-20948. Wenn er dabei flach (also mit seiner x,y-Ebene) aufliegt, sollte der g-Wert für die x- und y-Achse null und für die z-Achse eins betragen. Diese Werte sind aber mehr oder weniger weit verschoben. Die Funktion autoOffsets() misst die Offsetwerte und zieht sie bei zukünftigen Messungen ab. Sie korrigiert allerdings nicht die Steigung.

autoOffsets() funktioniert nur dann vernünftig,

  • wenn das Modul flach mit seiner x,y-Ebene aufliegt,
  • nicht bewegt wird
  • und die Funktion im Setup als erste Funktion aufgerufen wird (da sie bestimmte Einstellungen verändert)

Alternativ könnt ihr die Funktion setAccOffsets() verwenden. Sie führt zu weniger guten Ergebnissen bzgl. des Nullpunkts, allerdings ist sie bei größeren Neigungen akkurater. Außerdem korrigiert sie die Steigung und ihr müsst das Modul bei Programmstart nicht ausrichten.

Weitere Einstellungen

Die Funktion setAccRange() legt den Messbereich für die Beschleunigungsmessung fest.

Um die Datenrate des Beschleunigungssensors zu steuern, verwendet ihr die Funktion setAccSampleRateDivider(divider).

\text{acceleration data rate}=\frac{1125}{1+divider}\;[\text{Hz}]\;\;\;\;\;\;\;\;\text{mit}\;\;divider = 0...4095

Das funktioniert aber nur, wenn der digitale Tiefpassfilter (digital low pass filter = DLPF) aktiviert ist. Mit setAccDLPF(level) schaltet ihr den DLPF ein und wählt die Stufe. Mit dem Parameter ICM20948_DLPF_OFF deaktiviert ihr den DLPF. Je höher die Stufe, desto geringer ist das Rauschen. Nur die Stufe 7 fällt diesbezüglich aus der Reihe. Der Nachteil einer hohen Glättung ist eine gewisse Trägheit. Das heißt, dass es bei Änderung der Beschleunigung eine gewisse Zeit dauert, bis ihr den korrekten Wert erreicht.

Mit enableAcc(true/false) schaltet Ihr den Beschleunigungssensor an bzw. aus.

Die Ergebnisse

Die Messergebnisse fragt ihr mit den folgenden Funktionen ab:

  • readSensor() liest die Datenregister im Burst Verfahren (also „in einem Rutsch“) aus und schreibt das Ergebnis in ein Puffer-Array.
    • Ohne vorherigen readSensor() Aufruf erhaltet ihr keine aktuellen Werte.
  • getAccRawValues() liefert die Rohwerte der Beschleunigung, so wie sie im ICM-20948 Datenregister stehen.
  • getCorrectedAccRawValues() liest die Rohwerte und korrigiert sie um die Offsets.
  • getGValues() liefert die Beschleunigungswerte in g (basierend auf den korrigierten Rohdaten).
  • getResultantG(gValue) berechnet die resultierende Beschleunigung aus einem g-Wertetripel, also den Betrag der Summe der drei Vektoren.
\text{resultantG}=\sqrt{(gValue.x)^2+(gValue.y)^2+(gValue.z)^2} \;\;\text{[g]}

Wirkt nur die Schwerkraft auf den ICM-20948, sollte die Resultierende immer 1 sein. Mit der Resultierenden könnt ihr bequem Beschleunigungen messen, ohne die Bewegung auf eine Achse ausrichten zu müssen. Zieht einfach 1 ab, um die Erdbeschleunigung herauszurechnen.

Die Ausgabe von ICM20948_01_acceleration_data.ino

ICM-20948 - Ausgabe von ICM20948_01_acceleration_data.ino
Ausgabe von ICM20948_01_acceleration_data.ino

ICM20948_02_gyroscope_data.ino

Da ihr nun schon einige Funktionen kennt, kann die Erklärung zu den Gyroskop Funktionen deutlich kürzer ausfallen.

#include <Wire.h>
#include <ICM20948_WE.h>
#define ICM20948_ADDR 0x68

/* There are several ways to create your ICM20948 object:
 * ICM20948_WE myIMU = ICM20948_WE()              -> uses Wire / I2C Address = 0x68
 * ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR) -> uses Wire / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2)        -> uses the TwoWire object wire2 / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2, ICM20948_ADDR) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR);

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while(!Serial) {}
  
  if(!myIMU.init()){
    Serial.println("ICM20948 does not respond");
  }
  else{
    Serial.println("ICM20948 is connected");
  }

  Serial.println("Position your ICM20948 flat and don't move it - calibrating...");
  delay(1000);
  myIMU.autoOffsets();
  Serial.println("Done!"); 
  
  /*  The gyroscope data is not zero, even if you don't move the ICM20948. 
   *  To start at zero, you can apply offset values. These are the gyroscope raw values you obtain
   *  using the +/- 250 degrees/s range. 
   *  Use either autoOffsets or setGyrOffsets, not both.
   */
  //myIMU.setGyrOffsets(-115.0, 130.0, 105.0);
  
  /* enables or disables the gyroscope sensor, default: enabled */
  // myIMU.enableGyr(false);

  /*  ICM20948_GYRO_RANGE_250       250 degrees per second (default)
   *  ICM20948_GYRO_RANGE_500       500 degrees per second
   *  ICM20948_GYRO_RANGE_1000     1000 degrees per second
   *  ICM20948_GYRO_RANGE_2000     2000 degrees per second
   */
  myIMU.setGyrRange(ICM20948_GYRO_RANGE_250);
  
  /*  Choose a level for the Digital Low Pass Filter or switch it off. 
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF       3dB Bandwidth [Hz]      Output Rate [Hz]
   *    0              196.6               1125/(1+GSRD) 
   *    1              151.8               1125/(1+GSRD)
   *    2              119.5               1125/(1+GSRD)
   *    3               51.2               1125/(1+GSRD)
   *    4               23.9               1125/(1+GSRD)
   *    5               11.6               1125/(1+GSRD)
   *    6                5.7               1125/(1+GSRD) 
   *    7              361.4               1125/(1+GSRD)
   *    OFF          12106.0               9000
   *    
   *    GSRD = Gyroscope Sample Rate Divider (0...255)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setGyrDLPF(ICM20948_DLPF_6);  
  
  /*  Gyroscope sample rate divider divides the output rate of the gyroscope.
   *  Sample rate = Basic sample rate / (1 + divider) 
   *  It can only be applied if the corresponding DLPF is not OFF!
   *  Divider is a number 0...255
   *  If sample rates are set for the accelerometer and the gyroscope, the gyroscope
   *  sample rate has priority.
   */
  //myIMU.setGyrSampleRateDivider(10);
}

void loop() {
  myIMU.readSensor();
  xyzFloat gyrRaw = myIMU.getCorrectedGyrRawValues();
  xyzFloat gyr = myIMU.getGyrValues();
    
  Serial.println("Raw gyroscope values (x,y,z):");
  Serial.print(gyrRaw.x);
  Serial.print("   ");
  Serial.print(gyrRaw.y);
  Serial.print("   ");
  Serial.println(gyrRaw.z);

  Serial.println("Gyroscope values (x,y,z):");
  Serial.print(gyr.x);
  Serial.print("   ");
  Serial.print(gyr.y);
  Serial.print("   ");
  Serial.println(gyr.z);
  Serial.println();

  delay(500);
}

 

Auch hier könnt ihr die autoOffsets() Funktion verwenden. Das Gyroskop sollte im nicht bewegten Zustand für alle Achsen den Messwert null liefern, oder zumindest um null herum schwanken. Ihr werdet aber einen gewissen (lageunabhängigen) Offset feststellen, den diese Funktion ermittelt. Alternativ setzt ihr die im Messbereich +/-250 °/s ermittelten Werte in setGyrOffsets() ein.

Den Tiefpassfilter (DLPF) aktiviert ihr mit setGyrDLPF(value) und legt die Stärke fest. Auch hier führt ein stärkerer Filter zu größeren Verzögerungen. Und ebenso fällt die Stufe 7 wieder aus der Reihe. Übergebt ihr als Parameter ICM20948_DLPF_OFF, schaltet ihr den DLPF damit ab.

Weitere Funktionen:

  • setGyrSampleRateDivider() funktioniert wie beim Beschleunigungssensor.
  • setGyrRange() legt den Messbereich fest.
  • enableGyr(true/false) aktiviert bzw. deaktiviert das Gyroskop.
  • getGyrRawValues() liefert die aktuell vorhandenen Rohdaten.
  • getCorrectedGyrRawValues() zieht die Offsets von den Rohdaten ab und liefert die korrigierten Daten.
  • getGyrValues() liefert die Gyroskop Daten in Grad/Sekunde, basierend auf den korrigierten Rohdaten.

Die Ausgabe von ICM20948_02_gyroscope_data.ino

ICM-20948 - Ausgabe von ICM20948_02_gyroscope_data.ino
Ausgabe von ICM20948_02_gyroscope_data.ino

ICM20948_03_magnetometer.ino

Das Magnetometer (AK09916) verhält sich wie ein separates Bauteil. Es hat eine eigene I2C Adresse, eigene Register und muss deswegen auch separat initialisiert werden. Wenn ihr einen I2C Scanner auf den ICM-20948 anwendet, werdet ihr aber nur seine Adresse sehen. Das Magnetometer liegt nämlich, nicht direkt zugänglich, sozusagen hinter dem ICM-20948.  Die Ansteuerung läuft über die oben beschriebene I2C Hilfsschnittstelle, die über den ICM-20948 eingerichtet wird. Davon bekommt ihr allerdings nichts mit, da die Bibliothek das im Hintergrund erledigt.

#include <Wire.h>
#include <ICM20948_WE.h>
#define ICM20948_ADDR 0x68

/* There are several ways to create your ICM20948 object:
 * ICM20948_WE myIMU = ICM20948_WE()              -> uses Wire / I2C Address = 0x68
 * ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR) -> uses Wire / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2)        -> uses the TwoWire object wire2 / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2, ICM20948_ADDR) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR);

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while (!Serial) {}

  if (!myIMU.init()) {
    Serial.println("ICM20948 does not respond");
  }
  else {
    Serial.println("ICM20948 is connected");
  }

  if (!myIMU.initMagnetometer()) {
    Serial.println("Magnetometer does not respond");
  }
  else {
    Serial.println("Magnetometer is connected");
  }

  /* You can set the following modes for the magnetometer:
   * AK09916_PWR_DOWN          Power down to save energy
   * AK09916_TRIGGER_MODE      Measurements on request, a measurement is triggered by 
   *                           calling setMagOpMode(AK09916_TRIGGER_MODE)
   * AK09916_CONT_MODE_10HZ    Continuous measurements, 10 Hz rate
   * AK09916_CONT_MODE_20HZ    Continuous measurements, 20 Hz rate
   * AK09916_CONT_MODE_50HZ    Continuous measurements, 50 Hz rate
   * AK09916_CONT_MODE_100HZ   Continuous measurements, 100 Hz rate (default)
   */
  myIMU.setMagOpMode(AK09916_CONT_MODE_20HZ);
}

void loop() {
  myIMU.readSensor();
  xyzFloat magValue = myIMU.getMagValues(); // returns magnetic flux density [µT]

  Serial.println("Magnetometer Data in µTesla: ");
  Serial.print(magValue.x);
  Serial.print("   ");
  Serial.print(magValue.y);
  Serial.print("   ");
  Serial.println(magValue.z);

  delay(1000);
}

 

Einstellungen für das Magnetometer

Das Magnetometer hat einen Power-Down Modus, einen Trigger Modus und vier kontinuierliche Modi. Die kontinuierlichen Modi unterscheiden sich in der Datenrate, nämlich 10, 20, 40 oder 100 Hz. Ihr legt den Modus mit setMagOpMode() fest.

Die Messwerte erhaltet ihr mit getMagValues(). Das Ergebnis wird in Mikrotesla ausgegeben. Die Funktion liefert die Werte, die sich gerade im Datenpuffer befinden. Zuvor müsst ihr wieder mit readSensor() dafür sorgen, dass die Sensordaten aus den Datenspeichern gelesen werden.

Im Triggermodus löst setMagOpMode(AK09916_TRIGGER_MODE) eine Messung aus. Wenn ihr diese Option wählt, müsst ihr dem Magnetometer genügend Zeit lassen, die Messung durchzuführen und die Daten an den ICM-20948 zu übermitteln.

Die Ausgabe von ICM20948_03_magnetometer.ino

Für folgende Ausgabe habe ich den ICM-20948 flach in der x,y-Ebene gedreht:

ICM20948 - Ausgabe von ICM20948_03_magnetometer.ino
Ausgabe von ICM20948_03_magnetometer.ino

In Mitteleuropa liegt die magnetische Flussdichte des Erdmagnetfeldes bei ca. 20 µT in der Horizontalen und um 44 µT in der Vertikalen (Quelle: Wikipedia). Dementsprechend sollte die Differenz zwischen dem maximalen und dem minimalen Wert bei Drehung in der Horizontalen oder in der Vertikalen bei 40 beziehungsweise 88 µT liegen. Das werdet ihr wahrscheinlich so nicht messen, denn:

  • Es gibt auch hier Offsets.
  • Ihr messt wahrscheinlich drinnen, und je nach Bauweise des Gebäudes wird das Erdmagnetfeld abgeschirmt.
  • Wenn ihr auf dem Breadboard messt, können die Steckbrückenkabel und die Metallleitungen des Breadboards die Messungen beeinflussen.

ICM20948_04_calibration.ino

Dieser Sketch soll euch helfen, die Offsets für den Beschleunigungssensor und das Gyroskop zu ermitteln. Dazu wird zunächst der jeweils geringste Messbereich und der maximale Tiefpassfilter eingestellt. Das gewährleistet hohe Auflösung und geringes Rauschen.

Für die Offset-Ermittlung des Beschleunigungssensors dreht ihr den ICM-20948 langsam (!) um seine Achsen und notiert euch die maximalen und minimalen Rohwerte. Am besten stützt ihr dabei die Arme auf, denn jedes Zittern gibt zusätzliche Beschleunigung. Diese Daten benutzt ihr dann für die setAccOffsets() Funktion. Intern werden daraus die Offsets ermittelt. Wenn ihr den Messbereich wechselt, werden die Offsets automatisch angepasst. Mit dieser Methode seid ihr dann nicht mehr auf die ebene Ausrichtung des ICM-20948 bei Programmstart angewiesen. Für die Ermittlung präziser kleiner Neigungswinkel empfehle ich aber weiterhin die autoOffsets() Funktion.

Für die Gyroskop Offsets braucht ihr den ICM-20948 nicht zu drehen, denn der Offset ist unabhängig von der Neigung. Hier schreibt ihr die Ruhewerte für die x-, y- und z-Achse in die Funktion setGyrOffsets().

#include <Wire.h>
#include <ICM20948_WE.h>
#define ICM20948_ADDR 0x68

/* There are several ways to create your ICM20948 object:
 * ICM20948_WE myIMU = ICM20948_WE()              -> uses Wire / I2C Address = 0x68
 * ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR) -> uses Wire / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2)        -> uses the TwoWire object wire2 / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2, ICM20948_ADDR) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR);

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while(!Serial) {}
  
  if(!myIMU.init()){
    Serial.println("ICM20948 does not respond");
  }
  else{
    Serial.println("ICM20948 is connected");
  }

  /*  This is a method to calibrate. You have to determine the minimum and maximum 
   *  raw acceleration values of the axes determined in the range +/- 2 g. 
   *  You call the function as follows: setAccOffsets(xMin,xMax,yMin,yMax,zMin,zMax);
   *  The parameters are floats. 
   *  The calibration changes the slope / ratio of raw accleration vs g. The zero point is 
   *  set as (min + max)/2.
   */
  myIMU.setAccOffsets(-16330.0, 16450.0, -16600.0, 16180.0, -16520.0, 16690.0);
    
  /*  The gyroscope data is not zero, even if you don't move the ICM20948. 
   *  To start at zero, you can apply offset values. These are the gyroscope raw values you obtain
   *  using the +/- 250 degrees/s range. 
   *  Use either autoOffset or setGyrOffsets, not both.
   */
  myIMU.setGyrOffsets(-115.0, 130.0, 105.0);
    
  /*  ICM20948_ACC_RANGE_2G      2 g   (default)
   *  ICM20948_ACC_RANGE_4G      4 g
   *  ICM20948_ACC_RANGE_8G      8 g   
   *  ICM20948_ACC_RANGE_16G    16 g
   */
  myIMU.setAccRange(ICM20948_ACC_RANGE_2G); // highest res for calibration
  
  /*  Choose a level for the Digital Low Pass Filter or switch it off.  
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF       3dB Bandwidth [Hz]      Output Rate [Hz]
   *    0              246.0               1125/(1+ASRD) 
   *    1              246.0               1125/(1+ASRD)
   *    2              111.4               1125/(1+ASRD)
   *    3               50.4               1125/(1+ASRD)
   *    4               23.9               1125/(1+ASRD)
   *    5               11.5               1125/(1+ASRD)
   *    6                5.7               1125/(1+ASRD) 
   *    7              473.0               1125/(1+ASRD)
   *    OFF           1209.0               4500
   *    
   *    ASRD = Accelerometer Sample Rate Divider (0...4095)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setAccDLPF(ICM20948_DLPF_6); // lowest noise for calibration   
  
  /*  ICM20948_GYRO_RANGE_250       250 degrees per second (default)
   *  ICM20948_GYRO_RANGE_500       500 degrees per second
   *  ICM20948_GYRO_RANGE_1000     1000 degrees per second
   *  ICM20948_GYRO_RANGE_2000     2000 degrees per second
   */
  myIMU.setGyrRange(ICM20948_GYRO_RANGE_250); //highest resolution for calibration
  
  /*  Choose a level for the Digital Low Pass Filter or switch it off. 
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF       3dB Bandwidth [Hz]      Output Rate [Hz]
   *    0              196.6               1125/(1+GSRD) 
   *    1              151.8               1125/(1+GSRD)
   *    2              119.5               1125/(1+GSRD)
   *    3               51.2               1125/(1+GSRD)
   *    4               23.9               1125/(1+GSRD)
   *    5               11.6               1125/(1+GSRD)
   *    6                5.7               1125/(1+GSRD) 
   *    7              361.4               1125/(1+GSRD)
   *    OFF          12106.0               9000
   *    
   *    GSRD = Gyroscope Sample Rate Divider (0...255)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setGyrDLPF(ICM20948_DLPF_6); // lowest noise for calibration
  
  /*  Choose a level for the Digital Low Pass Filter. 
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF          Bandwidth [Hz]      Output Rate [Hz]
   *    0             7932.0                    9
   *    1              217.9                 1125
   *    2              123.5                 1125
   *    3               65.9                 1125
   *    4               34.1                 1125
   *    5               17.3                 1125
   *    6                8.8                 1125
   *    7             7932.0                    9
   *                 
   *    
   *    GSRD = Gyroscope Sample Rate Divider (0...255)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setTempDLPF(ICM20948_DLPF_6); // lowest noise for calibration
}

void loop() {
  myIMU.readSensor();
  xyzFloat accRaw;
  xyzFloat gyrRaw;
  xyzFloat corrAccRaw;
  xyzFloat corrGyrRaw;
  accRaw = myIMU.getAccRawValues();
  gyrRaw = myIMU.getGyrRawValues();
  corrAccRaw = myIMU.getCorrectedAccRawValues();
  corrGyrRaw = myIMU.getCorrectedGyrRawValues();
  xyzFloat gVal = myIMU.getGValues();
  
  Serial.println("Acceleration raw values without offset:");
  Serial.print(accRaw.x);
  Serial.print("   ");
  Serial.print(accRaw.y);
  Serial.print("   ");
  Serial.println(accRaw.z);

  Serial.println("Gyroscope raw values without offset:");
  Serial.print(gyrRaw.x);
  Serial.print("   ");
  Serial.print(gyrRaw.y);
  Serial.print("   ");
  Serial.println(gyrRaw.z);

  Serial.println("Acceleration raw values with offset:");
  Serial.print(corrAccRaw.x);
  Serial.print("   ");
  Serial.print(corrAccRaw.y);
  Serial.print("   ");
  Serial.println(corrAccRaw.z);

  Serial.println("Gyroscope raw values with offset:");
  Serial.print(corrGyrRaw.x);
  Serial.print("   ");
  Serial.print(corrGyrRaw.y);
  Serial.print("   ");
  Serial.println(corrGyrRaw.z);

  Serial.println("g-values, based on corrected raws (x,y,z):");
  Serial.print(gVal.x);
  Serial.print("   ");
  Serial.print(gVal.y);
  Serial.print("   ");
  Serial.println(gVal.z);


  Serial.println("********************************************");

  delay(500);
}

 

Alles zusammen: ICM20948_05_acc_gyr_temp_mag_data.ino

Mithilfe dieses Sketches bestimmt ihr die Beschleunigung, die Gyroskopwerte, die Magnetometerwerte und die Temperatur. Die Temperatur ruft ihr mit getTemperature() ab, die anderen Funktionen habe ich schon erklärt. Ihr findet den Sketch in den Beispielen.

Das Thermometer dient weniger dazu, die Umgebungstemperatur zu messen, als die Temperaturentwicklung im ICM-20948 zu verfolgen. Sie liegt höher als die Raumtemperatur und steigt in Betrieb abhängig von den Messbedingungen.

Die Ausgabe von ICM20948_05_acc_gyr_temp_mag_data.ino

Die Ausgabe von ICM20948_05_acc_gyr_temp_mag_data.ino
Die Ausgabe von ICM20948_05_acc_gyr_temp_mag_data.ino

Winkel mit dem ICM-20948 messen

Ihr könnt die (Erd-)beschleunigungsdaten nutzen, um Neigungswinkel zu berechnen. Dabei müsst ihr sicherstellen, dass keine zusätzliche Beschleunigung auf den ICM-20948 wirkt. Ich habe dazu zwei Methoden implementiert.

ICM20948_06_angles_and_orientation.ino

Mit der in diesem Sketch vorgestellten Methode wird der Winkel α zwischen den Achsen und der Horizontalen ganz schlicht aus dem Arcussinus des Beschleunigungswertes berechnet.

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

Für kleine Winkel funktioniert das einwandfrei, bei größeren Winkeln nimmt der Fehler zu. Warum das so ist, habe ich in meinem Beitrag über den MMA7361 erklärt. Bis 60° war die Abweichung bei meinen Versuchen kleiner als ein Grad.

Für diesen Sketch empfehle ich die autoOffset() Methode. Mithilfe dieser Funktion startet ihr mit einem Winkel von 0° für die x- und y-Achse. Für die z-Achse erhaltet ihr einen Startwert um 90°. Als großer Winkel schwankt der z-Achsen-Winkel recht stark. Für die Neigungswinkel erhaltet ihr nur dann vernünftige Werte, wenn ihr den ICM-20948 für die Kalibrierung flach positioniert.

#include <Wire.h>
#include <ICM20948_WE.h>
#define ICM20948_ADDR 0x68

/* There are several ways to create your ICM20948 object:
 * ICM20948_WE myIMU = ICM20948_WE()              -> uses Wire / I2C Address = 0x68
 * ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR) -> uses Wire / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2)        -> uses the TwoWire object wire2 / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2, ICM20948_ADDR) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR);

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while(!Serial) {}
  
  if(!myIMU.init()){
    Serial.println("ICM20948 does not respond");
  }
  else{
    Serial.println("ICM20948 is connected");
  }

  /*  This is a method to calibrate. You have to determine the minimum and maximum 
   *  raw acceleration values of the axes determined in the range +/- 2 g. 
   *  You call the function as follows: setAccOffsets(xMin,xMax,yMin,yMax,zMin,zMax);
   *  The parameters are floats. 
   *  The calibration changes the slope / ratio of raw acceleration vs g. The zero point is 
   *  set as (min + max)/2.
   */
  myIMU.setAccOffsets(-16330.0, 16450.0, -16600.0, 16180.0, -16640.0, 16560.0);
    
  /* The starting point, if you position the ICM20948 flat, is not necessarily 0g/0g/1g for x/y/z. 
   * The autoOffset function measures offset. It assumes your ICM20948 is positioned flat with its 
   * x,y-plane. The more you deviate from this, the less accurate will be your results.
   * It overwrites the zero points of setAccOffsets, but keeps the the correction of the slope.
   * The function also measures the offset of the gyroscope data. The gyroscope offset does not   
   * depend on the positioning.
   * This function needs to be called after setAccOffsets but before other other settings since 
   * it will overwrite settings!
   */
  Serial.println("Position your ICM20948 flat and don't move it - calibrating...");
  delay(1000);
  myIMU.autoOffsets();
  Serial.println("Done!"); 
  
  /* enables or disables the acceleration sensor, default: enabled */
  // myIMU.enableAcc(true);

  /*  ICM20948_ACC_RANGE_2G      2 g   (default)
   *  ICM20948_ACC_RANGE_4G      4 g
   *  ICM20948_ACC_RANGE_8G      8 g   
   *  ICM20948_ACC_RANGE_16G    16 g
   */
  myIMU.setAccRange(ICM20948_ACC_RANGE_2G);
  
  /*  Choose a level for the Digital Low Pass Filter or switch it off.  
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF       3dB Bandwidth [Hz]      Output Rate [Hz]
   *    0              246.0               1125/(1+ASRD) 
   *    1              246.0               1125/(1+ASRD)
   *    2              111.4               1125/(1+ASRD)
   *    3               50.4               1125/(1+ASRD)
   *    4               23.9               1125/(1+ASRD)
   *    5               11.5               1125/(1+ASRD)
   *    6                5.7               1125/(1+ASRD) 
   *    7              473.0               1125/(1+ASRD)
   *    OFF           1209.0               4500
   *    
   *    ASRD = Accelerometer Sample Rate Divider (0...4095)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setAccDLPF(ICM20948_DLPF_6);    
  
  /*  Acceleration sample rate divider divides the output rate of the accelerometer.
   *  Sample rate = Basic sample rate / (1 + divider) 
   *  It can only be applied if the corresponding DLPF is not off!
   *  Divider is a number 0...4095 (different range compared to gyroscope)
   *  If sample rates are set for the accelerometer and the gyroscope, the gyroscope
   *  sample rate has priority.
   */
  myIMU.setAccSampleRateDivider(10);
}

void loop() {
  myIMU.readSensor();
  
  xyzFloat gValue = myIMU.getGValues();
  xyzFloat angle = myIMU.getAngles();
  
/* For g-values the corrected raws are used */
  Serial.print("g-x      = ");
  Serial.print(gValue.x);
  Serial.print("  |  g-y      = ");
  Serial.print(gValue.y);
  Serial.print("  |  g-z      = ");
  Serial.println(gValue.z);

/* Angles are also based on 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);

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

  delay(1000);
}

 

Hier kommen nun folgende Funktionen hinzu:

  • getAngles() liefert den Winkel der x-, y- und z-Achse gegenüber der Horizontalen in Grad zurück.
    • g-Werte über 1 werden auf 1 beschnitten, da die Arcussinus Funktion nicht für größere Werte definiert ist.
  • getOrientationAsString() gibt an, welche Achse den größten positiven Winkel hat.
    • Mögliche Rückgabewerte sind: x up, x down, y up, y down, z up, z down.
    • Eine Alternative ist getOrientation(). Der Rückgabewert ist ein enum (ICM20948_orientation). Zur Definition schaut in ICM20948_WE.h.

Ausgabe von ICM20948_06_angles_and_orientation.ino

Für die folgende Ausgabe habe ich das Modul um die y-Achse gedreht, also die x-Achse aufgerichtet.

Ausgabe von ICM20948_06_angles_and_orientation.ino
Ausgabe von ICM20948_06_angles_and_orientation.ino

ICM20948_07_pitch_and_roll.ino

Bei dieser Methode werden für die Berechnung der Winkel mehrere Achsen herangezogen. Dadurch sind die Werte bei großen Winkeln genauer als bei der schlichten Arcussinus Methode. Hingegen ist bei kleinen Winkeln die letztere Methode vorzuziehen. Um die Methode abzugrenzen, habe ich mich der Nomenklatur anderer Bibliotheken bedient und den Neigungswinkel der x-Achse als „pitch“ (Steigungswinkel) und den der y-Achse als „roll“ (Rollwinkel) bezeichnet.

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

Zum Vergleich verwende ich in diesem Sketch beide Methoden. Probiert es aus und wählt, was euch mehr zusagt.

#include <Wire.h>
#include <ICM20948_WE.h>
#define ICM20948_ADDR 0x68

/* There are several ways to create your ICM20948 object:
 * ICM20948_WE myIMU = ICM20948_WE()              -> uses Wire / I2C Address = 0x68
 * ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR) -> uses Wire / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2)        -> uses the TwoWire object wire2 / ICM20948_ADDR
 * ICM20948_WE myIMU = ICM20948_WE(&wire2, ICM20948_ADDR) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
ICM20948_WE myIMU = ICM20948_WE(ICM20948_ADDR);

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while(!Serial) {}
  
  if(!myIMU.init()){
    Serial.println("ICM20948 does not respond");
  }
  else{
    Serial.println("ICM20948 is connected");
  }

  /*  This is a method to calibrate. You have to determine the minimum and maximum 
   *  raw acceleration values of the axes determined in the range +/- 2 g. 
   *  You call the function as follows: setAccOffsets(xMin,xMax,yMin,yMax,zMin,zMax);
   *  The parameters are floats. 
   *  The calibration changes the slope / ratio of raw acceleration vs g. The zero point 
   *  is set as (min + max)/2.
   */
  //myIMU.setAccOffsets(-16330.0, 16450.0, -16600.0, 16180.0, -16520.0, 16690.0);
    
  /* The starting point, if you position the ICM20948 flat, is not necessarily 0g/0g/1g for x/y/z. 
   * The autoOffset function measures offset. It assumes your ICM20948 is positioned flat with its 
   * x,y-plane. The more you deviate from this, the less accurate will be your results.
   * It overwrites the zero points of setAccOffsets, but keeps the the correction of the slope.
   * The function also measures the offset of the gyroscope data. The gyroscope offset does not   
   * depend on the positioning.
   * This function needs to be called after setAccsOffsets but before other other settings since it will 
   * overwrite settings!
   */
  Serial.println("Position your ICM20948 flat and don't move it - calibrating...");
  delay(1000);
  myIMU.autoOffsets();
  Serial.println("Done!"); 
  
  /* enables or disables the acceleration sensor, dafault: enabled */
  // myIMU.enableAcc(true);

  /*  ICM20948_ACC_RANGE_2G      2 g   (default)
   *  ICM20948_ACC_RANGE_4G      4 g
   *  ICM20948_ACC_RANGE_8G      8 g   
   *  ICM20948_ACC_RANGE_16G    16 g
   */
  myIMU.setAccRange(ICM20948_ACC_RANGE_2G);
  
  /*  Choose a level for the Digital Low Pass Filter or switch it off.  
   *  ICM20948_DLPF_0, ICM20948_DLPF_2, ...... ICM20948_DLPF_7, ICM20948_DLPF_OFF 
   *  
   *  DLPF       3dB Bandwidth [Hz]      Output Rate [Hz]
   *    0              246.0               1125/(1+ASRD) 
   *    1              246.0               1125/(1+ASRD)
   *    2              111.4               1125/(1+ASRD)
   *    3               50.4               1125/(1+ASRD)
   *    4               23.9               1125/(1+ASRD)
   *    5               11.5               1125/(1+ASRD)
   *    6                5.7               1125/(1+ASRD) 
   *    7              473.0               1125/(1+ASRD)
   *    OFF           1209.0               4500
   *    
   *    ASRD = Accelerometer Sample Rate Divider (0...4095)
   *    You achieve lowest noise using level 6  
   */
  myIMU.setAccDLPF(ICM20948_DLPF_6);    
  
  /*  Acceleration sample rate divider divides the output rate of the accelerometer.
   *  Sample rate = Basic sample rate / (1 + divider) 
   *  It can only be applied if the corresponding DLPF is not off!
   *  Divider is a number 0...4095 (different range compared to gyroscope)
   *  If sample rates are set for the accelerometer and the gyroscope, the gyroscope
   *  sample rate has priority.
   */
  //myIMU.setAccSampleRateDivider(10);
}

void loop() {
  myIMU.readSensor();
  xyzFloat gValue = myIMU.getGValues();
  xyzFloat angle = myIMU.getAngles();
  float pitch = myIMU.getPitch();
  float roll  = myIMU.getRoll();
 
  Serial.println("G values (x,y,z):");
  Serial.print(gValue.x);
  Serial.print("   ");
  Serial.print(gValue.y);
  Serial.print("   ");
  Serial.println(gValue.z);
  Serial.println("Angles (x,y,z):");
  Serial.print(angle.x);
  Serial.print("   ");
  Serial.print(angle.y);
  Serial.print("   ");
  Serial.println(angle.z);

  Serial.print("Pitch = "); 
  Serial.print(pitch); 
  Serial.print("  |  Roll = "); 
  Serial.println(roll); 
  
  Serial.println();
  Serial.println();

  delay(1000);
}

 

Zwei neue Funktionen werden hier verwendet:

  • getPitch() liefert den Pitch Winkel (x-Achse).
  • getRoll() liefert den Roll Winkel (y-Achse).

Ausgabe von ICM20948_07_pitch_and_roll.ino

ICM-20948 - Ausgabe von ICM20948_07_pitch_and_roll.ino
Ausgabe von ICM20948_07_pitch_and_roll.ino

Ausblick

Im zweiten Teil des Beitrages geht es dann um Interrupts, den Low-Power Modus und den FIFO Puffer.

3 thoughts on “ICM-20948 9-Achsensensor Teil I

  1. Genial DANKE für die Bibliothek sobald der Sensor eintrifft wird der Ausgecheckt..

    Was kann der DMP denn ? Bewegungsmuster speichern und erkennen?

    Wenn ich auf den Tisch schlage, kann ich dann die Schwingungen messen wie der Tisch schwingt?

    Gruß Moe

    1. Hi Moe,

      in irgendeiner Form werden die Daten der drei Sensoren kombiniert. Damit lässt sich dann die Lage im Raum besser bestimmen. Offen gestanden habe ich das nicht vollständig verstanden. Die Sparkfun Bibliothek hat das zumindest teilweise implementiert. Hier ist eine Zusammenfassung:

      https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/blob/main/DMP.md

      Alles klar? Mir nicht! Nachdem ich mich ein paar Stunden damit beschäftigt habe, habe ich für mich das Fazit gezogen, dass mich das zuviel Zeit kosten würde es vollständig zu verstehen und zu implementieren.

      VG, Wolfgang

Schreibe einen Kommentar

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