SparkFun Forums 

Where electronics enthusiasts find answers.

Have questions about a SparkFun product or board? This is the place to be.
By eawendtjr
#194248
I am working on determining north with the HMC6343 for a project. After calibration, the sensor works quite well on a flat surface but gives inaccurate readings when pitch and roll are introduced. I was under the impression that tilt compensation was handled automatically by the chip but this does not appear to be the case.

The typical approach to tilt compensation seems to be adjusting the raw magnetometer values but this chip seems to take care of the heading calculation automatically so those raw values are unavailable to the user (at least with the MBED library I am using). Is there an approach that has proved successful for tilt compensating this sensor?

Thanks,
Eric
By jremington
#194256
You can access any of the data the chip produces, or use the chip in various modes. Study the data sheet for the options.

The problem appears to be that you are using the library blindly.
By eawendtjr
#194293
The datasheet (link below) suggests this chip offers "complete, ready to use tilt-compensated electronic compass". This suggests to me that tilt compensation is the factory default and that it should be factored in when I access heading data using the commands 0x32 and 0x50 to access tilt and heading data respectively. I see no information in the data sheet to suggest that I can activate tilt compensation with a command.

I want to avoid pulling raw magnetometer values and doing the calculations myself because I doubt I could ever do better than the factory firmware. Does anybody have experience with tilt compensation working after simply calibrating the device and sending those commands?

https://aerocontent.honeywell.com/aero/ ... MC6343.pdf
By eawendtjr
#194305
Here is the function I am using to grab the orientation from the sensor:
Code: Select all
void HMC6343_Compass::findOrientation() {

    char tx[1];
    char rx[6];

    tx[0] = HMC6343_GET_HEADING_DATA;
    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 1);
    wait_ms(1);

    i2c_->read((HMC6343_I2C_ADDRESS << 1) | 0x01, rx, 6, true);
    
    h = ((((int)rx[0] << 8) | (int)rx[1]));
    r = ((((int)rx[4] << 8) | (int)rx[5]));
    p = ((((int)rx[2] << 8) | (int)rx[3]));

    heading = (float)h / 10;
    roll = (float)r / 10;
    pitch = (float)p / 10;
}
That is probably the critical section but here is the whole cpp file from the library I am using:
Code: Select all
/**
 * @author Aaron Berk
 * @author Serge Sozonoff
 * Partially based on the work of Aaron Berk for the HMC6352
 *
 * @section LICENSE
 *
 * Copyright (c) 2010 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * Honeywell HMC6343 tilt compensated digital compass.
 *
 * Datasheet:
 *
 * http://www.ssec.honeywell.com/magnetic/datasheets/HMC6343.pdf
 */

/**
 * Includes
 */
#include "HMC6343_Compass.h"

template<class TYPE> inline TYPE BIT(const TYPE & x) {
    return TYPE(1) << x;
}

template<class TYPE> inline bool IsBitSet(const TYPE & x, const TYPE & y) {
    return 0 != (x & y);
}

HMC6343_Compass::HMC6343_Compass(PinName sda, PinName scl) {
    i2c_ = new I2C(sda, scl);
    i2c_->frequency(100000);
    operationMode_ = getOpMode();
}

HMC6343_Compass::HMC6343_Compass(I2C& p_i2c) : i2c_(&p_i2c) {
    i2c_->frequency(100000);
    operationMode_ = getOpMode();
}

void HMC6343_Compass::findOrientation() {

    char tx[1];
    char rx[6];

    tx[0] = HMC6343_GET_HEADING_DATA;
    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 1);
    wait_ms(1);

    i2c_->read((HMC6343_I2C_ADDRESS << 1) | 0x01, rx, 6, true);
    
    h = ((((int)rx[0] << 8) | (int)rx[1]));
    r = ((((int)rx[4] << 8) | (int)rx[5]));
    p = ((((int)rx[2] << 8) | (int)rx[3]));

    heading = (float)h / 10;
    roll = (float)r / 10;
    pitch = (float)p / 10;
}

float HMC6343_Compass::getHeading() {
    return heading;
}

float HMC6343_Compass::getRoll() {
    return roll;
}

float HMC6343_Compass::getPitch() {
    return pitch;
}

void HMC6343_Compass::setReset(void) {
    char tx[1];
    tx[0] = HMC6343_RESET;
    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 1);
    wait_ms(500);
}

void HMC6343_Compass::setCalibrationMode(int exitOrEnter) {
    char tx[1];
    int delay = 0;

    tx[0] = exitOrEnter;

    if (exitOrEnter == HMC6343_EXIT_CALIB) {
        delay = 50;
    } else if (exitOrEnter == HMC6343_ENTER_CALIB) {
        delay = 1;
    }

    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 1);
    wait_ms(delay);
}

int HMC6343_Compass::getSlaveAddress(void) {
    return readEeprom(HMC6343_SLAVE_ADDR);
}

int HMC6343_Compass::getOffset(int axis) {

    char rx[2] = {0x00, 0x00};

    if (axis == HMC6343_X_AXIS) {
        rx[0] = readEeprom(HMC6343_XOFFSET_MSB);
        rx[1] = readEeprom(HMC6343_XOFFSET_LSB);

    } else if (axis == HMC6343_Y_AXIS) {
        rx[0] = readEeprom(HMC6343_YOFFSET_MSB);
        rx[1] = readEeprom(HMC6343_YOFFSET_LSB);

    } else {
        rx[0] = readEeprom(HMC6343_ZOFFSET_MSB);
        rx[1] = readEeprom(HMC6343_ZOFFSET_LSB);
    }

    return ((rx[0] << 8) | (rx[1]));

}

char HMC6343_Compass::getMeasurementRate() {
    if (IsBitSet(operationMode_, HMC6343_CM_MR_10HZ) && !IsBitSet(operationMode_, HMC6343_CM_MR_5HZ)) { return 10; }
    else if (IsBitSet(operationMode_, HMC6343_CM_MR_5HZ) && !IsBitSet(operationMode_, HMC6343_CM_MR_10HZ)) { return 5; }
    else return 1;
}

int HMC6343_Compass::getSoftwareVersion(void) {
    return readEeprom(HMC6343_SOFT_VER);
}

int HMC6343_Compass::getOpMode(void) {
    char tx[1];
    tx[0] = HMC6343_GET_OPMODE;

    char rx[2];

    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 1);
    wait_ms(1);
    i2c_->read((HMC6343_I2C_ADDRESS << 1) | 0x01, rx, 2, true);

    operationMode_ = (rx[1] << 8) | (rx[0]);

    return operationMode_;
}

bool HMC6343_Compass::isOpModeFlagSet(int flag) {
    return IsBitSet(operationMode_, flag);
}

void HMC6343_Compass::setOpMode(int opMode) {
    writeShort(HMC6343_OPMOD_REG1, (short)opMode);
    operationMode_ = getOpMode();
}

void HMC6343_Compass::writeShort(int lsb_address, short data) {
    writeEeprom(lsb_address, data & 0x00FF);
    writeEeprom(lsb_address + 1, data >> 8);
}

short HMC6343_Compass::readShort(int lsb_eprom_address) {
    return (short)(readEeprom(lsb_eprom_address + 1) << 8) | (readEeprom(lsb_eprom_address));
}

void HMC6343_Compass::setMagneticDeviation(float data) {
    short v;
    v = (short)(data * 100); // move decimal right two places
    if (v <= 1800 && v >= -1800) {
        writeShort(HMC6343_DEV_LSB, v);
    }
}

float HMC6343_Compass::getMagneticDeviation() {
    return (float)(readShort(HMC6343_DEV_LSB)) / 100;
}

void HMC6343_Compass::setMagneticVariation(float data) {
    short v;
    v = (short)(data * 100); // move decimal right two places
    if (v <= 1800 && v >= -1800) {
        writeShort(HMC6343_VAR_LSB, v);
    }
}

float HMC6343_Compass::getMagneticVariation() {
    return (float)(readShort(HMC6343_VAR_LSB)) / 100;
}

void HMC6343_Compass::setIIRFilter(short data) {
    writeShort(HMC6343_IIRF_LSB, data);
}

short HMC6343_Compass::getIIRFilter() {
    return (short)readShort(HMC6343_IIRF_LSB);
}

void HMC6343_Compass::setMagOffset(int axis, int offset) {
    if (axis == HMC6343_X_AXIS) {
        writeShort(HMC6343_XOFFSET_LSB, offset);
    } else if (axis == HMC6343_Y_AXIS) {
        writeShort(HMC6343_YOFFSET_LSB, offset);
    } else {
        writeShort(HMC6343_ZOFFSET_LSB, offset);
    }
}

int HMC6343_Compass::getMagOffset(int axis) {
    if (axis == HMC6343_X_AXIS) {
        return readShort(HMC6343_XOFFSET_LSB);
    } else if (axis == HMC6343_Y_AXIS) {
        return readShort(HMC6343_YOFFSET_LSB);
    } else {
        return readShort(HMC6343_ZOFFSET_LSB);
    }
}

void HMC6343_Compass::writeEeprom(int address, int data) {
    char tx[3];

    tx[0] = HMC6343_EEPROM_WRITE;
    tx[1] = address;
    tx[2] = data;

    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 3, false);
    wait_ms(10);
}

int HMC6343_Compass::readEeprom(int address) {
    char tx[2];
    char rx[1];

    tx[0] = HMC6343_EEPROM_READ;
    tx[1] = address;

    i2c_->write((HMC6343_I2C_ADDRESS << 1) & 0xFE, tx, 2, false);
    wait_ms(1);
    i2c_->read((HMC6343_I2C_ADDRESS << 1) | 0x01, rx, 1);
    wait_ms(1);

    return rx[0];
}
And here is the h file:
Code: Select all
/**
 * @author Serge Sozonoff 
 * @author Aaron Berk
 * Partially based on the work of Aaron Berk for the HMC6352
 *
 * @section LICENSE
 *
 * Copyright (c) 2010 ARM Limited
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * Honeywell HMC6343 digital compass.
 *
 * Datasheet:
 *
 * http://www.ssec.honeywell.com/magnetic/datasheets/HMC6343.pdf
 */

#ifndef HMC6343_H
#define HMC6343_H

/**
 * Includes
 */
#include "mbed.h"

/**
 * Defines
 */
#define HMC6343_I2C_ADDRESS  0x32 >> 1 //7-bit address

//Commands.
#define HMC6343_EEPROM_WRITE     0xF1
#define HMC6343_EEPROM_READ      0xE1
#define HMC6343_ENTER_SLEEP      0x83
#define HMC6343_EXIT_SLEEP       0x84
#define HMC6343_ENTER_STANDBY    0x76
#define HMC6343_ENTER_RUN        0x75
#define HMC6343_RESET            0x82
#define HMC6343_ENTER_CALIB      0x71
#define HMC6343_EXIT_CALIB       0x7E
#define HMC6343_GET_ACCEL_DATA   0x40
#define HMC6343_GET_MAG_DATA     0x45
#define HMC6343_GET_HEADING_DATA 0x50
#define HMC6343_GET_TILT_DATA    0x55
#define HMC6343_GET_HEADING_DATA 0x50
#define HMC6343_SET_XFWD_PLS_ZUP 0x72
#define HMC6343_SET_XFWD_YUP     0x73
#define HMC6343_SET_ZFWD_NEG_XUP 0x74
#define HMC6343_GET_OPMODE       0x65

//EEPROM locations.
#define HMC6343_SLAVE_ADDR   0x00
#define HMC6343_OPMOD_REG1   0x04
#define HMC6343_OPMOD_REG2   0x05
#define HMC6343_SN_LSB       0x06
#define HMC6343_SN_MSB       0x07
#define HMC6343_DEV_LSB      0x0A
#define HMC6343_DEV_MSB      0x0B
#define HMC6343_VAR_LSB      0x0C
#define HMC6343_VAR_MSB      0x0D
#define HMC6343_XOFFSET_LSB  0x0E
#define HMC6343_XOFFSET_MSB  0x0F
#define HMC6343_YOFFSET_LSB  0x10
#define HMC6343_YOFFSET_MSB  0x11
#define HMC6343_ZOFFSET_LSB  0x12
#define HMC6343_ZOFFSET_MSB  0x13
#define HMC6343_IIRF_LSB     0x14
#define HMC6343_IIRF_MSB     0x15
#define HMC6343_SOFT_VER     0x02


#define HMC6343_X_AXIS 0x01
#define HMC6343_Y_AXIS 0x02
#define HMC6343_Z_AXIS 0x04


// Operation mode bit masks
// LSB
#define HMC6343_COMP    0x80
#define HMC6343_CAL     0x40
#define HMC6343_FILTER  0x20
#define HMC6343_RUN     0x10
#define HMC6343_STDBY   0x08
#define HMC6343_UF      0x04
#define HMC6343_UE      0x02
#define HMC6343_LEVEL   0x01


//Operational mode register masks.
#define HMC6343_CM_MR_1HZ    0x00
#define HMC6343_CM_MR_5HZ    0x100
#define HMC6343_CM_MR_10HZ   0x200


/**
 * Honeywell HMC6343 digital compass.
 */
class HMC6343_Compass {

public:

    /**
     * Constructor.
     *
     * @param sda mbed pin to use for SDA line of I2C interface.
     * @param scl mbed pin to use for SCL line of I2C interface.
     */
    HMC6343_Compass(PinName sda, PinName scl);
    
    HMC6343_Compass(I2C& p_itc);

    /**
     * Sample the device and return the result.
     *
     * @return In heading output mode, the current heading as a number between
     *         0-3599, representing 0-359.9 degrees.
     *         In raw magnetometer X output mode, the raw output of the X-axis
     *         magnetometer.
     *         In raw magnetometer Y mode, the raw output of the Y-axis
     *         magnetometer.
     *         In magnetometer X mode, the corrected output of the X-axis
     *         magnetometer.
     *         In magnetometer Y mode, the corrected output of the Y-axis
     *         magnetometer.
     */
    void findOrientation();
    
    /**
    * Returns Heading
    *
    */
    float getHeading();
    
    /**
    * Returns Roll
    *
    */
    float getRoll();

    /**
    * Returns Pitch
    *
    */
    float getPitch();
    
    /**
    * Update bridge offsets.
    *
    * Performs a set/reset immediately.
    */
    void setReset(void);

    /**
     * Enter into or exit from calibration mode.
     *
     * @param enterOrExit 0x45 -> Exit
     *                    0x43 -> Enter
     */
    void setCalibrationMode(int enterOrExit);

    /**
     * Save the current operation mode byte to EEPROM.
     */
    void saveOpMode(void);

    /**
     * Read the memory location on the device which contains the slave address.
     *
     * @return The slave address of the device.
     */
    int getSlaveAddress(void);

    /**
     * Read the current offset for X or Y axis magnetometer.
     *
     * @param axis 0x01 -> X-axis
     *             0x02 -> Y-axis
     *             0x04 -> Z-axis
     * @return The current offset for the axis as a 16-bit number.
     */
    int getOffset(int axis);


    /**
     * Get the software version on the device.
     *
     * @return The software version number.
     */
    int getSoftwareVersion(void);

    /**
     * Get the current operation mode.
     *
     * @return 0x00 -> Standby mode
     *         0x01 -> Query mode
     *         0x02 -> Continuous mode
     */
    int getOpMode(void);

    /**
     * Set the operation mode.
     *
     * @param periodicSetReset 0x00 -> No periodic set/reset
     *                         0x01 -> Periodic set/reset
     */
    void setOpMode(int opMode);


    void setMagneticVariation(float var);
    float getMagneticVariation();
    void setMagneticDeviation(float data);
    float getMagneticDeviation();
    bool isOpModeFlagSet(int flag);
    void setIIRFilter(short data);
    short getIIRFilter();      
    void setMagOffset(int axis, int offset);
    int getMagOffset(int axis);  
    char getMeasurementRate();

private:
    
    //Functions
    I2C* i2c_;
    int  operationMode_;

    /**
     * Write to EEPROM on the device.
     *
     * @param address Address to write to.
     * @param data Data to write.
     */
    void writeEeprom(int address, int data);

    /**
     * Read EEPROM on the device.
     *
     * @param address Address to read from.
     * @return The contents of the memory address.
     */
    int readEeprom(int address);

    void writeShort(int lsb_address, short data);
    short readShort(int lsb_eprom_address);    
    
    //Variables
    float heading;
    float pitch;
    float roll;
    
    short h, r, p;
};

#endif /* HMC6343_H */
This library was made years ago (other than some limited modifications that I made to it) but I think it is still consistent with the datasheet.
By jremington
#194306
Need to know the initialization commands, which you did not post.

Do you have the "mounting orientation" setup correctly specified, so that your definition of the pitch and roll axes agrees with the compass definition?

Have you run the absolutely essential "hard iron calibration" procedure? Regardless, try it again and examine the offsets (before and after calibration).
By eawendtjr
#194311
Here is the main code with initialization and calibration.
Code: Select all

#include "mbed.h"
#include "HMC6343_Compass.h"

Serial            pc(USBTX, USBRX);

HMC6343_Compass   hmc(PB_9, PB_8);


int main()
{
    wait(0.5);
    pc.baud(9600);

    hmc.setCalibrationMode(HMC6343_ENTER_CALIB);
    wait(240);
    hmc.setCalibrationMode(HMC6343_EXIT_CALIB);

    while(1)
    {
        hmc.findOrientation();
    
        float heading = hmc.getHeading();
        float pitch = hmc.getPitch();
        float roll = hmc.getRoll();
    
        pc.printf("Heading = %f, Pitch = %f, Roll = %f \r\n", heading, pitch, roll);
    
        wait(0.1);
    }
}

I re-ran the calibration and it is looking a little better but it is still off the data sheet specified accuracy of 4 degrees by a factor of 2-4 depending on the tilt. I used a motorized camera mount to get better stability. Do you think calibration is what I should focus on or do you see any other issue with the code. Do you have any experience/insights as to successful calibration practices? Is the wait statement approach I employed sufficient?
By jremington
#194313
Yes, try the calibration procedure again.

The built in calibration procedure obviously does not use individual axial scale factors and without those, you can't achieve the best possible accuracy. With axial scale factors, you should be able to obtain heading accuracy of about +/- 1 degree.

For an excellent overview of calibration see https://edwardmallon.wordpress.com/2015 ... r-arduino/

If you go that route you have to use the raw accelerometer and magnetometer data, apply the corrections and do the tilt compensation afterward. It is not difficult, just a couple of lines of code with trig functions, or vector algebra. For one reference see https://cache.freescale.com/files/senso ... AN4248.pdf
By eawendtjr
#194366
I got it functioning a lot better but when I mount it near the main pcb--inside a plastic enclosure--for my project it locks onto a value of around 190 and changes very little. Perhaps the local field is so strong that it is not sensitive to the earth's field. Can you suggest any simple techniques (elctrical tape, peices of plastic etc.) to mitigate this effect? More design work will go into the layout once we are past the breakout board stage.
By jremington
#194372
Perhaps the local field is so strong that it is not sensitive to the earth's field.
Unlikely, but if so there is nothing you can do but to relocate the compass.

In any case you MUST calibrate the compass in its final resting place.