More efficient way to average compass readings

How can I simplify the following code to calculate the average of my compass reads? Here's the chunk of code in question:

compass.read();

//***************** Calculate the Average read from the Compass ********************** int CompassHeading = compass.heading();

C1 = CompassHeading; delay(1); C2 = CompassHeading; delay(1); C3 = CompassHeading; delay(1); C4 = CompassHeading; delay(1); C5 = CompassHeading; delay(1); C6 = CompassHeading; delay(1); C7 = CompassHeading; delay(1); C8 = CompassHeading; delay(1); C9 = CompassHeading; delay(1); C10 = CompassHeading; delay(1); C11 = CompassHeading; delay(1); C12 = CompassHeading; delay(1); C13 = CompassHeading; delay(1); C14 = CompassHeading; delay(1); C15 = CompassHeading; delay(1); C16 = CompassHeading; delay(1); C17 = CompassHeading; delay(1); C18 = CompassHeading; delay(1); C19 = CompassHeading; delay(1); C20 = CompassHeading; delay(1); C21 = CompassHeading; delay(1); C22 = CompassHeading; delay(1); C23 = CompassHeading; delay(1); C24 = CompassHeading; delay(1); C25 = CompassHeading; delay(1); C26 = CompassHeading; delay(1); C27 = CompassHeading; delay(1); C28 = CompassHeading; delay(1); C29 = CompassHeading; delay(1); C30 = CompassHeading; delay(1); C31 = CompassHeading; delay(1); C32 = CompassHeading; delay(1); C33 = CompassHeading; delay(1); C34 = CompassHeading; delay(1); C35 = CompassHeading; delay(1); C36 = CompassHeading; delay(1); C37 = CompassHeading; delay(1); C38 = CompassHeading; delay(1); C39 = CompassHeading; delay(1); C40 = CompassHeading; delay(1);

CompTotal = C1 + C2 + C3 + C4 + C5 + C6 + C7 + C8 + C9 + C10 + C11 + C12 + C13 + C14 + C15 + C16 + C17 + C18 + C19 + C20 + C21 + C22 + C23 + C24 + C25 + C26 + C27 + C28 + C29 + C30 + C31 + C32 + C33 + C34 + C35 + C36 + C37 + C38 + C39 + C40;

CompAvg = CompTotal / 40; CompassHeading = CompAvg;

Adding the same number over and over isn't what you want to do - you need to make multiple readings and sum them

for (int i = 0; i < nSamples; i++)
{
 compTotal +=  compass.heading();
}

etc.

Yep. What you posted does nothing useful, because CompassHeading doesn’t change. Nothing to average.

Always use code tags to post, so the code looks like this:

long sum = 0;
for (int i=0; i<40; i++) {
   sum=sum + compass.heading(); // presumably this is a function returning an integer
}
  int CompAvg=sum/40;

Don’t forget that there is a problem with “compass wrap”. Headings near 0 degrees might return 359 or 1, etc.

compass.read();
for (int i = 1 ; i <= 40 ; i++) {
  int average += compass.heading();
  delay(1);
  average = average / 2;
}

Circular Mean

How do you calculate the average of a set of circular data?

How to average cyclic quantities?

Circular Mean

Those are awful! trig functions, floating point, unexpected results, situations with undefined answers... Surely there's a simple integer algorithm?

For doing an average of a compass bearing, where all the values should be "approximate" the same, I think that normalizing around a "near-middle value" will probably be effective...

Although... Presumably at some point you don't have a "compass heading" so much as some measurement of magnetic flux from an A2D converter that is an integer. The "correct" thing to do is to compute your mean (using a loop, as per the jbellavance post), using those "raw" numbers, and convert to a compass heading AFTER you have the mean.

The "correct" thing to do is to compute your mean (using a loop, as per the jbellavance post), using those "raw" numbers, and convert to a compass heading AFTER you have the mean.

In this particular case, there is no difference between the operation of taking the circular mean, and your proposal. Averaging the X and Y components that are the arguments to the atan2() function that computes the heading, is the same operation as reducing the individual headings back to their X and Y components, averaging, and taking atan2() -- the circular mean. No need to fear trigonometry, it was mastered millennia ago.

as per the jbellavance post

Do you think that code would work? I certainly don't. Can't make heads or tails of it.

Averaging the X and Y components that are the arguments to the atan2() function that computes the heading, is the same operation as reducing the individual headings back to their X and Y components, averaging, and taking atan2() -- the circular mean.

I want to do the averaging of X/Y components (if that's what you get from the sensor) BEFORE converting them to "headings" in the first place. So over the 40 readings the OP wants to average, that's some 120 fewer "costly" trig functions used used to convert the X/Y components (atan2() * 40) into Headings, and then back again into X/Y components (sin()*40, cos()*40) plus converting the A2D numbers into floats/etc...

(OTTH, if you're not doing anything in between the readings other than delay(), and the trig/float code is already included in your binary to do the calculations once, I guess it doesn't make too much difference...)

jremington:
Yep. What you posted does nothing useful, because CompassHeading doesn’t change. Nothing to average.

Always use code tags to post, so the code looks like this:

long sum = 0;

for (int i=0; i<40; i++) {
  sum=sum + compass.heading(); // presumably this is a function returning an integer
}
  int CompAvg=sum/40;




Don't forget that there is a problem with "compass wrap". Headings near 0 degrees might return 359 or 1, etc.

Had an identical problem which resulted in a class, which can also take the length into account (weight).

This class does not cope with the sum of the vectors being (0, 0). also a serious problem.
Then the average heading does not exist mathematically (singularity?).

==> made an issue to have a “clean fix” in the lib.

(off topic) reminds me of my wind direction converting experiment - https://forum.arduino.cc/index.php?topic=94507.0

In case you didn't pick up on -dev's subtle point, you CANNOT just blindly sum up and divide circular information like a heading angle. In the case, you absolutely do not want the average of 1 and 359 to be 180.

How noisy is your compass data that you need to average it so heavily? It'd be much easier to implement a deadband. Don't recognize a change in the compass value until it drifts by a certain amount.

you CANNOT just blindly sum up and divide circular information like a heading angle.

I must admit I didn't see this one coming.

Thank you all for your time and assistance. After looking at my code (i'm very new), you all bring up good concerns and solutions. Thanks once again.

When averaging angles one allways assumes that the heading moves via the shortest angle from A to B to C.

westfw:
Those are awful! trig functions, floating point, unexpected results, situations with undefined answers… Surely there’s a simple integer algorithm?

If one doesn’t want to convert from polar to rect coordinates and back, and one sees that some values lay far apart (e.g. 350 … 10 ) then one could add 360 to the small angles (e.g. all smaller than 45°) so they are close to each other. Then take the mathematical average and subtract 360 if needed to get between 0…360

Drawback you must know the numbers in advance and that is not always the case.

<think think think … aha>

To solve that you can apply the following trick.

Make 4 sums + counts, 1 per quadrant .

average them per quadrant.

now you have typically 1 or 2 (max 4) Quadrant averages and the count as weight.

These Quadrant averages can be averaged (use weight!) , optionally with the 360 trick

example
357 358 359 360 1 2 3 4 5 6

Q1: avg 3.5 weight 6
Q2: avg 0 weight 0
Q3: avg 0 weight 0
Q4: avg 358.5 weight 4

We have Q1 Q4 detected so 3.5 become 363.5

weight average
(6 x 363.5 + 4 x 358.5)/10 = (2181 + 1434)/10 = 3615 / 10 = 361.5 ==> 361.5 - 360 = 1.5

same example but now 360 == 0

357 358 359 0 1 2 3 4 5 6
Q1: avg 3 weight 7
Q4: avg 358 weight 3

3 → 363

(3x358 + 7x363)/10 = 3615/10 = 361.5 ==> 361.5 - 360 = 1.5

@westfw
So yes, it can be done without trigonometry, just simple ±*/
with some carefully chosen intermediate variables.

Make 4 sums + counts, 1 per quadrant . average them per quadrant.

Sounds pretty good. Also sounds a lot like the "normalize" method I mentioned; perhaps the quadrants are easier to visualize.

hj5150: which compass library are you using? My general interpretation is that this sort of averaging is something that should happen in the library, or maybe even in the hardware.

westfw: Sounds pretty good. Also sounds a lot like the "normalize" method I mentioned; perhaps the quadrants are easier to visualize.

hj5150: which compass library are you using? My general interpretation is that this sort of averaging is something that should happen in the library, or maybe even in the hardware.

I'm using the LSM303D.

robtillaart:
When averaging angles one allways assumes that the heading moves via the shortest angle from A to B to C.

If one doesn’t want to convert from polar to rect coordinates and back, and one sees that some values lay far apart (e.g. 350 … 10 ) then one could add 360 to the small angles (e.g. all smaller than 45°) so they are close to each other. Then take the mathematical average and subtract 360 if needed to get between 0…360

Drawback you must know the numbers in advance and that is not always the case.

<think think think … aha>

To solve that you can apply the following trick.

Make 4 sums + counts, 1 per quadrant .

average them per quadrant.

now you have typically 1 or 2 (max 4) Quadrant averages and the count as weight.

These Quadrant averages can be averaged (use weight!) , optionally with the 360 trick

example
357 358 359 360 1 2 3 4 5 6

Q1: avg 3.5 weight 6
Q2: avg 0 weight 0
Q3: avg 0 weight 0
Q4: avg 358.5 weight 4

We have Q1 Q4 detected so 3.5 become 363.5

weight average
(6 x 363.5 + 4 x 358.5)/10 = (2181 + 1434)/10 = 3615 / 10 = 361.5 ==> 361.5 - 360 = 1.5

same example but now 360 == 0

357 358 359 0 1 2 3 4 5 6
Q1: avg 3 weight 7
Q4: avg 358 weight 3

3 → 363

(3x358 + 7x363)/10 = 3615/10 = 361.5 ==> 361.5 - 360 = 1.5

@westfw
So yes, it can be done without trigonometry, just simple ±*/
with some carefully chosen intermediate variables.

Hi Rob,

This is exactly what I am needing for a wind vane, and I am just trying to figure out how to implement it. I am wondering if your RunningAverage library would work for this?
4 running averages for the quadrants. But what is getting me stuck is the weight would just add up to the running average size, so not sure a running average is OK here…
Its a nice lib, with the getCount() getAverage() etc, so it could be made to work I guess, and maybe just clear it out each time so the weights start again?
Problem I have is the readings come in about every 500ms, so if you clear it out then its going to take quite a few seconds before the next average is ready, which is why I thought the running average would work nicely, but I just dont know how to implement it due to the weight issue. If the RunningAverage instances are each 10 readings in size, it will soon get to the point where all of the weight is 10, so that wont work in my eyes.

Some help would be hugely appreciated as to how to actually implement this.
Thanks so much in advance

That won't work. getCount() will only give a useful number if you have not yet filled the circular buffer. Once the buffer is full then it will be the same as the size. So keeping 4 of those objects won't allow you to calculate the proper weight for each quadrant.

I would copy the library and make my own version, adding another function called getCircularAverage(). This would use one of the techniques above to calculate the correct circular average from the data in the buffer.