Path: blob/master/libraries/AP_Compass/AP_Compass_LSM303D.cpp
9448 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#include "AP_Compass_LSM303D.h"1617#if AP_COMPASS_LSM303D_ENABLED1819#include <utility>2021#include <AP_HAL/AP_HAL.h>22#include <AP_Math/AP_Math.h>2324extern const AP_HAL::HAL &hal;2526#if CONFIG_HAL_BOARD == HAL_BOARD_LINUX27#include <AP_HAL_Linux/GPIO.h>28#endif2930#ifndef LSM303D_DRDY_M_PIN31#define LSM303D_DRDY_M_PIN -132#endif3334/* SPI protocol address bits */35#define DIR_READ (1<<7)36#define DIR_WRITE (0<<7)37#define ADDR_INCREMENT (1<<6)3839/* register addresses: A: accel, M: mag, T: temp */40#define ADDR_WHO_AM_I 0x0F41#define WHO_I_AM 0x494243#define ADDR_OUT_TEMP_L 0x0544#define ADDR_OUT_TEMP_H 0x0645#define ADDR_STATUS_M 0x0746#define ADDR_OUT_X_L_M 0x0847#define ADDR_OUT_X_H_M 0x0948#define ADDR_OUT_Y_L_M 0x0A49#define ADDR_OUT_Y_H_M 0x0B50#define ADDR_OUT_Z_L_M 0x0C51#define ADDR_OUT_Z_H_M 0x0D5253#define ADDR_INT_CTRL_M 0x1254#define ADDR_INT_SRC_M 0x1355#define ADDR_REFERENCE_X 0x1c56#define ADDR_REFERENCE_Y 0x1d57#define ADDR_REFERENCE_Z 0x1e5859#define ADDR_STATUS_A 0x2760#define ADDR_OUT_X_L_A 0x2861#define ADDR_OUT_X_H_A 0x2962#define ADDR_OUT_Y_L_A 0x2A63#define ADDR_OUT_Y_H_A 0x2B64#define ADDR_OUT_Z_L_A 0x2C65#define ADDR_OUT_Z_H_A 0x2D6667#define ADDR_CTRL_REG0 0x1F68#define ADDR_CTRL_REG1 0x2069#define ADDR_CTRL_REG2 0x2170#define ADDR_CTRL_REG3 0x2271#define ADDR_CTRL_REG4 0x2372#define ADDR_CTRL_REG5 0x2473#define ADDR_CTRL_REG6 0x2574#define ADDR_CTRL_REG7 0x267576#define ADDR_FIFO_CTRL 0x2e77#define ADDR_FIFO_SRC 0x2f7879#define ADDR_IG_CFG1 0x3080#define ADDR_IG_SRC1 0x3181#define ADDR_IG_THS1 0x3282#define ADDR_IG_DUR1 0x3383#define ADDR_IG_CFG2 0x3484#define ADDR_IG_SRC2 0x3585#define ADDR_IG_THS2 0x3686#define ADDR_IG_DUR2 0x3787#define ADDR_CLICK_CFG 0x3888#define ADDR_CLICK_SRC 0x3989#define ADDR_CLICK_THS 0x3a90#define ADDR_TIME_LIMIT 0x3b91#define ADDR_TIME_LATENCY 0x3c92#define ADDR_TIME_WINDOW 0x3d93#define ADDR_ACT_THS 0x3e94#define ADDR_ACT_DUR 0x3f9596#define REG1_RATE_BITS_A ((1<<7) | (1<<6) | (1<<5) | (1<<4))97#define REG1_POWERDOWN_A ((0<<7) | (0<<6) | (0<<5) | (0<<4))98#define REG1_RATE_3_125HZ_A ((0<<7) | (0<<6) | (0<<5) | (1<<4))99#define REG1_RATE_6_25HZ_A ((0<<7) | (0<<6) | (1<<5) | (0<<4))100#define REG1_RATE_12_5HZ_A ((0<<7) | (0<<6) | (1<<5) | (1<<4))101#define REG1_RATE_25HZ_A ((0<<7) | (1<<6) | (0<<5) | (0<<4))102#define REG1_RATE_50HZ_A ((0<<7) | (1<<6) | (0<<5) | (1<<4))103#define REG1_RATE_100HZ_A ((0<<7) | (1<<6) | (1<<5) | (0<<4))104#define REG1_RATE_200HZ_A ((0<<7) | (1<<6) | (1<<5) | (1<<4))105#define REG1_RATE_400HZ_A ((1<<7) | (0<<6) | (0<<5) | (0<<4))106#define REG1_RATE_800HZ_A ((1<<7) | (0<<6) | (0<<5) | (1<<4))107#define REG1_RATE_1600HZ_A ((1<<7) | (0<<6) | (1<<5) | (0<<4))108109#define REG1_BDU_UPDATE (1<<3)110#define REG1_Z_ENABLE_A (1<<2)111#define REG1_Y_ENABLE_A (1<<1)112#define REG1_X_ENABLE_A (1<<0)113114#define REG2_ANTIALIAS_FILTER_BW_BITS_A ((1<<7) | (1<<6))115#define REG2_AA_FILTER_BW_773HZ_A ((0<<7) | (0<<6))116#define REG2_AA_FILTER_BW_194HZ_A ((0<<7) | (1<<6))117#define REG2_AA_FILTER_BW_362HZ_A ((1<<7) | (0<<6))118#define REG2_AA_FILTER_BW_50HZ_A ((1<<7) | (1<<6))119120#define REG2_FULL_SCALE_BITS_A ((1<<5) | (1<<4) | (1<<3))121#define REG2_FULL_SCALE_2G_A ((0<<5) | (0<<4) | (0<<3))122#define REG2_FULL_SCALE_4G_A ((0<<5) | (0<<4) | (1<<3))123#define REG2_FULL_SCALE_6G_A ((0<<5) | (1<<4) | (0<<3))124#define REG2_FULL_SCALE_8G_A ((0<<5) | (1<<4) | (1<<3))125#define REG2_FULL_SCALE_16G_A ((1<<5) | (0<<4) | (0<<3))126127#define REG5_ENABLE_T (1<<7)128129#define REG5_RES_HIGH_M ((1<<6) | (1<<5) | (1<<7))130#define REG5_RES_LOW_M ((0<<6) | (0<<5))131132#define REG5_RATE_BITS_M ((1<<4) | (1<<3) | (1<<2))133#define REG5_RATE_3_125HZ_M ((0<<4) | (0<<3) | (0<<2))134#define REG5_RATE_6_25HZ_M ((0<<4) | (0<<3) | (1<<2))135#define REG5_RATE_12_5HZ_M ((0<<4) | (1<<3) | (0<<2))136#define REG5_RATE_25HZ_M ((0<<4) | (1<<3) | (1<<2))137#define REG5_RATE_50HZ_M ((1<<4) | (0<<3) | (0<<2))138#define REG5_RATE_100HZ_M ((1<<4) | (0<<3) | (1<<2))139#define REG5_RATE_DO_NOT_USE_M ((1<<4) | (1<<3) | (0<<2))140141#define REG6_FULL_SCALE_BITS_M ((1<<6) | (1<<5))142#define REG6_FULL_SCALE_2GA_M ((0<<6) | (0<<5))143#define REG6_FULL_SCALE_4GA_M ((0<<6) | (1<<5))144#define REG6_FULL_SCALE_8GA_M ((1<<6) | (0<<5))145#define REG6_FULL_SCALE_12GA_M ((1<<6) | (1<<5))146147#define REG7_CONT_MODE_M ((0<<1) | (0<<0))148149#define INT_CTRL_M 0x12150#define INT_SRC_M 0x13151152#define LSM303D_MAG_DEFAULT_RANGE_GA 2153#define LSM303D_MAG_DEFAULT_RATE 100154155AP_Compass_LSM303D::AP_Compass_LSM303D(AP_HAL::OwnPtr<AP_HAL::Device> dev)156: _dev(std::move(dev))157{158}159160AP_Compass_Backend *AP_Compass_LSM303D::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev,161enum Rotation rotation)162{163if (!dev) {164return nullptr;165}166AP_Compass_LSM303D *sensor = NEW_NOTHROW AP_Compass_LSM303D(std::move(dev));167if (!sensor || !sensor->init(rotation)) {168delete sensor;169return nullptr;170}171172return sensor;173}174175uint8_t AP_Compass_LSM303D::_register_read(uint8_t reg)176{177uint8_t val = 0;178179reg |= DIR_READ;180_dev->read_registers(reg, &val, 1);181182return val;183}184185bool AP_Compass_LSM303D::_block_read(uint8_t reg, uint8_t *buf, uint32_t size)186{187reg |= DIR_READ | ADDR_INCREMENT;188return _dev->read_registers(reg, buf, size);189}190191void AP_Compass_LSM303D::_register_write(uint8_t reg, uint8_t val)192{193_dev->write_register(reg, val);194}195196void AP_Compass_LSM303D::_register_modify(uint8_t reg, uint8_t clearbits, uint8_t setbits)197{198uint8_t val;199200val = _register_read(reg);201val &= ~clearbits;202val |= setbits;203_register_write(reg, val);204}205206/**207* Return true if the LSM303D has new data available for both the mag and208* the accels.209*/210bool AP_Compass_LSM303D::_data_ready()211{212return _drdy_pin_m == nullptr || (_drdy_pin_m->read() != 0);213}214215216// Read Sensor data217bool AP_Compass_LSM303D::_read_sample()218{219struct PACKED {220uint8_t status;221int16_t x;222int16_t y;223int16_t z;224} rx;225226if (_register_read(ADDR_CTRL_REG7) != _reg7_expected) {227DEV_PRINTF("LSM303D _read_data_transaction_accel: _reg7_expected unexpected\n");228return false;229}230231if (!_data_ready()) {232return false;233}234235if (!_block_read(ADDR_STATUS_M, (uint8_t *) &rx, sizeof(rx))) {236return false;237}238239/* check for overrun */240if ((rx.status & 0x70) != 0) {241return false;242}243244if (rx.x == 0 && rx.y == 0 && rx.z == 0) {245return false;246}247248_mag_x = rx.x;249_mag_y = rx.y;250_mag_z = rx.z;251252return true;253}254255bool AP_Compass_LSM303D::init(enum Rotation rotation)256{257if (LSM303D_DRDY_M_PIN >= 0) {258_drdy_pin_m = hal.gpio->channel(LSM303D_DRDY_M_PIN);259_drdy_pin_m->mode(HAL_GPIO_INPUT);260}261262bool success = _hardware_init();263264if (!success) {265return false;266}267268_initialised = true;269270/* register the compass instance in the frontend */271_dev->set_device_type(DEVTYPE_LSM303D);272if (!register_compass(_dev->get_bus_id())) {273return false;274}275276set_rotation(rotation);277278// read at 91Hz. We don't run at 100Hz as fetching data too fast can cause some very279// odd periodic changes in the output data280_dev->register_periodic_callback(11000, FUNCTOR_BIND_MEMBER(&AP_Compass_LSM303D::_update, void));281282return true;283}284285bool AP_Compass_LSM303D::_hardware_init()286{287_dev->get_semaphore()->take_blocking();288289// initially run the bus at low speed290_dev->set_speed(AP_HAL::Device::SPEED_LOW);291292// Test WHOAMI293uint8_t whoami = _register_read(ADDR_WHO_AM_I);294if (whoami != WHO_I_AM) {295goto fail_whoami;296}297298uint8_t tries;299for (tries = 0; tries < 5; tries++) {300// ensure the chip doesn't interpret any other bus traffic as I2C301_disable_i2c();302303/* enable mag */304_reg7_expected = REG7_CONT_MODE_M;305_register_write(ADDR_CTRL_REG7, _reg7_expected);306_register_write(ADDR_CTRL_REG5, REG5_RES_HIGH_M);307308// DRDY on MAG on INT2309_register_write(ADDR_CTRL_REG4, 0x04);310311_mag_set_range(LSM303D_MAG_DEFAULT_RANGE_GA);312_mag_set_samplerate(LSM303D_MAG_DEFAULT_RATE);313314hal.scheduler->delay(10);315if (_data_ready()) {316break;317}318}319if (tries == 5) {320DEV_PRINTF("Failed to boot LSM303D 5 times\n");321goto fail_tries;322}323324_dev->set_speed(AP_HAL::Device::SPEED_HIGH);325_dev->get_semaphore()->give();326327return true;328329fail_tries:330fail_whoami:331_dev->get_semaphore()->give();332_dev->set_speed(AP_HAL::Device::SPEED_HIGH);333return false;334}335336void AP_Compass_LSM303D::_update()337{338if (!_read_sample()) {339return;340}341342Vector3f raw_field = Vector3f(_mag_x, _mag_y, _mag_z) * _mag_range_scale;343344accumulate_sample(raw_field, 10);345}346347// Read Sensor data348void AP_Compass_LSM303D::read()349{350if (!_initialised) {351return;352}353354drain_accumulated_samples();355}356357void AP_Compass_LSM303D::_disable_i2c()358{359// TODO: use the register names360uint8_t a = _register_read(0x02);361_register_write(0x02, (0x10 | a));362a = _register_read(0x02);363_register_write(0x02, (0xF7 & a));364a = _register_read(0x15);365_register_write(0x15, (0x80 | a));366a = _register_read(0x02);367_register_write(0x02, (0xE7 & a));368}369370bool AP_Compass_LSM303D::_mag_set_range(uint8_t max_ga)371{372uint8_t setbits = 0;373uint8_t clearbits = REG6_FULL_SCALE_BITS_M;374float new_scale_ga_digit = 0.0f;375376if (max_ga == 0) {377max_ga = 12;378}379380if (max_ga <= 2) {381_mag_range_ga = 2;382setbits |= REG6_FULL_SCALE_2GA_M;383new_scale_ga_digit = 0.080f;384} else if (max_ga <= 4) {385_mag_range_ga = 4;386setbits |= REG6_FULL_SCALE_4GA_M;387new_scale_ga_digit = 0.160f;388} else if (max_ga <= 8) {389_mag_range_ga = 8;390setbits |= REG6_FULL_SCALE_8GA_M;391new_scale_ga_digit = 0.320f;392} else if (max_ga <= 12) {393_mag_range_ga = 12;394setbits |= REG6_FULL_SCALE_12GA_M;395new_scale_ga_digit = 0.479f;396} else {397return false;398}399400_mag_range_scale = new_scale_ga_digit;401_register_modify(ADDR_CTRL_REG6, clearbits, setbits);402403return true;404}405406bool AP_Compass_LSM303D::_mag_set_samplerate(uint16_t frequency)407{408uint8_t setbits = 0;409uint8_t clearbits = REG5_RATE_BITS_M;410411if (frequency == 0) {412frequency = 100;413}414415if (frequency <= 25) {416setbits |= REG5_RATE_25HZ_M;417_mag_samplerate = 25;418} else if (frequency <= 50) {419setbits |= REG5_RATE_50HZ_M;420_mag_samplerate = 50;421} else if (frequency <= 100) {422setbits |= REG5_RATE_100HZ_M;423_mag_samplerate = 100;424} else {425return false;426}427428_register_modify(ADDR_CTRL_REG5, clearbits, setbits);429430return true;431}432433#endif // AP_COMPASS_LSM303D_ENABLED434435436