Math problem , rollover IF condition , HOW ?

Hi

i have a logic problem to solve , and my brain can't get around it !

i have a GPS that send serial data , specificaly heading data in degree between 0 and 359

i would like to make an IF condition that trigger only if the value has changed by at least 15 degree , up OR down

only problem is when value is = 359 , it could go straigth to = 1 (only 2 degree movement)

it would trigger my broken if condition , but it is not wanted at that moment

something like that

int previous_value = 359;
int actual_value = 1;

if (actual_value >= previous_value + 15 || actual_value <= previous_value - 15) {
Serial.println("TRIGGER");
previous_value = actual_value;
}

can some better brain than mine help me please

You want the distance between two numbers on a 360 degree circle.

You want min(|A−B|,360−|A−B|).

That's whichever is less, the absolute value of the difference, or 360 minus the absolute value of the difference.

HTH

a7
.

To determine the shortest path between A and B, something like this works:

if ((last_value - actual_value + 360)%360 > 15) do_something();

Haha, I saw that…

a7

do you also need to handle the negative case: 5 - 350

yeah both ways

awk '
function delta (a, b) {
        d = (b - a)
        if (180 < d)
            return d - 360
        if (-180 > d)
            return d + 360
        return d
}

BEGIN {
    for (n = 335; n <= 400; n += 10)  {
        deg  = n % 360
        degL = (deg - 15) % 360
        if (0 > degL)
            degL += 360
        degR = (deg + 15) % 360

        printf " %6d", delta(deg, degL)
        printf " %6d", deg - degL
        printf " %6d", degL
        printf " %6d", deg
        printf " %6d", degR
        printf " %6d", deg - degR
        printf " %6d", delta(deg, degR)

        printf "\n"
    }
}'
                 left    hdg  right
    -15     15    320    335    350    -15     15
    -15     15    330    345      0    345     15
    -15     15    340    355     10    345     15
    -15   -345    350      5     20    -15     15
    -15     15      0     15     30    -15     15
    -15     15     10     25     40    -15     15
    -15     15     20     35     50    -15     15

this code does not seem to work , with
last = 359 and actual = 1 , it trigger when it must not

You're right, I'll have to dig up my notes. I've forgotten exactly how that is formulated.

void setup() {
  Serial.begin(115200);
  Serial.println("\nModulo\n");

  int a, b, c, d, e;

  a = 1;
  b = 355;

  c = abs(a - b);
  d = 360 - c;
  e = min(c, d);

  Serial.println(e);   // prints 6

  a = 270;
  b = 280;

  c = abs(a - b);
  d = 360 - c;
  e = min(c, d);

  Serial.println(e);   // prints 10

  a = 10;
  b = 20;

  c = abs(a - b);
  d = 360 - c;
  e = min(c, d);

  Serial.println(e);   // prints 6
}

void loop() {
}

HTH

a7

2 Likes

was trying your function before edit and did not get the expected result , will try later i have to go to work now

simple , and yet seems to work for every case
thank you
you "unlocked" my brain , now i understand the math

1 Like

A different approach to constrain angular deviation to -180, 180.

If you want the deviation to be always positive, use the "abs" version below.

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(1);
  int a = 1, b = 359;
  Serial.println(constrain180(a - b));
  Serial.println(constrain180(b - a));
  Serial.println(abs(constrain180(a - b)));
  Serial.println(abs(constrain180(b - a)));
}
// constrain angle to lie between -180 and 180
int constrain180(int angle) {
  angle = (angle + 180) % 360;
  if (angle < 0) angle += 360;
  return angle - 180;
}

void loop() {}
1 Like

This is makes it easier

The distance between any two numbers is the absolute value of their difference.

In the case of headings, you might be going from 10 to 350, which is 340 degrees, but it is closer to cross over 0, that's just the rest of the circle or 360 - 340 is 20 degrees.

a7

Similar problem solved here
Based on that, this function will do what you want:

bool differBy15DegOrMore(int degreesA, int degreesB) {
  int difference = (degreesA - degreesB);

  // Force difference to be in range -180 to +180
  if (difference < -180) difference += 360;
  else if (difference > 180) difference -= 360;

  // Return true if difference <=-15 or >=+15
  return (difference <= -15 ? true : difference >= +15 ? true : false);
}

This is a sketch with test code for that function

void setup() {
  Serial.begin(115200);
  doTest(0, 14);
  doTest(0, 15);
  doTest(0, 346);
  doTest(0, 345);
  doTest(180, 194);
  doTest(180, 195);
  doTest(180, 166);
  doTest(180, 165);
}

void loop() {
}

bool differBy15DegOrMore(int degreesA, int degreesB) {
  int difference = (degreesA - degreesB);

  // Force difference to be in range -180 to +180
  if (difference < -180) difference += 360;
  else if (difference > 180) difference -= 360;

  // Return true if difference <=-15 or >=+15
  return (difference <= -15 ? true : difference >= +15 ? true : false);
}

void doTest(int degreesA, int degreesB) {
  Serial.print("A=");
  Serial.print(degreesA);
  Serial.print(" : B=");
  Serial.print(degreesB);
  Serial.print(" : differBy15DegOrMore() return=");
  Serial.println(differBy15DegOrMore(degreesA, degreesB) ? "true" : "false");
}

Serial monitor output:

A=0 : B=14 : differBy15DegOrMore() return=false
A=0 : B=15 : differBy15DegOrMore() return=true
A=0 : B=346 : differBy15DegOrMore() return=false
A=0 : B=345 : differBy15DegOrMore() return=true
A=180 : B=194 : differBy15DegOrMore() return=false
A=180 : B=195 : differBy15DegOrMore() return=true
A=180 : B=166 : differBy15DegOrMore() return=false
A=180 : B=165 : differBy15DegOrMore() return=true

Added a few more tests to show what happens when left input is close to 360:

  doTest(346, 0);
  doTest(346, 1);
  doTest(359, 0);
  doTest(359, 14);

Serial monitor output:

A=346 : B=0 : differBy15DegOrMore() return=false
A=346 : B=1 : differBy15DegOrMore() return=true
A=359 : B=0 : differBy15DegOrMore() return=false
A=359 : B=14 : differBy15DegOrMore() return=true

Well, for a similar problem I had for an anemometer returning the wind direction (not for Arduino, it was a C# project I worked on), I needed to know the degrees difference between two readings, to be able to detect wind direction changes. I just did a simple function like this (converted to Arduino-style):

int DiffDeg(int iFrom, int iTo) {
  int Delta = (iTo - iFrom);
  if (Delta > 180) Delta -= 360;
  if (Delta < -180) Delta += 360;
  return Delta;
}

This gives the shortest path degrees difference, assuming the wind hasn't dramatically changed (e.g. less than 180 degrees rotation between susequent measurements).

So to show an Arduino example, these are the results with directions crossing the 360 boundary, comments are the function output:

  Serial.println(DiffDeg(10, 20)); // 10
  Serial.println(DiffDeg(20, 5));  // -15
  Serial.println(DiffDeg(5, 355)); // -10
  Serial.println(DiffDeg(355, 2)); // 7

For example, to be able to detect changes from 5 degrees or more, you could just get the absolute value:

  bool jump = (abs(DiffDeg(from, to)) >= 15);

HTH

Alex

1 Like

That seems to be the simplest / most obvious method to me. It took me a while the first time I had to solve this problem. I was working on SONAR systems ~30 years ago. I had to calculate the difference between two headings. It's a lesson I've never forgotten.

Naturally I am sticking with the idea in post #2 and the code in #10. :expressionless:

If you are coming from regular maths, and have gotten the idea of measuring distance by absolute value of a subtraction, switching out the result that is greater than 180 for the one that is less is the simple step that gives you the low number of degrees of separation.

All the code that works and gives numbers between 0 and 180 as the result are performing the same calculations.

All the code that does not work, or does something different or additional shows how things like this can go.

Hey! That same code could solve the millis() rollover problem!

a7

What problem :stuck_out_tongue:

1 Like