compass.read blocking code execution in void loop();

I am currently working on a project utilizing a GY-511 compass module, and a GT-U7 GPS module, my MCU is an Arduino DUE. The problem I am encountering is that when I add the line compass.read(); my code no longer outputs any data to the serial port. Now, I have tested with a compass code, and a GPS code matching both portions of the following sketch, I only get problems from compass.read(); when it is included with the GPS code. I should also mention that I am getting no errors from the debug console.

#include <Servo.h>
#include <TinyGPS++.h>
#include <LSM303.h>
#include <Wire.h>


//Object Declaration
Servo leftMotor;
Servo rightMotor;
LSM303 compass;
TinyGPSPlus gps;

//Global Variables
float bearing;

//Timers
int lastMillis;
int updateTimer;
int printTimer;

//GPS Data
double latitude;
double longitude;
double Altitude;
double speedKnots;
double speedMPS;
double Course;
int satelliteCount;

//GPS Positions
int homeTrigger;
double homeLatitude;
double homeLongitude;

//GPS Waypoint variables;
double waypoints[] = {};



void setup() {
  Serial.begin(115200);
  Serial.println("Serial communications established");
  Serial1.begin(9600);
  Wire.begin();
  compass.init();
  compass.enableDefault();
  //Compass Calibration
  compass.m_min = (LSM303::vector<int16_t>) {-381, +744, -1146};
  compass.m_max = (LSM303::vector<int16_t>) {-372, +752, -1140};
  
}

void loop() {
while (Serial1.available() > 0){
 gps.encode(Serial1.read());
}

if (gps.location.isUpdated()){
  updateTimer = millis();
  latitude = gps.location.lat();
  longitude = gps.location.lng();
  Altitude = gps.altitude.meters();
  speedKnots = gps.speed.knots();
  satelliteCount = gps.satellites.value();
}
updateCompass();
Serial.println(latitude, 8);

}

void updateCompass() {
  compass.read();
}

I have tested with and without the compass module electrically connected and the results are the same.

My electrical connections are as follows:
TX1 → GPS RX
RX1 → GPS TX
3.3v → GPS VCC
GND → GPS GND

3.3v → Compass 3.3v
GND → Compass GND
SDA → Compass SDA
SCL → Compass SCL

Water_Rover.ino (1.32 KB)

With a new I2C device, it is a good idea to run the I2C address scanner program to verify I2C communications.

Could you give a link to your compass module (a link to where you bought it), and a link to the compass library that you use.

Do you use a breadboard and have the sensor and servo motor connected on the same breadboard ? I give that 90% chance that it will go wrong.

If you do something with millis(), then use 'unsigned long' variables.

Can you be more specific ? Which serial port does no longer output data ?

The Arduino Due has 1k5 (sometimes 1k) pullup resistors on SDA and SCL and no pullup resistors on SDA1 and SCL1.
Those 1k5 is a mistake, they are too low. They should be 10k.
You can remove them with a solder iron.

You could also use "Wire1" with SDA1 and SCL1, but then there must be pullup resistors somewhere. They are already on your compass module.

The GY-511 seems to have a voltage level converter for the I2C bus. However both sides are 3.3V, so the level converter only makes the SDA and SCL signals weaker.

The combination of the bad I2C bus of the Due with the weak I2C bus of the module is not okay.

Conclusion:

  • Don't use the Arduino Due. Toss it away and use a SAMD21 board (Arduino Zero or most MKR boards).
  • If you want to use the Arduino Due, then remove those wrong pullup resistors.
  • If you don't have a solder iron, you might try "Wire1".
  • Buy a compass module that is a plain 3.3V module without voltage regulator and without level shifter.

Here is the compass module: Amazon.com: HiLetgo GY-511 LSM303DLHC Module e-Compass 3 Axis Accelerometer + 3 Axis Magnetometer Module Sensor: Home Improvement

Here is the GPS Module: Amazon.com: Geekstory GT-U7 GPS Module GPS Receiver Navigation Satellite with EEPROM Compatible with 6M 51 Microcontroller STM32 UO R3+ IPEX Active GPS Antenna for Arduino Drone Raspberry Pi Flight: GPS & Navigation

The tests I have run have not had the servo hooked up, and the servo is actually a brushless ESC.
Serial1 is used for GPS communications
Serial is used for the serial terminal output.
Serial 1 is the port that is no longer outputting data after compass.read has run.
Based on what you say If I understand correctly SDA1 and SCL 1 do not have resistors, I am currently using SDA0 and SCL0 ANd have had no problems when the compass sketch is run by it’self. But If the resistors are a problem, couldn’t I just use SDA/SCL 1?

I will post the individual sketches in which both systems operate flawlessly, it is only when I add the line compass.read(); to the sketch including GPS where I get problems.

Here is the stand alone GPS code I have used.

Here is the LSM303 library I am using for the compass.

/*
 * 
 */
 #include <TinyGPS++.h>
 
 double latitude;
 double longitude;
 double Altitude;
 int Satellites;
 unsigned long previousTime;
 int period = 5000;
 double waypointLat;
 double waypointLon;

 TinyGPSPlus gps;
 
void setup() {
  Serial.begin(115200);
  Serial1.begin(9600);
  Serial.print("Serial Communbications Established Successfully");
  Serial.println();
  previousTime = 0;
  waypointLat = 35.044;

}

void loop() {
while (Serial1.available() > 0){
 gps.encode(Serial1.read());
}
if (gps.location.isUpdated()){
  latitude = gps.location.lat();
  longitude = gps.location.lng();
  Satellites = gps.satellites.value();
  Altitude = gps.altitude.meters();
}
Serial.println(latitude);

}

Here is the stand alone compass Code I have used.

#include <Wire.h>
#include <LSM303.h>

LSM303 compass;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  compass.init();
  compass.enableDefault();
  
  /*
  Calibration values; the default values of +/-32767 for each axis
  lead to an assumed magnetometer bias of 0. Use the Calibrate example
  program to determine appropriate values for your particular unit.
  */
  compass.m_min = (LSM303::vector<int16_t>){-32767, -32767, -32767};
  compass.m_max = (LSM303::vector<int16_t>){+32767, +32767, +32767};
}

void loop() {
  compass.read();
  
  /*
  When given no arguments, the heading() function returns the angular
  difference in the horizontal plane between a default vector and
  north, in degrees.
  
  The default vector is chosen by the library to point along the
  surface of the PCB, in the direction of the top of the text on the
  silkscreen. This is the +X axis on the Pololu LSM303D carrier and
  the -Y axis on the Pololu LSM303DLHC, LSM303DLM, and LSM303DLH
  carriers.
  
  To use a different vector as a reference, use the version of heading()
  that takes a vector argument; for example, use
  
    compass.heading((LSM303::vector<int>){0, 0, 1});
  
  to use the +Z axis as a reference.
  */
  float heading = compass.heading();
  
  Serial.println(heading);
  delay(100);
}

The I2C lines absolutely require pullup resistors (2.2K to 4.7K typically). If they are not there, or have inappropriate values, that is the problem.

OK, I may in the future use SDA/ACL1 with external resistors, but for now I2C does not appear to be the problem, as everything functions properly as long as the compass code and GPS code are not in the same sketch, and I have re tested with minimal code and it is indeed compass.read() that is throwing it off, I will have to look in the library to see what that is doing.

I have made some “progress” first of all, I didn’t know that compass.init(); would halt execution if the compass were not attached, however, I still have a problem with compass.read(); halting execution, and due to the i2c problems with DUE, I am now using an UNO. I know it is compass.read() because I have made a sketch to test and it prints a check before and after possible culprits, and check 3 before compass is printed, and check 4 is not.
I have also learned that it is not because of GPS and compass being together, as when I remove all GPS code from voidLoop check 4 still fails.

#include <SoftwareSerial.h>
SoftwareSerial SSerial(2, 3);

#include <TinyGPS++.h>
TinyGPSPlus gps;

#include <Wire.h>
#include <LSM303.h>
LSM303 compass;

float bearing;

double latitude;
double longitude;

void setup() {
  Serial.begin(9600);
  SSerial.begin(9600);
  Serial.println("Serial Port Open");
  Serial.println("check 1");
  compass.init();
  compass.enableDefault();
  Serial.println("Check 2");


}

void loop() {
  Serial.println("check 3");
  compass.read();
  Serial.println("check 4");
  while (SSerial.available() > 0) {
    gps.encode(SSerial.read());
  }
  if (gps.location.isUpdated()) {
    latitude = gps.location.lat(); 
    longitude = gps.location.lng();
    Serial.println(longitude, 8);
  }

}

Why are these lines not in the program that is failing?

  compass.m_min = (LSM303::vector<int16_t>){-32767, -32767, -32767};
  compass.m_max = (LSM303::vector<int16_t>){+32767, +32767, +32767};

In any case, those are the wrong values. The compass won’t work properly or at all if it is not calibrated.

Good tests.
So we can put hardware problems aside ?

Can you give links to both the compass and gps libraries that you use ?
If you used the Library Manager, please tell so, then I can find them myself.
I would like to see the code of the libraries, although it is hard to believe that they somehow influence each other.

How about a photo of the boards and the wiring ?

You probably use the Pololu library, but they have not fixed my issue from 4 years ago: After Wire.requestFrom() there is no need for a timeout and no endTransmission. · Issue #10 · pololu/lsm303-arduino · GitHub.
That is a serious issue, because there is a 'io_timeout' variable that is set default to zero. With that veriable being zero, there can be an endless loop (if something is wrong with the I2C).
I also see "translated_regs[-OUT_X_L_M]" in that library. I don't understand that. That is the first time in my life that I see a negative array index.

Is there an other library for the compass ?