diff --git a/README.md b/README.md
index fc4f82adb6..83fba333a1 100644
--- a/README.md
+++ b/README.md
@@ -789,30 +789,31 @@ your specific needs.
PCA9535 |
PCA9548A |
PCA9685 |
+QMC5883L |
SH1106 |
-SIEMENS-S65 |
+SIEMENS-S65 |
SIEMENS-S75 |
SK6812 |
SK9822 |
SSD1306 |
ST7586S |
-ST7789 |
+ST7789 |
STTS22H |
STUSB4500 |
SX1276 |
SX128X |
TCS3414 |
-TCS3472 |
+TCS3472 |
TLC594x |
TMP102 |
TMP12x |
TMP175 |
TOUCH2046 |
-VL53L0 |
+VL53L0 |
VL6180 |
WS2812 |
diff --git a/examples/avr/qmc5883l/main.cpp b/examples/avr/qmc5883l/main.cpp
new file mode 100644
index 0000000000..ebfe0bb8bb
--- /dev/null
+++ b/examples/avr/qmc5883l/main.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023, Alexander Solovets
+ *
+ * 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/.
+ */
+// ----------------------------------------------------------------------------
+
+#include
+#include
+#include
+#include
+
+using namespace Board;
+using namespace std::chrono_literals;
+
+using Compass = Qmc5883l;
+Compass::Data data;
+Compass compass(data);
+modm::atomic::Flag dataReady(true);
+
+MODM_ISR(INT2)
+{
+ dataReady.set();
+}
+
+int
+main()
+{
+ Board::initialize();
+ // DRDY pin must be connected to Board pin 19 (Port D2).
+ Board::D19::setInput(Gpio::InputType::PullUp);
+ Board::D19::setInputTrigger(Gpio::InputTrigger::RisingEdge);
+ Board::D19::enableExternalInterrupt();
+
+ RF_CALL_BLOCKING(compass.initialize());
+ auto mode = Compass::Mode_t(Compass::Mode::Continious);
+ auto rate = Compass::OutputDataRate_t(Compass::OutputDataRate::_10Hz);
+ auto scale = Compass::FullScale_t(Compass::FullScale::_8G);
+ RF_CALL_BLOCKING(compass.configure(mode, rate | scale));
+
+ for (;;)
+ {
+ if (dataReady.testAndSet(false))
+ {
+ if (RF_CALL_BLOCKING(compass.readData()))
+ {
+ MODM_LOG_INFO << "X:" << compass.x() << " Y: " << compass.y()
+ << " Z: " << compass.z()
+ << " OVL: " << (compass.status() & Compass::Status::OVL)
+ << modm::endl;
+ } else
+ {
+ serialStream << "readDataBlocking(): Error: " << uint8_t(I2cMaster::getErrorState())
+ << modm::endl;
+ }
+ }
+ }
+}
diff --git a/examples/avr/qmc5883l/project.xml b/examples/avr/qmc5883l/project.xml
new file mode 100644
index 0000000000..dd13835c4f
--- /dev/null
+++ b/examples/avr/qmc5883l/project.xml
@@ -0,0 +1,11 @@
+
+ modm:mega-2560-pro
+
+
+
+
+ modm:platform:i2c
+ modm:driver:qmc5883l
+ modm:build:scons
+
+
diff --git a/src/modm/driver/inertial/qmc5883l.hpp b/src/modm/driver/inertial/qmc5883l.hpp
new file mode 100644
index 0000000000..18d4ececcd
--- /dev/null
+++ b/src/modm/driver/inertial/qmc5883l.hpp
@@ -0,0 +1,196 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+template
+class Qmc5883l;
+
+struct Qmc5883lRegisters
+{
+protected:
+ /// @cond
+ /// The addresses of the Configuration and Data Registers
+ enum class Register : uint8_t
+ {
+ DataX_Lsb = 0x00,
+ DataX_Msb = 0x01,
+ DataY_Lsb = 0x02,
+ DataY_Msb = 0x03,
+ DataZ_Lsb = 0x04,
+ DataZ_Msb = 0x05,
+ Status = 0x06,
+ Tout_Lsb = 0x07,
+ Tout_Msb = 0x08,
+ Control1 = 0x09,
+ Control2 = 0x0a,
+ SetReset = 0x0b,
+ };
+ /// @endcond
+public:
+ enum class Status : uint8_t
+ {
+ DOR = modm::Bit2,
+ OVL = modm::Bit1,
+ DRDY = modm::Bit0,
+ };
+ MODM_FLAGS8(Status);
+
+public:
+ enum class Control1 : uint8_t
+ {
+ OSR1 = modm::Bit7,
+ OSR0 = modm::Bit6,
+ OversampleRate_Mask = OSR1 | OSR0,
+
+ RNG1 = modm::Bit5,
+ RNG0 = modm::Bit4,
+ FullScale_Mask = RNG1 | RNG0,
+
+ ODR1 = modm::Bit3,
+ ODR0 = modm::Bit2,
+ OutputDataRate_Mask = ODR1 | ODR0,
+
+ MODE1 = modm::Bit1,
+ MODE0 = modm::Bit0,
+ Mode_Mask = MODE1 | MODE0,
+ };
+ MODM_FLAGS8(Control1);
+
+ enum class Control2 : uint8_t
+ {
+ SOFT_RST = modm::Bit7,
+ ROL_PNT = modm::Bit6,
+ INT_ENB = modm::Bit0,
+ };
+ MODM_FLAGS8(Control2);
+
+public:
+ enum class OversampleRate : uint8_t
+ {
+ _512 = 0,
+ _256 = int(Control1::OSR0),
+ _128 = int(Control1::OSR1),
+ _64 = int(Control1::OSR0) | int(Control1::OSR1),
+ };
+
+ MODM_FLAGS_CONFIG(Control1, OversampleRate);
+
+ enum class FullScale : uint8_t
+ {
+ _2G = 0,
+ _8G = int(Control1::RNG0),
+ };
+
+ MODM_FLAGS_CONFIG(Control1, FullScale);
+
+ enum class OutputDataRate : uint8_t
+ {
+ _10Hz = 0,
+ _50Hz = int(Control1::ODR0),
+ _100Hz = int(Control1::ODR1),
+ _200Hz = int(Control1::ODR0) | int(Control1::ODR1),
+ };
+
+ MODM_FLAGS_CONFIG(Control1, OutputDataRate);
+
+ enum class Mode : uint8_t
+ {
+ StandBy = 0,
+ Continious = int(Control1::MODE0),
+ };
+
+ MODM_FLAGS_CONFIG(Control1, Mode);
+
+public:
+ struct modm_packed Data
+ {
+ template
+ friend class Qmc5883l;
+
+ protected:
+ uint8_t data[7];
+
+ template
+ int16_t inline getWord()
+ {
+ static_assert(Offset < sizeof data);
+ const auto value = reinterpret_cast(data + Offset);
+ return modm::fromLittleEndian(*value);
+ }
+ };
+};
+
+template
+class Qmc5883l : public Qmc5883lRegisters, public modm::I2cDevice
+{
+ /// @cond
+ Data &data;
+ /// @endcond
+ uint8_t buffer[sizeof data.data];
+
+ modm::ResumableResult
+ writeRegister(Register reg, uint8_t value)
+ {
+ RF_BEGIN();
+
+ buffer[0] = uint8_t(reg);
+ buffer[1] = value;
+ this->transaction.configureWrite(buffer, 2);
+
+ RF_END_RETURN_CALL(this->runTransaction());
+ }
+
+public:
+ Qmc5883l(Data &data, uint8_t address = 0x0d) : modm::I2cDevice(address), data(data)
+ {}
+
+ auto x() { return data.getWord(); }
+
+ auto y() { return data.getWord(); }
+
+ auto z() { return data.getWord(); }
+
+ auto status() { return Status_t(data.data[uint8_t(Register::Status)]); }
+
+public:
+ modm::ResumableResult
+ initialize()
+ {
+ // Per datasheet:
+ // wihtout any additional explanations recommended to set to 0x01.
+ return writeRegister(Register::SetReset, 0x01);
+ }
+
+ modm::ResumableResult
+ configure(Mode_t mode, Control1_t control)
+ {
+ control |= mode;
+ return writeRegister(Register::Control1, control.value);
+ }
+
+ modm::ResumableResult
+ configure(Control2_t control)
+ {
+ return writeRegister(Register::Control2, control.value);
+ }
+
+ modm::ResumableResult
+ readData()
+ {
+ RF_BEGIN();
+
+ buffer[0] = uint8_t(Register::DataX_Lsb);
+ this->transaction.configureWriteRead(buffer, 1, buffer, sizeof buffer);
+
+ if (RF_CALL(this->runTransaction()))
+ {
+ std::copy_n(buffer, sizeof data.data, data.data);
+ RF_RETURN(true);
+ }
+
+ RF_END_RETURN(false);
+ }
+};
diff --git a/src/modm/driver/inertial/qmc5883l.lb b/src/modm/driver/inertial/qmc5883l.lb
new file mode 100644
index 0000000000..fae3c4466d
--- /dev/null
+++ b/src/modm/driver/inertial/qmc5883l.lb
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2023, Alexander Solovets
+#
+# 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:qmc5883l"
+ module.description = """\
+# QMC5883L 3-Axis Digital Magnetometer
+
+The QMC5883L is a multi-chip three-axis magnetic sensor. This surface-mount,
+small sized chip has integrated magnetic sensors with signal condition ASIC,
+targeted for high precision applications such as compassing, navigation and
+gaming in drone, robot, mobile and personal hand-held devices.
+
+The QMC5883L is based on state-of-the-art, high resolution, magneto-resistive
+technology licensed from Honeywell AMR technology. Along with custom-designed
+16-bit ADC ASIC, it offers the advantages of low noise, high accuracy, low
+power consumption, offset cancellation and temperature compensation. QMC5883L
+enables 1° to 2° compass heading accuracy. The I²C serial bus allows for easy
+interface.
+"""
+
+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("qmc5883l.hpp")