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")