Path: blob/master/libraries/AP_Compass/AP_Compass_HMC5843.cpp
9437 views
/*1This program is free software: you can redistribute it and/or modify2it under the terms of the GNU General Public License as published by3the Free Software Foundation, either version 3 of the License, or4(at your option) any later version.56This program is distributed in the hope that it will be useful,7but WITHOUT ANY WARRANTY; without even the implied warranty of8MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9GNU General Public License for more details.1011You should have received a copy of the GNU General Public License12along with this program. If not, see <http://www.gnu.org/licenses/>.13*/1415/*16* AP_Compass_HMC5843.cpp - Arduino Library for HMC5843 I2C magnetometer17* Code by Jordi Muñoz and Jose Julio. DIYDrones.com18*19* Sensor is connected to I2C port20* Sensor is initialized in Continuos mode (10Hz)21*22*/23#include "AP_Compass_HMC5843.h"2425#if AP_COMPASS_HMC5843_ENABLED2627#include <assert.h>28#include <utility>29#include <stdio.h>3031#include <AP_Math/AP_Math.h>32#include <AP_HAL/utility/sparse-endian.h>33#include <AP_HAL/AP_HAL.h>34#include <AP_InertialSensor/AP_InertialSensor.h>35#include <AP_InertialSensor/AuxiliaryBus.h>3637extern const AP_HAL::HAL& hal;3839/*40* Default address: 0x1E41*/4243#define HMC5843_REG_CONFIG_A 0x0044// Valid sample averaging for 5883L45#define HMC5843_SAMPLE_AVERAGING_1 (0x00 << 5)46#define HMC5843_SAMPLE_AVERAGING_2 (0x01 << 5)47#define HMC5843_SAMPLE_AVERAGING_4 (0x02 << 5)48#define HMC5843_SAMPLE_AVERAGING_8 (0x03 << 5)4950#define HMC5843_CONF_TEMP_ENABLE (0x80)5152// Valid data output rates for 5883L53#define HMC5843_OSR_0_75HZ (0x00 << 2)54#define HMC5843_OSR_1_5HZ (0x01 << 2)55#define HMC5843_OSR_3HZ (0x02 << 2)56#define HMC5843_OSR_7_5HZ (0x03 << 2)57#define HMC5843_OSR_15HZ (0x04 << 2)58#define HMC5843_OSR_30HZ (0x05 << 2)59#define HMC5843_OSR_75HZ (0x06 << 2)60// Sensor operation modes61#define HMC5843_OPMODE_NORMAL 0x0062#define HMC5843_OPMODE_POSITIVE_BIAS 0x0163#define HMC5843_OPMODE_NEGATIVE_BIAS 0x0264#define HMC5843_OPMODE_MASK 0x036566#define HMC5843_REG_CONFIG_B 0x0167#define HMC5883L_GAIN_0_88_GA (0x00 << 5)68#define HMC5883L_GAIN_1_30_GA (0x01 << 5)69#define HMC5883L_GAIN_1_90_GA (0x02 << 5)70#define HMC5883L_GAIN_2_50_GA (0x03 << 5)71#define HMC5883L_GAIN_4_00_GA (0x04 << 5)72#define HMC5883L_GAIN_4_70_GA (0x05 << 5)73#define HMC5883L_GAIN_5_60_GA (0x06 << 5)74#define HMC5883L_GAIN_8_10_GA (0x07 << 5)7576#define HMC5843_GAIN_0_70_GA (0x00 << 5)77#define HMC5843_GAIN_1_00_GA (0x01 << 5)78#define HMC5843_GAIN_1_50_GA (0x02 << 5)79#define HMC5843_GAIN_2_00_GA (0x03 << 5)80#define HMC5843_GAIN_3_20_GA (0x04 << 5)81#define HMC5843_GAIN_3_80_GA (0x05 << 5)82#define HMC5843_GAIN_4_50_GA (0x06 << 5)83#define HMC5843_GAIN_6_50_GA (0x07 << 5)8485#define HMC5843_REG_MODE 0x0286#define HMC5843_MODE_CONTINUOUS 0x0087#define HMC5843_MODE_SINGLE 0x018889#define HMC5843_REG_DATA_OUTPUT_X_MSB 0x039091#define HMC5843_REG_ID_A 0x0A929394AP_Compass_HMC5843::AP_Compass_HMC5843(AP_HMC5843_BusDriver *bus,95bool force_external, enum Rotation rotation)96: _bus(bus)97, _rotation(rotation)98, _force_external(force_external)99{100}101102AP_Compass_HMC5843::~AP_Compass_HMC5843()103{104delete _bus;105}106107AP_Compass_Backend *AP_Compass_HMC5843::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev,108bool force_external,109enum Rotation rotation)110{111if (!dev) {112return nullptr;113}114AP_HMC5843_BusDriver *bus = NEW_NOTHROW AP_HMC5843_BusDriver_HALDevice(std::move(dev));115if (!bus) {116return nullptr;117}118119AP_Compass_HMC5843 *sensor = NEW_NOTHROW AP_Compass_HMC5843(bus, force_external, rotation);120if (!sensor || !sensor->init()) {121delete sensor;122return nullptr;123}124125return sensor;126}127128#if AP_INERTIALSENSOR_ENABLED129AP_Compass_Backend *AP_Compass_HMC5843::probe_mpu6000(enum Rotation rotation)130{131AP_InertialSensor &ins = *AP_InertialSensor::get_singleton();132133AP_HMC5843_BusDriver *bus =134NEW_NOTHROW AP_HMC5843_BusDriver_Auxiliary(ins, HAL_INS_MPU60XX_SPI,135HAL_COMPASS_HMC5843_I2C_ADDR);136if (!bus) {137return nullptr;138}139140AP_Compass_HMC5843 *sensor = NEW_NOTHROW AP_Compass_HMC5843(bus, false, rotation);141if (!sensor || !sensor->init()) {142delete sensor;143return nullptr;144}145146return sensor;147}148#endif149150bool AP_Compass_HMC5843::init()151{152AP_HAL::Semaphore *bus_sem = _bus->get_semaphore();153154if (!bus_sem) {155DEV_PRINTF("HMC5843: Unable to get bus semaphore\n");156return false;157}158bus_sem->take_blocking();159160// high retries for init161_bus->set_retries(10);162163if (!_bus->configure()) {164DEV_PRINTF("HMC5843: Could not configure the bus\n");165goto errout;166}167168if (!_check_whoami()) {169goto errout;170}171172if (!_calibrate()) {173DEV_PRINTF("HMC5843: Could not calibrate sensor\n");174goto errout;175}176177if (!_setup_sampling_mode()) {178goto errout;179}180181if (!_bus->start_measurements()) {182DEV_PRINTF("HMC5843: Could not start measurements on bus\n");183goto errout;184}185186_initialised = true;187188// lower retries for run189_bus->set_retries(3);190191bus_sem->give();192193// perform an initial read194read();195196//register compass instance197_bus->set_device_type(DEVTYPE_HMC5883);198if (!register_compass(_bus->get_bus_id())) {199return false;200}201202set_rotation(_rotation);203204if (_force_external) {205set_external(true);206}207208// read from sensor at 75Hz209_bus->register_periodic_callback(13333,210FUNCTOR_BIND_MEMBER(&AP_Compass_HMC5843::_timer, void));211212DEV_PRINTF("HMC5843 found on bus 0x%x\n", (unsigned)_bus->get_bus_id());213214return true;215216errout:217bus_sem->give();218return false;219}220221/*222* take a reading from the magnetometer223*224* bus semaphore has been taken already by HAL225*/226void AP_Compass_HMC5843::_timer()227{228bool result = _read_sample();229230// always ask for a new sample231_take_sample();232233if (!result) {234return;235}236237// get raw_field - sensor frame, uncorrected238Vector3f raw_field = Vector3f(_mag_x, _mag_y, _mag_z);239raw_field *= _gain_scale;240241// rotate to the desired orientation242if (is_external()) {243raw_field.rotate(ROTATION_YAW_90);244}245246// We expect to do reads at 10Hz, and we get new data at most 75Hz, so we247// don't expect to accumulate more than 8 before a read; let's make it248// 14 to give more room for the initialization phase249accumulate_sample(raw_field, 14);250}251252/*253* Take accumulated reads from the magnetometer or try to read once if no254* valid data255*256* bus semaphore must not be locked257*/258void AP_Compass_HMC5843::read()259{260if (!_initialised) {261// someone has tried to enable a compass for the first time262// mid-flight .... we can't do that yet (especially as we won't263// have the right orientation!)264return;265}266267drain_accumulated_samples(&_scaling);268}269270bool AP_Compass_HMC5843::_setup_sampling_mode()271{272_gain_scale = (1.0f / 1090) * 1000;273if (!_bus->register_write(HMC5843_REG_CONFIG_A,274HMC5843_CONF_TEMP_ENABLE |275HMC5843_OSR_75HZ |276HMC5843_SAMPLE_AVERAGING_1) ||277!_bus->register_write(HMC5843_REG_CONFIG_B,278HMC5883L_GAIN_1_30_GA) ||279!_bus->register_write(HMC5843_REG_MODE,280HMC5843_MODE_SINGLE)) {281return false;282}283return true;284}285286/*287* Read Sensor data - bus semaphore must be taken288*/289bool AP_Compass_HMC5843::_read_sample()290{291struct PACKED {292be16_t rx;293be16_t ry;294be16_t rz;295} val;296int16_t rx, ry, rz;297298if (!_bus->block_read(HMC5843_REG_DATA_OUTPUT_X_MSB, (uint8_t *) &val, sizeof(val))){299return false;300}301302rx = be16toh(val.rx);303ry = be16toh(val.rz);304rz = be16toh(val.ry);305306if (rx == -4096 || ry == -4096 || rz == -4096) {307// no valid data available308return false;309}310311_mag_x = -rx;312_mag_y = ry;313_mag_z = -rz;314315return true;316}317318319/*320ask for a new oneshot sample321*/322void AP_Compass_HMC5843::_take_sample()323{324_bus->register_write(HMC5843_REG_MODE,325HMC5843_MODE_SINGLE);326}327328bool AP_Compass_HMC5843::_check_whoami()329{330uint8_t id[3];331if (!_bus->block_read(HMC5843_REG_ID_A, id, 3)) {332// can't talk on bus333return false;334}335if (memcmp(id, "H43", 3) != 0) {336// not a HMC5x83 device337return false;338}339340return true;341}342343bool AP_Compass_HMC5843::_calibrate()344{345uint8_t calibration_gain;346int numAttempts = 0, good_count = 0;347bool success = false;348349calibration_gain = HMC5883L_GAIN_2_50_GA;350351/*352* the expected values are based on observation of real sensors353*/354float expected[3] = { 1.16*600, 1.08*600, 1.16*600 };355356uint8_t base_config = HMC5843_OSR_15HZ;357uint8_t num_samples = 0;358359while (success == 0 && numAttempts < 25 && good_count < 5) {360numAttempts++;361362// force positiveBias (compass should return 715 for all channels)363if (!_bus->register_write(HMC5843_REG_CONFIG_A,364base_config | HMC5843_OPMODE_POSITIVE_BIAS)) {365// compass not responding on the bus366continue;367}368369hal.scheduler->delay(50);370371// set gains372if (!_bus->register_write(HMC5843_REG_CONFIG_B, calibration_gain) ||373!_bus->register_write(HMC5843_REG_MODE, HMC5843_MODE_SINGLE)) {374continue;375}376377// read values from the compass378hal.scheduler->delay(50);379if (!_read_sample()) {380// we didn't read valid values381continue;382}383384num_samples++;385386float cal[3];387388// hal.console->printf("mag %d %d %d\n", _mag_x, _mag_y, _mag_z);389390cal[0] = fabsf(expected[0] / _mag_x);391cal[1] = fabsf(expected[1] / _mag_y);392cal[2] = fabsf(expected[2] / _mag_z);393394// hal.console->printf("cal=%.2f %.2f %.2f\n", cal[0], cal[1], cal[2]);395396// we throw away the first two samples as the compass may397// still be changing its state from the application of the398// strap excitation. After that we accept values in a399// reasonable range400if (numAttempts <= 2) {401continue;402}403404#define IS_CALIBRATION_VALUE_VALID(val) (val > 0.7f && val < 1.35f)405406if (IS_CALIBRATION_VALUE_VALID(cal[0]) &&407IS_CALIBRATION_VALUE_VALID(cal[1]) &&408IS_CALIBRATION_VALUE_VALID(cal[2])) {409// hal.console->printf("car=%.2f %.2f %.2f good\n", cal[0], cal[1], cal[2]);410good_count++;411412_scaling[0] += cal[0];413_scaling[1] += cal[1];414_scaling[2] += cal[2];415}416417#undef IS_CALIBRATION_VALUE_VALID418419#if 0420/* useful for debugging */421hal.console->printf("MagX: %d MagY: %d MagZ: %d\n", (int)_mag_x, (int)_mag_y, (int)_mag_z);422hal.console->printf("CalX: %.2f CalY: %.2f CalZ: %.2f\n", cal[0], cal[1], cal[2]);423#endif424}425426_bus->register_write(HMC5843_REG_CONFIG_A, base_config);427428if (good_count >= 5) {429_scaling[0] = _scaling[0] / good_count;430_scaling[1] = _scaling[1] / good_count;431_scaling[2] = _scaling[2] / good_count;432success = true;433} else {434/* best guess */435_scaling[0] = 1.0;436_scaling[1] = 1.0;437_scaling[2] = 1.0;438if (num_samples > 5) {439// a sensor can be broken for calibration but still440// otherwise workable, accept it if we are reading samples441success = true;442}443}444445#if 0446printf("scaling: %.2f %.2f %.2f\n",447_scaling[0], _scaling[1], _scaling[2]);448#endif449450return success;451}452453/* AP_HAL::Device implementation of the HMC5843 */454AP_HMC5843_BusDriver_HALDevice::AP_HMC5843_BusDriver_HALDevice(AP_HAL::OwnPtr<AP_HAL::Device> dev)455: _dev(std::move(dev))456{457// set read and auto-increment flags on SPI458if (_dev->bus_type() == AP_HAL::Device::BUS_TYPE_SPI) {459_dev->set_read_flag(0xC0);460}461}462463bool AP_HMC5843_BusDriver_HALDevice::block_read(uint8_t reg, uint8_t *buf, uint32_t size)464{465return _dev->read_registers(reg, buf, size);466}467468bool AP_HMC5843_BusDriver_HALDevice::register_read(uint8_t reg, uint8_t *val)469{470return _dev->read_registers(reg, val, 1);471}472473bool AP_HMC5843_BusDriver_HALDevice::register_write(uint8_t reg, uint8_t val)474{475return _dev->write_register(reg, val);476}477478AP_HAL::Semaphore *AP_HMC5843_BusDriver_HALDevice::get_semaphore()479{480return _dev->get_semaphore();481}482483AP_HAL::Device::PeriodicHandle AP_HMC5843_BusDriver_HALDevice::register_periodic_callback(uint32_t period_usec, AP_HAL::Device::PeriodicCb cb)484{485return _dev->register_periodic_callback(period_usec, cb);486}487488489#if AP_INERTIALSENSOR_ENABLED490/* HMC5843 on an auxiliary bus of IMU driver */491AP_HMC5843_BusDriver_Auxiliary::AP_HMC5843_BusDriver_Auxiliary(AP_InertialSensor &ins, uint8_t backend_id,492uint8_t addr)493{494/*495* Only initialize members. Fails are handled by configure or while496* getting the semaphore497*/498_bus = ins.get_auxiliary_bus(backend_id);499if (!_bus) {500return;501}502503_slave = _bus->request_next_slave(addr);504}505506AP_HMC5843_BusDriver_Auxiliary::~AP_HMC5843_BusDriver_Auxiliary()507{508/* After started it's owned by AuxiliaryBus */509if (!_started) {510delete _slave;511}512}513514bool AP_HMC5843_BusDriver_Auxiliary::block_read(uint8_t reg, uint8_t *buf, uint32_t size)515{516if (_started) {517/*518* We can only read a block when reading the block of sample values -519* calling with any other value is a mistake520*/521if (reg != HMC5843_REG_DATA_OUTPUT_X_MSB) {522return false;523}524525int n = _slave->read(buf);526return n == static_cast<int>(size);527}528529int r = _slave->passthrough_read(reg, buf, size);530531return r > 0 && static_cast<uint32_t>(r) == size;532}533534bool AP_HMC5843_BusDriver_Auxiliary::register_read(uint8_t reg, uint8_t *val)535{536return _slave->passthrough_read(reg, val, 1) == 1;537}538539bool AP_HMC5843_BusDriver_Auxiliary::register_write(uint8_t reg, uint8_t val)540{541return _slave->passthrough_write(reg, val) == 1;542}543544AP_HAL::Semaphore *AP_HMC5843_BusDriver_Auxiliary::get_semaphore()545{546return _bus->get_semaphore();547}548549550bool AP_HMC5843_BusDriver_Auxiliary::configure()551{552if (!_bus || !_slave) {553return false;554}555return true;556}557558bool AP_HMC5843_BusDriver_Auxiliary::start_measurements()559{560if (_bus->register_periodic_read(_slave, HMC5843_REG_DATA_OUTPUT_X_MSB, 6) < 0) {561return false;562}563564_started = true;565566return true;567}568569AP_HAL::Device::PeriodicHandle AP_HMC5843_BusDriver_Auxiliary::register_periodic_callback(uint32_t period_usec, AP_HAL::Device::PeriodicCb cb)570{571return _bus->register_periodic_callback(period_usec, cb);572}573574// set device type within a device class575void AP_HMC5843_BusDriver_Auxiliary::set_device_type(uint8_t devtype)576{577_bus->set_device_type(devtype);578}579580// return 24 bit bus identifier581uint32_t AP_HMC5843_BusDriver_Auxiliary::get_bus_id(void) const582{583return _bus->get_bus_id();584}585#endif // AP_INERTIALSENSOR_ENABLED586587#endif // AP_COMPASS_HMC5843_ENABLED588589590