Skip to content

Commit

Permalink
[driver] Add MMC5603 compass driver
Browse files Browse the repository at this point in the history
  • Loading branch information
salkinium committed Feb 11, 2021
1 parent e93c818 commit 6b44e95
Show file tree
Hide file tree
Showing 2 changed files with 387 additions and 0 deletions.
342 changes: 342 additions & 0 deletions src/modm/driver/inertial/mmc5603.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
/*
* Copyright (c) 2020, Niklas Hauser
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#pragma once

#include <modm/architecture/interface/i2c_device.hpp>
#include <modm/processing/protothread.hpp>
#include <modm/math/utils.hpp>

namespace modm
{

// forward declaration for friending with mmc5603::Data
template < class I2cMaster >
class Mmc5603;

/// @ingroup modm_driver_mmc5603
struct mmc5603
{
static constexpr
uint8_t addr(uint8_t factory=0)
{ return 0b0110000 | (factory & 0b111); }

static constexpr uint8_t ProductID{0x10};

enum class
Register : uint8_t
{
Xout0 = 0x00, // Xout[19:12]
Xout1 = 0x01, // Xout[11:4]
Yout0 = 0x02, // Yout[19:12]
Yout1 = 0x03, // Yout[11:4]
Zout0 = 0x04, // Zout[19:12]
Zout1 = 0x05, // Zout[11:4]
Xout2 = 0x06, // Xout[3:0]
Yout2 = 0x07, // Yout[3:0]
Zout2 = 0x08, // Zout[3:0]
Tout = 0x09, // Temperature output
Status1 = 0x18, // Device status1
ODR = 0x1A, // Output data rate
InternalControl0 = 0x1B, // Control register 0
InternalControl1 = 0x1C, // Control register 1
InternalControl2 = 0x1D, // Control register 2
ST_X_TH = 0x1E, // X-axis selftest threshold
ST_Y_TH = 0x1F, // Y-axis selftest threshold
ST_Z_TH = 0x20, // Z-axis selftest threshold
ST_X = 0x27, // X-axis selftest set value
ST_Y = 0x28, // Y-axis selftest set value
ST_Z = 0x29, // Z-axis selftest set value
ProductID = 0x39, // Product ID
};

public:
enum class
Status1 : uint8_t
{
/// This bit is an indicator of successfully reading its OTP memory
/// either as part of its power up sequence, or after an I2C command
/// that reloads the OTP memory, such as resetting the chip and
/// refreshing the OTP registers.
OTP_read_done = Bit4,
/// This bit is an indicator of the selftest signal, it keeps low once
/// the device PASS selftest.
Sat_sensor = Bit5,
/// This bit indicates that a measurement of magnetic field is done and
/// the data is ready to be read. This bit is reset only when any of the
/// magnetic data registers is read.
Meas_m_done = Bit6,
/// This bit indicates that a measurement of temperature is done and
/// the data is ready to be read. This bit is reset only when the
/// temperature register is read.
Meas_t_done = Bit7,
};
MODM_FLAGS8(Status1);

enum class
Control0 : uint8_t
{
/// Take Measure of Magnetic field, or TM_M bit. Writing a 1 into this
/// location causes the chip to perform a magnetic measurement.
/// This bit is self-clearing at the end of each measurement.
Take_meas_M = Bit0,
/// Take Measure of Temperature, or TM_T bit. Writing a 1 into this
/// location causes the chip to perform a temperature measurement.
/// This bit is self-clearing at the end of each measurement.
Take_meas_T = Bit1,
/// Writing a 1 into this location will cause the chip to do the Set
/// operation, which will allow large set current to flow through the
/// sensor coils for 375ns. This bit is self-cleared at the end of Set operation.
DoSet = Bit3,
/// Writing a 1 into this location will cause the chip to do the Reset
/// operation, which will allow large reset current to flow through the
/// sensor coils for 375ns. This bit is self-cleared at the end of Reset operation.
DoReset = Bit4,
/// Writing a 1 into this location will enable the function of automatic
/// set/reset. This function applies to both on-demand and continuous-time
/// measurements. This bit must be set to 1 in order to activate the feature
/// of periodic set. This bit is recommended to set to “1” in the application.
Auto_SR_en = Bit5,
/// Writing a 1 into this location will enable the function of automatic
/// self-test. The threshold in register 1EH, 1FH, 20H should be set before
/// this bit is set to 1. This bit clears itself after the operation is completed.
Auto_st_en = Bit6,
/// Writing a 1 into this location will start the calculation of the measurement
/// period according to the ODR. This bit should be set before continuous-mode
/// measurements are started. This bit is self- cleared after the measurement
/// period is calculated by internal circuits.
Cmm_freq_en = Bit7,
};
MODM_FLAGS8(Control0);

enum class
Control1 : uint8_t
{
Bandwidth_Mask = 0b11,
/// Writing “1” will disable this channel, and reduce Measurement Time and
/// total charge per measurement.When a channel is disabled it is simply
/// skipped during Take Measure routine. Its output register is not reset
/// and will maintain the last value written to it when this channel was
/// active. Note: Y/Z needs to be inhibited the same time in case needed.
X_inhibit = Bit2,
Y_inhibit = Bit3,
Z_inhibit = Bit4,
/// Writing 1 into this location will bring a DC current through the
/// self-test coil of the sensor. This current will cause an offset of
/// the magnetic field. This function is used to check whether the sensor
/// has been saturated.
St_enp = Bit5,
/// The function of this bit is similar to ST_ENP, but the offset of the
/// magnetic field is of opposite polarity.
St_enm = Bit6,
/// Software Reset. Writing "1" will cause the part to reset, similar to
/// power-up. It will clear all registers and also re-read OTP as part
/// of its startup routine. The power on time is 20mS.
Sw_reset = Bit7,
};
MODM_FLAGS8(Control1);

enum class
Bandwidth : uint8_t
{
Ms6_6 = 0x00,
Ms3_5 = 0x01,
Ms2_0 = 0x02,
Ms1_2 = 0x03,
};
MODM_FLAGS_CONFIG(Control1, Bandwidth);

enum class
Control2 : uint8_t
{
PeriodicalSet_Mask = 0b111,
/// Writing 1 into this location will enable the function of periodical set.
En_prd_set = Bit3,
/// The device will enter continuous mode, if ODR has been set to a
/// non-zero value and a 1 has been written into Cmm_freq_en. The
/// internal counter will start counting as well since this bit is set.
Cmm_en = Bit4,
/// Factory use only, reset value is 0.
INT_mdt_en = Bit5,
/// Factory use only, reset value is 0.
INT_meas_done_en = Bit6,
/// If this bit is set to 1 to achieve 1000Hz ODR.
Hpower = Bit7,
};
MODM_FLAGS8(Control2);

/// These bits determine how many measurements are done before a set is
/// executed, when the part is in continuous mode and the automatic
/// set/reset is enabled. From 000 to 111, the sensor will do one set
/// for every 1, 25, 75, 100, 250, 500, 1000, and 2000 samples. In order
/// to enable this feature, both En_prd_set and Auto_SR must be set to
/// 1, and the part should work in continuous mode. Please note that
/// during this operation, the sensor will not be reset.
enum class
PeriodicalSet : uint8_t
{
Samples1 = 0x00,
Samples25 = 0x01,
Samples75 = 0x02,
Samples100 = 0x03,
Samples250 = 0x04,
Samples500 = 0x05,
Samples1000 = 0x06,
Samples2000 = 0x07,
};
MODM_FLAGS_CONFIG(Control2, PeriodicalSet);

struct modm_packed
Data
{
template < class I2cMaster >
friend class Mmc5603;

// DATA ACCESS
/// returns the magnetic field in Gauss
///@{
float inline
x() const { return swapData(0); }

float inline
y() const { return swapData(1); }

float inline
z() const { return swapData(2); }
///@}

/// returns the temperature in Celcius
int8_t inline
t() const { return int16_t(data[9]) * 4/5 - 75; }

float inline
operator [](uint8_t index) const
{ return (index < 3) ? swapData(index) : 0; }

private:
uint8_t data[10];

float inline
swapData(uint8_t index) const
{ return (int32_t((data[2*index] << 8) | data[2*index+1]) - 32768) / 1024.f; }
};

protected:
/// @cond
static constexpr uint8_t
i(Register reg) { return uint8_t(reg); }
static constexpr uint8_t
i(Bandwidth bw) { return uint8_t(bw); }
static constexpr uint8_t
i(PeriodicalSet ps) { return uint8_t(ps); }
using Register_t = FlagsGroup<Control0_t, Control1_t, Control2_t>;
/// @endcond
}; // struct mmc5603

/**
* @ingroup modm_driver_mmc5603
* @author Niklas Hauser
*/
template < class I2cMaster >
class Mmc5603 : public mmc5603, public modm::I2cDevice< I2cMaster, 3 >
{
public:
/// Constructor, requires a mmc5603::Data object
Mmc5603(Data &data, uint8_t address=addr())
: I2cDevice<I2cMaster,3>(address), data(data)
{}

modm::ResumableResult<bool>
startTemperatureMeasurement()
{ return update(Register::InternalControl0, Control0::Take_meas_T); }

modm::ResumableResult<bool>
configureContinuousMode(uint8_t frequency)
{
RF_BEGIN();
if (not RF_CALL(write(Register::ODR, frequency)))
RF_RETURN(false);
if (not RF_CALL(update(Register::InternalControl1, Register_t(i(Bandwidth::Ms1_2)))))
RF_RETURN(false);
if (not RF_CALL(update(Register::InternalControl0, Control0::Cmm_freq_en | Control0::Auto_SR_en)))
RF_RETURN(false);
RF_END_RETURN_CALL(update(Register::InternalControl2, Control2::Cmm_en));
}

public:
Data& getData() { return data; }
Status1_t getStatus1() { return Status1(rb(Register::Status1)); }
uint8_t getOutputDataRate() { return rb(Register::ODR); }
Control0_t getControl0() { return rb(Register::InternalControl0); }
Control1_t getControl1() { return rb(Register::InternalControl1); }
Control2_t getControl2() { return rb(Register::InternalControl2); }

public:
modm::ResumableResult<bool>
readProductId(uint8_t &value)
{ return read(Register::ProductID, value); }

modm::ResumableResult<bool>
readMagneticField()
{ return read(Register::Xout0, data.data, 10); }

public:
modm::ResumableResult<bool>
update(Register reg, Register_t setMask, Register_t clearMask = Register_t(0))
{
RF_BEGIN();
rb(reg) = (rb(reg) & ~clearMask.value) | setMask.value;
RF_END_RETURN_CALL(write(reg, rb(reg)));
}

modm::ResumableResult<bool>
write(Register reg, uint8_t value)
{
RF_BEGIN();
buffer[0] = i(reg);
buffer[1] = value;
this->transaction.configureWrite(buffer, 2);
RF_END_RETURN_CALL(this->runTransaction());
}

modm::ResumableResult<bool>
read(Register reg, uint8_t &value)
{ return read(reg, &value, 1); }

modm::ResumableResult<bool>
read(Register reg, uint8_t *data, uint8_t size)
{
RF_BEGIN();
buffer[0] = i(reg);
this->transaction.configureWriteRead(buffer, 1, data, size);
RF_END_RETURN_CALL(this->runTransaction());
}

protected:
Data &data;
// Internal register are write-only, so a read-modify-write does not work
// 0: 0x18 status1
// 1: 0x19 [reserved]
// 2: 0x1A ODR
// 3: 0x1B Internal Control 0
// 4: 0x1C Internal Control 1
// 5: 0x1D Internal Control 2
// 6: 0x1E X threshold
// 7: 0x1F Y threshold
// 8: 0x20 Z threshold
uint8_t regBuffer[9] = {};
uint8_t& rb(Register reg) { return regBuffer[i(reg) - 0x18]; }
// I2C buffer
uint8_t buffer[2];
};

} // namespace modm

45 changes: 45 additions & 0 deletions src/modm/driver/inertial/mmc5603.lb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020, Niklas Hauser
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------


def init(module):
module.name = ":driver:mmc5603"
module.description = """
# MMC5603 3-Axis Digital Magnetometer
The MMC5603NJ is a monolithic complete 3-axis AMR magnetic sensor with on-chip
signal processing and integrated I2C bus.
It can measure magnetic fields within the full scale range of ±30 Gauss (G),
with up to 0.0625mG per LSB resolution at 20bits operation mode and 2mG total
RMS noise level, enabling heading accuracy of ±1° in electronic compass applications.
An integrated SET/RESET function provides for the elimination of error due to
Null Field output change with temperature. In addition it clears the sensors of
any residual magnetic polarization resulting from exposure to strong external
magnets. The SET/RESET function can be performed for each measurement or
periodically as the specific application requires.
The MMC5603NJ is in wafer level package with an ultra-small size of 0.8x0.8x0.4mm
and with an operating temperature range from -40°C to +85°C.
"""

def prepare(module, options):
module.depends(
":architecture:i2c.device",
":architecture:register",
":math:utils")
return True

def build(env):
env.outbasepath = "modm/src/modm/driver/inertial"
env.copy("mmc5603.hpp")

0 comments on commit 6b44e95

Please sign in to comment.