diff --git a/README.md b/README.md
index 899e26f1ee..87c045de72 100644
--- a/README.md
+++ b/README.md
@@ -593,86 +593,87 @@ you specific needs.
AD7928 |
ADNS9800 |
ADS7843 |
+ADS816X |
AMS5915 |
-APA102 |
+APA102 |
SPI-FLASH |
BME280 |
BMP085 |
BNO055 |
CAT24AA |
-DRV832X |
+DRV832X |
DS1302 |
DS1631 |
DS18B20 |
EA-DOG |
ENCODER-INPUT |
-ENCODER-INPUT-BITBANG |
+ENCODER-INPUT-BITBANG |
ENCODER-OUTPUT-BITBANG |
FT245 |
FT6X06 |
GPIO-SAMPLER |
HCLAx |
-HD44780 |
+HD44780 |
HMC58x |
HMC6343 |
HX711 |
I2C-EEPROM |
ILI9341 |
-IS31FL3733 |
+IS31FL3733 |
ITG3200 |
L3GD20 |
LAN8720A |
LAWICEL |
LIS302DL |
-LIS3DSH |
+LIS3DSH |
LIS3MDL |
LM75 |
LP503X |
LSM303A |
LSM6DS33 |
-LTC2984 |
+LTC2984 |
MAX6966 |
MAX7219 |
MCP23X17 |
MCP2515 |
MCP7941X |
-MMC5603 |
+MMC5603 |
NOKIA5110 |
NRF24 |
TFT-DISPLAY |
PAT9125EL |
PCA8574 |
-PCA9535 |
+PCA9535 |
PCA9548A |
PCA9685 |
SH1106 |
SIEMENS-S65 |
SIEMENS-S75 |
-SK6812 |
+SK6812 |
SK9822 |
SSD1306 |
ST7586S |
STTS22H |
STUSB4500 |
-SX1276 |
+SX1276 |
TCS3414 |
TCS3472 |
TLC594X |
TMP102 |
TMP12X |
-TMP175 |
+TMP175 |
TOUCH2046 |
VL53L0 |
VL6180 |
diff --git a/src/modm/driver/adc/ads816x.hpp b/src/modm/driver/adc/ads816x.hpp
new file mode 100644
index 0000000000..f4421df552
--- /dev/null
+++ b/src/modm/driver/adc/ads816x.hpp
@@ -0,0 +1,139 @@
+// coding: utf-8
+// ----------------------------------------------------------------------------
+/*
+ * Copyright (c) 2021, Raphael Lehmann
+ *
+ * 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/.
+ */
+// ----------------------------------------------------------------------------
+
+#ifndef MODM_ADS816X_HPP
+#define MODM_ADS816X_HPP
+
+#include
+#include
+#include
+#include
+#include
+
+namespace modm
+{
+
+/// @ingroup modm_driver_ads816x
+struct ads816x
+{
+ enum class
+ Register : uint16_t
+ {
+ REG_ACCESS = 0x000,
+ PD_CNTL = 0x004,
+ SDI_CNTL = 0x008,
+ SDO_CNTL1 = 0x00C,
+ SDO_CNTL2 = 0x00D,
+ SDO_CNTL3 = 0x00E,
+ SDO_CNTL4 = 0x00F,
+ DATA_CNTL = 0x010,
+ PARITY_CNTL = 0x011,
+
+ OFST_CAL = 0x018,
+ REF_MRG1 = 0x019,
+ REF_MRG2 = 0x01A,
+ REFby2_MRG = 0x01B,
+
+ AIN_CFG = 0x024,
+ COM_CFG = 0x027,
+
+ DEVICE_CFG = 0x01C,
+ CHANNEL_ID = 0x01D,
+ SEQ_START = 0x01E,
+ SEQ_STOP = 0x01F,
+ ON_THE_FLY_CFG = 0x02A,
+ AUTO_SEQ_CFG1 = 0x080,
+ AUTO_SEQ_CFG2 = 0x082,
+
+ // Alert, etc...
+ };
+
+ enum class
+ Command : uint8_t
+ {
+ NoOperation = 0b00000 << 3,
+ Write = 0b00001 << 3,
+ Read = 0b00010 << 3,
+ SetBits = 0b00011 << 3,
+ ClearBits = 0b00100 << 3,
+ };
+
+ enum class
+ Mode : uint8_t
+ {
+ Manual = 0b00,
+ OnTheFly = 0b01,
+ AutoSequence = 0b10,
+ CustomSequence = 0b11,
+ };
+
+ enum class
+ DataFormat : uint8_t
+ {
+ OnlyResult = (0b00 << 4),
+ ResultChannel = (0b01 << 4),
+ ResultChannelStatus = (0b10 << 4),
+ //Reserved = (0b11 << 4),
+ };
+};
+
+/**
+ * @tparam SpiMaster SpiMaster interface
+ * @tparam Cs Chip-select pin
+ *
+ * @author Raphael Lehmann
+ * @ingroup modm_driver_ads816x
+ */
+template
+class Ads816x : public ads816x, public modm::SpiDevice, protected modm::NestedResumable<3>
+{
+public:
+ Ads816x() = default;
+
+ /// Call this function before using the device or to change operation mode
+ /// \warning Only Mode::Manual is currently supported!
+ modm::ResumableResult
+ initialize(Mode mode = Mode::Manual);
+
+ /// Initiate a single conversion and return the result of the conversion.
+ /// Simultanously the channel for the after next conversion will be set,
+ /// i.e. before reading the first valid data two dummy conversions have to
+ /// be executed.
+ modm::ResumableResult
+ manualModeConversion(uint8_t afterNextChannel);
+
+ /*
+ // FIXME: Does not work correctly...
+ /// Start a "auto sequence" conversion.
+ /// The device will automatically cycle through the specified channels in
+ /// the bitmask.
+ template
+ modm::ResumableResult
+ autoSequenceConversion(uint8_t channelsBitmask, std::span result);
+ */
+
+private:
+ modm::ResumableResult
+ registerAccess(Command command, Register reg, uint8_t value = 0);
+
+ uint8_t buffer[3];
+ uint8_t buffer2[3];
+
+ modm::ShortPreciseTimeout timeout;
+};
+
+} // namespace modm
+
+#include "ads816x_impl.hpp"
+
+#endif // MODM_ADS816X_HPP
diff --git a/src/modm/driver/adc/ads816x.lb b/src/modm/driver/adc/ads816x.lb
new file mode 100644
index 0000000000..84f75ece22
--- /dev/null
+++ b/src/modm/driver/adc/ads816x.lb
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021, Raphael Lehmann
+#
+# 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:ads816x"
+ module.description = """\
+# ADS816x ADC
+
+The ADS8166/ADS8167/ADS8168 are, 16 bit analog-digital converters with, sample
+rates of 1 MSPS (ADS8168), 500 kSPS (ADS8167), and 250 kSPS (ADS8166).
+The conversion time is determined by the chip select signal.
+A maximum Spi clock of 70 MHz is supported.
+
+This driver only implements the _manual mode_ ~~and _auto sequence mode_~~
+using the SPI interface.
+Only the default multiplexer configuration with 8 single-ended inputs and no
+pseudo-differential inputs is supported by now.
+"""
+
+
+def prepare(module, options):
+ module.depends(
+ ":architecture:gpio",
+ ":architecture:spi.device",
+ ":io",
+ ":processing:resumable",
+ ":processing:timer")
+ return True
+
+def build(env):
+ env.outbasepath = "modm/src/modm/driver/adc"
+ env.copy("ads816x.hpp")
+ env.copy("ads816x_impl.hpp")
diff --git a/src/modm/driver/adc/ads816x_impl.hpp b/src/modm/driver/adc/ads816x_impl.hpp
new file mode 100644
index 0000000000..04656eba18
--- /dev/null
+++ b/src/modm/driver/adc/ads816x_impl.hpp
@@ -0,0 +1,155 @@
+// coding: utf-8
+// ----------------------------------------------------------------------------
+/*
+ * Copyright (c) 2021, Raphael Lehmann
+ *
+ * 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/.
+ */
+// ----------------------------------------------------------------------------
+
+#ifndef MODM_ADS816X_HPP
+# error "Don't include this file directly! Use 'ads816x.hpp' instead."
+#endif
+
+//#include
+//#include
+
+namespace modm
+{
+
+template
+ResumableResult
+Ads816x::initialize(Mode mode)
+{
+ using namespace std::chrono_literals;
+ RF_BEGIN();
+
+ // Unlock register access by writing magic byte
+ RF_CALL(registerAccess(Command::Write, Register::REG_ACCESS, 0b1010'1010));
+
+ // Power up everything except the internal REFby2 buffer
+ RF_CALL(registerAccess(Command::Write, Register::PD_CNTL, 0b0001'0000));
+
+ // Data type: Only ADC value
+ RF_CALL(registerAccess(Command::Write, Register::DATA_CNTL, uint8_t(DataFormat::OnlyResult)));
+
+ // V_ref = 4.096V (internal reference)
+ RF_CALL(registerAccess(Command::Write, Register::OFST_CAL, 0b010));
+
+ // AIN0 ... AIN7 are separate channels
+ RF_CALL(registerAccess(Command::Write, Register::AIN_CFG, 0x00));
+
+ // All individual channels are single-ended inputs; connect the AIN-COM pin to GND
+ RF_CALL(registerAccess(Command::Write, Register::COM_CFG, 0x00));
+
+ // Set mode
+ RF_CALL(registerAccess(Command::Write, Register::DEVICE_CFG, uint8_t(mode)));
+
+ RF_END();
+}
+
+template
+ResumableResult
+Ads816x::manualModeConversion(uint8_t afterNextChannel)
+{
+ RF_BEGIN();
+
+ RF_WAIT_UNTIL(this->acquireMaster());
+ Cs::reset();
+
+ buffer[0] = uint8_t(Command::Write) | (uint16_t(Register::CHANNEL_ID) >> 8);
+ buffer[1] = uint16_t(Register::CHANNEL_ID);
+ buffer[2] = (afterNextChannel & 0b0000'0111);
+
+ RF_CALL(SpiMaster::transfer(buffer, buffer2, 3));
+
+ if (this->releaseMaster()) {
+ Cs::set();
+ }
+
+ RF_END_RETURN( static_cast((static_cast(buffer2[0]) << 8) | buffer2[1]) );
+}
+
+/*
+// FIXME: Does not work correctly...
+template
+template
+ResumableResult
+Ads816x::autoSequenceConversion(uint8_t channelsBitmask, std::span result)
+{
+ using namespace std::chrono_literals;
+ RF_BEGIN();
+
+ if (std::popcount(channelsBitmask) > N) {
+ RF_RETURN();
+ }
+
+ // Write channel bitmask config (AUTO_SEQ_CH)
+ RF_CALL(registerAccess(Command::Write, Register::AUTO_SEQ_CFG1, channelsBitmask));
+
+ timeout.restart(4us);
+ RF_WAIT_UNTIL(timeout.isExpired());
+
+ // Set SEQ_START (0b1 << 0) bit
+ RF_CALL(registerAccess(Command::SetBits, Register::SEQ_START, 0b1));
+
+ // Read conversion results
+ for (buffer2[0] = 0; buffer2[0] < std::popcount(channelsBitmask); buffer2[0]++) {
+ timeout.restart(4us);
+ RF_WAIT_UNTIL(timeout.isExpired());
+
+ RF_WAIT_UNTIL(this->acquireMaster());
+ Cs::reset();
+
+ RF_CALL(SpiMaster::transfer(nullptr, buffer, 2));
+
+ if (this->releaseMaster()) {
+ Cs::set();
+ }
+
+ result[buffer2[0]] = buffer[0] << 8 | buffer[1];
+ }
+
+ RF_END();
+}
+*/
+
+template
+modm::ResumableResult
+Ads816x::registerAccess(Command command, Register reg, uint8_t value)
+{
+ using namespace std::chrono_literals;
+ RF_BEGIN();
+
+ buffer[0] = uint8_t(command) | (uint16_t(reg) >> 8);
+ buffer[1] = uint16_t(reg);
+ buffer[2] = value;
+
+ RF_WAIT_UNTIL(this->acquireMaster());
+ Cs::reset();
+
+ RF_CALL(SpiMaster::transfer(buffer, nullptr, 3));
+
+ if (command == Command::Read) {
+ Cs::set();
+ timeout.restart(4us);
+ RF_WAIT_UNTIL(timeout.isExpired());
+
+ // To retrieve the result of the read command we issue another 3 byte
+ // transfer, nullptr instead of manual NoOperation (= 0b000...) command
+ Cs::reset();
+ RF_CALL(SpiMaster::transfer(nullptr, buffer, 3));
+ }
+
+ if (this->releaseMaster()) {
+ Cs::set();
+ }
+
+ RF_END_RETURN(buffer[0]);
+}
+
+} // namespace modm