HMC5883 compass chip question

Hello!
I have a sketch that uses an Adafruit compass chip based on the HMC5883 chip that I use to return heading values to several functions I have written. It works very well. My question is this: In the event that the compass fails to return a heading value, such as it getting unhooked, burnt up, whatever, is there some way I can modify the sketch to post an error to the serial monitor? In other words, in a more generic manner, is there some way for an Arduino sketch to signal an error if it gets no value at all when it is expecting a value? I have posted the relevant function below. Thanks.

Gary

//This function uses the input from the compass chip to calculate a bearing, and returns that value to the turnEast function.

float getHeading ()
{
  sensors_event_t event; 
  mag.getEvent(&event);
  float heading = atan2(event.magnetic.y, event.magnetic.x);


  float declinationAngle = (LOCAL_DECLINATION_ANGLE);
  heading += declinationAngle;

  // Correct for when signs are reversed.
  if(heading < 0)
    heading += 2*PI;

  // Check for wrap due to addition of declination.
  if(heading > 2*PI)
    heading -= 2*PI;

  // Convert radians to degrees for readability.
  float headingDegrees = heading * 180/M_PI;
  return headingDegrees;
  }

The relevant code is in the library. That is where one would detect compass failure.

jremington:
The relevant code is in the library. That is where one would detect compass failure.

That makes sense. Did I mention that I am a rank amateur at this stuff? LOL I'll try and read through the relevant library files & see if I can gain some insight. Thanks!

The answer was right in the example sketch in the library. Whoda thunk it? I'll give it a try as is, and even if it doesn't do what I want, I now have a starting point. Thank you jremington!

Well, it wasn’t to be that simple. The relevant snippet from the example sketch:

void setup(void) 
{
  Serial.begin(9600);
  Serial.println("HMC5883 Magnetometer Test"); Serial.println("");
  
  /* Initialise the sensor */
  if(!mag.begin())
  {
    /* There was a problem detecting the HMC5883 ... check your connections */
    Serial.println("Ooops, no HMC5883 detected ... Check your wiring!");
    while(1);
  }
  
  /* Display some basic information on this sensor */
  displaySensorDetails();
}

does nothing. If the chip is connected, I get data printed out after the “HMC5883 Magnetometer Test”;
if it is disconnected, I get nothing. What am I missing? Shouldn’t it it print “Ooops, no HMC5883 detected … Check your wiring!”? I’m stumped.

The example sketch in it’s entirety:

/***************************************************************************
  This is a library example for the HMC5883 magnentometer/compass

  Designed specifically to work with the Adafruit HMC5883 Breakout
  http://www.adafruit.com/products/1746
 
  *** You will also need to install the Adafruit_Sensor library! ***

  These displays use I2C to communicate, 2 pins are required to interface.

  Adafruit invests time and resources providing this open source code,
  please support Adafruit andopen-source hardware by purchasing products
  from Adafruit!

  Written by Kevin Townsend for Adafruit Industries with some heading example from
  Love Electronics (loveelectronics.co.uk)
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the version 3 GNU General Public License as
 published by the Free Software Foundation.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 ***************************************************************************/

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_HMC5883_U.h>

/* Assign a unique ID to this sensor at the same time */
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345);

void displaySensorDetails(void)
{
  sensor_t sensor;
  mag.getSensor(&sensor);
  Serial.println("------------------------------------");
  Serial.print  ("Sensor:       "); Serial.println(sensor.name);
  Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
  Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" uT");
  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" uT");
  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" uT");  
  Serial.println("------------------------------------");
  Serial.println("");
  delay(500);
}

void setup(void) 
{
  Serial.begin(9600);
  Serial.println("HMC5883 Magnetometer Test"); Serial.println("");
  
  /* Initialise the sensor */
  if(!mag.begin())
  {
    /* There was a problem detecting the HMC5883 ... check your connections */
    Serial.println("Ooops, no HMC5883 detected ... Check your wiring!");
    while(1);
  }
  
  /* Display some basic information on this sensor */
  displaySensorDetails();
}

void loop(void) 
{
  /* Get a new sensor event */ 
  sensors_event_t event; 
  mag.getEvent(&event);
 
  /* Display the results (magnetic vector values are in micro-Tesla (uT)) */
  Serial.print("X: "); Serial.print(event.magnetic.x); Serial.print("  ");
  Serial.print("Y: "); Serial.print(event.magnetic.y); Serial.print("  ");
  Serial.print("Z: "); Serial.print(event.magnetic.z); Serial.print("  ");Serial.println("uT");

  // Hold the module so that Z is pointing 'up' and you can measure the heading with x&y
  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  float heading = atan2(event.magnetic.y, event.magnetic.x);
  
  // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location.
  // Find yours here: http://www.magnetic-declination.com/
  // Mine is: -13* 2' W, which is ~13 Degrees, or (which we need) 0.22 radians
  // If you cannot find your Declination, comment out these two lines, your compass will be slightly off.
  float declinationAngle = 0.22;
  heading += declinationAngle;
  
  // Correct for when signs are reversed.
  if(heading < 0)
    heading += 2*PI;
    
  // Check for wrap due to addition of declination.
  if(heading > 2*PI)
    heading -= 2*PI;
   
  // Convert radians to degrees for readability.
  float headingDegrees = heading * 180/M_PI; 
  
  Serial.print("Heading (degrees): "); Serial.println(headingDegrees);
  
  delay(500);
}

if it is disconnected, I get nothing. What am I missing?

The library code can't detect all possible failures. The Wire routines won't work if nothing is connected.

The Arduino IDE is not a professional, "fail safe" programming environment. You have to provide fail safe programming if you require it (and it is possible to do so, as long at the processor chip itself doesn't fail).

You should always run the I2C Scanner program if you think there is a problem. It is instructive to see how that program detects that nothing is connected.

jremington:
The library code can't detect all possible failures. The Wire routines won't work if nothing is connected.

The Arduino IDE is not a professional, "fail safe" programming environment. You have to provide fail safe programming if you require it (and it is possible to do so, as long at the processor chip itself doesn't fail).

You should always run the I2C Scanner program if you think there is a problem. It is instructive to see how that program detects that nothing is connected.

Thanks for the reference to the scanner program. That looks like it might do what I need with some tinkering.

That did it. After some modification, it now works like a charm. If the compass is not detected, the sketch turns on a red LED, prints an error message and continuously loops until the compass chip is reconnected. Thanks jremington!

Great! Why not post your code, in case others need to see how it is done.

jremington:
Great! Why not post your code, in case others need to see how it is done.

Will do, here is the function I made from it: This function gets called by any function which needs heading data, so if the compass is not detected, the sketch goes into an endless loop.

//This function checks to be sure the compass chip is connected.  
void checkCompass()
{
  byte error, address;
  int nDevices;
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.println("Compass found");
      nDevices++;
    }
    else if (error==4)
    {

    }    
  }
  if (nDevices == 0)
    Serial.println("Compass Fault");
    digitalWrite(COMPASS_FAULT_LED, HIGH);
    delay(500);          
}

I would just check the compass address.

As it is, if any other properly functioning I2C device is connected, your program will also think the compass is attached.

My newbie-ness is showing again. You are right, but I didn't know I could add more things to the i2c pins. I know the protocol supports more than one device at a time, but at the risk of going off-topic, how would I attach, say a RTC to it?
Also, again off-topic, but would the compass always have the same address, allowing me to poll just the one address for each possble i2c device?

The I2C bus is intended to be connected to several devices at once, all in parallel.

Each device must have a unique address, and some devices allow you to change the default address or select from a small number of possibilities (e.g. by setting a device pin LOW or HIGH).

The default (factory) HMC5883L 8-bit slave address is 0x3C for write operations, or 0x3D for read operations.

I would try this (UNTESTED):

//This function checks to be sure the compass chip is connected. 
void checkCompass()
{
  byte error;
  Wire.beginTransmission(0x1E);  //seven bit address for HMC5883L
    error = Wire.endTransmission();
    if (error == 0)
    {
      Serial.println("Compass found");
    }
    else
    {
    Serial.println("Compass Fault");
    digitalWrite(COMPASS_FAULT_LED, HIGH);
    delay(500);         
    }
}

Each device has a unique address - those that do not sometimes have a pin or two or three to allow setting the address plus some offsets so multiple parts can be used without multiplexers.
If you have two parts without address options, then a multiplexer is needed.
For example dsscircuits.com is for sale | HugeDomains

Thank you both! I'll try the modified sketch when I get a minute, but it should do exactly what I want & if I install a RTC, it can be modified to check that and report back as well. I'm a bit rusty on networking protocols, but since each i2c device has (or can be made to have) a unique address, I assume I can just pile them onto the same pair of data pins? Or is it more complicated than that? How does the Atmega chip assign priorities/interrupts, or whatever arduino calls them? I am using interrupts 4 and 5 (pins 18 and 19) already, but have 3 more if needed, as I understand it

This is just theoretical at this point; this particular sketch does as I want it to now, so it doesn't really need a clock at the moment

BTW, the mods can feel free to split this topic, since it is now more of an i2C general thread now....

CrossRoads:
Each device has a unique address - those that do not sometimes have a pin or two or three to allow setting the address plus some offsets so multiple parts can be used without multiplexers.
If you have two parts without address options, then a multiplexer is needed.
For example http://www.dsscircuits.com/sale/product/dssc0107

@Crossroads, thanks for the link. it is now bookmarked for future projects!