diff --git a/README.md b/README.md index ad4780cc24..325ab0d85c 100644 --- a/README.md +++ b/README.md @@ -235,31 +235,33 @@ can easily configure them for you specific needs. MCP23X17 MCP2515 +MMC5603 NOKIA5110 NRF24 TFT-DISPLAY PAT9125EL -PCA8574 +PCA8574 PCA9535 PCA9548A PCA9685 SIEMENS-S65 SIEMENS-S75 -SK6812 +SK6812 SK9822 SSD1306 SX1276 TCS3414 TCS3472 -TLC594X +TLC594X TMP102 TMP175 VL53L0 VL6180 WS2812 + diff --git a/src/modm/driver/inertial/mmc5603.hpp b/src/modm/driver/inertial/mmc5603.hpp new file mode 100644 index 0000000000..abfe1778fe --- /dev/null +++ b/src/modm/driver/inertial/mmc5603.hpp @@ -0,0 +1,374 @@ +/* + * 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 +#include +#include + +namespace modm +{ + +// forward declaration for friending with mmc5603::Data +template < class I2cMaster > +class Mmc5603; + +/// @ingroup modm_driver_mmc5603 +struct mmc5603 +{ + /// The MEMSIC device 7-bit device address is 0110000 where the three + /// LSB’s are pre-programmed into the MMC5603NJ by the factory. + 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); + + /// The bandwidth adjusts the length of the decimation filter. It controls + /// the duration of each measurement. + /// @note X/Y/Z channel measurements are taken sequentially. Delay Time among + /// those measurements is 1/3 of the Measurement Time defined in the table. + enum class + Bandwidth : uint8_t + { + Ms6_6 = 0x00, ///< 6.6ms measurement time + Ms3_5 = 0x01, ///< 3.5ms measurement time + Ms2_0 = 0x02, ///< 2.0ms measurement time + Ms1_2 = 0x03, ///< 1.2ms measurement time + }; + 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 as signed 32-bit value with 16384 counts/Gauss + ///@{ + int32_t inline + getX() const { return swapData(0); } + + int32_t inline + getY() const { return swapData(1); } + + int32_t inline + getZ() const { return swapData(2); } + ///@} + + /// returns the magnetic field in Gauss as floating point + ///@{ + float inline + getXf() const { return swapDataFloat(0); } + + float inline + getYf() const { return swapDataFloat(1); } + + float inline + getZf() const { return swapDataFloat(2); } + ///@} + + /// returns the temperature in Celcius + int8_t inline + getTemperature() const { return int16_t(data[9]) * 4/5 - 75; } + + int32_t inline + operator [](uint8_t index) const + { return (index < 3) ? swapData(index) : 0; } + + protected: + int32_t inline + swapData(uint8_t index) const + { return int32_t(((data[2*index] << 16) | (data[2*index+1] << 8) | (data[6+index])) >> 4) - 524288l; } + + float inline + swapDataFloat(uint8_t index) const + { return swapDataFloat(index) / 16384.f; } + + uint8_t data[10]; + }; + +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; + /// @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(address), data(data) + {} + + modm::ResumableResult + ping() + { + RF_BEGIN(); + buffer[1] = 0; + RF_CALL(read(Register::ProductID, buffer[1])); + RF_END_RETURN(buffer[1] == ProductID); + } + + modm::ResumableResult + startTemperatureMeasurement() + { return update(Register::InternalControl0, Control0::Take_meas_T); } + + /// Continuous Mode up to 250Hz with Automatic Set/Reset, 2ms Measurement Time + modm::ResumableResult + configureContinuousMode(uint8_t frequency, Bandwidth bandwidth=Bandwidth::Ms2_0) + { + RF_BEGIN(); + if (not RF_CALL(write(Register::ODR, frequency))) + RF_RETURN(false); + if (not RF_CALL(update(Register::InternalControl1, Register_t(i(bandwidth))))) + 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 + readProductId(uint8_t &value) + { return read(Register::ProductID, value); } + + modm::ResumableResult + readMagneticField() + { return read(Register::Xout0, data.data, 10); } + +public: + modm::ResumableResult + 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 + 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 + read(Register reg, uint8_t &value) + { return read(reg, &value, 1); } + + modm::ResumableResult + 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 + diff --git a/src/modm/driver/inertial/mmc5603.lb b/src/modm/driver/inertial/mmc5603.lb new file mode 100644 index 0000000000..437ebea722 --- /dev/null +++ b/src/modm/driver/inertial/mmc5603.lb @@ -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")