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
312 lines
9.4 KiB
C++
312 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/>.
|
|
*/
|
|
|
|
#include "AP_Compass_MMC5xx3.h"
|
|
|
|
#if AP_COMPASS_MMC5XX3_ENABLED
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
#include <stdio.h>
|
|
|
|
extern const AP_HAL::HAL &hal;
|
|
|
|
#define REG_PRODUCT_ID 0x2F
|
|
#define REG_XOUT_L 0x00
|
|
#define REG_STATUS 0x08
|
|
#define REG_CONTROL0 0x09
|
|
#define REG_CONTROL1 0x0A
|
|
#define REG_CONTROL2 0x0B
|
|
|
|
// bits in REG_CONTROL0
|
|
#define REG_CONTROL0_RESET 0x10 // Set coil for measuring offset
|
|
#define REG_CONTROL0_SET 0x08 // Reset coil for measuring offset
|
|
#define REG_CONTROL0_TMM 0x01 // Take Measurement for Magnetic field
|
|
#define REG_CONTROL0_TMT 0x02 // Take Measurement for Temperature
|
|
|
|
// bits in REG_CONTROL1
|
|
#define REG_CONTROL1_SW_RST 0x80 // Software reset
|
|
#define REG_CONTROL1_BW0 0x01
|
|
#define REG_CONTROL1_BW1 0x02
|
|
|
|
#define MMC5983_ID 0x30
|
|
|
|
AP_Compass_Backend *AP_Compass_MMC5XX3::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev,
|
|
bool force_external,
|
|
enum Rotation rotation)
|
|
{
|
|
if (!dev) {
|
|
return nullptr;
|
|
}
|
|
AP_Compass_MMC5XX3 *sensor = NEW_NOTHROW AP_Compass_MMC5XX3(std::move(dev), force_external, rotation);
|
|
if (!sensor || !sensor->init()) {
|
|
delete sensor;
|
|
return nullptr;
|
|
}
|
|
|
|
return sensor;
|
|
}
|
|
|
|
AP_Compass_MMC5XX3::AP_Compass_MMC5XX3(AP_HAL::OwnPtr<AP_HAL::Device> _dev,
|
|
bool _force_external,
|
|
enum Rotation _rotation)
|
|
: dev(std::move(_dev))
|
|
, force_external(_force_external)
|
|
, have_initial_offset(false)
|
|
, rotation(_rotation)
|
|
{
|
|
}
|
|
|
|
bool AP_Compass_MMC5XX3::init()
|
|
{
|
|
// take i2c bus semaphore
|
|
WITH_SEMAPHORE(dev->get_semaphore());
|
|
|
|
dev->set_retries(10);
|
|
|
|
// setup to allow reads on SPI
|
|
if (dev->bus_type() == AP_HAL::Device::BUS_TYPE_SPI) {
|
|
dev->set_read_flag(0x80);
|
|
}
|
|
|
|
// Reading REG_PRODUCT_ID fails sometimes on SPI, so we retry up to 10 times
|
|
uint8_t whoami = 0;
|
|
uint8_t tries = 10;
|
|
while (whoami == 0 && tries > 0) {
|
|
tries--;
|
|
dev->read_registers(REG_PRODUCT_ID, &whoami, 1);
|
|
hal.scheduler->delay(5);
|
|
}
|
|
|
|
if (whoami != MMC5983_ID) {
|
|
printf("MMC5983 got unexpected product id: %d, expected: %d\n", whoami, MMC5983_ID);
|
|
// not a MMC5983
|
|
return false;
|
|
}
|
|
|
|
// reset sensor
|
|
dev->write_register(REG_CONTROL1, REG_CONTROL1_SW_RST);
|
|
|
|
// 10ms minimum startup time
|
|
hal.scheduler->delay(15);
|
|
|
|
// setup for 100Hz output
|
|
if (!dev->write_register(REG_CONTROL1, 0)) {
|
|
return false;
|
|
}
|
|
|
|
|
|
/* register the compass instance in the frontend */
|
|
dev->set_device_type(DEVTYPE_MMC5983);
|
|
if (!register_compass(dev->get_bus_id())) {
|
|
return false;
|
|
}
|
|
|
|
printf("Found a MMC5983 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(10000U,
|
|
FUNCTOR_BIND_MEMBER(&AP_Compass_MMC5XX3::timer, void));
|
|
|
|
return true;
|
|
}
|
|
|
|
void AP_Compass_MMC5XX3::timer()
|
|
{
|
|
// recalculate the offset with set/reset operation every measure_count_limit measurements
|
|
// sensor is read at about 100Hz, so about every 10 seconds
|
|
const uint16_t measure_count_limit = 1000U;
|
|
const uint16_t zero_offset = 32768U; // 16 bit mode
|
|
const uint16_t sensitivity = 4096U; // counts per Gauss, 16 bit mode
|
|
constexpr float counts_to_milliGauss = 1.0e3f / sensitivity;
|
|
|
|
/*
|
|
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) {
|
|
|
|
// perform a set operation
|
|
case MMCState::STATE_SET: {
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_SET)) {
|
|
break;
|
|
}
|
|
// minimum time to wait after set/reset before take measurement request is 1ms
|
|
state = MMCState::STATE_SET_MEASURE;
|
|
break;
|
|
}
|
|
|
|
// request a measurement for field and offset calculation after set operation
|
|
case MMCState::STATE_SET_MEASURE: {
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_TMM)) {
|
|
break;
|
|
}
|
|
state = MMCState::STATE_SET_WAIT;
|
|
break;
|
|
}
|
|
|
|
// wait for measurement to be ready after set operation, then read the
|
|
// measurement data and request a reset operation
|
|
case MMCState::STATE_SET_WAIT: {
|
|
uint8_t status;
|
|
if (!dev->read_registers(REG_STATUS, &status, 1)) {
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
|
|
// check if measurement is ready
|
|
if (!(status & 1)) {
|
|
break;
|
|
}
|
|
|
|
// read measurement
|
|
if (!dev->read_registers(REG_XOUT_L, (uint8_t *)&data0[0], 6)) {
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
|
|
// request set operation
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_RESET)) {
|
|
break;
|
|
}
|
|
// minimum time to wait after set/reset before take measurement request is 1ms
|
|
state = MMCState::STATE_RESET_MEASURE;
|
|
break;
|
|
}
|
|
|
|
// request a measurement for field and offset calculation after reset operation
|
|
case MMCState::STATE_RESET_MEASURE: {
|
|
// take measurement request
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_TMM)) {
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
|
|
state = MMCState::STATE_RESET_WAIT;
|
|
break;
|
|
}
|
|
|
|
// wait for measurement to be ready after reset operation,
|
|
// then read the measurement data, calculate the field and offset,
|
|
// and begin requesting field measurements
|
|
case MMCState::STATE_RESET_WAIT: {
|
|
uint8_t status;
|
|
if (!dev->read_registers(REG_STATUS, &status, 1)) {
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
// check if measurement is ready
|
|
if (!(status & 1)) {
|
|
break;
|
|
}
|
|
|
|
uint8_t data1[6];
|
|
if (!dev->read_registers(REG_XOUT_L, (uint8_t *)&data1[0], 6)) {
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
calculate field and offset
|
|
*/
|
|
Vector3f f1 {float((data0[0] << 8) + data0[1]) - zero_offset,
|
|
float((data0[2] << 8) + data0[3]) - zero_offset,
|
|
float((data0[4] << 8) + data0[5]) - zero_offset};
|
|
Vector3f f2 {float((data1[0] << 8) + data1[1]) - zero_offset,
|
|
float((data1[2] << 8) + data1[3]) - zero_offset,
|
|
float((data1[4] << 8) + data1[5]) - zero_offset};
|
|
|
|
Vector3f field {(f2 - f1) * counts_to_milliGauss * 0.5f};
|
|
Vector3f new_offset {(f1 + f2) * counts_to_milliGauss * 0.5f};
|
|
|
|
if (!have_initial_offset) {
|
|
offset = new_offset;
|
|
have_initial_offset = true;
|
|
} else {
|
|
// low pass changes to the offset
|
|
offset = offset * 0.5f + new_offset * 0.5f;
|
|
}
|
|
|
|
accumulate_sample(field);
|
|
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_TMM)) {
|
|
printf("failed to initiate measurement\n");
|
|
state = MMCState::STATE_SET;
|
|
} else {
|
|
state = MMCState::STATE_MEASURE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// take repeated field measurements, set/reset is performed again after
|
|
// measure_count_limit measurements
|
|
case MMCState::STATE_MEASURE: {
|
|
uint8_t status;
|
|
if (!dev->read_registers(REG_STATUS, &status, 1)) {
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
|
|
// check if measurement is ready
|
|
if (!(status & 1)) {
|
|
break;
|
|
}
|
|
|
|
uint8_t data1[6];
|
|
if (!dev->read_registers(REG_XOUT_L, (uint8_t *)&data1[0], 6)) {
|
|
printf("cant read data\n");
|
|
state = MMCState::STATE_SET;
|
|
break;
|
|
}
|
|
|
|
Vector3f field {float((data1[0] << 8) + data1[1]) - zero_offset,
|
|
float((data1[2] << 8) + data1[3]) - zero_offset,
|
|
float((data1[4] << 8) + data1[5]) - zero_offset};
|
|
field *= counts_to_milliGauss;
|
|
field -= offset;
|
|
accumulate_sample(field);
|
|
|
|
// we stay in STATE_MEASURE for measure_count_limit cycles
|
|
if (measure_count++ >= measure_count_limit) {
|
|
measure_count = 0;
|
|
state = MMCState::STATE_SET;
|
|
} else {
|
|
if (!dev->write_register(REG_CONTROL0, REG_CONTROL0_TMM)) { // Take Measurement
|
|
state = MMCState::STATE_SET;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AP_Compass_MMC5XX3::read()
|
|
{
|
|
drain_accumulated_samples();
|
|
}
|
|
|
|
#endif // AP_COMPASS_MMC5XX3_ENABLED
|
|
|