Digital Compass with Nano BLE 33 sense

Dear all,

I would like to make a compass using the IMU on the Nano BLE 33 sense, but I do not know how to convert the raw values into a 0-360° heading value. In the past I used a different sensor which had a heading() function included in the library.
Is there anybody who tried to build something similar and wants to share some suggestions?

Thank you in advance,

Ciao,

Lorenzo

To convert the microTesla readings into a 0-360 degree compass heading, you can use the atan2() function to compute the angle of the vector defined by the Y and X axis readings. The result will be in radians, so you multiply by 180 degrees and divide by Pi to convert that to degrees.

float heading = (atan2(event.magnetic.y,event.magnetic.x) * 180) / Pi;

from here learn.adafruit.com

Thank you a lot for your reply,

I tried it, but unfortunately I get only values from 25 to 75...

Dear all,

it still does not work and I really do not understand why.
Here is the code:

#include <Arduino_LSM9DS1.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);
  Serial.println("Started");

  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (1);
  }

}

void loop() {
  float x, y, z;

  if (IMU.magneticFieldAvailable()) {
    IMU.readMagneticField(x, y, z);
    
    
    float h = (atan2(y,x) * 180) / PI;
    
    Serial.print(x);
    Serial.print('\t');
    Serial.print(y);
    Serial.print('\t');
    Serial.print(z);
    Serial.print('\t');
    Serial.println(h);
    
  }
  delay(500);
}

And here the value I get when I point it to:
NORTH: 0
EAST: 100
SOUTH: 69
WEST: 28

Any idea...?

I am fairly certain its a noise issue. If you take a good magnet and placed it at a few cm/inch away from the board and then you should see the compass "algorithm" working as expected. You need to experiment a bit with the distance and the horizontal position. My magnet saturated the values when I placed it too close.

There are different sources of the noise. One could be magnetic. I do not know what the recommended layout for the sensor is. Its not as much an issue with the magnet but the earth field is weaker.

The second one could be the voltage. The MPM3610 Step-Down Converter used on the new Arduino Nano 33 creates some serious noise on the Vdd.

I had the same problem. While turning the board in small steps I measured the X and Y magnetic values and plotted them against each other. This resulted in a circle with the centre not at 0,0 but way off. (red plot line). The offset was even larger than the measured earth magnetic field. All directions point east this way. The blue line is what it should look like. It was constructed by subtracting an offset of (31.95 , -10.34) from the magnetic reading.
Unfortunately this offset depends on the setup environment as well as on the board.

The method to get good results is to measure the maximum and minimum magnetic values by turning the board slowly over a full circle. Use the average of the maximum and minimum values as offset to be subtracted from the corresponding magnetic reading and feed that into the atan2() function.

The LSM9DS1 chip has offset registers onboard. Unfortunately the Arduino_LSM9DS1 library offers no way of setting a value here.
Does anybody know how?

femmeverbeek:
The LSM9DS1 chip has offset registers onboard. Unfortunately the Arduino_LSM9DS1 library offers no way of setting a value here.
Does anybody know how?

You need to modify the library. For a start add the following modifications into the LSM9DS1.cpp file.

// Add these right after defines at the beginning
#define LSM9DS1_OFFSET_X_REG_L_M   0x05
#define LSM9DS1_OFFSET_X_REG_H_M   0x06
#define LSM9DS1_OFFSET_Y_REG_L_M   0x07
#define LSM9DS1_OFFSET_Y_REG_H_M   0x08
#define LSM9DS1_OFFSET_Z_REG_L_M   0x09
#define LSM9DS1_OFFSET_Z_REG_H_M   0x0A

// Add these inside the int LSM9DS1Class::begin() just before line 72 with the comment 119 Hz, 2000 dps, 16 Hz BW
// Change the 0x00 into some value other than 0x00 
// 0x7F into H and 0xFF into L is max positiv
// every value for H that is larger than 0x7F gives a negative value
// They are the high and low part of a twos complement 16bit

writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_L_M, 0x00);
writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_H_M, 0x00);
writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Y_REG_L_M, 0x00);
writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Y_REG_H_M, 0x00);
writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Z_REG_L_M, 0x00);
writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Z_REG_H_M, 0x00);

If this works we can add some functions to the library that will allow the value to be set more comfortably.

I wanted to try something like this, but I seem to have trouble modifying libraries. I don't understand the IDE philosophy. OK I'm a newby :o :o :o :o . From the IDE I can't edit the library. I don't like to edit original code anyway. One should always work with a copy.

I made a copy in my sketch directory, now I can edit, but recompiling does not work as it still finds the original one making everything double declared.

What is the best way of working?

My approach to tweaking libraries is to copy the files (not the directories) I am tweaking and saving backup versions under a different name or file type. For example, you could name the file "LSM9DS1_backup.cpp" or even "LSM9DS1.bak" or "LSM9DS1.cpp.bak" which just changes the file type but none of the data. That way you can keep good copies but it makes it easier for the compiler to find what it needs.

Also, you might want to get something like Notepad++ as it makes tweaking libraries pretty easy and it has the ability to recognise different code languages. It won't compile though, but if you compile your sketch it will tell you if there are errors in the libraries. You might also want to enable verbose compiling under file -> preference -> show verbose output during: .

Finally, you can always reinstall the libraries if it all goes wrong! Also, make sure you keep track of changes as if you update the library it will probably wipe it all.

femmeverbeek:
What is the best way of working?

I just modify the library. If you delete the folder for the specific library you can simply reinstall it from the library manager. You need to be careful. If you update the library your changes are gone.

You can also create a second Project/Sketchbook folder and change the setting in File -> Preferences -> Sketchbook Location.

Then you need to reinstall all libraries you need for your test project. When you are done modifying your library and want to work with your other projects, you switch the Sketchbook location back. If you want to continue working on the modified library you just switch the Sketchbook location again.

For now I just zipped the original files in their folder.
Notepad++ works nicely.

On with the testing.
The modifications Klaus K suggested definitely have an effect on the magnetic output.

The registers are supposed to be non-volatile, so I wanted to make something you can run once to calibrate the magnometer.

Probably another newby question:

I'm trying to make functions for reading and setting, so in the LSM9DS1.h I added

virtual float readMagneticOffsetX(); // read offset register

In the .cpp

float LSM9DS1Class::readMagneticOffsetX() // read offset register
{ return 0.0;
}

The problem is that the function never returns any value.
For some reason the value is always 19482689560.00
When I repurpose the magneticFieldSampleRate() function it works nicely.

Further I noticed that in the sketch my new functions don't show the orange colored lettering as the other functions do.

What am I doing wrong?

femmeverbeek:
I'm trying to make functions for reading and setting, so in the LSM9DS1.h I added
...
The problem is that the function never returns any value.
For some reason the value is always 19482689560.00

Can you please post your code? I have added your functions into my library and it "works" with the fixed value. I have not added any register reading yet.

femmeverbeek:
Further I noticed that in the sketch my new functions don't show the orange colored lettering as the other functions do.

What am I doing wrong?

To get the functions to show up in orange you need to add the name to the keyword.txt file inside the library folder. Make sure to use a TAB between the function name and the "KEYWORD2" and not a SPACE. It does not seem to work with SPACE. You will need to restart the IDE to make sure the changes are read by the IDE.

femmeverbeek:
Notepad++ works nicely.

I have been using Notepad++ for years. Its a really good lightweight editor.

I don't know if it is related or not. Compiling and uploading takes an incredibly long time for the Nano 33 BLE sense, much longer than for an UNO. Very often it fails at all due to the Comport changing it's address.

Can you please post your code? I have added your functions into my library and it "works" with the fixed value. I have not added any register reading yet.

the sketch

#include <Arduino_LSM9DS1.h>

int IMUsucces=2;
void setup() {
     Serial.begin(115200);
     while(!Serial);
     Serial.println("Start of program");
     IMUsucces= IMU.begin();
     if (IMUsucces==0) Serial.println("Failed to initialize IMU!");
     else Serial.println(" IMU available and working");
}

void loop() {
    Serial.print(' X offset = ');
    Serial.println(IMU.readMagneticOffsetX());  
    Serial.println(IMU.magneticFieldSampleRate()); //currently contains the readMagneticOffsetX() code
    IMU.setMagneticOffsetX(0);   
    
  while(1);
}

the .h

    virtual float magneticFieldSampleRate(); // Sampling rate of the sensor.
    virtual float readMagneticOffsetX();  // read offset register
    virtual void setMagneticOffsetX(float offset); // set offset register

the .cpp

float LSM9DS1Class::magneticFieldSampleRate()
{
  //return 20.0;
  int16_t data = (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_H_M) << 8) 
	             + readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_L_M) ;
  //  float answer = data * 4.0 * 100.0 / 32768.0; 
    answer = data;
	return answer;	
 }

float LSM9DS1Class::readMagneticOffsetX() // read offset register
{
	return 0.0;	   
}

void LSM9DS1Class::setMagneticOffsetX(float offset) // write offset to register
{
//to be done

}

result with init value 0x00

Start of program
 IMU available and working
19482698560.00
0.00

An init value of
writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_H_M, 0xEF);
produces
-4352.00

I did not check the correctness of the value, but at least it is doing something.

Woaaa, found it

I blamed the function but its in the sketch
I wrote
Serial.print(' X offset = ');
instead of
Serial.print(" X offset = ");

The code below now works nicely for reading and setting the magnetic offset registers.
The scale is the same as the magnetic output. Note that the set value gets subtracted from the outputvalue. I've removed the init settings from the ::begin()

Does anybody know how to store these values in nonvolatile memory without the programmer.
It turns out that restarting the board resets the offsetvalues to zero.

Another challenge is to write a procedure for measuring the required offset in situ. So what I imagine is turning the board over a full circle while measuring the magnetic output values. Best is to fit a circle through the data and use it's centre coordinates as offset. This is not as straight forward as a simple linear regression and there are several methods. I did not find any Arduino code, but there is some C++ code available.

Is this likely to work straight forward?

Code
in the .h

    virtual float readMagneticOffsetX();  // read offset register
    virtual void setMagneticOffsetX(float offset); // set offset register
    virtual float readMagneticOffsetY();  // read offset register
    virtual void setMagneticOffsetY(float offset); // set offset register
    virtual float readMagneticOffsetZ();  // read offset register
    virtual void setMagneticOffsetZ(float offset); // set offset register

in the .cpp

void LSM9DS1Class::setMagneticOffsetX(float offset) // write offset to register
{  offset= offset *32768/400 ;
   int16_t data = round(offset);
   writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_L_M, lowByte(data));
   writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_X_REG_H_M, highByte(data));   
}

float LSM9DS1Class::readMagneticOffsetY() // read offset register
{   int16_t data = (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Y_REG_H_M) << 8) 
	             + readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Y_REG_L_M) ;
   return  (400.0 / 32768.0) * data;
}

void LSM9DS1Class::setMagneticOffsetY(float offset) // write offset to register
{  offset= offset *32768/400 ;
   int16_t data = round(offset);
   writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Y_REG_L_M, lowByte(data));
   writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Y_REG_H_M, highByte(data));   
}

float LSM9DS1Class::readMagneticOffsetZ() // read offset register
{   int16_t data = (readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Z_REG_H_M) << 8) 
	             + readRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Z_REG_L_M) ;
   return  (400.0 / 32768.0) * data;
}

void LSM9DS1Class::setMagneticOffsetZ(float offset) // write offset to register
{  offset= offset *32768/400 ;
   int16_t data = round(offset);
   writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Z_REG_L_M, lowByte(data));
   writeRegister(LSM9DS1_ADDRESS_M, LSM9DS1_OFFSET_Z_REG_H_M, highByte(data));   
}

femmeverbeek:
Does anybody know how to store these values in nonvolatile memory without the programmer.
It turns out that restarting the board resets the offsetvalues to zero.

mbedOS has some non volatile storage. There was a question in a post just recently. Maybe the answers help.

https://forum.arduino.cc/index.php?topic=676306.0

femmeverbeek:
Another challenge is to write a procedure for measuring the required offset in situ. So what I imagine is turning the board over a full circle while measuring the magnetic output values. Best is to fit a circle through the data and use it's centre coordinates as offset. This is not as straight forward as a simple linear regression and there are several methods. I did not find any Arduino code, but there is some C++ code available.
C++ codes for fitting ellipses, circles, lines
Is this likely to work straight forward?

No, I looked at the code and it would need some major modification and some understanding of what is going on. I suspect this code will require more memory than the Arduino has and more processing cycles. It is full of functions with reals which are double aka 64-bit floating point numbers. It has been written for Linux, so the writer did not have to care about memory and processor cycles too much.

It might be possible to extract something from the code that can run on the Arduino. I suspect you would need to understand a bit about the algorithms, so you can simplify data types and remove some of the code.

o, I looked at the code and it would need some major modification and some understanding of what is going on. I suspect this code will require more memory than the Arduino has and more processing cycles. It is full of functions with reals which are double aka 64-bit floating point numbers. It has been written for Linux, so the writer did not have to care about memory and processor cycles too much.

It might be possible to extract something from the code that can run on the Arduino. I suspect you would need to understand a bit about the algorithms, so you can simplify data types and remove some of the code.

I guess it takes some studying. Most articles were rather complex mathematically.
The definition of reals looks very similar to a trick I invented in prehistory. The standard 6 byte software real could be re-typedef'd for a 4 byte single or a 8 byte double to be handeled handeled by the coprocessr

mbedOS has some non volatile storage. There was a question in a post just recently. Maybe the answers help.

Can't say I understand the lot of it. Isn't it tricky to write in the OS location. Couldn't it just accidently damage the bootloader?

What about the SPI library. Is it suitable for the 33BLE. The latest added processor is nRF52832 not the nRF52840.

Back on topic

I tried measuring compass directions, with measured averages as offset.

The good news is that in spite of my flimsy method, the compass directions are fairly accurate. Even more accurate than the individual magnetic components appear to be.

The bad news is that I had to recalibrate the offset, which is currently time consuming as I write down the values in Excel. I guess making the board do the measurements and setting the offset itself, is a lot faster.
I don't know what caused the magnetic bias to change so dramatically. Perhaps I re-inserted the Arduino differently in the bread board. Perhaps the long term stability is not very good.