How to make compass degrees "circular"?

Hello!

I'm working on an magnetometer & servo sailboat auto-pilot.

The basics are very simple: the arduino Uno subtracts the desired course (in degrees) from the boat's heading (also in degrees), and the resulting number used for a servo that steers the boat. Thus the angle of the rudder will be directly impacted by the angle of the course error. Here's the catch: If the set course is 0 degrees and the boat's heading is 359 (just one degree error), the boat will steer hard to the opposite direction and do a full circle in order to get back to 0.

I'm quite new to coding, and although I generally get along well with Google and reading forums, I just can't think of a solution. My autopilot works flawlessly except if I get too near to 360 or 0 degrees (North). Any tips?

Here is the critical part of the code:

int Course = map(analogRead(potCourse), 0, 1023, 0, 360);  //Convert bit to degrees
float heading = atan2(event.magnetic.y, event.magnetic.x); //read magnetometer
float HeadingDeg = heading * 180/M_PI; //convert radians to degrees

int ErrorAngle = (HeadingDeg - Course); //calculate difference between heading and course

  ErrorAngle = constrain(trimtabOriginal, -90, 90); //maximum rudder movement
  ServoAngle = map(trimtabAngle, -90, 90, 0, 180); //convert to servo values

  steeringServo.write(ServoAngle);

Think back to school… sin() and cos()
will help you plot a circle

Can't remember much apart from the girl at the desk in front of mine. :wink:

Thanks a lot, I'read about it! I had a feeling sin() and cos() might be the answer.

Play with the numbers on a piece of paper with a calculator.

Think about to your axes and the four quadrants. SOHCAHTOA might help.

Remember computers often express angles in radians rather than degrees, so you’ll have to multiply.

1 Like

Take a look at this test code. I think method 1 essentially is what you are doing at the moment. Method 2 hopefully solves your issue.

void setup() {
  Serial.begin(115200);

  Serial.println("\nTesting error angle calculation method 1");
  // These should all cause the same error angle (+2 deg)
  calculate1(358,  0);
  calculate1(359,  1);
  calculate1(  0,  2);
  // These should all cause the same error angle (-2 deg)
  calculate1(  0, 358);
  calculate1(  1, 359);
  calculate1(  2,  0);

  Serial.println("\nTesting error angle calculation method 2");
  // These should all cause the same error angle (+2 deg)
  calculate2(358,  0);
  calculate2(359,  1);
  calculate2(  0,  2);
  // These should all cause the same error angle (-2 deg)
  calculate2(  0, 358);
  calculate2(  1, 359);
  calculate2(  2,  0);
}

void loop() {

}

void calculate1(int courseDeg, int headingDeg) {
  int errorAngle = (headingDeg - courseDeg); //calculate difference between heading and course
  printAngle("headingDeg", headingDeg);
  printAngle("courseDeg", courseDeg);
  printAngle("errorAngle", errorAngle);
  Serial.println();
}

void calculate2(int courseDeg, int headingDeg) {
  int errorAngle = (headingDeg - courseDeg); //calculate difference between heading and course
  if (errorAngle < -180) errorAngle += 360;
  else if (errorAngle > 180) errorAngle -= 360;
  printAngle("headingDeg", headingDeg);
  printAngle("courseDeg", courseDeg);
  printAngle("errorAngle", errorAngle);
  Serial.println();
}

void printAngle(const char* text, int angle) {
  Serial.print(text);
  Serial.print("=");
  Serial.print(angle);
  Serial.print(" ");
}

This produces the following output on the serial monitor


Testing error angle calculation method 1
headingDeg=0 courseDeg=358 errorAngle=-358 
headingDeg=1 courseDeg=359 errorAngle=-358 
headingDeg=2 courseDeg=0 errorAngle=2 
headingDeg=358 courseDeg=0 errorAngle=358 
headingDeg=359 courseDeg=1 errorAngle=358 
headingDeg=0 courseDeg=2 errorAngle=-2 

Testing error angle calculation method 2
headingDeg=0 courseDeg=358 errorAngle=2 
headingDeg=1 courseDeg=359 errorAngle=2 
headingDeg=2 courseDeg=0 errorAngle=2 
headingDeg=358 courseDeg=0 errorAngle=-2 
headingDeg=359 courseDeg=1 errorAngle=-2 
headingDeg=0 courseDeg=2 errorAngle=-2 

2 Likes

I didn't know about these two calculation functions! From your output they seem to work indeed! I'll look into it!

Thanks a lot!

There are two cases, each need its own formula.

int Course = map(analogRead(potCourse), 0, 1023, 0, 360);  //Convert bit to degrees
float heading = atan2(event.magnetic.y, event.magnetic.x); //read magnetometer
float HeadingDeg = heading * 180/M_PI; //convert radians to degrees

//  
float deltaAngle = abs(HeadingDeg - Course);
if ( deltaAngle > 180)  
{
   // direction = counterclockwise;
   servoAngle = 90 + (360 - deltaAngle) / 2; 
}
else
{
   // direction = clockwise;
  servoAngle = 90 - deltaAngle / 2;
}

maybe I swapped the formulas, but you should get the idea.
(assumption 90 == midposition servo)
Probably you have better formulas for the servoAngle ,
In the above way they adjust more or less in a linear way.

isn't the issue adjusting the math error for large angle to be relative

aren't there just 4 cases. "course" is the desired direction, "heading" is the actual direction of the boat. "delta" is the mathematical difference, "correction" is the relative difference

  course heading   delta correction
      10     350    -340      20
      10      35     -25     -25
     350     340      10      10
     350      10     340     -20

awk script generating above results

function correction (course, heading) {
    delta = course - heading

    printf " %7d", course
    printf " %7d", heading
    printf " %7d", delta

    if (delta < -180)
        delta += 360
    if (delta > 180)
        delta -= 360

    printf " %7d", delta
    printf "\n"
}
2 Likes

I think so, it's what I did here, except maybe it should be course-heading, but I was just 'correcting' the original code, so I kept the subtraction the same:

1 Like

Or, a^2+b^2=c^2 with an offset origin...

Given r, and (x−h)^2 + (y−k)^2 = r^2, solve for y

(x-h), (y-k) is the circle center, offset from 0,0 by h,k

https://saylordotorg.github.io/text_intermediate-algebra/s11-02-circles.html

These single input systems will fail when you need it the most.
Boat may point the right direction but the waves, wind and tide have other ideas on where it ends up.

A large (45') yacht ended up on the rocks in a beach near here some 10 years ago from using single ended steering guidance.
Had to be cut into pieces to remove it after a month or so.

Suggest you look at 3 or more satellite input systems.

Thank you for your comment. It's an experimental system, based on the most reliable self-steering: a windvane. I basically copy the "trim tab windvane" design (the simplest windvane in existence, used by Bernard Moitessier to circumnavigate), but replace the windvane action by a compass and servo. Of course ultimately the sailor is always responsible for regularly verifying the course of his craft. Going down for a nap while the boat is sailing close to shore is NEVER a good idea.

Works like a charm! Thank you very much!

Works flawlessly, very very appreciated! Thank you!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.