diff --git a/CHANGELOG b/CHANGELOG index e2a62a0..9c623a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,3 +7,19 @@ Arduino_LSM9DS1 1.0.0 - 2019.07.31 Arduino_LSM9DS1 1.1.0 - 2020.02.11 * Added support for FIFO continuous reading of values + +Arduino_LSM9DS1 2.0.0 - 2020.07.10 + +* Added support for Full scale setting, +* Added support for Operational mode: off, Accel only, Gyro + Accel +* Added support for ODR sample rate, ODR values are automatically calibrated, include fast ODR for magnet +* Added support for Band width +* Added support for changing output unit +* Added support for separate or combined sensor calibration on all 9 DOF +* Calibration parameters are dimensionless, independent of each other and of the other settings +* Includes DIY calibration programs producing copy/paste-able code +* Includes readme.md and Getting Started.md +* Includes examples RPM_meter_Rev_Counter, Water_Leveler, XY_Compass, SimpleGyroscope, SimpleAccelerometer, SimpleMagnetometer +* Includes Register_test: demonstrates and verifies all new settings +* The included programs use a minimal amount of RAM to enable running on something as small as an Arduino Uno. +* The IMU.end() function restores the system to low power usage diff --git a/Getting Started.md b/Getting Started.md new file mode 100644 index 0000000..29de5b4 --- /dev/null +++ b/Getting Started.md @@ -0,0 +1,68 @@ +# Getting started with LSM9DS1 V2 library + +The library provides methods to get IMU data out of the LSM9DS1 chip and was developed on an +Arduino Nano 33 BLE Sense board. +This V2 library was designed to replace the older version 1.01 +More information about the LSM9DS1 chip can be found in the [datasheet](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) + +## Installation + +In the Arduino IDE select: *Sketch \ Include Library \ Add .ZIP Library* and point to the location where the .Zip file is stored. +When the library has been added to the Arduino library list, it will be possible to use the +Library manager as well. + +If there was a previous version of the LSM9DS1 library already installed you should probably delete or remove that first +from the Arduino library installation directory + +..../Arduino/libraries/Arduino_LSM9DS1 + +## Calibration + +If you want to make use of the new calibration possibilities run the interactive example sketches +DIY_Calibration first. Follow the directions on the screen of the serial monitor. Copy the code +from the screen into the setup() of your sketch. +See [instruction video](https://youtu.be/BLvYFXoP33o) + +It turned out that the best calibration for the Gyroscope depended on the chip's Full Scale and Output Data Rate. +For this reason changing these settings has been added in the calibration programs. + +The functions readAccel, readGyro, readMagnet produce calibrated data in the unit of choise +The functions readRawAccel, readRawGyro, readRawMagnet produce uncalibrated data and are meant for calibration purposes. + +## Use any of the new setting possibilities by placing commands in your sketch +All the "set" methods have a corresponding "get" function. + +**Output Data Rate** +(for read... and readRaw... functions) +``` +(range)= (0..5) -> { off, 10, 50, 119, 238, 476} Hz default = 119hz + IMU.setAccelODR(range); + IMU.setGyroODR (range); + +(range)= (0..8) -> {0.625, 1.25, 2.5, 5, 10, 20, 40, 80, 400} Hz default = 20hz +IMU.setMagnetODR(range); +``` + +**Full Scale setting** +(for read... and readRaw... functions) +``` + IMU.setAccelFS(range); // 0: ±2g ; 1: ±24g ; 2: ±4g ; 3: ±8g + IMU.setGyroFS(range); // (0= ±245, 1= ±500, 2= ±1000, 3= ±2000) °/s + IMU.setMagnetFS(range); // 0=±400.0; 1=±800.0; 2=±1200.0 , 3=±1600.0 (µT) +``` + +**Output unit unit you want to get the output in** +(for read... and readRaw... functions) +``` + IMU.accelUnit = GRAVITY; // GRAVITY OR METERPERSECOND2 + IMU.gyroUnit = DEGREEPERSECOND; // DEGREEPERSECOND RADIANSPERSECOND REVSPERMINUTE REVSPERSECOND + IMU.magnetUnit = MICROTESLA; // GAUSS, MICROTESLA NANOTESLA +``` +**Choose a BandWidth filter** +For advanced users only +``` + IMU.setAccelBW(range); //0,1,2,3 Override autoBandwidth setting see doc.table 67 + IMU.setGyroBW(range); //Bandwidth setting 0,1,2,3 see doc. table 46 and 47 + +``` + diff --git a/README.adoc b/Licence.adoc similarity index 95% rename from README.adoc rename to Licence.adoc index 579fd6c..aaceb3a 100644 --- a/README.adoc +++ b/Licence.adoc @@ -3,6 +3,7 @@ image:https://github.com/arduino-libraries/Arduino_LSM9DS1/workflows/Compile%20Examples/badge.svg["Compile Examples Status", link="https://github.com/arduino-libraries/Arduino_LSM9DS1/actions?workflow=Compile+Examples"] image:https://github.com/arduino-libraries/Arduino_LSM9DS1/workflows/Spell%20Check/badge.svg["Spell Check Status", link="https://github.com/arduino-libraries/Arduino_LSM9DS1/actions?workflow=Spell+Check"] Allows you to read the accelerometer, magnetometer and gyroscope values from the LSM9DS1 IMU on your Arduino Nano 33 BLE Sense. +For more accurate measurements the sensor must be calibrated first. == License == diff --git a/examples/DIY_Calibration_Accelerometer/DIY_Calibration_Accelerometer.ino b/examples/DIY_Calibration_Accelerometer/DIY_Calibration_Accelerometer.ino new file mode 100644 index 0000000..39f5cf5 --- /dev/null +++ b/examples/DIY_Calibration_Accelerometer/DIY_Calibration_Accelerometer.ino @@ -0,0 +1,214 @@ +/* DIY calibration program for the LSM9DS1 chip + * + * Follow the instructions on the screen how to do calibration measurements. + * See instruction video https://youtu.be/BLvYFXoP33o + * No special tools or setups are needed, however it is handy if the board with the LSM9DS1 chip is fitted inside + * a non-metalic rectangular box. + * The Full Scale (FS)and Output DATA Rate (ODR) settings as well as offset and slope factors + * are displayed on screen as code that can be copy/pasted directly into a sketch. + * Each new instance of the chip will require it's own unique set of calibration factors. + * It is recommended that the sketch uses the same FS and ODR settings as the calibration program + * + * Menu operation: type a letter in the input box of the serial monitor followed by enter + * + * written by Femme Verbeek 6 July 2020 + * + * This program uses V2 of the LSM9DS1 library + * Tested on an Arduino Nano 33 BLE Sense board. + * + * + */ + +#include + + +const float accelCriterion = 0.1; +char xyz[3]= {'X','Y','Z'}; +float maxAX = 1, maxAY=1, maxAZ=1, minAX=-1, minAY=-1, minAZ=-1; // Accel Slope +float zeroAX1 =0,zeroAX2 =0,zeroAY1 =0,zeroAY2 =0,zeroAZ1 =0,zeroAZ2 =0; //Accel Offset +boolean accelOK=false; +uint8_t acceMMlOK=0; // bit 0..2 maxXYZ bit 3..5 minXYZ +uint8_t accelODRindex=5; // Sample Rate 0:off, 1:10Hz, 2:50Hz, 3:119Hz, 4:238Hz, 5:476Hz, (6:952Hz=na) +uint8_t accelFSindex=3; // Full Scale// 0: ±2g ; 1: ±24g ; 2: ±4g ; 3: ±8g + +void setup() { + Serial.begin(115200); + while (!Serial); + pinMode(LED_BUILTIN,OUTPUT); + delay(10); + if (!IMU.begin()) { Serial.println(F("Failed to initialize IMU!")); while (1); } + IMU.setAccelFS(accelFSindex); + IMU.setAccelODR(accelODRindex); + calibrateAccelMenu(); +} + +void loop(){ } + +void printParam(char txt[], float param[3]) +{ for (int i= 0; i<=2 ; i++) + { Serial.print(txt);Serial.print("["); + Serial.print(i);Serial.print("] = "); + Serial.print(param[i],6);Serial.print(";"); + } +} +void printSetParam(char txt[], float param[3]) +{ Serial.print(txt);Serial.print("("); + Serial.print(param[0],6);Serial.print(", "); + Serial.print(param[1],6);Serial.print(", "); + Serial.print(param[2],6);Serial.print(");"); +} +//********************************************************************************************************************************** +//********************************************* Accelerometer ********************************************** +//********************************************************************************************************************************** + +void calibrateAccelMenu() +{char incomingByte = 0; + byte b; + uint16_t NofCalibrationSamples = 1000; + while (1) //(incomingByte!='X') + { Serial.println(F("\n\n")); + Serial.println(F(" Calibrate Accelerometer Offset and Slope")); + Serial.println(F(" Before calibrating choose the Full Scale (FS) setting and Output Data Rate (ODR). The accelerometer and the")); + Serial.println(F(" gyroscope share their ODR, so the setting here must be the same as in the DIY_Calibration_Gyroscope sketch.")); + Serial.println(F(" Place the board on a horizontal surface with one of its axes vertical and hit enter to start a calibration")); + Serial.println(F(" measurement. Each of the axes must be measured pointing up and pointing down, so a total of 6 measurements.")); + Serial.println(F(" The program recognises which axis is vertical and shows which were measured successfully. If the angle is to")); + Serial.println(F(" far oblique the measurement is not valid.\n ")); + Serial.println(F(" (enter) Start a calibration measurement. ")); + Serial.print (F(" (N) Number of calibration samples "));Serial.println(NofCalibrationSamples); + Serial.print (F(" (F) Full Scale setting "));Serial.print(accelFSindex);Serial.print(" = ");Serial.print(IMU.getAccelFS(),0);Serial.println(F("g")); + Serial.print (F(" (R) Output Data Rate (ODR) setting "));Serial.print(accelODRindex);Serial.print(" = ");Serial.print(IMU.getAccelODR(),0);Serial.println(F("Hz (actual value)")); + +// Serial.println("Press (X) to exit \n"); + + Serial.print(F(" Measured status of axis \n ")); + for (int i=0;i<=2;i++){ Serial.print(xyz[i]); if (bitRead(acceMMlOK,i)==1)Serial.print("+ = ( -OK- ) "); else Serial.print("+ = not done "); } + Serial.print("\n "); + for (int i=0;i<=2;i++){ Serial.print(xyz[i]); if (bitRead(acceMMlOK,i+3)==1)Serial.print("- = ( -OK- ) "); else Serial.print("- = not done "); } + + // Serial.println(F("\n\nCurrent accelerometer calibration values (copy/paste-able)\n")); + Serial.println(F("\n\n // Accelerometer code")); + Serial.print(F(" IMU.setAccelFS(")); Serial.print(accelFSindex); + Serial.print(F(");\n IMU.setAccelODR("));Serial.print(accelODRindex);Serial.println(");"); + + printSetParam(" IMU.setAccelOffset",IMU.accelOffset); + Serial.println(); + printSetParam (" IMU.setAccelSlope ",IMU.accelSlope); + Serial.println("\n\n"); + incomingByte= readChar(); //wait for and get keyboard input + switch (incomingByte) + { case 'F': { Serial.print (F("\n\nEnter new FS nr 0: ±2g ; 1: ±24g ; 2: ±4g ; 3: ±8g > ")); + b= readChar()-48; Serial.println(b); + if (b!=accelFSindex && b >=0 && b<=3) accelFSindex=b; + IMU.setAccelFS(accelFSindex); + Serial.print("\n\n\n\n\n\n\n\n\n"); + break;} + case 'R': { Serial.print (F("\n\nEnter new ODR nr 1:10,2:50 3:119,4:238,5:476 Hz > ")); + b= readChar()-48; //Serial.println(b); + if (b!=accelODRindex && b>=1 && b<=5) accelODRindex=b; + IMU.setAccelODR(accelODRindex); + Serial.print("\n\n\n\n\n\n\n\n\n"); + break; + } + case 'N': { readAnswer("\n\n\n\n\n\nThe number of calibration samples ", NofCalibrationSamples); + break;} + case 'C': {}; + default : calibrateAccel(NofCalibrationSamples); + } + } +} + +void calibrateAccel(uint16_t NofSamples) +{ boolean validMmt=false; + float x,y,z; + Serial.println(F("\n\n\n\n\n\n\n\n\n\n\n")); + Serial.println(F("measuring \n")); +// IMU.setAccelODR(5); //476 Hz + raw_N_Accel(NofSamples,x,y,z); + if (abs(x)>max(abs(y),abs(z))) + { Serial.println(F("X detected")); + if (sqrt(y*y+z*z)/x0) {maxAX=x; + acceMMlOK=acceMMlOK | 0b00000001 ;} + else {minAX=x; + acceMMlOK=acceMMlOK | 0b00001000 ; } + } + } + if (abs(y)>max(abs(x),abs(z))) + { Serial.println(F("Y detected")); + if (sqrt(x*x+z*z)/y0) {maxAY=y; + acceMMlOK=acceMMlOK | 0b00000010 ; } + else {minAY=y; + acceMMlOK=acceMMlOK | 0b00010000 ; } + } + } + if (abs(z)>max(abs(x),abs(y))) + { Serial.println(F("Z detected")); + if ( sqrt(x*x+y*y)/z0) {maxAZ=z; + acceMMlOK=acceMMlOK | 0b00000100 ; } + else {minAZ=z; + acceMMlOK=acceMMlOK | 0b00100000 ; } + } + } + IMU.setAccelOffset((maxAX+minAX)/2,(maxAY+minAY)/2,(maxAZ+minAZ)/2); + IMU.setAccelSlope ((maxAX-minAX)/2,(maxAY-minAY)/2,(maxAZ-minAZ)/2); + if (acceMMlOK==0b00111111) accelOK = true; + + if ( !validMmt ) + { Serial.print(F("\n\n\nNot a valid measurement! ")); + Serial.println(" x=");Serial.print(x);Serial.print(" y=");Serial.print(y);Serial.println(" z=");Serial.print(z); + } +} + +char readChar() +{ char ch; + while (!Serial.available()) ; // wait for character to be entered + ch= toupper(Serial.read()); + delay(10); + while (Serial.available()){Serial.read();delay(1);} // empty readbuffer + return ch; +} + +void readAnswer(char msg[], uint16_t& param) +{ char ch=0; + byte count=0; + const byte NofChars = 8; + char ans[NofChars]; + while (Serial.available()){Serial.read();} //empty read buffer + Serial.print(msg); + Serial.print(param); + Serial.print(F(" Enter new value ")); + while (byte(ch)!=10 && byte(ch)!=13 && count<(NofChars-1) ) + { if (Serial.available()) + { ch= Serial.read(); + ans[count]=ch; + count++; + } + } + ans[count]=0; + Serial.println(ans); + if (count>1) param= atoi(ans); + while (Serial.available()){Serial.read();} + Serial.println(F("\n\n\n\n\n\n\n")); +} + + + +void raw_N_Accel(uint16_t N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.accelAvailable()); + IMU.readRawAccel(x, y, z); + averX += x; averY += y; averZ += z; + digitalWrite(LED_BUILTIN, (millis()/125)%2); // blink onboard led every 250ms + if ((i%30)==0)Serial.print('.'); + } + averX /= N; averY /= N; averZ /= N; + digitalWrite(LED_BUILTIN,0); // led off +} diff --git a/examples/DIY_Calibration_Gyroscope/DIY_Calibration_Gyroscope.ino b/examples/DIY_Calibration_Gyroscope/DIY_Calibration_Gyroscope.ino new file mode 100644 index 0000000..854b2d7 --- /dev/null +++ b/examples/DIY_Calibration_Gyroscope/DIY_Calibration_Gyroscope.ino @@ -0,0 +1,270 @@ +/* DIY calibration program for the LSM9DS1 chip + * + * Follow the instructions on the screen how to do calibration measurements. + * Menu operation: type a letter in the input box of the serial monitor followed by enter. + * See instruction video https://youtu.be/BLvYFXoP33o + * No special tools or setups are needed, however it is handy if the board with the LSM9DS1 chip is fitted inside + * a non-metalic rectangular box. + * + * Each new instance of the LSM9DS1 chip will require it's own unique set of calibration factors. + * For an improved accuracy it is recommended that the sketch and the calibration program use the same settings for + * Full Scale (FS) and Output Data rate (ODR). This is the reasin why the possibility was added in this program. + * The settings and calibration factors are displayed on screen as code that can be copy/pasted + * directly into a sketch. + * + * Gyroscope Calibration + * The program starts with a short menu that offers the possibility to change the FS and ODR value. + * Next the Offset must be measured. This takes only a few seconds, during which the board must be kept still. + * This also enables the next step, which is to calibrate the slope. During a slope calibration the sensor must be rotated + * calmly about one axis over a known angle. This angle can be changed to your convenience. A larger angle is more accurate, + * but more difficult to do. The rotation can be done by hand. It must must be pure, without much rotation about the other + * two axes. e.g. by keeping the board on a flat surface with the rotation axis vertical while turning is good enough. + * Press Enter to finish the measurement. + * Each of the axes X,Y and Z must be measured, so in total three measurements. The program automatically detects which + * and shows this on screen. The rotation direction clockwise or anti-clockwise is unimportant, both directions will work. + * It is important that the offset is measured before the slope calibration. If for some reason the offset has to be remeasured + * make sure you remeasure the slope as well + * + * + * written by Femme Verbeek 6 July 2020 + * + * This program uses V2 of the LSM9DS1 library + * Tested on an Arduino Nano 33 BLE Sense board. + */ + +#include + +const float gyroSlopeCriterion = 50; //Smaller value requires more pureness of the rotation +char xyz[3]= {'X','Y','Z'}; +boolean gyroOffsetOK=false; +boolean gyroSlopeOK[3]={false,false,false}; +uint8_t gyroODRindex=5; +uint8_t gyroFSindex=2; // (0= ±245 dps; 1= ±500 dps; 2= ±1000 dps; 3= ±2000 dps) + +void setup() { + Serial.begin(115200); + while (!Serial); + pinMode(LED_BUILTIN,OUTPUT); + delay(10); + if (!IMU.begin()) { Serial.println(F("Failed to initialize IMU!"));while (1); } + IMU.setGyroODR(gyroODRindex); //238Hz + IMU.setGyroFS(gyroFSindex); + calibrateGyroMenu(); +} + +void loop() { } + +/*void printParam(char txt[], float param[3]) +{ for (int i= 0; i<=2 ; i++) + { Serial.print(txt);Serial.print("["); + Serial.print(i);Serial.print("] = "); + Serial.print(param[i],6);Serial.print(";"); + } +}*/ + +void printSetParam(char txt[], float param[3]) +{ Serial.print(txt);Serial.print("("); + Serial.print(param[0],6);Serial.print(", "); + Serial.print(param[1],6);Serial.print(", "); + Serial.print(param[2],6);Serial.print(");"); +} + +//********************************************************************************************************************************** +//********************************************* Gyroscope ********************************************** +//********************************************************************************************************************************** + + +void calibrateGyroMenu() +{ char incomingByte = 0; + byte b; + uint16_t turnangle = 180; + uint16_t NofCalibrationSamples = 1000; + while (1)// (incomingByte!='X') + { + if (!gyroOffsetOK) + { Serial.println(F("\n\nStep 1 CALIBRATE GYROSCOPE OFFSET ")); + Serial.println(F("First choose the sample frequency (ODR) and the Full Scale value. The accelerometer and the gyroscope")); + Serial.println(F("share their ODR, so the setting here must be the same as in the DIY_Calibration_Gyroscope sketch.")); + Serial.println(F("This is far more important for the Gyroscope than for the accelerometer. ")); + Serial.println(F("Next enter \"O\" to start the gyroscope offset measurement. \nDuring this offset measurement the sensor must be kept still.\n")); + } else + { Serial.println(F("\n\nStep 2 CALIBRATE GYROSCOPE SLOPE")); + Serial.println(F("During a slope calibration the sensor must be rotated calmly about one axis over a known angle.")); + Serial.println(F("Change the angle to your convenience. A larger angle is more accurate, but more difficult to do.")); + Serial.println(F("The rotation must be pure, without much rotation about the other two axes. It can be done by hand.")); + Serial.println(F("Keeping the board on a flat surface with the rotation axis vertical while turning is good enough.")); + Serial.println(F("For an accurate result you can start and end with its side pushed against a non moving object.")); + Serial.println(F("When you're done turning the sensor, press (Enter) to stop measuring. Each of the axes X,Y and Z ")); + Serial.println(F("must be measured. The program automatically detects which. \n")); + + Serial.println(F(" (A) Change the measuring angle to turn the board")); + Serial.print (F(" (C) Calibrate Slope ==>> Turn the board over "));Serial.print(turnangle);Serial.println(F("° and press enter when finished ")); + } Serial.print (F(" (F) Full Scale setting "));Serial.print(gyroFSindex);Serial.print(" = "); Serial.print(IMU.getGyroFS(),0);Serial.println(F("°/s")); + Serial.print (F(" (R) Output Data Rate (ODR) setting "));Serial.print(gyroODRindex);Serial.print(" = ");Serial.print(IMU.getGyroODR(),0);Serial.println(F("Hz (actual value)")); + Serial.print (F(" (N) Number of calibration samples "));Serial.println(NofCalibrationSamples); + Serial.println(F(" (O) Calibrate Offset (keep board still during measurement)")); + + Serial.println(F("\nOffset calibration ( -OK- )")); + Serial.print (F("Slope calibration axis " )); + for (int i= 0; i<=2 ; i++) + { Serial.print(xyz[i]); + if (gyroSlopeOK[i]) Serial.print(F("= ( -OK- ) ")); else Serial.print(F("= not done ")); + } + Serial.println(F("\n\n // Gyroscope code")); + Serial.print (F (" IMU.setGyroFS(")); Serial.print(gyroFSindex); + Serial.print( F(");\n IMU.setGyroODR("));Serial.print(gyroODRindex);Serial.println(");"); + printSetParam(" IMU.setGyroOffset ",IMU.gyroOffset); + Serial.println(); + printSetParam(" IMU.setGyroSlope ",IMU.gyroSlope); + Serial.println(); + incomingByte= readChar(); + switch (incomingByte) + { case 'A': { readAnswer("\n\n\n\n\n\nMeasurement turnangle for the board ", turnangle); + break;} + case 'C': { Serial.print(F("\n\n\n\n\n\nMeasuring. Turn the sensor over "));Serial.print(turnangle);Serial.println(F(" degrees\n")); + Serial.println(F("Press Enter when finished.")); + calibrateGyroslope(turnangle); + break;} + case 'F': { Serial.print (F("\n\nEnter new FS nr 0: ±245 1: ±500 2: ±1000 3:± 2000 dps > ")); + b= readChar()-48; Serial.println(b); + if (b!=gyroFSindex && b >=0 && b<=3) gyroFSindex=b; + IMU.setGyroFS(gyroFSindex); + gyroOffsetOK=false; + Serial.print("\n\n\n"); + break;} + case 'R': { Serial.print (F("\n\nEnter new ODR nr 1:10,2:50 3:119,4:238,5:476 Hz> ")); + b= readChar()-48; //Serial.println(b); + if (b!=gyroODRindex && b>=1 && b<=5) + { gyroODRindex=b; + IMU.setGyroODR(gyroODRindex); + gyroOffsetOK=false; + } + Serial.print("\n\n\n"); + break; + } + case 'N': { readAnswer("\n\n\n\n\n\nThe number of calibration samples ", NofCalibrationSamples); + break;} + case 'O': { calibrateGyroOffset(NofCalibrationSamples); } + } + Serial.println(""); + } +} + +void calibrateGyroOffset(uint16_t N) // don't move the board during calibration +{ float x, y, z;// , addX=0, addY=0, addZ=0 ; + Serial.println(F("\n\n\n\nMeasuring offset. Just a moment.")); + Serial.println(F("\n\nKeep the board still during measurement")); + raw_N_Gyro(N,x,y,z); + IMU.setGyroOffset(x, y,z); // Store the average measurements as offset + Serial.print("\n\n\n\n\n"); + gyroOffsetOK=true; +} + +void calibrateGyroslope(unsigned int turnangle) // rotate board over known angle +{ boolean validMmt=false; + float dirX=0, dirY=0, dirZ=0,sigmaX2=0,sigmaY2=0,sigmaZ2=0; + float x, y, z, maxXYZ; + unsigned int count=0; + while (!Serial.available()) // measure until enter key pressed + { while (!IMU.gyroAvailable()); + IMU.readRawGyro(x, y, z); + dirX += (x - IMU.gyroOffset[0])/IMU.getGyroODR(); // slope is still raw but offset must already be calibrated + dirY += (y - IMU.gyroOffset[1])/IMU.getGyroODR(); + dirZ += (z - IMU.gyroOffset[2])/IMU.getGyroODR(); + sigmaX2 +=x*x; sigmaY2+=y*y; sigmaZ2+=z*z; + if (count==0) maxXYZ= abs(x); + maxXYZ= max(maxXYZ,abs(x)); maxXYZ= max(maxXYZ,abs(y)); maxXYZ= max(maxXYZ,abs(z)); + count++; + if ((count%30)==0)Serial.print('.'); + digitalWrite(LED_BUILTIN, (millis()/125)%2); // blink onboard led every 250ms + } + digitalWrite(LED_BUILTIN,0); // led off + Serial.readStringUntil(13); //Empty read buffer + Serial.print(F("\n\n\nMeasured direction change X ")); + Serial.print(dirX,6);Serial.print("°\tY "); Serial.print(dirY,6);Serial.print("°\t Z "); Serial.println(dirZ,6);Serial.print("°"); + sigmaX2 /= count; sigmaY2 /= count; sigmaZ2 /= count; + Serial.print(F("Std.dev. ")); + Serial.print(sigmaX2,6);Serial.print('\t'); Serial.print(sigmaY2,6);Serial.print('\t'); Serial.println(sigmaZ2,6); + Serial.print(F("percentage of Full Scale ")); Serial.print(100*maxXYZ/IMU.getGyroFS());Serial.println('%'); + if (maxXYZ/IMU.getGyroFS()>0.95) Serial.print(F("Maximum rotation speed reached. Choose a different FS setting or turn more slowly.")); + else + { dirX=abs(dirX); dirY=abs(dirY); dirZ=abs(dirZ); + if (dirX>max(dirY,dirZ)) + { if (sigmaY2max(dirX,dirZ)) + { if (sigmaX2max(dirY,dirX)) + { if (sigmaY21) param= atoi(ans); + while (Serial.available()){Serial.read();} + Serial.println("\n\n\n\n\n\n\n"); +} + +void raw_N_Gyro(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.gyroAvailable()); + IMU.readRawGyro(x, y, z); + averX += x; averY += y; averZ += z; + digitalWrite(LED_BUILTIN, (millis()/125)%2); // blink onboard led every 250ms + if ((i%30)==0)Serial.print('.'); + } + averX /= N; averY /= N; averZ /= N; + digitalWrite(LED_BUILTIN,0); // led off +} diff --git a/examples/DIY_Calibration_Magnetometer/DIY_Calibration_Magnetometer.ino b/examples/DIY_Calibration_Magnetometer/DIY_Calibration_Magnetometer.ino new file mode 100644 index 0000000..1092d6d --- /dev/null +++ b/examples/DIY_Calibration_Magnetometer/DIY_Calibration_Magnetometer.ino @@ -0,0 +1,194 @@ +/* DIY calibration program for the LSM9DS1 chip + * + * Follow the instructions on the screen how to do calibration measurements. + * See instruction video https://youtu.be/BLvYFXoP33o + * No special tools or setups are needed, however it is handy if the board with the LSM9DS1 chip is fitted inside + * a non-metalic rectangular box with flat sides. + * The offset and slope factors are displayed on screen as code that can be copy/pasted directly into a sketch. + * Each new instance of the chip will require it's own unique set of calibration factors. + * + * To operate the menu in the IDE serial monitor you must press a letter key followed by Enter + * + * Magnetometer + * During calibration of the Magnetometer each of it's sensor axes X, Y and Z must be aimed in both ways (positive + * and negative direction) parallel to the earth magnetic field lines. + * It helps a lot if you know roughly what this direction is. + * Info about the Earthmagnetic field https://en.wikipedia.org/wiki/Earth's_magnetic_field + * The angle above the horizon is called the inclination angle. If you live in the northern hemisphere roughly in + * southern direction, and in the southern hemisphere roughly in northern direction. + * + * written by Femme Verbeek 30-5-2020 + * + * This program uses V2 of the LSM9DS1 library + * Tested on an Arduino Nano 33 BLE Sense board. + */ + +#include + + +float EarthMagnetStrength = 49.0; //= µT + +boolean magnetOK=false; +uint8_t magnetODRindex=8; // (0..8)->{0.625,1.25,2.5,5.0,10,20,40,80,400}Hz +uint8_t magnetFSindex=0; // 0=±400.0; 1=±800.0; 2=±1200.0 , 3=±1600.0 (µT) + + +void setup() { + Serial.begin(115200); + while (!Serial); + pinMode(LED_BUILTIN,OUTPUT); + delay(10); + if (!IMU.begin()) { Serial.println(F("Failed to initialize IMU!")); while (1); } + IMU.setMagnetODR(magnetODRindex); + IMU.setMagnetFS(magnetFSindex); +} + +void loop() +{ // MainMenu(); + calibrateMagnetMenu(); +} + +void printParam(char txt[], float param[3]) +{ for (int i= 0; i<=2 ; i++) + { Serial.print(txt);Serial.print("["); + Serial.print(i);Serial.print("] = "); + Serial.print(param[i],6);Serial.print(";"); + } +} + +void printSetParam(char txt[], float param[3]) +{ Serial.print(txt);Serial.print("("); + Serial.print(param[0],6);Serial.print(", "); + Serial.print(param[1],6);Serial.print(", "); + Serial.print(param[2],6);Serial.print(");"); +} + +//********************************************************************************************************************************** +//********************************************* Magnetometer ********************************************** +//********************************************************************************************************************************** + + +void calibrateMagnetMenu() +{ char incomingByte = 0; + byte b; + // String ans; + Serial.println(F("\n\n\n\n\n\n\n\n\n\n\n")); + while (1) //(incomingByte!='X') +{ Serial.println(F("Calibrate Magnetometer")); + Serial.println(F("During measurement each of the sensor XYZ axes must be aligned in both directions with the Earth's magnetic field.")); + Serial.println(F("This takes about 10 seconds, if you know the local direction of the magnetic field lines. If you don't, it will take")); + Serial.println(F("several minutes, as you have to twist the board around, aiming every axis in every direction until the min and max ")); + Serial.println(F("values no longer change. Info about the Earthmagnetic field https://en.wikipedia.org/wiki/Earth's_magnetic_field ")); + Serial.println(F("E.g. in my case (Northern hemisphere)declination=0°, inclination=67°, means the aiming direction is South and a")); + Serial.println(F("rather steep 67° above the horizon. ")); + Serial.println(F("The magnetic field measurement will be heavily disturbed by your set-up, so an \"in-situ\" calibration is advised.\n")); + Serial.print (F(" (F) Full Scale setting "));Serial.print(magnetFSindex);Serial.print(" = ±"); Serial.print(IMU.getMagnetFS(),0);Serial.println(F(" µT")); + Serial.print (F(" (R) Output Data Rate (ODR) setting "));Serial.print(magnetODRindex);Serial.print(" = ");Serial.print(IMU.getMagnetODR(),0);Serial.println(F("Hz (actual value)")); + Serial.print (F(" (L) Local intensity of Earth magnetic field "));Serial.print(EarthMagnetStrength);Serial.println(F(" µT Change into your local value.")); + Serial.println(F(" (C) Calibrate Magnetometer, Twist board around to find min-max values or aim along earth mag field, press enter to stop\n")); + + Serial.println(F(" // Magnetometer code")); + Serial.print (F(" IMU.setMagnetFS("));Serial.print(magnetFSindex);Serial.println(");"); + Serial.print (F(" IMU.setMagnetODR("));Serial.print(magnetODRindex);Serial.println(");"); + printSetParam(" IMU.setMagnetOffset",IMU.magnetOffset); + Serial.println(); + printSetParam(" IMU.setMagnetSlope ",IMU.magnetSlope); + Serial.println(F("\n\n")); + incomingByte= readChar(); + switch (incomingByte) + { case 'L': { readAnswer("\n\nLocal Earth Magnetic Field intensity " ,EarthMagnetStrength ); break;} + case 'C': { calibrateMagnet() ; + Serial.print(F("\n\n\n\n\n\n")); + break;} + case 'F': { Serial.print (F("\n\nEnter new FS nr 0=±400.0; 1=±800.0; 2=±1200.0 , 3=±1600.0 (µT)> ")); + b= readChar()-48; Serial.println(b); + if (b!=magnetFSindex && b >=0 && b<=3) magnetFSindex=b; + IMU.setMagnetFS(magnetFSindex); + Serial.print("\n\n\n"); + break;} + case 'R': { Serial.print (F("\n\nEnter new ODR nr 6: 40Hz, 7: 80Hz, 8: 400Hz not adviced 0..5: 0.625,1.25,2.5,5.0,10,20 Hz ")); + b= readChar()-48; Serial.println(b); + if (b!=magnetODRindex && b>=1 && b<=8) magnetODRindex=b; + IMU.setMagnetODR(magnetODRindex); + Serial.print("\n\n\n\n\n\n\n\n\n"); + break; + } + default : {Serial.println(F("No menu choise\n\n"));Serial.print(incomingByte); break;} + } + } +} + + +char readChar() +{ char ch; + while (!Serial.available()) ; // wait for character to be entered + ch= toupper(Serial.read()); + delay(10); + while (Serial.available()){Serial.read();delay(1);} // empty readbuffer + return ch; +} + +void readAnswer(char msg[], float& param) +{ char ch=0; + byte count=0; + const byte NofChars = 8; + char ans[NofChars]; + while (Serial.available()){Serial.read();} //empty read buffer + Serial.print(msg); + Serial.print(param); + Serial.print(F(" Enter new value ")); + while (byte(ch)!=10 && byte(ch)!=13 && count<(NofChars-1) ) + { if (Serial.available()) + { ch= Serial.read(); + ans[count]=ch; + count++; + } + } + ans[count]=0; + Serial.println(ans); + if (count>1) param= atof(ans); + while (Serial.available()){Serial.read();} + Serial.println(F("\n\n\n\n\n\n\n")); +} + + +void calibrateMagnet() // measure Offset and Slope of XYZ +{ float x, y, z, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax ; + unsigned long count=0; + IMU.setMagnetODR(8); //Fast rate 400Hz + raw_N_Magnet(10, Xmin, Ymin, Zmin); // find some starting values + Xmax = Xmin; Ymax = Ymin; Zmax = Zmin; + while (!Serial.available()) // measure until enter key pressed + { raw_N_Magnet(10, x, y, z); //average over a number of samples to reduce the effect of outlyers + Xmax = max (Xmax, x); Xmin = min (Xmin, x); + Ymax = max (Ymax, y); Ymin = min (Ymin, y); + Zmax = max (Zmax, z); Zmin = min (Zmin, z); + count++; + if ((count & 5)==0) //reduce the number of prints by a factor + { Serial.print(F("Xmin = "));Serial.print(Xmin); Serial.print(F(" Xmax = "));Serial.print(Xmax); + Serial.print(F(" Ymin = "));Serial.print(Ymin); Serial.print(F(" Ymax = "));Serial.print(Ymax); + Serial.print(F(" Zmin = "));Serial.print(Zmin); Serial.print(F(" Zmax = "));Serial.print(Zmax); + Serial.println(); + } + } + while (Serial.available()) Serial.read(); //readStringUntil(13); //Empty read buffer + IMU.setMagnetOffset( (Xmax+Xmin)/2,(Ymax+Ymin)/2, (Zmax+Zmin)/2 ) ; // store offset + IMU.setMagnetSlope ( (2*EarthMagnetStrength)/(Xmax-Xmin),(2*EarthMagnetStrength)/(Ymax-Ymin),(2*EarthMagnetStrength)/(Zmax-Zmin)); // store slope + magnetOK=true; + IMU.setMagnetODR(magnetODRindex); +} + + +void raw_N_Magnet(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.magnetAvailable()); + IMU.readRawMagnet(x, y, z); + averX += x; averY += y; averZ += z; + digitalWrite(LED_BUILTIN, (millis()/125)%2); // blink onboard led every 250ms + if ((i%30)==0)Serial.print('.'); + } + averX /= N; averY /= N; averZ /= N; + digitalWrite(LED_BUILTIN,0); // led off +} diff --git a/examples/RPM_Meter_Rev_Counter/RPM_Meter_Rev_Counter.ino b/examples/RPM_Meter_Rev_Counter/RPM_Meter_Rev_Counter.ino new file mode 100644 index 0000000..c940efa --- /dev/null +++ b/examples/RPM_Meter_Rev_Counter/RPM_Meter_Rev_Counter.ino @@ -0,0 +1,78 @@ +/* + * Arduino LSM9DS1 - RPM Meter and Rev counter. + * Example for V2 of the LSM9DS1 library. + * + * Writtenby Femme Verbeek 14 july 2020 + * + * This program demonstrates the use of the LSM9DS1 Gyroscope + * When placed on a record turntable it will show the rotation speed in RPM, 33.33 or 45 + * It counts the revolutions by integrating the Gyroscope signal. + * + * Run the DIY calibration program to get the gyroOffset an gyroSlope settings and paste their values below + * where it is indicated + * + * This example code is in the public domain. + */ + +#include + +void setup() +{ Serial.begin(115200); + while (!Serial); + if (!IMU.begin()) + { Serial.println("Failed to initialize IMU!"); + while (1); + } + + IMU.gyroUnit = REVSPERMINUTE ; //change output unit of readGyro + +//******* The gyroscope needs to be calibrated. Offset controls drift and Slope scales the measured rotation angle ********* +//***************** Copy/Replace the lines below by the output of the DIY_Calibration_Gyroscope sketch ******************** + IMU.setGyroFS(3); + IMU.setGyroODR(5); + IMU.setGyroOffset (0,0,0); // = uncalibrated + IMU.setGyroSlope (1,1,1); // = uncalibrated +//***************************************************************************************************************************** +//********* FS Full Scale range 0: ±245°/s 1: ±500°/s 2: ±1000°/s Default= 3: ±2000°/s *************************** +//********* ODR Output Data Rate range 0:off, 1:10Hz, 2:50Hz, Default= 3:119Hz, 4:238Hz, 5:476Hz, (not working 6:952Hz) ***** +//***************************************************************************************************************************** + + Serial.println("RPM_X \t RPM_Y \t RPM_Z \t Revs_X \t Revs_y \t Revs_Z "); // legend in case you use the serial plotter +} + +float countRevsX=0, countRevsY=0, countRevsZ=0; + +void loop() +{ + int nrOfSamples=200; + float measureTime= nrOfSamples/(IMU.getGyroODR()*60); //in min + float x, y, z; + read_N_Gyro(nrOfSamples,x,y,z); + countRevsX += x * measureTime; + countRevsY += y * measureTime; + countRevsZ += z * measureTime; + + Serial.print(x); + Serial.print('\t'); + Serial.print(y); + Serial.print('\t'); + Serial.print(z); + Serial.print('\t'); + Serial.print(countRevsX); + Serial.print('\t'); + Serial.print(countRevsY); + Serial.print('\t'); + Serial.println(countRevsZ); +} + + +void read_N_Gyro(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.gyroAvailable()); + IMU.readGyro(x, y, z); + averX += x; averY += y; averZ += z; + } + averX /= N; averY /= N; averZ /= N; +} diff --git a/examples/RegisterTest/RegisterTest.ino b/examples/RegisterTest/RegisterTest.ino new file mode 100644 index 0000000..30e3ec0 --- /dev/null +++ b/examples/RegisterTest/RegisterTest.ino @@ -0,0 +1,300 @@ +/*Test program for Arduino__LSM9DS1 Library version 2.0 extensions + * Written by Femme Verbeek Pijnacker the Netherlands 6 July 2020. + * + * This program verifies that all the new LSM9DS1 set- and get- chip-setting-fuctions are working + * + * The LSM9DS1 datasheet (https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) + * + * The following anomalies in the functioning of the LSM9DS1 chip have been found. + * + - Upon setAccelFS(1); the needed multiplication factor should be 16, turns out to be 24, + but the sensor maxes out at 20 g + - The setGyroFS(2);* is *NA* according to the datasheet, but when tested it worked nicely at 1000 °/s + - The Gyroscope full scale setting does have a small effect on the offset that needs to be applied. So the + workaround is to apply the same ODR setting when calibrating. See the testGyroFS function below. + - The magnet ODR settings <= 20 Hz did not work on all systems. That means that the original library would + not have worked on the failing systems. For this reason the default ODR setting was changed from 20 to 40Hz. + - setAccelODR(6) and setGyroODR(6) should result in a sampling frequency of 952Hz, but it turned out to + be smaller than 500Hz and with a lot of spikes in the signal. As this affected the accuracy at virtually + no gain in speed, it is advised not to use setting 6. + * + * Additional anomalies found on an Arduino UNO with a separate LSM9DS1 + * + - Settings 1 of setAccelODR( ) and setGyroODR ( ) did not work + - setMagnetODR did not work for settings 0 to 5 + * + */ + +#include + +float x,y,z; + +void setup() { + Serial.begin(115200); + while(!Serial); //Wait for serial connection + delay(10); + if (!IMU.begin()) { + Serial.println(F("Failed to initialize IMU!")); + while (1); } + Serial.println(F("Testing all library chip settings\n")); + Serial.println (F("Full Scale settings should hardly inluence measured values")); + testAccelFS(); + testGyroFS(); + testMagnetFS(); + testOperationalMode(); + testGyroSharedODR(); + testAccelSharedODR(); + testAccelOnlyODR(); + testMagnetODR(); + testAccelAutomaticBW(); + testAccelBWOverride(); + testGyroBW(); + testAccelUnitChange(); + testGyroUnitChange(); + testMagnetUnitChange(); +} + +void loop() { } + +//----------------------------------- FS (Full scale) functions ------------------------------------- + +void testAccelFS() +{ for (int i = 0;i<=4;i++) + { if (IMU.setAccelFS(i)) + { printResult ("\nsetAccelFS(", i ,IMU.getAccelFS()," g "); + read_N_Accel(2,x,y,z); //throw away first two samples + read_N_Accel(50,x,y,z); + printXYZData("Accel uncalibrated ",x,y,z,"g "); + } + else Serial.println (F("\nsetAccelFS parameter out of range \n")); + } +} +void testGyroFS() +{ + IMU.setGyroODR(4); + Serial.print("setGyroODR ("); Serial.print(4);Serial.print(") = "); Serial.print(IMU.getGyroODR());Serial.print("Hz "); Serial.print(50);Serial.print(" samples"); + for (int i = 0;i<=4;i++) + { if (IMU.setGyroFS(i)) + { printResult ("\nsetGyroFS(",i,IMU.getGyroFS(),"°/s "); + read_N_Gyro(2,true,x,y,z); //throw away first two samples + read_N_Gyro(50,true,x,y,z); + IMU.setGyroOffset(x,y,z); //Calibrate Offset + printXYZData("Gyro raw ",x,y,z,"°/s "); + read_N_Gyro(50,false,x,y,z); + printXYZData("Gyro calibrated ",x,y,z,"°/s "); + } + else Serial.println (F("\nsetGyroFS parameter out of range ")); + } +} + +/*void testGyroFS() // test combinations of ODR, FS and a number of samples in the average +{ + for (int k=1;k<=6;k++) + { IMU.setGyroODR(k); + for (int j=2;j<=100;j*=1.5) + { Serial.print("setGyroODR ("); Serial.print(k);Serial.print(") = "); Serial.print(IMU.getGyroODR());Serial.print("Hz "); Serial.print(j);Serial.print(" samples"); + for (int i = 0;i<=4;i++) + { if (IMU.setGyroFS(i)) + { printResult ("\nsetGyroFS(",i,IMU.getGyroFS(),"°/s "); + readRaw_N_Gyro(2,true,x,y,z); //throw away first two samples + readRaw_N_Gyro(j,true,x,y,z); + IMU.setGyroOffset(x,y,z); //Calibrate Offset + printXYZData("Gyro raw ",x,y,z,"°/s "); + read_N_Gyro(j,false,x,y,z); + printXYZData("Gyro calibrated ",x,y,z,"°/s "); + } + else Serial.println (F("\nsetGyroFS parameter out of range ")); + } + } + } +} */ + +void testMagnetFS() +{ for (int i = 0;i<=4;i++) + { if (IMU.setMagnetFS(i)) + { printResult ("\nsetMagnetFS(", i ,IMU.getMagnetFS()," µT "); + read_N_Magnet(2,x,y,z); //throw away first two samples + read_N_Magnet(10,x,y,z); + printXYZData("Magnet ",x,y,z,"µT "); + } + else Serial.println (F("\nsetMagnetFS parameter out of range")); + } +} + +//------------------------------- ODR (Output Data Rate) functions ----------------------------- + +void testOperationalMode() +{ Serial.println(F("\nTest Operational Mode 0=off, 1 accelerometer only, 2= Accel + Gyro \n")); + Serial.print(F("Default operational mode "));Serial.println(IMU.getOperationalMode()); + IMU.setGyroODR(0); + Serial.print(F("setGyroODR(0) Operational mode "));Serial.println(IMU.getOperationalMode()); + IMU.setAccelODR(0); + Serial.print(F("setAccelODR(0) Operational mode "));Serial.println(IMU.getOperationalMode()); + IMU.setGyroODR(3); + Serial.print(F("setGyroODR(3) Operational mode "));Serial.println(IMU.getOperationalMode()); +} + +void testGyroSharedODR() +{ Serial.println(F("\n setGyroODR result (shared ODR, i>0 overrules the Accel setting)")); + for (int i=0;i<=7;i++) + if (IMU.setGyroODR(i)==0) {Serial.print(F("setGyroODR parameter out of range "));Serial.println(i); } + else printODRData(i); +} + +void testAccelSharedODR() +{ Serial.println(F("\n setAccelODR result (shared ODR, i=0 switches off Gyro and Accel)")); + for (int i=0;i<=7;i++) + if (IMU.setAccelODR(i)==0) {Serial.print(F("setAccelODR parameter out of range "));Serial.println(i);} + else printODRData(i); +} + +void testAccelOnlyODR() +{ Serial.println(F("\n Switch off Gyro (Accelerometer only mode)")); + IMU.setGyroODR(0); //switch off gyro + for (int i=0;i<=7;i++) + if (IMU.setAccelODR(i)==0) {Serial.print(F("setAccelODR parameter out of range "));Serial.println(i);} + else printODRData(i); +} + +void testMagnetODR() +{ Serial.println(F("\n setMagnetODR sample rate result")); + for (int i = 0;i<=16;i++) + { if (IMU.setMagnetODR(i)) + printResult ("setMagnetODR(", i , IMU.getMagnetODR()," Hz \n"); + else {Serial.print (F("setMagnetODR parameter out of range "));Serial.println(i);} + } +} + +//--------------------------------- BW (Band width) functions ------------------------------- +void testAccelAutomaticBW() +{ Serial.println(F("\n Accelerometer automatic band width result ")); + for (int i=0;i<=6;i++) + { IMU.setAccelODR(i); + Serial.print(F("Accel ODR = "));Serial.print(IMU.getAccelODR()); + Serial.print(F(" automatic BW setting (Hz) " ));Serial.println (IMU.getAccelBW()); + } +} + +void testAccelBWOverride() +{ Serial.println (F("\n Accelerometer band width override")); + for (int i = 0;i<=4;i++) + { if (IMU.setAccelBW(i) ) // override automatic bandwith + printResult("setAccelBW(", i ,IMU.getAccelBW(), "Hz \n" ); + else {Serial.print(F("setAccelBW parameter out of range "));Serial.println(i) ;} + } +} +void testGyroBW() +{ Serial.println (F("\n Gyroscope band width setting")); + for (int i = 0;i<=6;i++) + { if (IMU.setGyroODR(i)) + { Serial.print (F("Gyroscope ODR = " ));Serial.println( IMU.getGyroODR() ); + for (int j = 0;j<=4;j++) + { if (IMU.setGyroBW(j) ) // the gyro has no automatic bandwith + printResult("setGyroBW(", j ,IMU.getGyroBW(), "Hz \n" ); + else {Serial.print (F("setGyroBW parameter out of range"));Serial.println(j);} + } + } + else Serial.println (F("failed setting gyroscope sample rate ")); + } +} + +//---------------------------------------- Changing Output units ------------------------------- + +void testAccelUnitChange() +{ Serial.println (F("\n Changing Accelerometer output units ")); + IMU.setGyroODR(5); + IMU.accelUnit= METERPERSECOND2; + read_N_Accel(20,x, y, z); + printXYZData("\nreadAccel ",x,y,z,"m/s2 "); + IMU.accelUnit= GRAVITY; + read_N_Accel(20,x, y, z); + printXYZData("\nreadAccel ",x,y,z,"g "); +} + +void testGyroUnitChange() +{ Serial.print(F("\n Simulate gyro measurement by offsetting 100 dps")); + IMU.setGyroODR(5); + read_N_Gyro(20,true,x, y, z); + IMU.setGyroOffset(100+x,100+y,100+z); //Add 100 dps to measurement to simulate a measurement + + IMU.gyroUnit = DEGREEPERSECOND; + read_N_Gyro(20,false,x, y, z); + printXYZData("\nreadGyro ",x,y,z," deg/s "); + IMU.gyroUnit = RADIANSPERSECOND; + read_N_Gyro(20,false,x, y, z); + printXYZData("\nreadGyro ",x,y,z," rad/s "); + IMU.gyroUnit = REVSPERMINUTE; + read_N_Gyro(20,false,x, y, z); + printXYZData("\nreadGyro ",x,y,z," RPM "); + IMU.gyroUnit = REVSPERSECOND; + read_N_Gyro(20,false,x, y, z); + printXYZData("\nreadGyro ",x,y,z," RPS "); +} + +void testMagnetUnitChange() +{ IMU.magnetUnit = MICROTESLA; + read_N_Magnet(20,x, y, z); + printXYZData("\nreadMagnet ",x,y,z," µT "); + IMU.magnetUnit = NANOTESLA; + read_N_Magnet(20,x, y, z); + printXYZData("\nreadMagnet ",x,y,z," nT "); + IMU.magnetUnit = GAUSS; + read_N_Magnet(20,x, y, z); + printXYZData("\nreadMagnet ",x,y,z," G "); +} + + +//--------------------------------------- print functions ------------------------------- +void printResult (char msg[], int nr,float value, char dimension[]) +{ Serial.print (msg);Serial.print(nr); + Serial.print(F(") Setting "));Serial.print(value); + Serial.print(dimension); +} +void printODRData(int i) +{ Serial.print(F("Settting "));Serial.print(i); + Serial.print(F(" Oper.mode "));Serial.print(IMU.getOperationalMode()); + Serial.print(F(" ODR Gyro "));Serial.print( IMU.getGyroODR(),3 ); + Serial.print(F("Hz Accel "));Serial.print( IMU.getAccelODR(),3 ); + Serial.println(F("Hz")); +} +void printXYZData(char msg[], float x,float y,float z,char dimension[]) +{ Serial.print (msg); + Serial.print (x); Serial.print (dimension ); + Serial.print (y); Serial.print (dimension ); + Serial.print (z); Serial.print (dimension ); +} + +//-------------------------------------------- measurements ---------------------------------- +void read_N_Accel(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.accelAvailable()); + IMU.readAccel(x, y, z); + averX += x/N; averY += y/N; averZ += z/N; + } +} +void read_N_Gyro(unsigned int N, boolean raw, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.gyroAvailable()); + if (raw) IMU.readRawGyro(x, y, z); + else IMU.readGyro(x, y, z); + averX += x/N; averY += y/N; averZ += z/N; + } +} + +void read_N_Magnet(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.magnetAvailable()); + IMU.readMagnet(x, y, z); + averX += x/N; averY += y/N; averZ += z/N; + } +} + + + + diff --git a/examples/SimpleAccelerometer/SimpleAccelerometer.ino b/examples/SimpleAccelerometer/SimpleAccelerometer.ino index 3ad425b..c34ebbc 100644 --- a/examples/SimpleAccelerometer/SimpleAccelerometer.ino +++ b/examples/SimpleAccelerometer/SimpleAccelerometer.ino @@ -1,49 +1,69 @@ /* Arduino LSM9DS1 - Simple Accelerometer + Extended with library V2.0 function calls This example reads the acceleration values from the LSM9DS1 sensor and continuously prints them to the Serial Monitor or Serial Plotter. The circuit: - - Arduino Nano 33 BLE Sense + - Arduino Nano 33 BLE (Sense) + - or Arduino Uno connected to LSM9DS1 breakout board created 10 Jul 2019 by Riccardo Rizzo + Modified by Femme Verbeek 10 jul 2020 + This example code is in the public domain. */ #include +boolean viewInSerialPlotter=true; //true optimises for serial plotter, false for serial monitor -void setup() { - Serial.begin(9600); - while (!Serial); - Serial.println("Started"); - - if (!IMU.begin()) { - Serial.println("Failed to initialize IMU!"); - while (1); - } - - Serial.print("Accelerometer sample rate = "); - Serial.print(IMU.accelerationSampleRate()); - Serial.println(" Hz"); - Serial.println(); - Serial.println("Acceleration in G's"); - Serial.println("X\tY\tZ"); +void setup() +{ Serial.begin(115200); + while (!Serial); + + if (!IMU.begin()) + { Serial.println("Failed to initialize IMU!"); + while (1); + } +/******************* For an improved accuracy run the DIY_Calibration_Accelerometer sketch first. **************** +******************** Copy/Replace the lines below by the code output of the program ****************/ + IMU.setAccelFS(3); + IMU.setAccelODR(5); // + IMU.setAccelOffset(0, 0, 0); // uncalibrated + IMU.setAccelSlope (1, 1, 1); // uncalibrated +/*********************************************************************************************************************************** +******* FS Full Scale range 0:±2g | 1:±24g | 2: ±4g | 3: ±8g (default=2) ****** +******* ODR Output Data Rate range 0:off | 1:10Hz | 2:50Hz | 3:119Hz | 4:238Hz | 5:476Hz, (default=3)(not working 6:952Hz) ****** +************************************************************************************************************************************/ + IMU.accelUnit= GRAVITY; // or METERPERSECOND2 + + if (!viewInSerialPlotter) + { Serial.println("Gyroscope in degrees/second \n"); + Serial.print("Accelerometer Full Scale = ±"); + Serial.print(IMU.getAccelFS()); + Serial.println ("g"); + Serial.print("Accelerometer sample rate = "); + Serial.print(IMU.getAccelODR()); // alias AccelerationSampleRate()); + Serial.println(" Hz \n"); + delay(4000); + } + Serial.println(" X \t Y \t Z "); } void loop() { float x, y, z; - if (IMU.accelerationAvailable()) { - IMU.readAcceleration(x, y, z); + if (IMU.accelAvailable()) // alias IMU.accelerationAvailable in library version 1.01 + { IMU.readAccel(x, y, z); // alias IMU.readAcceleration in library version 1.01 - Serial.print(x); - Serial.print('\t'); - Serial.print(y); - Serial.print('\t'); - Serial.println(z); + Serial.print(x); + Serial.print('\t'); + Serial.print(y); + Serial.print('\t'); + Serial.println(z); } } diff --git a/examples/SimpleGyroscope/SimpleGyroscope.ino b/examples/SimpleGyroscope/SimpleGyroscope.ino index 9aab70a..351139b 100644 --- a/examples/SimpleGyroscope/SimpleGyroscope.ino +++ b/examples/SimpleGyroscope/SimpleGyroscope.ino @@ -1,5 +1,6 @@ /* Arduino LSM9DS1 - Simple Gyroscope + Extended with library V2.0 function calls This example reads the gyroscope values from the LSM9DS1 sensor and continuously prints them to the Serial Monitor @@ -7,42 +8,61 @@ The circuit: - Arduino Nano 33 BLE Sense + - or Arduino Uno connected to LSM9DS1 breakout board created 10 Jul 2019 by Riccardo Rizzo - + + Modified by Femme Verbeek 14 jul 2020 + This example code is in the public domain. */ #include +boolean viewInSerialPlotter=false; // true optimises for serial plotter, false for serial monitor void setup() { - Serial.begin(9600); + Serial.begin(115200); while (!Serial); - Serial.println("Started"); - if (!IMU.begin()) { - Serial.println("Failed to initialize IMU!"); + if (!IMU.begin()) + { Serial.println("Failed to initialize IMU!"); while (1); - } - Serial.print("Gyroscope sample rate = "); - Serial.print(IMU.gyroscopeSampleRate()); - Serial.println(" Hz"); - Serial.println(); - Serial.println("Gyroscope in degrees/second"); - Serial.println("X\tY\tZ"); + } +/******* The gyroscope needs to be calibrated. Offset controls drift and Slope scales the measured rotation angle ********* +***************** Copy/Replace the lines below by the output of the DIY_Calibration_Gyroscope sketch ********************/ + IMU.setAccelFS(3); + IMU.setAccelODR(3); + IMU.setGyroOffset (0, 0, 0); // = uncalibrated + IMU.setGyroSlope (1, 1, 1); // = uncalibrated +/***************************************************************************************************************************** +********* FS Full Scale setting 0: ±245°/s | 1: ±500°/s | 2: ±1000°/s | 3: ±2000°/s **************************** +********* ODR Output Data Rate setting 0:off | 1:10Hz | 2:50Hz | 3:119Hz | 4:238Hz | 5:476Hz, (not working 6:952Hz) ******* +*****************************************************************************************************************************/ + IMU.gyroUnit= DEGREEPERSECOND; // DEGREEPERSECOND RADIANSPERSECOND REVSPERMINUTE REVSPERSECOND + if (!viewInSerialPlotter) + { Serial.println("Gyroscope in degrees/second \n"); + Serial.print("Gyroscope Full Scale = ±"); + Serial.print(IMU.getGyroFS()); + Serial.println ("°/s"); + Serial.print("Gyroscope sample rate = "); + Serial.print(IMU.getGyroODR()); //alias IMU.gyroscopeSampleRate()); + Serial.println(" Hz"); + delay(4000); + } + Serial.println("X \t Y \t Z"); } void loop() { float x, y, z; - if (IMU.gyroscopeAvailable()) { - IMU.readGyroscope(x, y, z); - - Serial.print(x); - Serial.print('\t'); - Serial.print(y); - Serial.print('\t'); - Serial.println(z); + if (IMU.gyroAvailable()) // alias IMU.gyroscopeAvailable + { + IMU.readGyro(x, y, z); // alias IMU.readGyroscope + Serial.print(x); + Serial.print('\t'); + Serial.print(y); + Serial.print('\t'); + Serial.println(z); } } diff --git a/examples/SimpleMagnetometer/SimpleMagnetometer.ino b/examples/SimpleMagnetometer/SimpleMagnetometer.ino index 77e21f1..ca6d095 100644 --- a/examples/SimpleMagnetometer/SimpleMagnetometer.ino +++ b/examples/SimpleMagnetometer/SimpleMagnetometer.ino @@ -1,5 +1,6 @@ /* Arduino LSM9DS1 - Simple Magnetometer + Extended with library V2.0 function calls This example reads the magnetic field values from the LSM9DS1 sensor and continuously prints them to the Serial Monitor @@ -7,37 +8,60 @@ The circuit: - Arduino Nano 33 BLE Sense + - or Arduino connected to LSM9DS1 breakout board created 10 Jul 2019 by Riccardo Rizzo + Modified by Femme Verbeek + 10 July 2020 + This example code is in the public domain. */ #include +boolean viewInSerialPlotter=true; // true optimises for serial plotter, false for serial monitor -void setup() { - Serial.begin(9600); +void setup() +{ Serial.begin(115200); while (!Serial); - Serial.println("Started"); - if (!IMU.begin()) { + if (!IMU.begin()) + { Serial.println("Failed to initialize IMU!"); while (1); } - Serial.print("Magnetic field sample rate = "); - Serial.print(IMU.magneticFieldSampleRate()); - Serial.println(" uT"); - Serial.println(); - Serial.println("Magnetic Field in uT"); - Serial.println("X\tY\tZ"); + +/***************** For a proper functioning of the magnetometer it needs to be calibrated ******************** +***************** Replace the lines below by the output of the DIY_Calibration_Magnetometer sketch ********************/ + IMU.setMagnetFS(0); + IMU.setMagnetODR(8); + IMU.setMagnetOffset(0,0,0); // uncalibrated + IMU.setMagnetSlope (1,1,1); // uncalibrated +/****************************************************************************************************************************** +**** FS Full Scale range (0=±400 | 1=±800 | 2=±1200 | 3=±1600 (µT) ***** +**** ODR Output Data Rate range (6,7,8)=(40,80,400)Hz | not available on all chips (0..5): (0.625,1.25,2.5,5.0,10,20)Hz ***** +*******************************************************************************************************************************/ + IMU.magnetUnit = MICROTESLA; // GAUSS MICROTESLA NANOTESLA + + if (!viewInSerialPlotter) + { Serial.println("Magnetic Field in µT"); + Serial.print("Magnetometer Full Scale = ±"); + Serial.print(IMU.getMagnetFS()); + Serial.println ("µT"); + Serial.print("Magnetic field sample rate = "); + Serial.print(IMU.getMagnetODR()); // alias IMU.magneticFieldSampleRate in library version 1.01 + Serial.println(" Hz"); + delay(4000); + } + Serial.println("X \t Y\t Z"); } void loop() { float x, y, z; - if (IMU.magneticFieldAvailable()) { - IMU.readMagneticField(x, y, z); + if (IMU.magnetAvailable()) // alias IMU.magneticFieldAvailable in library version 1.01 + { IMU.readMagnet(x, y, z); // alias IMU.readMagneticField in library version 1.01 Serial.print(x); Serial.print('\t'); diff --git a/examples/Water_Leveler/Water_Leveler.ino b/examples/Water_Leveler/Water_Leveler.ino new file mode 100644 index 0000000..92c62a5 --- /dev/null +++ b/examples/Water_Leveler/Water_Leveler.ino @@ -0,0 +1,59 @@ +/* Water level example for the Nano 33 BLE (Sense) + * You need version 2.0 or higher of the LMS9DS1 library to run this example + * + * Calibration makes the difference between a few degrees and within a degree accuracy + * Run the DIY calibration program first and copy/paste the Accelerometer calibration data below where it's indicated. + * + * The circuit: + * - Arduino Nano 33 BLE (Sense) + * - or Arduino connected to LSM9DS1 breakout board + * + * Written by Femme Verbeek + * 6 June 2020 + * Released to the public domain +*/ +#include + +void setup() +{ + Serial.begin(115200); + while(!Serial); //wait for a serial connection + delay(1); + if (!IMU.begin()) {Serial.println("Failed to initialize IMU!"); while (1); } + +/***************** For an improved accuracy run the DIY_Calibration_Accelerometer sketch first. **************** +***************** Copy/Replace the lines below by the code output of the program ****************/ + IMU.setAccelFS(2); + IMU.setAccelODR(3); + IMU.setAccelOffset(0, 0, 0); // 0,0,0 = uncalibrated + IMU.setAccelSlope (1, 1, 1); // 1,1,1 = uncalibrated +/*********************************************************************************************************************************** +******* FS Full Scale range 0:±2g | 1:±24g | 2: ±4g | 3: ±8g (default=2) ****** +******* ODR Output Data Rate range 0:off | 1:10Hz | 2:50Hz | 3:119Hz | 4:238Hz | 5:476Hz, (default=3)(not working 6:952Hz) ****** +************************************************************************************************************************************/ + + Serial.println(" pitchX \t pitchY"); //shows in the legend of the serial plotter. +} + +void loop() +{ float x, y, z, pitchx, pitchy; + read_N_Accel(50,x,y,z); + if (abs(x)>0.1 || abs(z)>0.1) pitchx = atan2(x,z)*180/PI; + else pitchx=0; + if (abs(y)>0.1 || abs(z)>0.1) pitchy = atan2(y,z)*180/PI; + else pitchy=0; + Serial.print(pitchx, 1); + Serial.print('\t'); + Serial.println(pitchy, 1); +} + +void read_N_Accel(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0;averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.accelAvailable()); + IMU.readAccel(x, y, z); + averX += x; averY += y; averZ += z; + } + averX /= float(N); averY /= float(N); averZ /= float(N); +} diff --git a/examples/XY_compass/XY_compass.ino b/examples/XY_compass/XY_compass.ino new file mode 100644 index 0000000..21e42e4 --- /dev/null +++ b/examples/XY_compass/XY_compass.ino @@ -0,0 +1,73 @@ +/* Compass example for the Nano 33 BLE (Sense) + * You need version 2.0 or higher of the LMS9DS1 library to run this example + * + * The compass must be calibrated for the magnetic disturbance of the setup and the environment. + * Run the DIY calibration program first and copy/paste the Magnetometer calibration data below where it's indicated. + * + * Written by Femme Verbeek + * 6 June 2020 + * Released to the public domain +*/ + +#include + +boolean viewInSerialPlotter=true; // true optimises for serial plotter, false for serial monitor + +void setup() { + Serial.begin(115200); + while(!Serial); // wait till the serial monitor connects + delay(1); + if (!IMU.begin()) { // initialize the magnetometer + Serial.println("Failed to initialize IMU!"); + while (1); } + +/***************** For a proper functioning of the compass the magnetometer needs to be calibrated ******************** +***************** Replace the lines below by the output of the DIY_Calibration_Magnetometer sketch ********************/ + IMU.setMagnetFS(0); + IMU.setMagnetODR(8); + IMU.setMagnetOffset(0, 0, 0); // uncalibrated + IMU.setMagnetSlope (1, 1, 1); // uncalibrated +/****************************************************************************************************************************** +**** FS Full Scale range (0=±400 | 1=±800 | 2=±1200 | 3=±1600 (µT) ***** +**** ODR Output Data Rate range (6,7,8)=(40,80,400)Hz | not available on all chips (0..5): (0.625,1.25,2.5,5.0,10,20)Hz ***** +*******************************************************************************************************************************/ + + IMU.magnetUnit = MICROTESLA; // GAUSS MICROTESLA NANOTESLA + if (viewInSerialPlotter) Serial.println(" Heading \t Inclination \t Strength \t mag.X \t mag.Y \t mag.Z "); +} + +void loop () +{ float magY,magX,magZ; + doNMeasurements (50,magX,magY,magZ); //Average measurements to reduce noise + float heading= atan2(magY,magX)*180/PI +180; + float fieldStrength = sqrt(magX*magX +magY*magY+magZ*magZ); + float inclination = atan(-magZ/sqrt(magX*magX +magY*magY)) *180/PI; // by definition a positive inclination means the Z component is negative + + if (!viewInSerialPlotter) Serial.print("Heading "); + dump(heading,"° Inclination "); + dump(inclination,"° Intensity "); + dump(fieldStrength,"µT X "); + dump(magX,"µT Y "); + dump(magY,"µT Z "); + dump(magZ,"µT"); + Serial.println(); +} + +void dump (float Value, char txt[]) +{ + Serial.print(Value); + if (!viewInSerialPlotter) Serial.print(txt); + else Serial.print('\t'); +} + +void doNMeasurements(unsigned int N, float& averX, float& averY, float& averZ) +{ float x, y, z; + averX=0; averY =0; averZ =0; + for (int i=1;i<=N;i++) + { while (!IMU.magnetAvailable()); + IMU.readMagnet(x, y, z); + averX += x; averY += y; averZ += z; + } + averX /= N; averY /= N; averZ /= N; +} + diff --git a/images/Aim_axes_along_magnetic_field_lines.PNG b/images/Aim_axes_along_magnetic_field_lines.PNG new file mode 100644 index 0000000..f4a8ad5 Binary files /dev/null and b/images/Aim_axes_along_magnetic_field_lines.PNG differ diff --git a/images/Boxed_sensor.PNG b/images/Boxed_sensor.PNG new file mode 100644 index 0000000..39a4f32 Binary files /dev/null and b/images/Boxed_sensor.PNG differ diff --git a/keywords.txt b/keywords.txt index 8c39cca..4222520 100644 --- a/keywords.txt +++ b/keywords.txt @@ -14,19 +14,84 @@ IMU KEYWORD1 begin KEYWORD2 end KEYWORD2 +setContinuousMode KEYWORD2 +setOneShotMode KEYWORD2 +getOperationalMode KEYWORD2 +measureAccelGyroODR KEYWORD2 readAcceleration KEYWORD2 readGyroscope KEYWORD2 readMagneticField KEYWORD2 -gyroscopeAvailable KEYWORD2 + +readAccel KEYWORD2 +readGyro KEYWORD2 +readMagnet KEYWORD2 +readRawAccel KEYWORD2 +readRawGyro KEYWORD2 +readRawMagnet KEYWORD2 + accelerationAvailable KEYWORD2 +gyroscopeAvailable KEYWORD2 magneticFieldAvailable KEYWORD2 + +accelAvailable KEYWORD2 +gyroAvailable KEYWORD2 +magnetAvailable KEYWORD2 + accelerationSampleRate KEYWORD2 gyroscopeSampleRate KEYWORD2 magneticFieldSampleRate KEYWORD2 -setContinuousMode KEYWORD2 -setOneShotMode KEYWORD2 + +accelOffset KEYWORD2 +gyroOffset KEYWORD2 +magnetOffset KEYWORD2 + +setAccelOffset KEYWORD2 +setGyroOffset KEYWORD2 +setMagnetOffset KEYWORD2 + +accelSlope KEYWORD2 +gyroSlope KEYWORD2 +magnetSlope KEYWORD2 + +setAccelSlope KEYWORD2 +setGyroSlope KEYWORD2 +setMagnetSlope KEYWORD2 + +accelUnit KEYWORD2 +gyroUnit KEYWORD2 +magnetUnit KEYWORD2 + +getAccelODR KEYWORD2 +getGyroODR KEYWORD2 +getMagnetODR KEYWORD2 +setAccelODR KEYWORD2 +setGyroODR KEYWORD2 +setMagnetODR KEYWORD2 + +getAccelFS KEYWORD2 +getGyroFS KEYWORD2 +getMagnetFS KEYWORD2 +setAccelFS KEYWORD2 +setGyroFS KEYWORD2 +setMagnetFS KEYWORD2 + +setAccelBW KEYWORD2 +getAccelBW KEYWORD2 +setGyroBW KEYWORD2 +getGyroBW KEYWORD2 ####################################### # Constants ####################################### + +GAUSS LITERAL1 +NANOTESLA LITERAL1 +MICROTESLA LITERAL1 +GRAVITY LITERAL1 +METERPERSECOND2 LITERAL1 +DEGREEPERSECOND LITERAL1 +RADIANSPERSECOND LITERAL1 +REVSPERMINUTE LITERAL1 +REVSPERSECOND LITERAL1 + diff --git a/library.properties b/library.properties index 6933e90..a31ae6a 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=Arduino_LSM9DS1 -version=1.1.0 -author=Arduino -maintainer=Arduino +version=2.0.0 +author=Femme Verbeek +maintainer=fv@tcenl.com sentence=Allows you to read the accelerometer, magnetometer and gyroscope values from the LSM9DS1 IMU on your Arduino Nano 33 BLE Sense. -paragraph= +paragraph=Includes built in calibration of the sensors, "set" and "get" methods for Output Data Rate, Full Scale, Band Width and return the data in a unit of choise. category=Sensors -url=https://github.com/arduino-libraries/Arduino_LSM9DS1 +url=https://github.com/FemmeVerbeek/Arduino_LSM9DS1 architectures=* includes=Arduino_LSM9DS1.h diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..85a5fef --- /dev/null +++ b/readme.md @@ -0,0 +1,563 @@ +# Notes on version 2.0 of the LSM9DS1 library + +## Written by [Femme Verbeek]( https://www.linkedin.com/in/femmeverbeek/ ) +### Pijnacker, The Netherlands +### 11 june 2020 + +---------------------------------------------------------------------------- +links: + +[LSM9DS1 datasheet](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) + +[DIY Calibration instruction video](https://youtu.be/BLvYFXoP33o) + +[Information about Earth Magnetic field](https://en.wikipedia.org/wiki/Earth%27s_magnetic_field) + +[Geomagnetic Calculator of the NOAA](https://www.ngdc.noaa.gov/geomag/calculators/magcalc.shtml?#igrfwmm) + +----------------------------------------------------------------------------- +## Contents: +1. Introduction +1. Naming strategy +1. Output of Read method +1. Output Data Rate (ODR) +1. Full Scale setting (FS) +1. Band Width setting (BW) +1. Calibration + * Offset + * Slope +1. Overview of Code +1. Derivation of linear correction + * Offset + * Slope + +-------------------------------------------------------------------- +## 1 Introduction +---------------------------------------------------------------------- + +The reason for writing this update is that the LSM9DS1 chip has several settings that can be used to tweak the measurement +results. The sensors are not calibrated and the output may vary per instance of the chip. +In my case the magnetic field offset was larger than the Earth magnetic field. The gyroscope offset of 3 deg/s does not sound +like much, but when trying to track the orientation it corresponds to a full circle misalignment in two minutes. Without +calibration it is impossible to make a working magnetic or gyro compass, artificial horizon etc. + +This new version 2 provides three DIY calibration sketches, for the accelerometer, gyroscope and magnetometer. They give instructions +on what to do during a calibration measurement. For more clarity a [DIY Calibration instruction video](https://youtu.be/BLvYFXoP33o) was made. +The DIY calibration sketches return the results on screen as code that can be copy/pasted in a sketch. For a rough calibration you +have to do this only once per instance of the chip. But the magnetic field measurement is very easily disturbed by the set-up +the chip is mounted in. So for the magnetometer it is advised to do an in-situ calibration. + +The calibration method in this library gives a rather basic linear correction, which will be sufficient in most cases. +When used in combination with sensor fusion algorithms, quaternions, Euler transformations, those libraries usually come with +their own calibration methods. In those cases it may be better not to use the built-in calibration of this library. + + +New for all 9 DOF (degrees of freedom) is the possibility: +* to change the Output Data Rate (ODR), including fast rate magnetic sampling. The values returned by get...ODR are + now the actual values rather than those in the documentation. +* to change the internal full scale setting of the chip (FS) giving it more accuracy at the expense of the range +* to change the band width filtering of the chip +* to change the output unit +* to give it calibration zero offset and slope factors +* to change the operational mode (off, Acceleration only, Acceleration + Gyroscope + +The values returned by the read... methods change according to the settings. If left to their default, the output will still +be the same as from version 1.1.0. One exception was made for the magnetometer. The default sample rate of 20Hz did not work on +all set-ups. It has been changed to 40Hz. If you really need the 20Hz, the sketch call is *IMU.setMagnetODR(5);* + +In theory all the settings and calibration factors are independent of each other. + +This means that +* changing the FS does not change the read... value, giving it more accuracy at the expense of the range. +* calibration of slope and offset factors can be done combined or separately in any following order. +* calibration can be done in any chosen Unit, FS, ODR, +* the calibration factors can be copy/pasted in a sketch. They dont need changing when the sketch +uses any other setting combination of Unit, FS or ODR. + +In reality the gyroscope offset showed a small dependence on the FS setting. For this reason the possibility to change the settings +was added in the DIY calibration sketches. This improved the drift behavior of the gyro significantly. + +------------------------------------------------- +## 2 Naming strategy +------------------------------------------------- + +This new version 2.0 of the library supports al the function calls available in version 1.1.0 +Keeping the same naming convention of version 1.1.0 resulted in very long names for the new functions, making formulas +difficult to read, increasing the chance of making typo's and it was not always clear what was meant. +For this reason a number of shorter aliases were created, most of them following the [datasheet](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) + +new | alias +----- | --------------- +Accel | Acceleration +Gyro | Gyroscope +Magnet | MagneticField +ODR | SampleRate +FS | FullScale +BW | BandWidth + +Addition of "set" and "get" in the names of ODR, BW and FS functions. + +e.g. *magneticFieldSampleRate* still works, but in the library code it is now *getMagnetODR* reflecting that it is not longer +a constant but a function corresponding to the LSM9DS1 chip setting. + +Not used were the datasheet's XL, M and G since it may confuse with gravity, Gauss and of course the size of clothes. :tshirt: + +#### In the text below ... stands for any of the three measurement properties, Accel, Gyro, or Magnet. + +------------------------------------------ +## 3 Output of Read method +----------------------------------------- + +There are several ways in which a linear correction can be applied. The chosen method has the highest amount of advantages. +The offset and slope are independent of each other and of any of the other settings in the program. They can be measured +independently and counteract only the internal transducer differences. (For mathematical derivation see below) + +There are two read methods for each of the properties ... +``` + readRaw... = (FS / 32786) * Data +``` + and + +``` + read... = Unit * Slope *( readRaw... - Offset ) +``` +Data = the measurement value showing up on the chip registers +FS = the in Program Full Scale setting (float get...FS() ) counteracting the internal chip setting so that + the output result remains unchanged. +Unit = the unit the measured physical property is expressed in. +Slope and Offset = calibration parameters. + +readRaw... produces dimensionless uncalibrated output and is the method to be used when calibrating. +read... produces calibrated output in the unit of your choice. + +Note that when Unit, Slope and Offset are left to their default values, the read... and readRaw... methods +produce the same output, identical to that of library version 1.01. + +Measuring the offset shows that it scales roughly with the chip internal full scale setting (IFS). That means that it is caused +by the internal transducer and not by the ADC. So it is sufficient to calibrate for just one of the full scale settings +and compensate for the others by means of a multiplication (FS). The small dependency of the gyroscope offset on the FS setting +was discovered only recently. Calibrating and running at the same FS setting improved the accuracy especially of the gyro drift. + +------------------------------------------ +## 4 Output Data Rate (ODR) +----------------------------------------- + +This library offers the possibility of changing the sample rate of the LSM9DS1 chip. +``` + set...ODR(); //changes the sample rate according to the table below + get...ODR(); //returns the actual ODR value (Hz) that was previously measured +``` +The ODR values According to the datasheet + +| nr |setAccelODR(nr)| setGyroODR(nr)|setMagnetODR(nr) | +|:----:|:-------------:|:-------------:|:-----------------:| +|0 | Gyro&Accel off | Gyro off | 0.625 Hz | +|1 | 10 Hz | 10 Hz | 1.25 Hz | +|2 | 50 Hz | 50 Hz | 2.5 Hz| +|3 | 119 Hz | 119 Hz | 5 Hz| +|4 | 238 Hz | 238 Hz | 10 Hz| +|5 | 476 Hz | 476 Hz | 20 Hz| +|6 | don't use | don't use | 40 Hz| +|7 | | | 80 Hz| +|8 | | | 400 Hz| + +As it turned out the actual ODR values could deviate significantly from the ones in the datasheet. +For that reason the set...ODR functions quickly measure the actual ODR value upon calling. This +actual value is returned by the get...ODR functions. +Settings 6 of Accel and Gyro were supposed to deliver 952 Hz. The result was hardly +faster than that of setting 5 but with a lot more noise. For this reason it is not adviced to use them. + +The magnetometer settings 0..5 turned out not to be working on a separate LSM9DS1 connected to an Arduino Uno +On the same set-up setAccelODR(1) and setGyroODR(1) did not work either. + +To save on power demand the Gyro or both Gyro+Accel can be switched off by setting ...ODR(0). +In any other setting Gyro and Accel share their ODR, so changing one, changes the other. + +------------------------------------------------- +## 5 Full Scale setting (FS) +-------------------------------------------------- +This library offers the possibility of changing the Full Scale setting of the LSM9DS1 chip. It changes the +chip's internal multiplier, assigning more bits to the measurement but limiting it's range. +``` + set...FS(); //changes the FS according to the table below + get...FS(); //returns the FS multiplier +``` +FS settings + +| nr |setAccelFS(nr)| setGyroFS(nr)|setMagnetFS(nr) | +|:----:|:-------------:|:-------------:|:-----------------:| +|0 | ±2 g | ±245 °/s | ±400 µT| +|1 | ±24 g | ±500 °/s | ±800 µT| +|2 | ±4 g | ±1000 °/s | ±1200 µT| +|3 | ±8 g | ±2000 °/s | ±1600 µT| + +Datasheet anomalies +* Upon *setAccelFS(1);* the needed multiplication factor should be 16 but turns out to be 24, + but the sensor maxes out at 20 g +* The *setGyroFS(2);* is *NA* according to the datasheet, but when tested it worked nicely at 1000 °/s + +------------------------------------------------- +## 6 Band Witdh setting (BW) +-------------------------------------------------- + +The possibility of setting the band-width filtering was added for the purpose of getting rid of a very nasty +spike in the gyro/accel signal at the highest ODR. It turned out that there was no influence on the spike at all. +The methods are provided "as is". The set values can be found in the [datasheet](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) +tables 46, 47 and 67. +``` + set...BW(); + get...BW(); +``` + +------------------------------------------------- +## 7 Calibration +-------------------------------------------------- + +This library has a linear calibration possibility built in. +For each property, Accel, Gyro and Magnet and each of the three directions (x, y, z) +a set of two factors (Slope and Offset) must be found. So in total 18 calibration factors. +Setting their values correct, is all that is needed to change the output of the read...() methods from raw +into calibrated values. + +An interactive DIY_Calibration program is provided with this library. See [instruction video](https://youtu.be/BLvYFXoP33o) +It explains how to measure each of the factors, collects data and presents the result on screen as +copy/paste-able code. No special setup is required, but it helps to fix the board in a non-magnetic rectangular box. + +![](/images/Boxed_sensor.PNG) + +The calibration factors may be different per instance of the LSM9DS1 chip. They will hardly vary in time, +so it is sufficient to calibrate them only once. The magnetic field is easily disturbed by even the smallest +pieces of metal in the setup (car, drone, ...). An "in-situ" calibration is advised for the magnetic property. + +The calibration values don't depend on any of the settings in the program, like ...Unit, +...ODR, ...FS (full scale). You can change them in your sketch without the need to recalibrate. + +Sensor fusion programs usually have their own calibration methods. It is not advised to mix them unless you know +what you are doing. You will loose the advantage of the independency of parameters though. +If you want the fusion program to be using a changed ODR or FS setting, you may have to add some codelines +in the library itself in the file LSM9DS1.CPP / in the method int LSM9DS1Class::begin() +No guarantee that it works. + +If you want to design your own calibration method, the text below gives more explanation on what to do. +It is vital that the calibration measurements are done with the raw... functions to get uncalibrated data + +------------------------------------------------------------------------------------------ +### Offset + +In order to find the value of Offset we must do a zero point measurement. That means that the physical property ... +we are trying to measure should actually result in a value of 0. E.g. keeping the board still should result in zero +gyroscope values for x,y and z. For acceleration the two axes perpendicular to the Earth gravity should be zero. + +The offset calibration values are stored in the arrays +``` + ...Offset[3]={0,0,0} +``` +The value ...Offset should get: +``` + ...Offset = readRaw... (@zero-point) +``` +so the (average) output of the readRaw... functions during a zero point measurement. +In the calibrated read... function its value gets subtracted from readRaw, so the average read... output will +equal 0 in a zero point experiment. + +Alternatively if it is difficult to get a zero property, you could try to find the maximum and minimum values and +use the point in the middle + + readResult = ( Read_max + Read_min ) / 2 + +This is probably more complex than what it looks like. Both Read_min and Read_max can best be some sort of average. +If you simply use the min() and max() values of a lot of measurements, the final results are still just from one measurement +each. Most probably these are the outlyers in both directions, so actually the worst measurements. + +Calculating separate averages of the min and max values is a better method but also like a chicken and egg problem. +Before the calibration program can decide to which average a measurement belongs, it needs a rough calibration first. +If the offset is very large, like in case of the magnet, this is not straight forward. The DIY calibration program +uses the fastrate magnet ODR and simply calculates a lot of averages and takes the min and max of that. + +The best method is probably 3D elliptical regression, where the values of Offset correspond to the centre of the +ellipsoid. So far I did not venture into the mathematics of this. :dizzy_face: + +-------------------------------------------------------------------------------------------------------------------- +### Slope + +Slope is a dimensionless number that compensates for the sensitivity of the chip's internal transducer. Ideally it's value +should equal 1, meaning that the sensitivity is what it's supposed to be. I my case the accelerometer was very close, the +gyro 17%, 13% and 4% to low, the magnetometer 5%, 4% and 7% too high. For the magnet and the Accel only their relative +values matter, but for the Gyro it's the absolute value if you want to keep track of orientation. 17% means 61 degrees +misalignment on a full turn. + +In order to calibrate the Slope parameter at least two measurements (say 1 and 2) must be done where the physical quantity, +(let's call it Q), has a known difference, so Q_1 - Q_2 = known value. + +The value of slope follows from +``` + Result = abs (( Q_1 - Q_2) / ( readRaw_1 - readRaw_2 )) +``` +The slope calibration values are stored in the arrays +``` + ...Slope[3]={1,1,1} +``` +E.g. The local data of the magnetic field (in nT) can be found at [Wikipedia](https://en.wikipedia.org/wiki/Earth%27s_magnetic_field) +By pointing the sensor axes in both ways in the direction of the magnetic field-lines the difference in reading should be +2x the given field intensity. So +``` + magnetSlope[i]= (2*Intensity)/(NANOTESLA*(readRawMagnet_1 -readRawMagnet_2)) +``` + +![](/images/Aim_axes_along_magnetic_field_lines.PNG) + + +E.g. the Earth gravity should produce a value of 1g. Holding the board upside down should measure -1g. So the difference +``` + ( Q_1 - Q_2) = 2g +``` +Lets assume that readAccel produces values of 1.3 and - 0.9 +``` + Offset = (1.3 + (-0.9)) / 2 = 0.2 + Slope = (1 - (-1)) / (1.3 - (-0.9) = 2 / 2.2 = 0.90909 +``` + +---------------------- +## 8 Overview of Code +---------------------- + +(almost) unchanged +``` + int begin(); + void end(); + void setContinuousMode(); + void setOneShotMode(); + int accelAvailable(); // Number of samples in the FIFO + int gyroAvailable(); // Number of samples in the FIFO + int magnetAvailable(); // Number of samples in the FIFO +``` + +existing functions changed for new functionality. Results reflect calibration, full scale and unit settings +``` + int readAccel(float& x, float& y, float& z); // Results are in G (earth gravity) or m/s2 + int readGyro(float& x, float& y, float& z); // Results are in degrees/second or rad/s + int readMagnet(float& x, float& y, float& z); // default in uT (micro Tesla) +``` +new functions resembling the old read functions, use these for calibration purposes +``` + int rawAccel(float& x, float& y, float& z); // Return uncalibrated results + int rawGyro (float& x, float& y, float& z); // Return uncalibrated results + int rawMagnet(float& x, float& y, float& z); // Return uncalibrated results +``` +existing functions that now return a previously measured value +``` + float getAccelODR(); // Sampling rate of the sensor (Hz) + float getGyroODR(); // Sampling rate of the sensor (Hz) + float magnetODR(); // Sampling rate of the sensor (Hz) +``` +New the possibility to change the ODR registers (Output Data Rate) +``` + int setAccelODR(int8_t range); // 0:off, 1:10Hz, 2:50Hz, 3:119Hz, 4:238Hz, 5:476Hz, 6:952Hz + int setGyroODR(int8_t range); // 0:off, 1:10Hz, 2:50Hz, 3:119Hz, 4:238Hz, 5:476Hz, 6:952Hz + int setMagnetODR(int8_t range) // range (0..7) corresponds to {0.625,1.25,2.5,5,10,20,40,80,400}Hz +``` +Notes: +1. The 952Hz Accel and Gyro ODR does not seem to work. It's true value is close to setting 5, + but it produces a lot more noise in the form of spikes +1. The Magnetic 400Hz setting corresponds to the chip's fast rate setting +1. Changing any of the settings quickly measures the actual ODR value for the get...ODR to return. + +New constants used in ...Unit setting +``` +#define GAUSS 0.01 +#define MICROTESLA 1.0 // default +#define NANOTESLA 1000.0 +#define GRAVITY 1.0 // default +#define METERPERSECOND2 9.81 +#define DEGREEPERSECOND 1.0 //default +#define RADIANSPERSECOND 3.141592654/180 +#define REVSPERMINUTE 60.0/360.0 +#define REVSPERSECOND 1.0/360.0 +``` +Unit settings that read... returns measurement results in. +``` + float accelUnit = GRAVITY; // GRAVITY OR METERPERSECOND2 + float gyroUnit = DEGREEPERSECOND; // DEGREEPERSECOND, RADIANSPERSECOND, REVSPERMINUTE, REVSPERSECOND + float magnetUnit = MICROTESLA; // GAUSS MICROTESLA NANOTESLA +``` + +methods for internal use +``` + int getOperationalMode(); //0=off , 1= Accel only , 2= Gyro +Accel + float measureAccelGyroODR(unsigned int duration); + float measureMagnetODR(unsigned int duration); +``` + +Calibration parameters Slope ans Offset : See Calibration. +``` + float accelOffset[3] = {0,0,0}; // zero point offset correction factor for calibration + float gyroOffset[3] = {0,0,0}; // zero point offset correction factor for calibration + float magnetOffset[3] = {0,0,0};// zero point offset correction factor for calibration + float accelSlope[3] = {1,1,1}; // slope correction factor for calibration + float gyroSlope[3] = {1,1,1}; // slope correction factor for calibration + float magnetSlope[3] = {1,1,1}; // slope correction factor for calibration +``` + +Methods for setting the calibration +``` + void setAccelOffset(float x, float y, float z); + void setAccelSlope(float x, float y, float z); + void setGyroOffset(float x, float y, float z); + void setGyroSlope(float x, float y, float z); + void setMagnetOffset(float x, float y, float z); + void setMagnetSlope(float x, float y, float z); +``` + +New: changing the full scale sensitivity of the sensors. +The functions modify the FS (full scale) registers of the LSM9DS1 chip changing sensitivity at the expense of range. +Changing this setting does not change the x,y,z output of the read functions, but assigns just more or less bits +to the sensor measurement. +``` + int setAccelFS(uint8_t range); // 0: ±2g ; 1: ±24g ; 2: ±4g ; 3: ±8g + int setGyroFS(int8_t range); // 0= ±245 dps; 1= ±500 dps; 2= ±1000 dps; 3= ±2000 dps + int setMagnetFS(int8_t range); // 0=400.0; 1=800.0; 2=1200.0 , 3=1600.0 (µT) +``` +*note* According to the data sheet gyroscope setting 2 = 1000 dps should not be available. For some reason it worked + like a charm on my BLE Sense board, so I added the possibility. + +New functions return the Full Scale setting of the corresponding DOF as set with the corresponding set...FS functions +``` virtual float getAccelFS(); // Full Scale setting (output = 2.0, 24.0 , 4.0 , 8.0) + virtual float getGyroFS(); // output = 245.0, 500.0 , 1000.0, 2000.0) + virtual float getMagnetFS(); // output 400.0 ; 800.0 ; 1200.0 , 1600.0 +``` +Note: According to the datasheet setAccelFS(1) should correspoond to 16g. Measurement showed that it is actually 24g + but that the sensor maxes out at 20g. Since the actual value is used in a calculation the value of 24g is returned + by getAccelFS for that setting. + +The band with routines were added as an attempt to dampen a nasty spike that existed in the Gyro signal. It had no +observable effect. The methods are provided as is. +``` virtual float setAccelBW(uint8_t range); //0,1,2,3 Override autoBandwidth setting see doc.table 67 + virtual float getAccelBW(); //Bandwidth setting 0,1,2,3 see documentation table 67 + virtual int setGyroBW(uint8_t range); //Bandwidth setting 0,1,2,3 see documentation table 46 and 47 + virtual float getGyroBW(); //Bandwidth setting 0,1,2,3 see documentation table 46 and 47 +``` +----------------------------------------- +## 9 Derivation of linear correction method +------------------------------------------- + +Sorry for the very formal derivation below. It suits verifiability but probably only my own purpose :nerd_face: +It took a lot of puzzling to get it right. Don't read it if you don't want to. :dizzy_face: +The readRaw... method was introduced after this derivation was written. ReadRaw... produces output equal to Read... but with +boundary conditions Offset=0, Slope=1 and unit = 1 (default). + +----------------- +### Offset + +Assuming good linearity of the transducer, we can model the data output of the chip as + + Data = (32768 / IFS) *( A*Q + B) (1) + +Data = the measured value showing up on the chip registers +Q = the actual physical quantity we are trying to measure in any of the 9 DOF +IFS = the chip Internal Full Scale Setting. +A,B unknown constants representing chip instance differences + +Since the chip outputs dimensionless bits and bytes only, the dimensions of IFS and B must equal the dimension of Q. + +The challenge is to get rid of the unknown constants A and B and translate them into measurable quantities produced by the +library's Read methods. Since the calibrated Read should produce a number equal to the actual physical quantity +we can state + + Read = Q + +Further, we do not want to recalibrate when we change the Full Scale setting or the Unit. + +Define +FS = the in-program Full Scale function counteracting the IFS so that the output result remains unchanged. + (dimensionless, but its value corresponds to that of the chosen default unit) +Slope = in-program correction factor for the sensor sensitivity +Offset = in-program correction factor for the sensor zero point offset + +The output of the read methods is (see above) + + Read = Unit * Slope * (FS / 32786 * Data - Offset ) (2) + +Substitute eq(1) + + Read = Unit * slope * (FS / IFS * 32786/32786 * ( A*Q + B ) - Offset ) + + Read = Unit * slope * (FS / IFS * ( A*Q + B ) - Offset ) (3) + +In case of the calibrated zero point measurement Q = Read = 0 +Substitute in eq.(3) we get + + 0 = Unit * slope * (FS / IFS * ( A*0 + B ) - Offset ) + + Unit * slope * Offset = Unit * slope * FS / IFS * B (4) + +For an uncalibrated measurement of a zero point (ZP) filling in Offset=0 and Q=0 in eq(3) we get + + Read_uncalibrated_ZP = Unit * slope * FS / IFS * B (5) + +The righthand terms in equation (4) and (5) are identical so it follows that + + Offset = read_uncalibrated_ZP / (Unit * slope) (6) + +This defines how we should do the calibration measurements. In the library the methods called set...Offset +use of eq(6) to assign the uncalibrated zero-point read values to the corresponding program parameters. +Eq.(6) suggests that Offset depends on Unit and Slope, but this is not the case as the Read method scales +with the same value. To proof this we write eg(4) in a different form + + Offset = (Unit * Slope) / (Unit * Slope) * FS/IFS * B (7) + +Further since the dimensionless FS counteracts IFS in size +and the dimension of IFS equals that of Q, the ratio FS/IFS is 1/Unit. +or in other words + + Unit * FS /IFS = 1 (8) + +eq(7) reduces to + + Offset = B / Unit = B_dimensionless (9) + +So Offset equals the dimensionless form of B and does not depend on any other parameter. + +----------------- +### Slope + +In order to calibrate the Slope at least two measurements (say 1 and 2) must be done where the measured quantity Q +has a known difference. The calibrated Read equals Q so + + Q_1 - Q_2 = Read_1 - Read_2 = known value (10) + +Substitute eq(2) + + Q_1 - Q_2 = Unit*Slope*(FS/32786*Data_1 - Offset) - Unit*Slope*(FS/32786*Data_2 - Offset) + + = Unit*Slope*(FS/32786*(Data_1 - Data_2) (11) + +So the difference between the measurements gets rid of the Offset +For an uncalibrated measurement we must set Slope = 1. +Eq(2) becomes + + Read_uncalibrated = Unit * (FS / 32786 * Data - Offset ) + +In eq(11) we get + + Q_1 -Q_2 = Slope * ( Read_uncalibrated_1 - Read_uncalibrated_2) + + Slope = (( Read_uncalibrated_1 - Read_uncalibrated_2) / (Q_1 -Q_2) (12) + +So in order to measure the slope we must first set the Slope parameter to 1. + +In order to prove that Slope is independent of all the other program parameters we substitute eq(1) in eq(11) + + Q_1 - Q_2 = Unit*Slope*(FS/32786)*(32786/IFS)*(A*Q1 +B - A*Q2 -B)) + + (Q_1 - Q_2) = Slope* Unit*(FS/IFS)* A *(Q1 - Q2) (12) + +and with eq(8) + + ( 1 ) = Slope * 1 * A *( 1 ) + + Slope = 1 / A + +So Slope only depends on A, the (in)sensitivity of the sensor. The proportionality is reciprocal since Slope is the +compensation factor for the chip's insensitivity + +QED diff --git a/src/LSM9DS1.cpp b/src/LSM9DS1.cpp index 0403018..8b378e3 100644 --- a/src/LSM9DS1.cpp +++ b/src/LSM9DS1.cpp @@ -1,5 +1,12 @@ -/* +/* This file is part of the Arduino_LSM9DS1 library. + This version written by Femme Verbeek, Pijnacker, the Netherlands + Released to the public domain + version 2 + Release Date 10 July 2020 + + Original notice: + Copyright (c) 2019 Arduino SA. All rights reserved. This library is free software; you can redistribute it and/or @@ -15,6 +22,9 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + + */ #include "LSM9DS1.h" @@ -35,9 +45,11 @@ #define LSM9DS1_CTRL_REG1_M 0x20 #define LSM9DS1_CTRL_REG2_M 0x21 #define LSM9DS1_CTRL_REG3_M 0x22 +#define LSM9DS1_CTRL_REG4_M 0x23 #define LSM9DS1_STATUS_REG_M 0x27 #define LSM9DS1_OUT_X_L_M 0x28 + LSM9DS1Class::LSM9DS1Class(TwoWire& wire) : continuousMode(false), _wire(&wire) { @@ -72,13 +84,18 @@ int LSM9DS1Class::begin() writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G, 0x78); // 119 Hz, 2000 dps, 16 Hz BW writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL, 0x70); // 119 Hz, 4G - writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG1_M, 0xb4); // Temperature compensation enable, medium performance, 20 Hz + writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG1_M, 0b10111000); // Temperature compensation enable, medium performance, 40 Hz +// writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG1_M, 0xb4); // Temperature compensation enable, medium performance, 20 Hz writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG2_M, 0x00); // 4 Gauss +// writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG2_M, 0b01100000); // 16 Gauss writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG3_M, 0x00); // Continuous conversion mode + writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG4_M, 0b00000100); // Z-axis operative mode medium performance + measureODRcombined() ; // for Accelerometer/Gyro and Magnetometer. return 1; } + void LSM9DS1Class::setContinuousMode() { // Enable FIFO (see docs https://www.st.com/resource/en/datasheet/DM00103319.pdf) writeRegister(LSM9DS1_ADDRESS, 0x23, 0x02); @@ -93,7 +110,7 @@ void LSM9DS1Class::setOneShotMode() { writeRegister(LSM9DS1_ADDRESS, 0x23, 0x00); // Disable continuous mode writeRegister(LSM9DS1_ADDRESS, 0x2E, 0x00); - + continuousMode = false; } @@ -102,30 +119,46 @@ void LSM9DS1Class::end() writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG3_M, 0x03); writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G, 0x00); writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL, 0x00); - - _wire->end(); + + _wire->end(); + + // There has been a problem with the power usage of the Arduino Nano BLE boards. + // Due to a switch in pinnumbers the pull-ups keep drawing current after the call to IMU.end(); + // This shortens battery life. Most likely future updates solve the problem. + // see https://forum.arduino.cc/index.php?topic=691488.15 + // code for if the old value is used +#if defined(ARDUINO_ARDUINO_NANO33BLE) && PIN_ENABLE_SENSORS_3V3 == 32 + pinMode(PIN_ENABLE_I2C_PULLUP, OUTPUT); // this restores the energy usage to very low power + digitalWrite(PIN_ENABLE_I2C_PULLUP, HIGH); +#endif } -int LSM9DS1Class::readAcceleration(float& x, float& y, float& z) -{ - int16_t data[3]; +//************************************ Acceleration ***************************************** - if (!readRegisters(LSM9DS1_ADDRESS, LSM9DS1_OUT_X_XL, (uint8_t*)data, sizeof(data))) { - x = NAN; - y = NAN; - z = NAN; +int LSM9DS1Class::readAccel(float& x, float& y, float& z) // return calibrated data in a unit of choise +{ if (!readRawAccel(x,y,z)) return 0; + // See releasenotes read = Unit * Slope * (FS / 32786 * Data - Offset ) + x = accelUnit * accelSlope[0] * (x - accelOffset[0]); + y = accelUnit * accelSlope[1] * (y - accelOffset[1]); + z = accelUnit * accelSlope[2] * (z - accelOffset[2]); + return 1; +} - return 0; +int LSM9DS1Class::readRawAccel(float& x, float& y, float& z) // return raw uncalibrated data +{ int16_t data[3]; + if (!readRegisters(LSM9DS1_ADDRESS, LSM9DS1_OUT_X_XL, (uint8_t*)data, sizeof(data))) + { x = NAN; y = NAN; z = NAN; return 0; } - - x = data[0] * 4.0 / 32768.0; - y = data[1] * 4.0 / 32768.0; - z = data[2] * 4.0 / 32768.0; - + // See releasenotes read = Unit * Slope * (PFS / 32786 * Data - Offset ) + float scale = getAccelFS()/32768.0 ; + x = scale * data[0]; + y = scale * data[1]; + z = scale * data[2]; return 1; } -int LSM9DS1Class::accelerationAvailable() + +int LSM9DS1Class::accelAvailable() { if (continuousMode) { // Read FIFO_SRC. If any of the rightmost 8 bits have a value, there is data. @@ -137,79 +170,368 @@ int LSM9DS1Class::accelerationAvailable() return 1; } } - return 0; } -float LSM9DS1Class::accelerationSampleRate() -{ - return 119.0F; +// modified: the void is no longer for translating half calibrated measurements into offsets +// in stead the voids rawAccel() rawGyro() and rawMagnet must be used for calibration purposes + +void LSM9DS1Class::setAccelOffset(float x, float y, float z) +{ accelOffset[0] = x /(accelUnit * accelSlope[0]); + accelOffset[1] = y /(accelUnit * accelSlope[1]); + accelOffset[2] = z /(accelUnit * accelSlope[2]); +} +//Slope is already dimensionless, so it can be stored as is. +void LSM9DS1Class::setAccelSlope(float x, float y, float z) +{ if (x==0) x=1; //zero slope not allowed + if (y==0) y=1; + if (z==0) z=1; + accelSlope[0] = x ; + accelSlope[1] = y ; + accelSlope[2] = z ; } -int LSM9DS1Class::readGyroscope(float& x, float& y, float& z) -{ - int16_t data[3]; +// range 0: switch off Accel and Gyro write in CTRL_REG6_XL; +// range !=0: switch on Accel: write range in CTRL_REG6_XL ; +// Operational mode Accel + Gyro: write setting in CTRL_REG1_G, shared ODR +int LSM9DS1Class::setAccelODR(uint8_t range) //Sample Rate 0:off, 1:10Hz, 2:50Hz, 3:119Hz, 4:238Hz, 5:476Hz, 6:952Hz +{ if (range >= 7) return 0; + uint8_t setting = ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) & 0b00011111) | (range << 5)); + if (writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL,setting)==0) return 0; + switch (getOperationalMode()) { + case 0 : { accelODR=0; + gyroODR=0; + break; + } + case 1 : { accelODR= measureAccelGyroODR(); + gyroODR = 0; + break; + } + case 2 : { setting = ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) & 0b00011111) | (range << 5) ); + writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G,setting) ; + accelODR= measureAccelGyroODR(); + gyroODR = accelODR; + } + } + return 1; +} + +float LSM9DS1Class::getAccelODR() +{ return accelODR; +// float Ranges[] ={0.0, 10.0, 50.0, 119.0, 238.0, 476.0, 952.0, 0.0 }; +// uint8_t setting = readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) >> 5; +// return Ranges [setting]; +} - if (!readRegisters(LSM9DS1_ADDRESS, LSM9DS1_OUT_X_G, (uint8_t*)data, sizeof(data))) { - x = NAN; - y = NAN; - z = NAN; +float LSM9DS1Class::setAccelBW(uint8_t range) //0,1,2,3 Override autoBandwidth setting see doc.table 67 +{ if (range >=4) return 0; + uint8_t RegIs = readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) & 0b11111000; + RegIs = RegIs | 0b00000100 | (range & 0b00000011) ; + return writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL,RegIs) ; +} - return 0; - } +float LSM9DS1Class::getAccelBW() //Bandwidth setting 0,1,2,3 see documentation table 67 +{ float autoRange[] ={0.0, 408.0, 408.0, 50.0, 105.0, 211.0, 408.0, 0.0 }; + float BWXLRange[] ={ 408.0, 211.0, 105.0, 50.0 }; + uint8_t RegIs = readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL); + if (bitRead(RegIs,2)) return BWXLRange [RegIs & 0b00000011]; + else return autoRange [ RegIs >> 5 ]; +} + +int LSM9DS1Class::setAccelFS(uint8_t range) // 0: ±2g ; 1: ±16g ; 2: ±4g ; 3: ±8g +{ if (range >=4) return 0; + range = (range & 0b00000011) << 3; + uint8_t setting = ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) & 0xE7) | range); + return writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL,setting) ; +} - x = data[0] * 2000.0 / 32768.0; - y = data[1] * 2000.0 / 32768.0; - z = data[2] * 2000.0 / 32768.0; +float LSM9DS1Class::getAccelFS() // Full scale dimensionless, but its value corresponds to g +{ float ranges[] ={2.0, 24.0, 4.0, 8.0}; //g + uint8_t setting = (readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) & 0x18) >> 3; + return ranges[setting] ; +} +//************************************ Gyroscope ***************************************** + +int LSM9DS1Class::readGyro(float& x, float& y, float& z) // return calibrated data in a unit of choise +{ + if (!readRawGyro(x,y,z)) return 0; //get the register values + x = gyroUnit * gyroSlope[0] * (x - gyroOffset[0]); + y = gyroUnit * gyroSlope[1] * (y - gyroOffset[1]); + z = gyroUnit * gyroSlope[2] * (z - gyroOffset[2]); return 1; } -int LSM9DS1Class::gyroscopeAvailable() +int LSM9DS1Class::readRawGyro(float& x, float& y, float& z) // return raw data for calibration purposes +{ int16_t data[3]; + if (!readRegisters(LSM9DS1_ADDRESS, LSM9DS1_OUT_X_G, (uint8_t*)data, sizeof(data))) + { x = NAN; y = NAN; z = NAN; return 0; + } + float scale = getGyroFS() / 32768.0; + x = scale * data[0]; + y = scale * data[1]; + z = scale * data[2]; + return 1; +} +int LSM9DS1Class::gyroAvailable() { if (readRegister(LSM9DS1_ADDRESS, LSM9DS1_STATUS_REG) & 0x02) { return 1; } - return 0; } -float LSM9DS1Class::gyroscopeSampleRate() -{ - return 119.0F; +// modified: the void is no longer for translating half calibrated measurements into offsets +// Instead the voids rawAccel() rawGyro() and rawMagnet must be used for calibration purposes +void LSM9DS1Class::setGyroOffset(float x, float y, float z) +{ gyroOffset[0] = x; + gyroOffset[1] = y; + gyroOffset[2] = z; +} +//Slope is already dimensionless, so it can be stored as is. +void LSM9DS1Class::setGyroSlope(float x, float y, float z) +{ if (x==0) x=1; //zero slope not allowed + if (y==0) y=1; + if (z==0) z=1; + gyroSlope[0] = x ; + gyroSlope[1] = y ; + gyroSlope[2] = z ; } -int LSM9DS1Class::readMagneticField(float& x, float& y, float& z) +int LSM9DS1Class::getOperationalMode() //0=off , 1= Accel only , 2= Gyro +Accel { - int16_t data[3]; + if ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) & 0b11100000) ==0 ) return 0; + if ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) & 0b11100000) ==0 ) return 1; + else return 2; +} - if (!readRegisters(LSM9DS1_ADDRESS_M, LSM9DS1_OUT_X_L_M, (uint8_t*)data, sizeof(data))) { - x = NAN; - y = NAN; - z = NAN; +// range ==0 : switch off gyroscope - write 0 in CTRL_REG1_G; +// range !0 : switch on Accel+Gyro mode- write in CTRL_REG6_XL and CTRL_REG1_G; + +int LSM9DS1Class::setGyroODR(uint8_t range) // 0:off, 1:10Hz, 2:50Hz, 3:119Hz, 4:238Hz, 5:476Hz, 6:952Hz +{ if (range >= 7) return 0; + uint8_t setting = ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) & 0b00011111) | (range << 5 ) ); + writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G,setting); + if (range > 0 ) + { setting = ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL) & 0b00011111) | (range << 5)); + writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG6_XL,setting); + } + switch (getOperationalMode()) { + case 0: { accelODR=0; //off + gyroODR=0; + break; + } + case 1: { accelODR= measureAccelGyroODR(); //accelerometer only + gyroODR = 0; + break; + } + case 2: { accelODR= measureAccelGyroODR(); //shared ODR + gyroODR = accelODR; + } + } + return 1; +} - return 0; - } +float LSM9DS1Class::getGyroODR() +{ return gyroODR; +// float Ranges[] ={0.0, 10.0, 50.0, 119.0, 238.0, 476.0, 952.0, 0.0 }; //Hz +// uint8_t setting = readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) >> 5; +// return Ranges [setting]; // used to be return 119.0F; +} - x = data[0] * 4.0 * 100.0 / 32768.0; - y = data[1] * 4.0 * 100.0 / 32768.0; - z = data[2] * 4.0 * 100.0 / 32768.0; +int LSM9DS1Class::setGyroBW(uint8_t range) +{ if (range >=4) return 0; + range = range & 0b00000011; + uint8_t setting = readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) & 0b11111100; + return writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G,setting | range) ; +} +#define ODRrows 8 +#define BWcols 4 +float BWtable[ ODRrows ][ BWcols ] = // acc to table 47 dataheet + { { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 16, 16, 16, 16 }, + { 14, 31, 31, 31 }, + { 14, 29, 63, 78 }, + { 21, 28, 57, 100 }, + { 33, 40, 58, 100 }, + { 0, 0, 0, 0 } }; + +float LSM9DS1Class::getGyroBW() +{ uint8_t setting = readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) ; + uint8_t ODR = setting >> 5; + uint8_t BW = setting & 0b00000011; + return BWtable[ODR][BW]; +} + +int LSM9DS1Class::setGyroFS(uint8_t range) // (0: 245 dps; 1: 500 dps; 2: 1000 dps; 3: 2000 dps) +{ if (range >=4) return 0; + range = (range & 0b00000011) << 3; + uint8_t setting = ((readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) & 0xE7) | range ); + return writeRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G,setting) ; +} + +float LSM9DS1Class::getGyroFS() // dimensionless, but its value defaults to deg/s +{ float Ranges[] ={245.0, 500.0, 1000.0, 2000.0}; //dps + uint8_t setting = (readRegister(LSM9DS1_ADDRESS, LSM9DS1_CTRL_REG1_G) & 0x18) >> 3; + return Ranges[setting] ; +} + +//************************************ Magnetic field ***************************************** + +int LSM9DS1Class::readMagneticField(float& x, float& y, float& z) // return calibrated data in a unit of choise +{ if (!readRawMagnet(x,y,z)) return 0; + x = magnetUnit * magnetSlope[0] * (x - magnetOffset[0]); + y = magnetUnit * magnetSlope[1] * (y - magnetOffset[1]); + z = magnetUnit * magnetSlope[2] * (z - magnetOffset[2]); + return 1; +} + +// return raw data for calibration purposes +int LSM9DS1Class::readRawMagnet(float& x, float& y, float& z) +{ int16_t data[3]; + if (!readRegisters(LSM9DS1_ADDRESS_M, LSM9DS1_OUT_X_L_M, (uint8_t*)data, sizeof(data))) + { x = NAN; y = NAN; z = NAN; return 0; + } + float scale = getMagnetFS() / 32768.0; + x = scale * data[0] ; + y = scale * data[1] ; + z = scale * data[2] ; return 1; } + int LSM9DS1Class::magneticFieldAvailable() -{ +{ //return (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_STATUS_REG_M) & 0x08)==0x08; if (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_STATUS_REG_M) & 0x08) { return 1; } - return 0; } -float LSM9DS1Class::magneticFieldSampleRate() -{ - return 20.0; +// modified: the void is no longer for translating half calibrated measurements into offsets +// Instead the voids rawAccel() rawGyro() and rawMagnet must be used for calibration purposes + +void LSM9DS1Class::setMagnetOffset(float x, float y, float z) +{ magnetOffset[0] = x ; + magnetOffset[1] = y ; + magnetOffset[2] = z ; +} +//Slope is already dimensionless, so it can be stored as is. +void LSM9DS1Class::setMagnetSlope(float x, float y, float z) +{ if (x==0) x=1; //zero slope not allowed + if (y==0) y=1; + if (z==0) z=1; + magnetSlope[0] = x ; + magnetSlope[1] = y ; + magnetSlope[2] = z ; +} + +int LSM9DS1Class::setMagnetFS(uint8_t range) // 0=400.0; 1=800.0; 2=1200.0 , 3=1600.0 (µT) +{ if (range >=4) return 0; + range = (range & 0b00000011) << 5; + return writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG2_M,range) ; +} + +float LSM9DS1Class::getMagnetFS() // dimensionless, but its value defaults to µT +{ const float Ranges[] ={400.0, 800.0, 1200.0, 1600.0}; // + uint8_t setting = readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG2_M) >> 5; + return Ranges[setting] ; +} + +int LSM9DS1Class::setMagnetODR(uint8_t range) // range (0..8) = {0.625,1.25,2.5,5,10,20,40,80,400}Hz +{ if (range >=16) return 0; + uint8_t setting = ((range & 0b00000111) << 2) | ((range & 0b00001000) >> 2); // bit 2..4 see table 111, bit 1 = FAST_ODR + setting = setting | (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG1_M) & 0b11100001) ; + writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG1_M,setting) ; + uint16_t duration = 1750 / (range + 1); // 1750,875,666,500,400,333,285,250,222 calculate measuring time + magnetODR= measureMagnetODR(duration); //measure the actual ODR value +} + +float LSM9DS1Class::getMagnetODR() // Output {0.625, 1.25, 2.5, 5.0, 10.0, 20.0, 40.0 , 80.0}; //Hz +{ return magnetODR; // return previously measured value +// const float ranges[] ={0.625, 1.25,2.5, 5.0, 10.0, 20.0, 40.0 , 80.0}; //Hz +// uint8_t setting = (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_CTRL_REG1_M) & 0b00011100) >> 2; +// return ranges[setting]; +} + +//************************************ Private functions ***************************************** + +void LSM9DS1Class::measureODRcombined() //Combined measurement for faster startUp. +{ float x, y, z; + unsigned long lastEventTimeA,lastEventTimeM,startA,startM; + long countA=-3, countM = -2, countiter=0; //Extra cycles to compensate for slow startup + unsigned long start = micros(); + while ((micros()- start) < ODRCalibrationTime) + { countiter++; + if (accelAvailable()) + { lastEventTimeA = micros(); + readAccel(x, y, z); + countA++; + if (countA==0) {startA=lastEventTimeA; + start=lastEventTimeA;} + } + if (magnetAvailable()) + { lastEventTimeM = micros(); + readMagnet(x, y, z); + countM++; + if (countM==0) startM=lastEventTimeM; + } + } +// Serial.println("measure combined " ); +// Serial.println("countA= "+String(countA) ); +// Serial.println("countM= "+String(countM) ); +// Serial.println("countiter= "+String(countiter) ); +// Serial.println("dTa= "+String(lastEventTimeA-startA) ); +// Serial.println("dTb= "+String(lastEventTimeM-startM) ); + + accelODR= (1000000.0*float(countA)/float(lastEventTimeA-startA) ); + gyroODR = accelODR; + magnetODR= (1000000.0*float(countM)/float(lastEventTimeM-startM) ); +} + +float LSM9DS1Class::measureAccelGyroODR() +{ if (getOperationalMode()==0) return 0; + float x, y, z; //dummies + unsigned long lastEventTime, + start=micros(); + long count = -3; + int fifoEna=continuousMode; //store FIFO status + setOneShotMode(); //switch off FIFO + while ((micros()- start) < ODRCalibrationTime) // measure + { if (accelAvailable()) + { lastEventTime = micros(); + readAccel(x, y, z); + count++; + if (count<=0) start=lastEventTime; + } + } +// Serial.println("measureAccelGyroODR Count "+String( count ) ); +// Serial.println("dTa= "+String(lastEventTime-start) ); +// Serial.println("ODR= "+String(1000000.0*float(count)/float(lastEventTime-start)) ); + if (fifoEna) setContinuousMode(); + return (1000000.0*float(count)/float(lastEventTime-start) ); +} + +float LSM9DS1Class::measureMagnetODR(unsigned long duration) +{ float x, y, z; //dummies + unsigned long lastEventTime, + start =micros(); + long count = -2; // waste current registervalue and running cycle + duration *=1000; //switch to micros + while ((micros()- start) < duration) // measure + { if (magnetAvailable()) + { lastEventTime = micros(); + readMagnet(x, y, z); + count++; + if (count<=0) start=lastEventTime; + } + } +// Serial.println("MeasureMagnetODR Count "+String( count ) ); +// Serial.println("dTa= "+String(lastEventTime-start) ); +// Serial.println("ODR= "+String(1000000.0*float(count)/float(lastEventTime-start) )) ; + return (1000000.0*float(count)/float(lastEventTime-start) ); } int LSM9DS1Class::readRegister(uint8_t slaveAddress, uint8_t address) diff --git a/src/LSM9DS1.h b/src/LSM9DS1.h index 242d790..51e526d 100644 --- a/src/LSM9DS1.h +++ b/src/LSM9DS1.h @@ -1,5 +1,12 @@ /* + This file is part of the Arduino_LSM9DS1 library. + New version written by Femme Verbeek, Pijnacker, the Netherlands + Released to the public domain + version 2 + Release Date 5 june 2020 + + Original notice: Copyright (c) 2019 Arduino SA. All rights reserved. This library is free software; you can redistribute it and/or @@ -15,10 +22,48 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef LSM9DS1_V2 + #define LSM9DS1_V2 +#endif + +#define accelerationSampleRate getAccelODR +#define gyroscopeSampleRate getGyroODR +#define magneticFieldSampleRate getMagnetODR + +#define setAccelerationSampleRate setAccelODR //development +#define setGyroscopeSampleRate setGyroODR //development +#define setMagnetisFieldSampleRate setMagnetODR // development + +#define accelerationFullScale getAccelFS //development +#define gyroscopeFullScale getGyroFS //development +#define magneticFieldFullScale getMagnetFS //development + +#define setAccelerationFullScale setAccelFS //development +#define setGyroscopeFullScale setGyroFS //development +#define setMagneticFieldFullScale setMagnetFS //development + +#define readAcceleration readAccel +#define readGyroscope readGyro +#define readMagneticField readMagnet + +#define accelerationAvailable accelAvailable +#define gyroscopeAvailable gyroAvailable +#define magneticFieldAvailable magnetAvailable + #include #include +#define GAUSS 0.01 +#define MICROTESLA 1.0 // default +#define NANOTESLA 1000.0 +#define GRAVITY 1.0 // default +#define METERPERSECOND2 9.81 +#define DEGREEPERSECOND 1.0 //default +#define RADIANSPERSECOND 3.141592654/180 +#define REVSPERMINUTE 60.0/360.0 +#define REVSPERSECOND 1.0/360.0 class LSM9DS1Class { public: @@ -32,24 +77,62 @@ class LSM9DS1Class { // Defaults to one-shot. void setContinuousMode(); void setOneShotMode(); - + int getOperationalMode(); //0=off , 1= Accel only , 2= Gyro +Accel // Accelerometer - virtual int readAcceleration(float& x, float& y, float& z); // Results are in G (earth gravity). - virtual int accelerationAvailable(); // Number of samples in the FIFO. - virtual float accelerationSampleRate(); // Sampling rate of the sensor. + float accelOffset[3] = {0,0,0}; // zero point offset correction factor for calibration + float accelSlope[3] = {1,1,1}; // slope correction factor for calibration + float accelUnit = GRAVITY; // GRAVITY OR METERPERSECOND2 + virtual int readAccel(float& x, float& y, float& z); // Return calibrated data in unit of choise G or m/s2. + virtual int readRawAccel(float& x, float& y, float& z); // Return uncalibrated results + virtual int accelAvailable(); // Number of samples in the FIFO. + virtual void setAccelOffset(float x, float y, float z); //Store zero-point measurements as offset + virtual void setAccelSlope(float x, float y, float z); //Store measurements as slope + virtual int setAccelODR(uint8_t range); // Sample Rate 0:off, 1:10Hz, 2:50Hz, 3:119Hz, 4:238Hz, 5:476Hz, 6:952Hz Automatic BW setting + virtual float getAccelODR(); // Measured Sample Rate of the sensor. + virtual float setAccelBW(uint8_t range); //0,1,2,3 Override autoBandwidth setting see doc.table 67 + virtual float getAccelBW(); //Bandwidth setting 0,1,2,3 see documentation table 67 + virtual int setAccelFS(uint8_t range); // 0: ±2g ; 1: ±24g ; 2: ±4g ; 3: ±8g + virtual float getAccelFS(); // Full Scale setting (output = 2.0, 24.0 , 4.0 , 8.0) // Gyroscope - virtual int readGyroscope(float& x, float& y, float& z); // Results are in degrees/second. - virtual int gyroscopeAvailable(); // Number of samples in the FIFO. - virtual float gyroscopeSampleRate(); // Sampling rate of the sensor. + float gyroOffset[3] = {0,0,0}; // zero point offset correction factor for calibration + float gyroSlope[3] = {1,1,1}; // slope correction factor for calibration + float gyroUnit = DEGREEPERSECOND; // DEGREEPERSECOND RADIANSPERSECOND REVSPERMINUTE REVSPERSECOND + virtual int readGyro(float& x, float& y, float& z); // Return calibrated data in in unit of choise °/s or rad/s. + virtual int readRawGyro(float& x, float& y, float& z); // Return uncalibrated results + virtual int gyroAvailable(); // Number of samples in the FIFO. + virtual void setGyroOffset(float x, float y, float z); //Store zero-point measurements as offset + virtual void setGyroSlope(float x, float y, float z); //Store measurements as slope + virtual int setGyroODR(uint8_t range); //Sample Rate Hz 0:off,1:10,2:50 3:119,4:238,5:476,6:does not work 952Hz + virtual float getGyroODR(); // Measured Sample rate of the sensor. + virtual int setGyroBW(uint8_t range); //Bandwidth setting 0,1,2,3 see documentation table 46 and 47 + virtual float getGyroBW(); //Bandwidth setting 0,1,2,3 see documentation table 46 and 47 + virtual int setGyroFS(uint8_t range); // (0= ±245 dps; 1= ±500 dps; 2= ±1000 dps; 3= ±2000 dps) + virtual float getGyroFS(); // (output = 245.0, 500.0 , 1000.0, 2000.0) // Magnetometer - virtual int readMagneticField(float& x, float& y, float& z); // Results are in uT (micro Tesla). - virtual int magneticFieldAvailable(); // Number of samples in the FIFO. - virtual float magneticFieldSampleRate(); // Sampling rate of the sensor. + float magnetOffset[3] = {0,0,0}; // zero point offset correction factor for calibration + float magnetSlope[3] = {1,1,1}; // slope correction factor for calibration + float magnetUnit = MICROTESLA; // GAUSS, MICROTESLA NANOTESLA + virtual int readMagnet(float& x, float& y, float& z); // Return calibrated data in unit of choise µT , nT or G + virtual int readRawMagnet(float& x, float& y, float& z); // Return uncalibrated results + virtual int magnetAvailable(); // Number of samples in the FIFO. + virtual void setMagnetOffset(float x, float y, float z); //Store zero-point measurements as offset + virtual void setMagnetSlope(float x, float y, float z); //Store measurements as slope + virtual int setMagnetODR(uint8_t range); // Sampling rate (0..8)->{0.625,1.25,2.5,5.0,10,20,40,80,400}Hz + virtual float getMagnetODR(); // Sampling rate of the sensor in Hz. + virtual int setMagnetFS(uint8_t range); // 0=±400.0; 1=±800.0; 2=±1200.0 , 3=±1600.0 (µT) + virtual float getMagnetFS(); // get chip's full scale setting private: + unsigned long ODRCalibrationTime=250000; //µs + float accelODR; // Stores the actual value of Output Data Rate + float gyroODR; // Stores the actual value of Output Data Rate + float magnetODR; // Stores the actual value of Output Data Rate bool continuousMode; + void measureODRcombined(); + float measureAccelGyroODR(); + float measureMagnetODR(unsigned long duration); int readRegister(uint8_t slaveAddress, uint8_t address); int readRegisters(uint8_t slaveAddress, uint8_t address, uint8_t* data, size_t length); int writeRegister(uint8_t slaveAddress, uint8_t address, uint8_t value);