Jump to content
  • 0

CMPS2 compass module with Arduino - Heading steps accuracy


Erosand

Question

Hi!

I'm using the CMPS2 compass module with an Arduino Uno and the example code from https://reference.digilentinc.com/reference/pmod/pmodcmps2/start#example_projects to get a heading from the compass module, but it seems to be only jumping between specific values and never taking the values in between. For example, running the program while keeping the module horizontal and slowly spinnng it 360° around the z-axis gives an output like this:

Heading = 231.18°    
Heading = 231.19°    
Heading = 186.09°    
Heading = 141.09°    
Heading = 186.12°    
Heading = 231.10°    
Heading = 231.09°    
Heading = 231.08°    
Heading = 231.09°    
Heading = 231.03°    
Heading = 230.96°    
Heading = 321.25°    
Heading = 321.15°    
Heading = 321.04°    
Heading = 51.20°    
Heading = 51.06°    
Heading = 51.06°    
Heading = 50.96°    
Heading = 50.91°    
Heading = 96.12°    
Heading = 96.10°    
Heading = 51.00°    
Heading = 141.17°    
Heading = 141.11°    
Heading = 141.12°    
Heading = 141.04°    
Heading = 141.04°    
Heading = 186.11°    
Heading = 141.01°    
Heading = 231.16°    
Heading = 231.16°    
Heading = 231.16°    

Basically, plotting the heading as a function of time doesn't give a continuos plot no matter how slowly the module rotates.

I have tried to check for disturbances from metal objects or electronic devices using another compass, so I'm pretty sure that's not the problem.

I don't need to get down to the 1° accuracy promised by the maker (5° is probably enough for my project), but these giant leaps from 320° to 50° won't work.

All help is appreciated!

Link to comment
Share on other sites

4 answers to this question

Recommended Posts

  • 1

Hello @Erosand,

I do not know when I will have time to fix the library (or I guess the single file) but I identified a few weird things that are likely resulting in problems.

  • The CMPS2_init() sets the global maximum variable to -32768, and the global minimum variable to 32767. This mismatch causes problems later. Global midpoint is set to 0.
  • The CMPS2_read_XYZ() inside of the CMPS2_getHeading() then checks to see if the measured X and Y data (after already converting it into mG, so they aren't even the same units) is greater than the global maximum variable and the less than the global minimum variable. Because of bullet point 1, this (mostly likely) sets the global max and min values to be exactly the same as the measurement value. The global midpoint is set to the average of these two values, so exactly the same value.
  • In the CMPS2_getHeading() a Reset function is performed (to get an opposite phase shift I guess) after bullet point 2 and then CMPS_read_XYZ is performed again. However, because the global max and min values have not been reset, the new measurement data is getting compared to the exact same data. I guess in theory if you continually repeated this process as the module was rotated around, you would get the max and min values for the X and Y axis, but this isn't done in the code. So the max or minimum may have changed slightly, but not both, and the global midpoint is the bifurcation of this potential small change. I might be misunderstanding the code though.
  • In the CMPS2_getHeading() after bullet point 3, it says offset is eliminated between the two measurements, but all it does is assign the calculated offset to a temporary variable, component[i].
  • In the CMPS2_getHeading() after bullet point 4, it then uses these calculated offsets, rather than actual values, to compare to the global midpoint, in order to determine what quadrant and thus what equation should be used to determine the corresponding degree heading.

I'll ask around to see if somebody will be able to address some of these changes.

Thanks,
JColvin

Link to comment
Share on other sites

  • 1
5 hours ago, Erosand said:
On 4/6/2021 at 2:26 PM, JColvin said:

The CMPS2_init() sets the global maximum variable to -32768, and the global minimum variable to 32767. This mismatch causes problems later. Global midpoint is set to 0.

Could this be fixed by just setting the globlal minimum value to 32768? Or does it even matter considering the lines where it's actually used look like this?

if (measured_data[0] < Min[0]) { //x min
    Min[0] = measured_data[0];
  }

Setting the global minimum to 32768 won't help because comparing measured data to see if it is less than 2^16 or (2^16 -1) will give you the same end result.

---------------

5 hours ago, Erosand said:

I'm wondering if it would be possible at all to get an accurate heading without rotating it 360 degrees first for calibration? I don't see how that would work since it won't have any values to compare with unless it gets rotated.

If your initially read values are correct, then presumably you could get an accurate header. The caveat with this is that you are completely blind to if your data is being affected by ambient magnetic fields.

--------------

5 hours ago, Erosand said:

Looking at the guide for calibration here https://reference.digilentinc.com/reference/pmod/pmodcmps2/reference-manual (and assuming it's correct) where it says

Output1 = H + Offset

 

Output2 = −H + Offset

I think the math in the code makes sense since the line

components[0] = (components[0] - X) / 2.0; // H = (Output1 - Output2) / 2.0

should give just H ( H = (Output1 - Output2) / 2.0 ), or am I missing something? (where H is the desired measured value to use in the trigonometry calculations)

I suppose that is correct. I was misreading the function before as it does not calculate the offset as I claimed; it instead calculates the earth's magnetic field, H.

components[0] = (Output1 - Output2) / 2.0 = [(H + Offset) - (-H + Offset)] / 2.0 = 2H / 2.0 = H

I still think this is an unnecessary part of the code when (presuming the ambient magnetic fields in your work environment aren't changing) you could calculate the offset once via the equation below and then just subtract that offset from every future measurement.

Offset = (Output1 + Output2) / 2.0 = [(H + Offset) + (-H + Offset)] / 2.0 = 2Offset / 2.0 = H

-------------------

5 hours ago, Erosand said:

Another thing I've noticed is the line 

measured_data[0] = 1.0 * (int)(tmp[1] << 8 | tmp[0]); //x

which supposedly reconstructs the raw data. Doesn't interpreting the 16 bit binary number as an int cause overflows if the MSB from the sensor is unsigned? Or is the raw data supposed to be signed? Is the leftmost bit of tmp[1] part of the value or just indicating the sign?

When plotting the reconstructed raw data I'm seeing these overflow jumps (?) every now and then when rotating the compass around the z-axis.

Well I'll be dipped. I was under the impression that the data coming from the Memsic MMC34160PJ was signed because the datasheet proudly proclaims +/- 16G full scale resolution for each axis, but as you pointed out and as noted in the Register details on page 6 of the datasheet the data for each axis is unsigned. So the type casting would need to be changed to unsigned int (or uint16_t) to store the data. The maximum and minimum global values will need to be changed to reflect this.

But this confuses me on how one is supposed to determine if their Y axis and X axis data are positive or negative, so you can use the correct formula for the corresponding quadrant and orientation... and looking at the revision history for the Pmod CMPS2 reference manual, it's apparent that I'm the one that wrote it so I'm out of luck there for contacting the author to pick their brain about it.

Digilent's other module that includes a magnetometer (the Pmod NAV) does provide signed data for the magnetometer so you can correctly calculate heading, so I'm at a loss why Memsic's seemingly does not provide signed data, especially if it supposedly measures +/- 16 G...

I'll have to think about this more and figure out if the IC present on the Pmod CMPS2 is functional or if the Memsic datasheet is wrong.

Thanks,
JColvin

 

Link to comment
Share on other sites

  • 0

Thanks @JColvinfor the quick reply! I've been trying to modify the code in various ways for while now but still don't quite get it. Here's what I'm thinking/wondering so far:

On 4/6/2021 at 11:26 PM, JColvin said:

The CMPS2_init() sets the global maximum variable to -32768, and the global minimum variable to 32767. This mismatch causes problems later. Global midpoint is set to 0.

Could this be fixed by just setting the globlal minimum value to 32768? Or does it even matter considering the lines where it's actually used look like this?

if (measured_data[0] < Min[0]) { //x min
    Min[0] = measured_data[0];
  }

 

On 4/6/2021 at 11:26 PM, JColvin said:

In the CMPS2_getHeading() a Reset function is performed (to get an opposite phase shift I guess) after bullet point 2 and then CMPS_read_XYZ is performed again. However, because the global max and min values have not been reset, the new measurement data is getting compared to the exact same data. I guess in theory if you continually repeated this process as the module was rotated around, you would get the max and min values for the X and Y axis, but this isn't done in the code. So the max or minimum may have changed slightly, but not both, and the global midpoint is the bifurcation of this potential small change. I might be misunderstanding the code though.

I'm wondering if it would be possible at all to get an accurate heading without rotating it 360 degrees first for calibration? I don't see how that would work since it won't have any values to compare with unless it gets rotated.

 

On 4/6/2021 at 11:26 PM, JColvin said:

In the CMPS2_getHeading() after bullet point 3, it says offset is eliminated between the two measurements, but all it does is assign the calculated offset to a temporary variable, component[i].

Looking at the guide for calibration here https://reference.digilentinc.com/reference/pmod/pmodcmps2/reference-manual (and assuming it's correct) where it says

Output1 = H + Offset

 

Output2 = −H + Offset

I think the math in the code makes sense since the line

components[0] = (components[0] - X) / 2.0; // H = (Output1 - Output2) / 2.0

should give just H ( H = (Output1 - Output2) / 2.0 ), or am I missing something? (where H is the desired measured value to use in the trigonometry calculations)

 

 

Another thing I've noticed is the line 

measured_data[0] = 1.0 * (int)(tmp[1] << 8 | tmp[0]); //x

which supposedly reconstructs the raw data. Doesn't interpreting the 16 bit binary number as an int cause overflows if the MSB from the sensor is unsigned? Or is the raw data supposed to be signed? Is the leftmost bit of tmp[1] part of the value or just indicating the sign?

When plotting the reconstructed raw data I'm seeing these overflow jumps (?) every now and then when rotating the compass around the z-axis.

 

As before, all help is greatly appreciated!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...