mirror of
https://github.com/ArduPilot/ardupilot.git
synced 2026-10-06 19:00:27 +08:00
rather than each backend storing this itself. This allows many methods to assume that they are working on the current instance number rather than being passed the instance number registering the compass via the compass backend method now infers setting the device ID
306 lines
9.4 KiB
C++
306 lines
9.4 KiB
C++
/*
|
|
* This file is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This file 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/>.
|
|
*/
|
|
/*
|
|
Driver by Andrew Tridgell, Nov 2016
|
|
*/
|
|
#include "AP_Compass_MMC3416.h"
|
|
|
|
#if AP_COMPASS_MMC3416_ENABLED
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
#include <utility>
|
|
#include <AP_Math/AP_Math.h>
|
|
#include <stdio.h>
|
|
#include <AP_Logger/AP_Logger.h>
|
|
|
|
extern const AP_HAL::HAL &hal;
|
|
|
|
#define REG_PRODUCT_ID 0x20
|
|
#define REG_XOUT_L 0x00
|
|
#define REG_STATUS 0x06
|
|
#define REG_CONTROL0 0x07
|
|
#define REG_CONTROL1 0x08
|
|
|
|
// bits in REG_CONTROL0
|
|
#define REG_CONTROL0_REFILL 0x80
|
|
#define REG_CONTROL0_RESET 0x40
|
|
#define REG_CONTROL0_SET 0x20
|
|
#define REG_CONTROL0_NB 0x10
|
|
#define REG_CONTROL0_TM 0x01
|
|
|
|
// datasheet says 50ms min for refill
|
|
#define MIN_DELAY_SET_RESET 50
|
|
|
|
AP_Compass_Backend *AP_Compass_MMC3416::probe(AP_HAL::OwnPtr<AP_HAL::I2CDevice> dev,
|
|
bool force_external,
|
|
enum Rotation rotation)
|
|
{
|
|
if (!dev) {
|
|
return nullptr;
|
|
}
|
|
AP_Compass_MMC3416 *sensor = NEW_NOTHROW AP_Compass_MMC3416(std::move(dev), force_external, rotation);
|
|
if (!sensor || !sensor->init()) {
|
|
delete sensor;
|
|
return nullptr;
|
|
}
|
|
|
|
return sensor;
|
|
}
|
|
|
|
AP_Compass_MMC3416::AP_Compass_MMC3416(AP_HAL::OwnPtr<AP_HAL::Device> _dev,
|
|
bool _force_external,
|
|
enum Rotation _rotation)
|
|
: dev(std::move(_dev))
|
|
, force_external(_force_external)
|
|
, rotation(_rotation)
|
|
{
|
|
}
|
|
|
|
bool AP_Compass_MMC3416::init()
|
|
{
|
|
dev->get_semaphore()->take_blocking();
|
|
|
|
dev->set_retries(10);
|
|
|
|
uint8_t whoami;
|
|
if (!dev->read_registers(REG_PRODUCT_ID, &whoami, 1) ||
|
|
whoami != 0x06) {
|
|
// not a MMC3416
|
|
dev->get_semaphore()->give();
|
|
return false;
|
|
}
|
|
|
|
// reset sensor
|
|
dev->write_register(REG_CONTROL1, 0x80);
|
|
hal.scheduler->delay(10);
|
|
|
|
dev->write_register(REG_CONTROL0, 0x00); // single shot
|
|
dev->write_register(REG_CONTROL1, 0x00); // 16 bit, 7.92ms
|
|
|
|
dev->get_semaphore()->give();
|
|
|
|
/* register the compass instance in the frontend */
|
|
dev->set_device_type(DEVTYPE_MMC3416);
|
|
if (!register_compass(dev->get_bus_id())) {
|
|
return false;
|
|
}
|
|
|
|
printf("Found a MMC3416 on 0x%x as compass %u\n", unsigned(dev->get_bus_id()), instance);
|
|
|
|
set_rotation(rotation);
|
|
|
|
if (force_external) {
|
|
set_external(true);
|
|
}
|
|
|
|
dev->set_retries(1);
|
|
|
|
// call timer() at 100Hz
|
|
dev->register_periodic_callback(10000,
|
|
FUNCTOR_BIND_MEMBER(&AP_Compass_MMC3416::timer, void));
|
|
|
|
// wait 250ms for the compass to make it's initial readings
|
|
hal.scheduler->delay(250);
|
|
|
|
return true;
|
|
}
|
|
|
|
void AP_Compass_MMC3416::timer()
|
|
{
|
|
const uint16_t measure_count_limit = 50;
|
|
const uint16_t zero_offset = 32768; // 16 bit mode
|
|
const uint16_t sensitivity = 2048; // counts per Gauss, 16 bit mode
|
|
const float counts_to_milliGauss = 1.0e3f / sensitivity;
|
|
|
|
uint32_t now = AP_HAL::millis();
|
|
if (now - last_sample_ms > 500) {
|
|
// seems to be stuck or on first sample, reset state machine
|
|
state = STATE_REFILL1;
|
|
last_sample_ms = now;
|
|
}
|
|
|
|
/*
|
|
we use the SET/RESET method to remove bridge offset every
|
|
measure_count_limit measurements. This involves a fairly complex
|
|
state machine, but means we are much less sensitive to
|
|
temperature changes
|
|
*/
|
|
switch (state) {
|
|
case STATE_REFILL1:
|
|
if (dev->write_register(REG_CONTROL0, REG_CONTROL0_REFILL)) {
|
|
state = STATE_REFILL1_WAIT;
|
|
refill_start_ms = AP_HAL::millis();
|
|
}
|
|
break;
|
|
|
|
case STATE_REFILL1_WAIT: {
|
|
uint8_t status;
|
|
if (AP_HAL::millis() - refill_start_ms > MIN_DELAY_SET_RESET &&
|
|
dev->read_registers(REG_STATUS, &status, 1) &&
|
|
(status & 0x02) == 0) {
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_SET) ||
|
|
!dev->write_register(REG_CONTROL0, REG_CONTROL0_TM)) { // Take Measurement
|
|
state = STATE_REFILL1;
|
|
} else {
|
|
state = STATE_MEASURE_WAIT1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case STATE_MEASURE_WAIT1: {
|
|
uint8_t status;
|
|
if (dev->read_registers(REG_STATUS, &status, 1) && (status & 1)) {
|
|
if (!dev->read_registers(REG_XOUT_L, (uint8_t *)&data0[0], 6)) {
|
|
state = STATE_REFILL1;
|
|
break;
|
|
}
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_REFILL)) {
|
|
state = STATE_REFILL1;
|
|
} else {
|
|
state = STATE_REFILL2_WAIT;
|
|
refill_start_ms = AP_HAL::millis();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case STATE_REFILL2_WAIT: {
|
|
uint8_t status;
|
|
if (AP_HAL::millis() - refill_start_ms > MIN_DELAY_SET_RESET &&
|
|
dev->read_registers(REG_STATUS, &status, 1) &&
|
|
(status & 0x02) == 0) {
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_RESET) ||
|
|
!dev->write_register(REG_CONTROL0, REG_CONTROL0_TM)) { // Take Measurement
|
|
state = STATE_REFILL1;
|
|
} else {
|
|
state = STATE_MEASURE_WAIT2;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case STATE_MEASURE_WAIT2: {
|
|
uint8_t status;
|
|
if (!dev->read_registers(REG_STATUS, &status, 1) || !(status & 1)) {
|
|
break;
|
|
}
|
|
uint16_t data1[3];
|
|
if (!dev->read_registers(REG_XOUT_L, (uint8_t *)&data1[0], 6)) {
|
|
state = STATE_REFILL1;
|
|
break;
|
|
}
|
|
Vector3f field;
|
|
|
|
/*
|
|
calculate field and offset
|
|
*/
|
|
Vector3f f1(float(data0[0]) - zero_offset,
|
|
float(data0[1]) - zero_offset,
|
|
float(data0[2]) - zero_offset);
|
|
Vector3f f2(float(data1[0]) - zero_offset,
|
|
float(data1[1]) - zero_offset,
|
|
float(data1[2]) - zero_offset);
|
|
field = (f1 - f2) * (counts_to_milliGauss / 2);
|
|
Vector3f new_offset = (f1 + f2) * (counts_to_milliGauss / 2);
|
|
if (!have_initial_offset) {
|
|
offset = new_offset;
|
|
have_initial_offset = true;
|
|
} else {
|
|
// low pass changes to the offset
|
|
offset = offset * 0.95f + new_offset * 0.05f;
|
|
}
|
|
|
|
#if 0
|
|
// @LoggerMessage: MMO
|
|
// @Description: MMC3416 compass data
|
|
// @Field: TimeUS: Time since system startup
|
|
// @Field: Nx: new measurement X axis
|
|
// @Field: Ny: new measurement Y axis
|
|
// @Field: Nz: new measurement Z axis
|
|
// @Field: Ox: new offset X axis
|
|
// @Field: Oy: new offset Y axis
|
|
// @Field: Oz: new offset Z axis
|
|
AP::logger().Write("MMO", "TimeUS,Nx,Ny,Nz,Ox,Oy,Oz", "Qffffff",
|
|
AP_HAL::micros64(),
|
|
(double)new_offset.x,
|
|
(double)new_offset.y,
|
|
(double)new_offset.z,
|
|
(double)offset.x,
|
|
(double)offset.y,
|
|
(double)offset.z);
|
|
printf("F(%.1f %.1f %.1f) O(%.1f %.1f %.1f)\n",
|
|
field.x, field.y, field.z,
|
|
offset.x, offset.y, offset.z);
|
|
#endif
|
|
|
|
last_sample_ms = AP_HAL::millis();
|
|
|
|
// sensor is not FRD
|
|
field.y = -field.y;
|
|
|
|
accumulate_sample(field);
|
|
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_TM)) {
|
|
state = STATE_REFILL1;
|
|
} else {
|
|
state = STATE_MEASURE_WAIT3;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case STATE_MEASURE_WAIT3: {
|
|
uint8_t status;
|
|
if (!dev->read_registers(REG_STATUS, &status, 1) || !(status & 1)) {
|
|
break;
|
|
}
|
|
uint16_t data1[3];
|
|
if (!dev->read_registers(REG_XOUT_L, (uint8_t *)&data1[0], 6)) {
|
|
state = STATE_REFILL1;
|
|
break;
|
|
}
|
|
Vector3f field(float(data1[0]) - zero_offset,
|
|
float(data1[1]) - zero_offset,
|
|
float(data1[2]) - zero_offset);
|
|
field *= -counts_to_milliGauss;
|
|
field += offset;
|
|
|
|
// sensor is not FRD
|
|
field.y = -field.y;
|
|
|
|
last_sample_ms = AP_HAL::millis();
|
|
accumulate_sample(field);
|
|
|
|
// we stay in STATE_MEASURE_WAIT3 for measure_count_limit cycles
|
|
if (measure_count++ >= measure_count_limit) {
|
|
measure_count = 0;
|
|
state = STATE_REFILL1;
|
|
} else {
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_TM)) { // Take Measurement
|
|
state = STATE_REFILL1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AP_Compass_MMC3416::read()
|
|
{
|
|
drain_accumulated_samples();
|
|
}
|
|
|
|
#endif // AP_COMPASS_MMC3416_ENABLED
|