XRAD'S How to use azimuth for turning a robot 90 degrees

So I am using QMC5883LCompass.h and I am getting excellent and repeatable values as follows: North +90, East -180/+180, South -90, West 0/+1

robot exists on a flat surface. I do not want to use distance, heading, acceleration, pitch or roll....just azimuth.

So if I want to program my robot to turn LEFT 90 degrees from a current azimuth of 0, I would code for (current azimuth) - 90, and get a new direction value, then drive the motors to that new direction value and stopping the motors say within a range of + or - 10 degrees (accuracy not that important, and this allows for overrun or underrun). The problem I have is when I pass beyond 180 or 0. Ex: If current azimuth is +140 and I want to turn right 90 degrees, then the new direction value would be 140+90=230. This puts the new direction value out of azimuth range of the expected azimuth of -130.

I am not sure how to approch coding for this...should I make an array of 281 look up azimuth values and 281 expected new values ? Should I try to write a function that automatically calculates the new negative direction values? or maybe a combination? I was also considering trying to us sin and cos (as in drawing circles) and plot a new point on a 360 circle and then convert that to the new direction value...

I do not have any code to share yet because I do not know where to begin...any ideas much appreciated!!

With conventional measurement of yaw angles (N=0, E=90, S=180 and W=270), the sign problem is trivially handled by adding 360 to negative angles.

Thx JRemington! I will reevaluate yaw for my program. I just like trying to figure out the issue with azimuth because I am getting such good reads...1/2 horizon circle is '-', and the other '+'.

edit to my first post: NORTH is 0.

I can do this type of code but it's not pretty....

#include <QMC5883LCompass.h>

QMC5883LCompass compass;

int currentAzimuth = 0;
int updatedHeading = 0;

void setup() {
  Serial.begin(9600);
  compass.init();
}

void loop() { 
  compass.read();
  currentAzimuth = compass.getAzimuth();

  Serial.print("A: ");
  Serial.print(currentAzimuth);
  Serial.println();
  delay(1000);

  if (currentAzimuth >= 0 && currentAzimuth <= 90) {
    updatedHeading = 0;
    updatedHeading = currentAzimuth + 90;
}
  
  if (currentAzimuth >=91 && currentAzimuth <= 180) {
    updatedHeading = 0;
    updatedHeading = ((currentAzimuth + 90)-180);
    updatedHeading = -(180 - updatedHeading); 
  }

  Serial.print("U: ");
  Serial.print(updatedHeading);
  Serial.println();
  delay(1000);
}

So... JRemington, this works fine too , after calibrating....I guess i'm going to put azimuth on hold at this time (but it was so consistant without calibration!). Thx!

#include <QMC5883LCompass.h>

QMC5883LCompass compass;

int headingDegreesNoDecimal = 0;

void setup() {
  Serial.begin(115200);  //(9600); teensy 4.0
  compass.init();
  //compass.setCalibration(-1326, 861, -1675, 841, -1590, 483);
  //compass.setCalibrationOffsets(55.00, -722.00, -158.00);
  //compass.setCalibrationScales(0.99, 1.08, 0.94);
//below was the best calibration
  compass.setCalibration(-1763, 1522, -2108, 1168, -1640, 557);
}

void loop() {
  int x, y, z;
 
  compass.read();
 
  x = compass.getX();
  y = compass.getY();
  z = compass.getZ();

  byte a = compass.getAzimuth();

  char myArray[3];
  compass.getDirection(myArray, a);

  float headingRadians = atan2(y, x);
  float headingDegrees = headingRadians * 180 / M_PI;
  if (headingDegrees < 0) {
    headingDegrees += 360;
  }
  headingDegreesNoDecimal = headingDegrees;
  //Serial.println(y);//EDIT-commented out
  Serial.print("Heading: ");  //
  Serial.print(headingDegreesNoDecimal);
  Serial.println(" degrees");
  
  delay(250);
}

I just wanted to update this thread in case anyone else is interested. I can now turn my robot to any set point from an initial compass reading. I modified the code a bit found in the last post from this thread:
Arduino Robot Turn Problem.._gaMTk4MzQ0NzM4MS4xNzQyNTEwMDg2*_ga_NEXN8H46L5*MTc0MjUxMDA4NS4xLjAuMTc0MjUxMDA4NS4wLjAuMTA4NjU4MDM0MQ..

from arduinoRobot.h. It works great now with a little degree leeway at selected value of +/_10 degrees for my project. pick any angle up to 180-/+ for the function. Thx to formum for pointing me in right direction! it's really cool watching the serial monitor while it shows current heading and the diff countdown! :+1::slightly_smiling_face:


#include <QMC5883LCompass.h> 

QMC5883LCompass compass;
int currentHeading = 0; 

//MOTOR A
#define enA 22
#define in1 14
#define in2 15

//Motor B
#define enB 23
#define in3 16
#define in4 17

enum motorDriveStates {
  STOP,     //stop motors
  FORWARD,  //drive forward
  BACKWARD,
  TURN_RIGHT,
  TURN_LEFT,
  FORWARD_SPEED_UP,   //go fro 0 to X
  FORWARD_SLOW_DOWN,  //go from X to 0
  REVERSE_SPEED_UP,
  REVERSE_SLOW_DOWN
};
motorDriveStates MOTOR_DRIVE_FUNCTIONS();

int motorPWM = 0;
int motorState = LOW;

void setup() {
  Serial.begin(9600);
  compass.init();
  compass.setCalibration(-1763, 1522, -2108, 1168, -1640, 557);

  pinMode(enA, OUTPUT);
  pinMode(enB, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  pinMode(in3, OUTPUT);
  pinMode(in4, OUTPUT);
  MOTOR_DRIVE_FUNCTIONS(0);
}

void loop() {
  turnControl(-90);// substitute any angle 
  //for turn requirement -90, 40, etc...up to 180+/-
}

//==============turn control===============
void turnControl(int angle) {
  int target = angle;
  target = target % 360;
  if (target < 0) {
    target += 360;
  }
  int direction = angle;
  //Serial.print("DIRECTION: ");  //
  //Serial.println(direction);
  while (1) {
    int x, y;
    compass.read();
    x = compass.getX();
    y = compass.getY(); 
    float headingRadians = atan2(y, x);
    float headingDegrees = headingRadians * 180 / M_PI;
    if (headingDegrees < 0) {
      headingDegrees += 360;
    }
    currentHeading = headingDegrees;
    Serial.print("Current Heading: ");  //
    Serial.println(currentHeading);
    int diff = target - currentHeading; 
    if (diff < -180)
      diff += 360;
    else if (diff > 180)
      diff -= 360;
    direction = -diff;
    if (direction > 0) {
      MOTOR_DRIVE_FUNCTIONS(TURN_RIGHT); 
    } else {
      MOTOR_DRIVE_FUNCTIONS(TURN_LEFT); 
    }
    Serial.print("DIFF: ");  //
    Serial.println(diff);
    if (abs(diff) < 10) {   
      MOTOR_DRIVE_FUNCTIONS(STOP);
      return;
    }
  }
}


//==============MOTOR FUNCTIONS==================
void MOTOR_DRIVE_FUNCTIONS(int var) {
  switch (var) {
    case 0:  //motorSTOP:
      analogWrite(enA, 0);
      analogWrite(enB, 0);
      digitalWrite(in1, LOW);
      digitalWrite(in2, LOW);
      digitalWrite(in3, LOW);
      digitalWrite(in4, LOW);
      break;
    case 1:  //motorBothForward:
      analogWrite(enA, 220);
      analogWrite(enB, 220);
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      break;
    case 2:  //motorBOTHback:
      analogWrite(enA, 190);
      analogWrite(enB, 190);
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      break;
    case 3:  //motorRIGHTturn:
      analogWrite(enA, 220);
      analogWrite(enB, 220);
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      break;
    case 4:  //motorLEFTturn:
      analogWrite(enA, 220);
      analogWrite(enB, 220);
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      break;
    case 5:  //motorForwardSpeedUP:
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      for (int i = 0; i < 220; i++) {
        //motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(20);
      }
      break;
    case 6:  //motorForwardSlowDown:
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      for (int i = 220; i >= 0; --i) {
        motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(5);
      }
      break;
    case 7:  //motorBackwardSpeedup:
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      for (int i = 0; i < 220; i++) {
        motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(5);
      }
      break;
    case 8:  //motorBackwardSlowDown:
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      for (int i = 220; i >= 0; --i) {
        motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(5);
      }
      break;
  }
}

So here is my code update. The code above is for using '0' degrees as starting point. Now, If you have a sensor that detects a clear area to drive robot to, denote the degrees, and replace the turn function argument. In this case, I just set it to -90 for testing. works fine with Pos and Neg degrees. This is good for turning a robot a certain amount without motor timers or motor position feedback or SLAM.

#include <QMC5883LCompass.h>
#include <FastLED.h>
#include <Ultrasonic.h>

QMC5883LCompass compass;
int currentHeading = 0;
int initialHeading = 0; 

const int LED = 13;
Ultrasonic ultrasonic(2, 20);
int distance;
unsigned long US_Distance_Ping_Timer = 0;


bool turnFlag = true;
//MOTOR A
#define enA 22
#define in1 14
#define in2 15

//Motor B
#define enB 23
#define in3 16
#define in4 17

enum motorDriveStates {
  STOP,     //stop motors
  FORWARD,  //drive forward
  BACKWARD,
  TURN_RIGHT,
  TURN_LEFT,
  FORWARD_SPEED_UP,   //go fro 0 to X
  FORWARD_SLOW_DOWN,  //go from X to 0
  REVERSE_SPEED_UP,
  REVERSE_SLOW_DOWN
};
motorDriveStates MOTOR_DRIVE_FUNCTIONS();

int motorPWM = 0;
int motorState = LOW;


const int buttonPin = 4;
int buttonState = 0;

void setup() {
  Serial.begin(9600);
  compass.init();
  compass.setCalibration(-1763, 1522, -2108, 1168, -1640, 557);//adjust for your location

  pinMode(enA, OUTPUT);
  pinMode(enB, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  pinMode(in3, OUTPUT);
  pinMode(in4, OUTPUT);

  MOTOR_DRIVE_FUNCTIONS(0);

  pinMode(LED, OUTPUT);
  pinMode(buttonPin, INPUT_PULLDOWN);
}


//=====these are the timer items==========
// millis variable used for many functions
unsigned long currentMillis = 0;

//array of subjective milli run times
int MillisRunLengthTime[8] = { 20, 100, 200, 800, 1000, 1500, 2000, 2500 };

//this is the key timer function, can use for MANY functions
boolean RunTIMERforThisAmountOfTime(unsigned long &startNowTimer, int runLengthTime) {
  currentMillis = millis();
  if (currentMillis - startNowTimer >= runLengthTime) {
    startNowTimer = currentMillis;
    return true;
  } else return false;
}


void loop() {

  EVERY_N_MILLISECONDS(100) {//keep track of current heading
    int x, y;
    compass.read();
    x = compass.getX();
    y = compass.getY();
    float headingRadians = atan2(y, x);
    float headingDegrees = headingRadians * 180 / M_PI;
    if (headingDegrees < 0) {
      headingDegrees += 360;
    }
    initialHeading = headingDegrees;
    //Serial.print("initialHeading Heading: ");
    //Serial.println(initialHeading);
  }
  /*
  buttonState = digitalRead(buttonPin);  //for debugging
  if (buttonState == HIGH) {
    digitalWrite(LED, HIGH);  //for debugging
    turnFlag = true;
    turnControl(50);
    // MOTOR_DRIVE_FUNCTIONS(FORWARD);
  } else {
    digitalWrite(LED, LOW);
    //MOTOR_DRIVE_FUNCTIONS(STOP);
  }
}
*/

  if (RunTIMERforThisAmountOfTime(US_Distance_Ping_Timer, MillisRunLengthTime[1])) {
    distance = ultrasonic.read();
    //Serial.print("Distance in CM: ");
    //Serial.println(distance);
  }

  if (distance <= 25) {//set for your system
    turnControl(-90);  // substitute any angle
    //for turn requirement -90, 40, etc...up to 179+/-
    //add a rotating sensor to determine best turn angle
  }
  if (distance >= 26) {
    //turnFlag = true;
    MOTOR_DRIVE_FUNCTIONS(FORWARD);
  }
}


//==============turn control===============
void turnControl(int angle) {

  int initialDirection = initialHeading;//get initial heading
  //and normalize 'x' for 0-359
  initialDirection %= 360;
  if (initialDirection < 0) {
    initialDirection += 360;
  }

  int initialTARGETAngle = initialDirection + angle;//get the initial target angle
  //and normalize 'x' for 0-359
  initialTARGETAngle %= 360;
  if (initialTARGETAngle < 0) {
    initialTARGETAngle += 360;
  }

  int direction;

  while (1) {
    //need live compass reading while turning, so....
    int x, y;
    compass.read();
    x = compass.getX();
    y = compass.getY();
    float headingRadians = atan2(y, x);
    float headingDegrees = headingRadians * 180 / M_PI;
    if (headingDegrees < 0) {
      headingDegrees += 360;
    }
    currentHeading = headingDegrees;

    Serial.print("initialDirection: ");
    Serial.println(initialDirection);

    Serial.print("initialTARGETAngle: ");
    Serial.println(initialTARGETAngle);

    int diff = initialTARGETAngle - currentHeading;
    //TODO: fix the jitter at the 180 transition
    if (diff < -180)
      diff += 360;
    else if (diff > 180)
      diff -= 360;
    direction = -diff;
    
    if (direction > 0) {
      MOTOR_DRIVE_FUNCTIONS(TURN_RIGHT);
    } else {
      MOTOR_DRIVE_FUNCTIONS(TURN_LEFT);
    }
    Serial.print("DIFF: "); 
    Serial.println(diff);
    if (abs(diff) < 10) {//a little leeway here as needed
      MOTOR_DRIVE_FUNCTIONS(STOP);
      return;
    }
  }
}


//==============MOTOR FUNCTIONS==================
void MOTOR_DRIVE_FUNCTIONS(int var) {
  switch (var) {
    case 0:  //motorSTOP:
      analogWrite(enA, 0);
      analogWrite(enB, 0);
      digitalWrite(in1, LOW);
      digitalWrite(in2, LOW);
      digitalWrite(in3, LOW);
      digitalWrite(in4, LOW);
      break;
    case 1:  //motorBothForward:
      analogWrite(enA, 220);
      analogWrite(enB, 220);
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      break;
    case 2:  //motorBOTHback:
      analogWrite(enA, 190);
      analogWrite(enB, 190);
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      break;
    case 3:  //motorRIGHTturn:
      analogWrite(enA, 220);
      analogWrite(enB, 220);
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      break;
    case 4:  //motorLEFTturn:
      analogWrite(enA, 220);
      analogWrite(enB, 220);
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      break;
    case 5:  //motorForwardSpeedUP:
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      for (int i = 0; i < 220; i++) {
        //motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(20);
      }
      break;
    case 6:  //motorForwardSlowDown:
      digitalWrite(in1, HIGH);
      digitalWrite(in2, LOW);
      digitalWrite(in3, HIGH);
      digitalWrite(in4, LOW);
      for (int i = 220; i >= 0; --i) {
        motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(5);
      }
      break;
    case 7:  //motorBackwardSpeedup:
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      for (int i = 0; i < 220; i++) {
        motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(5);
      }
      break;
    case 8:  //motorBackwardSlowDown:
      digitalWrite(in1, LOW);
      digitalWrite(in2, HIGH);
      digitalWrite(in3, LOW);
      digitalWrite(in4, HIGH);
      for (int i = 220; i >= 0; --i) {
        motorPWM = i;
        analogWrite(enA, i);
        analogWrite(enB, i);
        delay(5);
      }
      break;
  }
}