diff --git a/README.md b/README.md
index 23dbf4aeb9..6d93882896 100644
--- a/README.md
+++ b/README.md
@@ -365,7 +365,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
○ |
○ |
-○ |
+✅ |
○ |
✅ |
✅ |
@@ -692,93 +692,94 @@ you specific needs.
ADS816x |
AMS5915 |
APA102 |
+AT24MAC402 |
SPI Flash |
-BME280 |
+BME280 |
BMP085 |
BNO055 |
CAT24AA |
DRV832X |
DS1302 |
-DS1631 |
+DS1631 |
DS18B20 |
EA-DOG |
Encoder Input |
Encoder Input BitBang |
Encoder Output BitBang |
-FT245 |
+FT245 |
FT6x06 |
Gpio Sampler |
HCLAx |
HD44780 |
HMC58x |
-HMC6343 |
+HMC6343 |
HX711 |
I2C-EEPROM |
ILI9341 |
IS31FL3733 |
ITG3200 |
-L3GD20 |
+L3GD20 |
LAN8720A |
LAWICEL |
LIS302DL |
LIS3DSH |
LIS3MDL |
-LM75 |
+LM75 |
LP503x |
LSM303A |
LSM6DS33 |
LSM6DSO |
LTC2984 |
-MAX31855 |
+MAX31855 |
MAX6966 |
MAX7219 |
MCP23x17 |
MCP2515 |
MCP7941x |
-MCP990X |
+MCP990X |
MMC5603 |
MS5611 |
MS5837 |
NOKIA5110 |
NRF24 |
-TFT-DISPLAY |
+TFT-DISPLAY |
PAT9125EL |
PCA8574 |
PCA9535 |
PCA9548A |
PCA9685 |
-SH1106 |
+SH1106 |
SIEMENS-S65 |
SIEMENS-S75 |
SK6812 |
SK9822 |
SSD1306 |
-ST7586S |
+ST7586S |
ST7789 |
STTS22H |
STUSB4500 |
SX1276 |
TCS3414 |
-TCS3472 |
+TCS3472 |
TLC594x |
TMP102 |
TMP12x |
TMP175 |
TOUCH2046 |
-VL53L0 |
+VL53L0 |
VL6180 |
WS2812 |
diff --git a/examples/samv71_xplained_ultra/i2c-eeprom/at24mac402/main.cpp b/examples/samv71_xplained_ultra/i2c-eeprom/at24mac402/main.cpp
new file mode 100644
index 0000000000..d4413f455f
--- /dev/null
+++ b/examples/samv71_xplained_ultra/i2c-eeprom/at24mac402/main.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2023, Christopher Durand
+ *
+ * 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
+
+using namespace Board;
+using namespace modm::literals;
+
+// On board AT24MAC402 EEPROM driver example
+int main()
+{
+ Board::initialize();
+
+ MODM_LOG_INFO << "AT24MAC402 Test" << modm::endl;
+
+ /* Defined in board support package:
+ * using Sda = GpioA3;
+ * using Scl = GpioA4;
+ * using I2c = I2cMaster0;
+ */
+ I2c::connect();
+ I2c::initialize();
+
+ // address 0x57 = 1 0 1 0 A0 A1 A2
+ // with A0 = A1 = A2 = 1 connected to 3.3V
+ modm::At24Mac402 eeprom{0x57};
+
+ MODM_LOG_INFO << "EEPROM detected: " << RF_CALL_BLOCKING(eeprom.ping()) << "\n\n";
+
+ std::array buffer{};
+
+ // Read pre-programmed MAC address
+ bool readSuccess = RF_CALL_BLOCKING(eeprom.readMac(buffer));
+ if (readSuccess)
+ {
+ MODM_LOG_INFO.printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n\n",
+ buffer[0], buffer[1], buffer[2],
+ buffer[3], buffer[4], buffer[5]);
+
+ }
+ else
+ {
+ MODM_LOG_INFO << "Reading MAC address from EEPROM failed!\n";
+ }
+
+ // Write 4 data bytes to address 0x80
+ /*constexpr std::array data = {0xAA, 0xBB, 0xCC, 0xDD};
+ const bool writeSuccess = RF_CALL_BLOCKING(eeprom.write(0x80, data.data(), 4));
+ MODM_LOG_INFO << "write successful: " << writeSuccess << "\n\n";*/
+
+ // Read 4 data bytes from address 0x80
+ // retry read until device responds after finishing previous write
+ readSuccess = false;
+ while (!readSuccess)
+ {
+ readSuccess = RF_CALL_BLOCKING(eeprom.read(0x80, buffer.data(), buffer.size()));
+ }
+ MODM_LOG_INFO.printf("data: 0x%02x 0x%02x 0x%02x 0x%02x\n\n",
+ buffer[0], buffer[1], buffer[2], buffer[3]);
+
+ while (true)
+ {
+ Led0::toggle();
+ Led1::toggle();
+ modm::delay(500ms);
+ }
+
+ return 0;
+}
diff --git a/examples/samv71_xplained_ultra/i2c-eeprom/at24mac402/project.xml b/examples/samv71_xplained_ultra/i2c-eeprom/at24mac402/project.xml
new file mode 100644
index 0000000000..5f7c10e243
--- /dev/null
+++ b/examples/samv71_xplained_ultra/i2c-eeprom/at24mac402/project.xml
@@ -0,0 +1,11 @@
+
+ modm:samv71-xplained-ultra
+
+
+
+
+ modm:build:scons
+ modm:driver:at24mac402
+ modm:platform:i2c:0
+
+
diff --git a/examples/samv71_xplained_ultra/i2c-eeprom/generic/main.cpp b/examples/samv71_xplained_ultra/i2c-eeprom/generic/main.cpp
new file mode 100644
index 0000000000..01f890df21
--- /dev/null
+++ b/examples/samv71_xplained_ultra/i2c-eeprom/generic/main.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2023, Christopher Durand
+ *
+ * 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
+
+using namespace Board;
+using namespace modm::literals;
+
+// Generic I2C EEPROM driver example using on-board 2 kBit AT24MAC402 EEPROM
+// For extended AT24MAC402 functionality see specific example and documentation.
+int main()
+{
+ Board::initialize();
+
+ MODM_LOG_INFO << "I2C EEPROM Test" << modm::endl;
+
+ /* Defined in board support package:
+ * using Sda = GpioA3;
+ * using Scl = GpioA4;
+ * using I2c = I2cMaster0;
+ */
+ I2c::connect();
+ I2c::initialize();
+
+ // address 0x57 = 1 0 1 0 A0 A1 A2
+ // with A0 = A1 = A2 = 1 connected to 3.3V
+ // 8 bit address width
+ constexpr auto AddressWidth = 1; // use 2 for 16 bit address EEPROMs
+ modm::I2cEeprom eeprom{0x57};
+
+ MODM_LOG_INFO << "EEPROM detected: " << RF_CALL_BLOCKING(eeprom.ping()) << "\n\n";
+
+ // Write 4 data bytes to address 0x80
+ /*constexpr std::array data = {0xAA, 0xBB, 0xCC, 0xDD};
+ const bool writeSuccess = RF_CALL_BLOCKING(eeprom.write(0x80, data.data(), 4));
+ MODM_LOG_INFO << "write successful: " << writeSuccess << "\n\n";*/
+
+ // Read 4 data bytes from address 0x80
+ std::array buffer{};
+ bool readSuccess = false;
+ // retry read until device responds after finishing previous write
+ while (!readSuccess)
+ {
+ readSuccess = RF_CALL_BLOCKING(eeprom.read(0x80, buffer.data(), buffer.size()));
+ }
+ MODM_LOG_INFO << "read successful: " << readSuccess << "\n";
+ MODM_LOG_INFO.printf("data: 0x%02x 0x%02x 0x%02x 0x%02x\n\n", buffer[0], buffer[1], buffer[2], buffer[3]);
+
+ while (true)
+ {
+ Led0::toggle();
+ Led1::toggle();
+ modm::delay(500ms);
+ }
+
+ return 0;
+}
diff --git a/examples/samv71_xplained_ultra/i2c-eeprom/generic/project.xml b/examples/samv71_xplained_ultra/i2c-eeprom/generic/project.xml
new file mode 100644
index 0000000000..f01217b264
--- /dev/null
+++ b/examples/samv71_xplained_ultra/i2c-eeprom/generic/project.xml
@@ -0,0 +1,11 @@
+
+ modm:samv71-xplained-ultra
+
+
+
+
+ modm:build:scons
+ modm:driver:i2c.eeprom
+ modm:platform:i2c:0
+
+
diff --git a/src/modm/board/samv71_xplained_ultra/board.hpp b/src/modm/board/samv71_xplained_ultra/board.hpp
index 4b4ae300a4..bb5afc0f8b 100644
--- a/src/modm/board/samv71_xplained_ultra/board.hpp
+++ b/src/modm/board/samv71_xplained_ultra/board.hpp
@@ -31,6 +31,7 @@ struct SystemClock
static constexpr uint32_t Mck = Frequency / 2; // 150 MHz max.
static constexpr uint32_t Usart1 = Mck;
static constexpr uint32_t Spi0 = Mck;
+ static constexpr uint32_t Twihs0 = Mck;
// static constexpr uint32_t Usb = 48_MHz;
static bool inline
@@ -62,6 +63,10 @@ struct Debug
using UartRx = GpioA21;
};
+using I2c = I2cMaster0;
+using Sda = GpioA3;
+using Scl = GpioA4;
+
using LoggerDevice = modm::IODeviceWrapper;
inline void
diff --git a/src/modm/board/samv71_xplained_ultra/module.lb b/src/modm/board/samv71_xplained_ultra/module.lb
index 1b08b54603..8dcdd3672c 100644
--- a/src/modm/board/samv71_xplained_ultra/module.lb
+++ b/src/modm/board/samv71_xplained_ultra/module.lb
@@ -19,7 +19,7 @@ def prepare(module, options):
if not options[":target"].partname == "samv71q21b-aab":
return False
- module.depends(":debug", ":platform:clockgen", ":platform:gpio", ":platform:core", ":platform:usart:1") #, ":platform:usb")
+ module.depends(":debug", ":platform:clockgen", ":platform:gpio", ":platform:core", ":platform:usart:1", ":platform:i2c:0") #, ":platform:usb")
return True
def build(env):
diff --git a/src/modm/driver/storage/at24mac402.hpp b/src/modm/driver/storage/at24mac402.hpp
new file mode 100644
index 0000000000..cb97d9ec86
--- /dev/null
+++ b/src/modm/driver/storage/at24mac402.hpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2023, Christopher Durand
+ *
+ * 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_AT24MAC402_HPP
+#define MODM_AT24MAC402_HPP
+
+#include
+#include
+
+namespace modm
+{
+
+/**
+ * AT24MAC402 I2C EEPROM
+ *
+ * 2 kBit EEPROM with pre-programmed 48 bit MAC address, 128 bit unique id and
+ * 8 bit address pointer.
+ * The device address can be configured from 0x50 to 0x57 with 3 address input
+ * lines.
+ *
+ * @ingroup modm_driver_at24mac402
+ * @author Christopher Durand
+ */
+template
+class At24Mac402 : protected I2cEeprom
+{
+ using Base = I2cEeprom;
+ static constexpr uint8_t UuidAddress{0x80};
+ static constexpr uint8_t MacAddress{0x9A};
+public:
+ using Base::attachConfigurationHandler;
+
+ /// @param address i2c data base address (0b1010xxx)
+ At24Mac402(uint8_t address = 0x50);
+
+ /**
+ * Set I2C data base address (0b1010xxx).
+ * The upper 4 bits will always be forced to 0b1010.
+ */
+ void
+ setAddress(uint8_t address);
+
+ // Read 48 bit pre-programmed MAC
+ modm::ResumableResult
+ readMac(std::span data);
+
+ // Read 128 bit pre-programmed unique id
+ modm::ResumableResult
+ readUniqueId(std::span data);
+
+ // start documentation inherited
+ modm::ResumableResult
+ ping();
+
+ modm::ResumableResult
+ writeByte(uint32_t address, uint8_t data);
+
+ modm::ResumableResult
+ write(uint32_t address, const uint8_t* data, std::size_t length);
+
+ template
+ modm::ResumableResult
+ write(uint32_t address, const T& data);
+
+ modm::ResumableResult
+ readByte(uint32_t address, uint8_t& data);
+
+ modm::ResumableResult
+ read(uint32_t address, uint8_t* data, std::size_t length);
+
+ template
+ modm::ResumableResult
+ read(uint32_t address, T& data);
+ // end documentation inherited
+private:
+ uint8_t address_{};
+};
+
+} // namespace modm
+
+#include "at24mac402_impl.hpp"
+
+#endif // MODM_AT24MAC402_HPP
diff --git a/src/modm/driver/storage/at24mac402.lb b/src/modm/driver/storage/at24mac402.lb
new file mode 100644
index 0000000000..66375d4f7c
--- /dev/null
+++ b/src/modm/driver/storage/at24mac402.lb
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2023, Christopher Durand
+#
+# 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:at24mac402"
+ module.description = """\
+# AT24MAC404 I²C EEPROM
+
+2 kBit I²C EEPROM with pre-programmed read-only 48 bit MAC address and 128 bit
+unique id.
+"""
+
+def prepare(module, options):
+ module.depends(":architecture:i2c.device", ":driver:i2c.eeprom")
+ return True
+
+def build(env):
+ env.outbasepath = "modm/src/modm/driver/storage"
+ env.copy("at24mac402.hpp")
+ env.copy("at24mac402_impl.hpp")
diff --git a/src/modm/driver/storage/at24mac402_impl.hpp b/src/modm/driver/storage/at24mac402_impl.hpp
new file mode 100644
index 0000000000..3f084236e2
--- /dev/null
+++ b/src/modm/driver/storage/at24mac402_impl.hpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2023, Christopher Durand
+ *
+ * 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_AT24MAC402_HPP
+# error "Don't include this file directly, use 'at24mac402_impl.hpp' instead!"
+#endif
+
+// ----------------------------------------------------------------------------
+
+template
+modm::At24Mac402::At24Mac402(uint8_t address)
+{
+ setAddress(address);
+}
+
+template
+void
+modm::At24Mac402::setAddress(uint8_t address)
+{
+ address_ = (address & 0b111) | 0b1010'000;
+ Base::setAddress(address_);
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::readMac(std::span data)
+{
+ Base::setAddress(address_ | 0b1'000);
+ return Base::read(MacAddress, &data[0], 6);
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::readUniqueId(std::span data)
+{
+ Base::setAddress(address_ | 0b1'000);
+ return Base::read(UuidAddress, &data[0], 16);
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::ping()
+{
+ Base::setAddress(address_);
+ return Base::ping();
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::writeByte(uint32_t address, uint8_t data)
+{
+ Base::setAddress(address_);
+ return Base::writeByte(address, data);
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::write(uint32_t address, const uint8_t* data, std::size_t length)
+{
+ Base::setAddress(address_);
+ return Base::write(address, data, length);
+}
+
+template
+template
+modm::ResumableResult
+modm::At24Mac402::write(uint32_t address, const T& data)
+{
+ Base::setAddress(address_);
+ return Base::write(address, data);
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::readByte(uint32_t address, uint8_t& data)
+{
+ Base::setAddress(address_);
+ return Base::readByte(address, data);
+}
+
+template
+modm::ResumableResult
+modm::At24Mac402::read(uint32_t address, uint8_t* data, std::size_t length)
+{
+ Base::setAddress(address_);
+ return Base::read(address, data, length);
+}
+
+template
+template
+modm::ResumableResult
+modm::At24Mac402::read(uint32_t address, T& data)
+{
+ Base::setAddress(address_);
+ return read(address, reinterpret_cast(&data), sizeof(T));
+}
diff --git a/src/modm/driver/storage/i2c_eeprom.cpp b/src/modm/driver/storage/i2c_eeprom.cpp
deleted file mode 100644
index c5fdfa7414..0000000000
--- a/src/modm/driver/storage/i2c_eeprom.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2009, Martin Rosekeit
- * Copyright (c) 2009-2012, Fabian Greif
- * Copyright (c) 2011, Georgi Grinshpun
- * Copyright (c) 2012, Sascha Schade
- * Copyright (c) 2012-2015, 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/.
- */
-// ----------------------------------------------------------------------------
-
-#include "i2c_eeprom.hpp"
-
-// ----------------------------------------------------------------------------
-modm::i2cEeprom::DataTransmissionAdapter::DataTransmissionAdapter(uint8_t address) :
- I2cWriteReadTransaction(address), writeAddress(false)
-{}
-
-bool
-modm::i2cEeprom::DataTransmissionAdapter::configureWrite(uint16_t address, const uint8_t *buffer, std::size_t size)
-{
- if (I2cWriteReadTransaction::configureWrite(buffer, size))
- {
- addressBuffer[0] = address >> 8;
- addressBuffer[1] = address;
- writeAddress = true;
- return true;
- }
- return false;
-}
-
-bool
-modm::i2cEeprom::DataTransmissionAdapter::configureRead(uint16_t address, uint8_t *buffer, std::size_t size)
-{
- addressBuffer[0] = address >> 8;
- addressBuffer[1] = address;
- writeAddress = false;
- return I2cWriteReadTransaction::configureWriteRead(addressBuffer, 2, buffer, size);
-}
-
-modm::I2cTransaction::Writing
-modm::i2cEeprom::DataTransmissionAdapter::writing()
-{
- if (writeAddress)
- {
- writeAddress = false;
- return Writing(addressBuffer, 2, OperationAfterWrite::Write);
- }
- return I2cWriteReadTransaction::writing();
-}
diff --git a/src/modm/driver/storage/i2c_eeprom.hpp b/src/modm/driver/storage/i2c_eeprom.hpp
index 228b3fc061..a488ea9536 100644
--- a/src/modm/driver/storage/i2c_eeprom.hpp
+++ b/src/modm/driver/storage/i2c_eeprom.hpp
@@ -3,6 +3,7 @@
* Copyright (c) 2009-2012, Fabian Greif
* Copyright (c) 2011, Georgi Grinshpun
* Copyright (c) 2012-2016, Niklas Hauser
+ * Copyright (c) 2021, Christopher Durand
*
* This file is part of the modm project.
*
@@ -22,30 +23,35 @@ namespace modm
{
/// @cond
-struct i2cEeprom
+namespace i2c_eeprom::detail
{
- class DataTransmissionAdapter : public modm::I2cWriteReadTransaction
- {
- public:
- DataTransmissionAdapter(uint8_t address);
- bool
- configureWrite(uint16_t address, const uint8_t *buffer, std::size_t size);
+template
+class DataTransmissionAdapter : public I2cWriteReadTransaction
+{
+public:
+ static_assert(AddressBytes == 1 || AddressBytes == 2, "Only 8 or 16 bit addresses are supported");
+
+ DataTransmissionAdapter(uint8_t address);
- bool
- configureRead(uint16_t address, uint8_t *buffer, std::size_t size);
+ bool
+ configureWrite(uint16_t address, const uint8_t *buffer, std::size_t size);
- inline uint8_t getAddress() const {
- return this->address >> 1;
- }
+ bool
+ configureRead(uint16_t address, uint8_t *buffer, std::size_t size);
- protected:
- virtual Writing
- writing() override;
+ inline uint8_t getAddress() const {
+ return this->address >> 1;
+ }
+
+protected:
+ Writing
+ writing() override;
+
+ uint8_t addressBuffer[AddressBytes];
+ bool writeAddress;
+};
- uint8_t addressBuffer[2];
- bool writeAddress;
- };
};
/// @endcond
@@ -60,8 +66,8 @@ struct i2cEeprom
* @author Fabian Greif
* @author Niklas Hauser
*/
-template
-class I2cEeprom : public modm::I2cDevice< I2cMaster, 1, i2cEeprom::DataTransmissionAdapter >
+template
+class I2cEeprom : public I2cDevice>
{
public:
I2cEeprom(uint8_t address = 0x50);
diff --git a/src/modm/driver/storage/i2c_eeprom.lb b/src/modm/driver/storage/i2c_eeprom.lb
index 8e6e36699a..f8f9a1b005 100644
--- a/src/modm/driver/storage/i2c_eeprom.lb
+++ b/src/modm/driver/storage/i2c_eeprom.lb
@@ -17,7 +17,8 @@ def init(module):
# I2C Eeprom
Compatible with the 24C256 (ST) and 24FC1025 (Microchip) family and other
-I2C eeprom with an 16-bit address pointer.
+I2C eeprom with an 16-bit address pointer as well as devices with an 8-bit
+address pointer.
Base address for most 24xxyyyy eeproms is 0x50.
"""
@@ -28,5 +29,4 @@ def prepare(module, options):
def build(env):
env.outbasepath = "modm/src/modm/driver/storage"
env.copy("i2c_eeprom.hpp")
- env.copy("i2c_eeprom.cpp")
env.copy("i2c_eeprom_impl.hpp")
diff --git a/src/modm/driver/storage/i2c_eeprom_impl.hpp b/src/modm/driver/storage/i2c_eeprom_impl.hpp
index 798783b8e4..8ac0d77400 100644
--- a/src/modm/driver/storage/i2c_eeprom_impl.hpp
+++ b/src/modm/driver/storage/i2c_eeprom_impl.hpp
@@ -4,6 +4,7 @@
* Copyright (c) 2011, Georgi Grinshpun
* Copyright (c) 2012, Sascha Schade
* Copyright (c) 2012-2016, Niklas Hauser
+ * Copyright (c) 2021, Christopher Durand
*
* This file is part of the modm project.
*
@@ -18,21 +19,25 @@
#endif
// ----------------------------------------------------------------------------
-template
-modm::I2cEeprom::I2cEeprom(uint8_t address) :
- I2cDevice(address)
+template
+modm::I2cEeprom::I2cEeprom(uint8_t address) :
+ I2cDevice>{address}
{
}
// MARK: - write operations
-template
+template
modm::ResumableResult
-modm::I2cEeprom::write(uint32_t address, const uint8_t *data, std::size_t length)
+modm::I2cEeprom::write(uint32_t address, const uint8_t *data, std::size_t length)
{
RF_BEGIN();
- if (address & 0x10000) this->setAddress(this->transaction.getAddress() | 0b100);
- else this->setAddress(this->transaction.getAddress() & ~0b100);
+ if constexpr (AddressBytes > 1) {
+ if (address & 0x10000) this->setAddress(this->transaction.getAddress() | 0b100);
+ else this->setAddress(this->transaction.getAddress() & ~0b100);
+ } else {
+ this->setAddress(this->transaction.getAddress());
+ }
RF_WAIT_UNTIL( this->transaction.configureWrite(address, data, length) and this->startTransaction() );
@@ -42,14 +47,18 @@ modm::I2cEeprom::write(uint32_t address, const uint8_t *data, std::si
}
// MARK: - read operations
-template
+template
modm::ResumableResult
-modm::I2cEeprom::read(uint32_t address, uint8_t *data, std::size_t length)
+modm::I2cEeprom::read(uint32_t address, uint8_t *data, std::size_t length)
{
RF_BEGIN();
- if (address & 0x10000) this->setAddress(this->transaction.getAddress() | 0b100);
- else this->setAddress(this->transaction.getAddress() & ~0b100);
+ if constexpr (AddressBytes > 1) {
+ if (address & 0x10000) this->setAddress(this->transaction.getAddress() | 0b100);
+ else this->setAddress(this->transaction.getAddress() & ~0b100);
+ } else {
+ this->setAddress(this->transaction.getAddress());
+ }
RF_WAIT_UNTIL( this->transaction.configureRead(address, data, length) and this->startTransaction() );
@@ -57,3 +66,57 @@ modm::I2cEeprom::read(uint32_t address, uint8_t *data, std::size_t le
RF_END_RETURN( this->wasTransactionSuccessful() );
}
+
+namespace modm::i2c_eeprom::detail
+{
+
+template
+DataTransmissionAdapter::DataTransmissionAdapter(uint8_t address) :
+ I2cWriteReadTransaction(address), writeAddress(false)
+{}
+
+template
+bool
+DataTransmissionAdapter::configureWrite(uint16_t address, const uint8_t *buffer, std::size_t size)
+{
+ if (I2cWriteReadTransaction::configureWrite(buffer, size))
+ {
+ if constexpr (AddressBytes == 1) {
+ addressBuffer[0] = address;
+ } else {
+ addressBuffer[0] = address >> 8;
+ addressBuffer[1] = address;
+ }
+ writeAddress = true;
+ return true;
+ }
+ return false;
+}
+
+template
+bool
+DataTransmissionAdapter::configureRead(uint16_t address, uint8_t *buffer, std::size_t size)
+{
+ if constexpr (AddressBytes == 1) {
+ addressBuffer[0] = address;
+ } else {
+ addressBuffer[0] = address >> 8;
+ addressBuffer[1] = address;
+ }
+ writeAddress = false;
+ return I2cWriteReadTransaction::configureWriteRead(addressBuffer, AddressBytes, buffer, size);
+}
+
+template
+I2cTransaction::Writing
+DataTransmissionAdapter::writing()
+{
+ if (writeAddress)
+ {
+ writeAddress = false;
+ return Writing(addressBuffer, AddressBytes, OperationAfterWrite::Write);
+ }
+ return I2cWriteReadTransaction::writing();
+}
+
+}
diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in
new file mode 100644
index 0000000000..ef36040818
--- /dev/null
+++ b/src/modm/platform/i2c/sam_x7x/i2c_master.cpp.in
@@ -0,0 +1,376 @@
+// coding: utf-8
+/*
+ * Copyright (c) 2023, Christopher Durand
+ * Copyright (c) 2017, Niklas Hauser
+ * Copyright (c) 2017, Sascha Schade
+ *
+ * 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 "i2c_master_{{ id }}.hpp"
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+ static modm::I2c::Operation nextOperation;
+
+ // transaction queue management
+ struct ConfiguredTransaction
+ {
+ ConfiguredTransaction()
+ : transaction(nullptr), configuration(nullptr) {}
+
+ ConfiguredTransaction(modm::I2cTransaction* transaction, modm::I2c::ConfigurationHandler configuration)
+ : transaction(transaction), configuration(configuration) {}
+
+ modm::I2cTransaction* transaction;
+ modm::I2c::ConfigurationHandler configuration;
+ };
+
+ static modm::BoundedQueue queue;
+ static modm::I2c::ConfigurationHandler configuration{nullptr};
+
+ static modm::I2cTransaction* transaction{};
+ static modm::I2cMaster::Error error{modm::I2cMaster::Error::NoError};
+
+ static modm::I2cTransaction::Starting starting{0, modm::I2c::OperationAfterStart::Stop};
+ static modm::I2cTransaction::Writing writing{nullptr, 0, modm::I2c::OperationAfterWrite::Stop};
+ static modm::I2cTransaction::Reading reading{nullptr, 0, modm::I2c::OperationAfterRead::Stop};
+
+ static constexpr int MaxRetries{3};
+ static int arbitrationRetryCounter{MaxRetries};
+
+ enum class PreviousOperation
+ {
+ Idle = 0,
+ Write = 1,
+ Read = 2
+ };
+
+ static inline void
+ callStarting(PreviousOperation previous);
+
+ static inline void
+ callWriteOperation(PreviousOperation previous)
+ {
+ TWIHS{{ id }}->TWIHS_MMR = TWIHS_MMR_DADR(starting.address >> 1);
+
+ // The write transfer is started automatically when TWIHS_THR is written.
+ // In case of a repeated start TWIHS_CR_START must be set before writing
+ // the register.
+ if (previous != PreviousOperation::Idle) {
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_START;
+ }
+
+ // Write first data byte
+ if ((writing.length > 0) and (writing.buffer != nullptr)) {
+ TWIHS{{ id }}->TWIHS_THR = *writing.buffer++;
+ --writing.length;
+ } else {
+ // This case should never occcur.
+ // Transactions with zero data length are unsupported by hardware.
+ // A write must be performed to restore valid peripheral state.
+ writing.length = 0;
+ TWIHS{{ id }}->TWIHS_THR = 0;
+ }
+
+ if (writing.length == 0) {
+ if (nextOperation == modm::I2c::Operation::Restart) {
+ callStarting(PreviousOperation::Write);
+ } else if (nextOperation == modm::I2c::Operation::Write) {
+ writing = transaction->writing();
+ nextOperation = static_cast(writing.next);
+ TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_TXRDY;
+ } else {
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_STOP;
+ }
+ } else {
+ TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_TXRDY;
+ }
+
+ TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_TXCOMP;
+ }
+
+ static inline void
+ callReadOperation(PreviousOperation previous)
+ {
+ // In case of a restart after a read, the last byte from the previous
+ // operation has not been read yet and thus may not be cleared.
+ // The next read operation must be pre-programmed before the last byte
+ // of the previous one has been read.
+ // Reading that byte will trigger transmission of the repeated
+ // start condition.
+ if (previous != PreviousOperation::Read) {
+ (void) TWIHS{{ id }}->TWIHS_RHR;
+ }
+
+ TWIHS{{ id }}->TWIHS_MMR = TWIHS_MMR_DADR(starting.address >> 1) | TWIHS_MMR_MREAD;
+ if (reading.length <= 1) {
+ // For a one byte read START and STOP must be set simultaneously
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_START | TWIHS_CR_STOP;
+ } else {
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_START;
+ }
+ TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_RXRDY;
+ }
+
+ static inline void
+ callStarting(PreviousOperation previous)
+ {
+ starting = transaction->starting();
+ switch (starting.next)
+ {
+ case modm::I2c::OperationAfterStart::Read:
+ reading = transaction->reading();
+ nextOperation = static_cast(reading.next);
+
+ callReadOperation(previous);
+ break;
+
+ case modm::I2c::OperationAfterStart::Write:
+ writing = transaction->writing();
+ nextOperation = static_cast(writing.next);
+
+ callWriteOperation(previous);
+ break;
+
+ case modm::I2c::OperationAfterStart::Stop:
+ // It is impossible to do a transaction with a stop immediatately
+ // after the address has been ACK'ed. Writing the data output register is
+ // the only way to start a write transaction and TWIHS_CR_STOP will
+ // only output a stop condition after the first byte has been sent if an
+ // ACK has been received from the device.
+
+ // Use a dummy zero byte buffer, there is no way not to send the data :(
+ static constexpr uint8_t zero{0};
+ writing.length = 1;
+ writing.buffer = &zero;
+ reading.length = 0;
+ writing = transaction->writing();
+ nextOperation = modm::I2c::Operation::Stop;
+
+ callWriteOperation(previous);
+ break;
+ }
+
+ error = modm::I2cMaster::Error::NoError;
+ }
+
+ static inline void
+ callNextTransaction()
+ {
+ if (queue.isNotEmpty())
+ {
+ ConfiguredTransaction next = queue.get();
+ queue.pop();
+ // configure the peripheral if necessary
+ if (next.configuration and (configuration != next.configuration)) {
+ configuration = next.configuration;
+ configuration();
+ }
+
+ arbitrationRetryCounter = MaxRetries;
+ ::transaction = next.transaction;
+ callStarting(PreviousOperation::Idle);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+MODM_ISR(TWIHS{{ id }})
+{
+ const uint32_t status = TWIHS{{ id }}->TWIHS_SR;
+
+ // tx ready interrupt enabled, writing operation in progress
+ if (TWIHS{{ id }}->TWIHS_IMR & TWIHS_IMR_TXRDY) {
+ if (status & TWIHS_SR_TXRDY) {
+ if ((writing.length > 0) and (writing.buffer != nullptr)) {
+ TWIHS{{ id }}->TWIHS_THR = *writing.buffer++;
+ --writing.length;
+ }
+
+ if (writing.length == 0) {
+ if (nextOperation == modm::I2c::Operation::Restart) {
+ // disable tx ready interrupt
+ TWIHS{{ id }}->TWIHS_IDR = TWIHS_IDR_TXRDY;
+ callStarting(PreviousOperation::Write);
+ } else if (nextOperation == modm::I2c::Operation::Write) {
+ // continue writing with new data buffer
+ // do not disable tx ready interrupt
+ writing = transaction->writing();
+ nextOperation = static_cast(writing.next);
+ } else {
+ // disable tx ready interrupt
+ TWIHS{{ id }}->TWIHS_IDR = TWIHS_IDR_TXRDY;
+ // send stop after write, enable completion interrupt
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_STOP;
+ TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_TXCOMP;
+ }
+ }
+ }
+ // rx interrupt enabled, reading operation in progress
+ } else if (TWIHS{{ id }}->TWIHS_IMR & TWIHS_IMR_RXRDY) {
+ if (status & TWIHS_SR_RXRDY) {
+ if (reading.length <= 1) {
+ TWIHS{{ id }}->TWIHS_IDR = TWIHS_IDR_RXRDY;
+ // To end the current reading operation TWIHS_CR_STOP
+ // or TWIHS_CR_START must be programmed before the data of
+ // the last byte is read. Otherwise reading of the last byte
+ // would automatically start another read.
+ //
+ // Remember buffer pointer and length
+ // In case of a restart with a new reading operation
+ // reading.buffer will point to the next buffer.
+ uint8_t* const buffer = reading.buffer;
+ const auto length = reading.length;
+
+ if (nextOperation == modm::I2c::Operation::Restart) {
+ callStarting(PreviousOperation::Read);
+ } else {
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_STOP;
+ }
+ // must be read after setting TWIHS_CR_STOP or TWIHS_CR_START
+ const uint8_t data = TWIHS{{ id }}->TWIHS_RHR;
+ if (buffer != nullptr && length != 0) {
+ *buffer = data;
+ }
+ } else {
+ const uint8_t data = TWIHS{{ id }}->TWIHS_RHR;
+ if (reading.buffer != nullptr) {
+ *reading.buffer++ = data;
+ }
+ --reading.length;
+ }
+ }
+ }
+
+ // if transfer completed (after stop condition)
+ if (status & TWIHS_SR_TXCOMP) {
+ // disable interrupts
+ TWIHS{{ id }}->TWIHS_IDR = TWIHS_IDR_TXCOMP | TWIHS_IDR_RXRDY | TWIHS_IDR_TXRDY;
+ if (status & TWIHS_SR_NACK) { // NACK received
+ error = modm::I2cMaster::Error::AddressNack;
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_LOCKCLR;
+ if (transaction) {
+ transaction->detaching(modm::I2c::DetachCause::ErrorCondition);
+ transaction = nullptr;
+ }
+ } else if (status & TWIHS_SR_ARBLST) { // arbitration lost
+ if (arbitrationRetryCounter > 0) {
+ --arbitrationRetryCounter;
+ // TODO: is it safe with the transaction interface to call starting() again?
+ callStarting(PreviousOperation::Idle);
+ } else { // retries exceeded
+ error = modm::I2cMaster::Error::ArbitrationLost;
+ if (transaction) {
+ transaction->detaching(modm::I2c::DetachCause::ErrorCondition);
+ transaction = nullptr;
+ }
+ }
+ } else { // valid ACK from device
+ if (transaction) {
+ transaction->detaching(modm::I2c::DetachCause::NormalStop);
+ transaction = nullptr;
+ }
+ }
+ callNextTransaction();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+void
+modm::platform::I2cMaster{{ id }}::initializeWithClockConfig(uint32_t cwgrRegister)
+{
+ ClockGen::enable();
+
+ // disable all interrupts in case this function is called while the peripheral is operating
+ TWIHS{{ id }}->TWIHS_IDR = ~0ul;
+
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_SWRST;
+ TWIHS{{ id }}->TWIHS_CWGR = cwgrRegister;
+
+ // Enable master mode
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_MSDIS;
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_SVDIS;
+ TWIHS{{ id }}->TWIHS_CR = TWIHS_CR_MSEN;
+
+ // Enable arbitration lost interrupt
+ TWIHS{{ id }}->TWIHS_IER = TWIHS_IER_ARBLST;
+
+ // TODO: make priority configurable?
+ // 10 is also used in the STM32 extended driver
+ NVIC_SetPriority(TWIHS{{ id }}_IRQn, 10);
+ NVIC_EnableIRQ(TWIHS{{ id }}_IRQn);
+}
+
+void
+modm::platform::I2cMaster{{ id }}::reset()
+{
+ modm::atomic::Lock lock;
+ // TODO: reset peripheral?
+ reading.length = 0;
+ writing.length = 0;
+ error = Error::SoftwareReset;
+ if (transaction) transaction->detaching(DetachCause::ErrorCondition);
+ transaction = nullptr;
+ // remove all queued transactions
+ while (queue.isNotEmpty())
+ {
+ ConfiguredTransaction next = queue.get();
+ if (next.transaction) { next.transaction->detaching(DetachCause::ErrorCondition); }
+ queue.pop();
+ }
+}
+
+bool
+modm::platform::I2cMaster{{ id }}::start(I2cTransaction *transaction, ConfigurationHandler handler)
+{
+ modm::atomic::Lock lock;
+ // if we have a place in the queue and the transaction object is valid
+ if (queue.isNotFull() && transaction)
+ {
+ // if the transaction object wants to attach to the queue
+ if (transaction->attaching())
+ {
+ // if no current transaction is taking place
+ if (!modm::accessor::asVolatile(::transaction))
+ {
+ // configure the peripheral if necessary
+ if (handler and (configuration != handler)) {
+ configuration = handler;
+ configuration();
+ }
+
+ arbitrationRetryCounter = MaxRetries;
+ ::transaction = transaction;
+ callStarting(PreviousOperation::Idle);
+ }
+ else
+ {
+ // queue the transaction for later execution
+ queue.push(ConfiguredTransaction(transaction, configuration));
+ }
+ return true;
+ }
+ else {
+ transaction->detaching(modm::I2c::DetachCause::FailedToAttach);
+ }
+ }
+ return false;
+}
+
+modm::I2cMaster::Error
+modm::platform::I2cMaster{{ id }}::getErrorState()
+{
+ return error;
+}
diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in
new file mode 100644
index 0000000000..2fd036b0a7
--- /dev/null
+++ b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in
@@ -0,0 +1,180 @@
+// coding: utf-8
+/*
+ * Copyright (c) 2023, Christopher Durand
+ * Copyright (c) 2017, Sascha Schade
+ * Copyright (c) 2017-2018, 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/.
+ */
+// ----------------------------------------------------------------------------
+
+#ifndef MODM_STM32_I2C_{{ id }}_HPP
+#define MODM_STM32_I2C_{{ id }}_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace modm
+{
+
+namespace platform
+{
+
+/**
+ * I2C master implementation for TWIHS{{ id }} peripheral.
+ *
+ * @ingroup modm_platform_i2c modm_platform_i2c_{{id}}
+ */
+class I2cMaster{{ id }} : public ::modm::I2cMaster
+{
+public:
+ static constexpr size_t TransactionBufferSize = {{ options["buffer.transaction"] }};
+
+private:
+ // calculate required bits to represent value
+ static constexpr int
+ bits(uint32_t value)
+ {
+ return 32 - std::countl_zero(value);
+ }
+
+ template
+ static constexpr bool
+ checkBaudrate()
+ {
+ if (ckdiv > 7) {
+ return false;
+ }
+
+ const float tHigh = ((chdiv * (1 << ckdiv)) + 3) / float(clock);
+ const float tLow = ((cldiv * (1 << ckdiv)) + 3) / float(clock);
+ const float freq = 1.f / (tHigh + tLow);
+ const float error = std::fabs(1.f - freq / baudrate);
+
+ return error < pct2f(tolerance);
+ }
+
+ template
+ static consteval std::optional
+ calculateTimings()
+ {
+ constexpr auto clock = SystemClock::Twihs{{ id }};
+
+ // hold time = (HOLD + 3) * (1 / peripheral clock)
+ // => HOLD = hold time * (peripheral clock) - 3
+ constexpr float holdTime = 300.e-9f;
+ constexpr float holdIdeal = holdTime * clock - 3.f;
+ constexpr uint8_t hold = static_cast(std::clamp(std::ceil(holdIdeal), 0.f, 63.f));
+
+ constexpr bool fastMode = baudrate > 125'000;
+ // Baudrate threshold above which the low time is fixed to the minimum value of
+ // 1.3us in fast mode or 4.7us in standard mode.
+ constexpr auto minLowTime = fastMode ? 1.3e-6 : 4.7e-6;
+ constexpr auto minLowTimeLimit = 1.f / (2*minLowTime);
+
+ // t_high = ((CHDIV * 2^CKDIV) + 3) * (1 / peripheral clock)
+ // t_low = ((CLDIV * 2^CKDIV) + 3) * (1 / peripheral clock)
+ if constexpr (baudrate > minLowTimeLimit) {
+ // calculate ideal low and high prescaler values (formula from ASF vendor HAL)
+ constexpr auto cldiv = uint32_t(std::round((minLowTime * clock) - 3));
+ constexpr auto tHigh = 1.f / ((baudrate + (baudrate - minLowTimeLimit)) * 2.f);
+ constexpr auto chdiv = uint32_t(std::round((tHigh * clock) - 3));
+
+ // use 2^N pre-divider if max. prescaler exceeds 8 bits
+ constexpr auto ckdiv = std::max(0, std::max(bits(cldiv), bits(chdiv)) - 8);
+ constexpr uint32_t cldivScaled = std::round(float(cldiv) / (1 << ckdiv));
+ constexpr uint32_t chdivScaled = std::round(float(chdiv) / (1 << ckdiv));
+
+ if (!checkBaudrate())
+ return std::nullopt;
+
+ return TWIHS_CWGR_HOLD(hold) | TWIHS_CWGR_CLDIV(cldivScaled) |
+ TWIHS_CWGR_CHDIV(chdivScaled) | TWIHS_CWGR_CKDIV(ckdiv);
+ } else {
+ constexpr auto div = uint32_t(std::round(clock / (baudrate * 2.f) - 3));
+
+ // use 2^N pre-divider if max. prescaler exceeds 8 bits
+ constexpr auto ckdiv = std::max(0, bits(div) - 8);
+ constexpr uint32_t divScaled = std::round(float(div) / (1 << ckdiv));
+
+ if (!checkBaudrate())
+ return std::nullopt;
+
+ return TWIHS_CWGR_HOLD(hold) | TWIHS_CWGR_CLDIV(divScaled) |
+ TWIHS_CWGR_CHDIV(divScaled) | TWIHS_CWGR_CKDIV(ckdiv);
+ }
+ }
+
+public:
+ template
+ static void
+ connect(PullUps pullups = PullUps::External, ResetDevices reset = ResetDevices::Standard)
+ {
+ using Scl = GetPin_t;
+ using Sda = GetPin_t;
+
+ using Peripheral = Peripherals::Twihs<{{ id | int }}>;
+
+ using SclConnector = typename Scl::template Connector>;
+ using SdaConnector = typename Sda::template Connector>;
+
+ const InputType input =
+ (pullups == PullUps::Internal) ? InputType::PullUp : InputType::Floating;
+
+ Scl::configure(input);
+ Sda::configure(input);
+ if (reset != ResetDevices::NoReset) resetDevices(uint32_t(reset));
+ SclConnector::connect();
+ SdaConnector::connect();
+ }
+
+ /**
+ * Set up the I2C module for master operation.
+ *
+ * @param rate
+ * `Standard` (100 kHz) or `Fast` (400 kHz)
+ */
+ template
+ static void
+ initialize()
+ {
+ static_assert(baudrate <= 400'000, "Baudrate must not exceed 400 kHz for I2C fast mode");
+ constexpr std::optional registerValue = calculateTimings();
+ static_assert(bool(registerValue), "Could not find a valid clock configuration for the requested"
+ " baudrate and tolerance");
+
+ initializeWithClockConfig(registerValue.value());
+ }
+
+ // start documentation inherited
+ static bool
+ start(I2cTransaction* transaction, ConfigurationHandler handler = nullptr);
+
+ static Error
+ getErrorState();
+
+ static void
+ reset();
+ // end documentation inherited
+
+private:
+ static void
+ initializeWithClockConfig(uint32_t cwgrRegister);
+};
+
+
+} // namespace platform
+
+} // namespace modm
+
+#endif // MODM_STM32_I2C_{{ id }}_HPP
diff --git a/src/modm/platform/i2c/sam_x7x/module.lb b/src/modm/platform/i2c/sam_x7x/module.lb
new file mode 100644
index 0000000000..8c572e21e1
--- /dev/null
+++ b/src/modm/platform/i2c/sam_x7x/module.lb
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2023, Christopher DUrand
+# Copyright (c) 2016-2018, Niklas Hauser
+# Copyright (c) 2017, Fabian Greif
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+class Instance(Module):
+ def __init__(self, instance):
+ self.instance = instance
+
+ def init(self, module):
+ module.name = str(self.instance)
+ module.description = "Instance {}".format(self.instance)
+
+ def prepare(self, module, options):
+ module.depends(":platform:i2c")
+ module.add_option(
+ NumericOption(
+ name="buffer.transaction",
+ description="Length of transaction queue (in transactions)",
+ minimum=1,
+ maximum="64Ki-2",
+ default=8))
+
+ return True
+
+ def build(self, env):
+ device = env[":target"]
+ driver = device.get_driver("twihs")
+
+ properties = device.properties
+ properties["target"] = target = device.identifier
+ properties["id"] = self.instance
+
+ env.substitutions = properties
+ env.outbasepath = "modm/src/modm/platform/i2c"
+
+ env.template("i2c_master.cpp.in", "i2c_master_{}.cpp".format(self.instance))
+ env.template("i2c_master.hpp.in", "i2c_master_{}.hpp".format(self.instance))
+
+def init(module):
+ module.name = ":platform:i2c"
+ module.description = "Inter-Integrated Circuit (I²C)"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.has_driver("twihs:sam*"):
+ return False
+
+ module.depends(
+ ":architecture:accessor",
+ ":architecture:atomic",
+ ":architecture:clock",
+ ":architecture:i2c",
+ ":architecture:interrupt",
+ ":cmsis:device",
+ ":container",
+ ":platform:gpio")
+
+ for instance in listify(device.get_driver("twihs")["instance"]):
+ module.add_submodule(Instance(int(instance)))
+
+ return True
+
+def build(env):
+ pass
diff --git a/test/modm/platform/i2c/samx7x/i2c_platform_test.cpp b/test/modm/platform/i2c/samx7x/i2c_platform_test.cpp
new file mode 100644
index 0000000000..57c397cb98
--- /dev/null
+++ b/test/modm/platform/i2c/samx7x/i2c_platform_test.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2023, Christopher Durand
+ *
+ * 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 "i2c_platform_test.hpp"
+
+#include
+#include
+#include
+
+using namespace modm::platform;
+using namespace Board;
+
+namespace
+{
+ modm::At24Mac402 eeprom{0x57};
+}
+
+void
+I2cPlatformTest::setUp()
+{
+ I2c::connect();
+ I2c::initialize();
+}
+
+void
+I2cPlatformTest::testPing()
+{
+ // ping at wrong address
+ for (uint8_t address = 0x50; address <= 0x56; ++address) {
+ eeprom.setAddress(address);
+ const bool response = RF_CALL_BLOCKING(eeprom.ping());
+ TEST_ASSERT_FALSE(response);
+ }
+ // set correct address 0x57
+ eeprom.setAddress(0x57);
+ // ping at correct address
+ for (int i = 0; i < 20; ++i) {
+ const bool response = RF_CALL_BLOCKING(eeprom.ping());
+ TEST_ASSERT_TRUE(response);
+ }
+}
+
+void
+I2cPlatformTest::testDataRead()
+{
+ std::array buffer{};
+
+ // read pre-programmed MAC address
+
+ // read at wrong address
+ eeprom.setAddress(0x55);
+ bool readSuccess = RF_CALL_BLOCKING(eeprom.readMac(buffer));
+ TEST_ASSERT_FALSE(readSuccess);
+
+ // read at correct address
+ eeprom.setAddress(0x57);
+ readSuccess = RF_CALL_BLOCKING(eeprom.readMac(buffer));
+ TEST_ASSERT_TRUE(readSuccess);
+
+ TEST_ASSERT_EQUALS(buffer[0], 0xfc);
+ TEST_ASSERT_EQUALS(buffer[1], 0xc2);
+ TEST_ASSERT_EQUALS(buffer[2], 0x3d);
+}
diff --git a/test/modm/platform/i2c/samx7x/i2c_platform_test.hpp b/test/modm/platform/i2c/samx7x/i2c_platform_test.hpp
new file mode 100644
index 0000000000..5c622d1517
--- /dev/null
+++ b/test/modm/platform/i2c/samx7x/i2c_platform_test.hpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023, Christopher Durand
+ *
+ * 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
+
+/// @ingroup modm_test_test_platform_i2c
+class I2cPlatformTest : public unittest::TestSuite
+{
+public:
+ void
+ setUp() override;
+
+ void
+ testPing();
+
+ void
+ testDataRead();
+};
diff --git a/test/modm/platform/i2c/samx7x/module.lb b/test/modm/platform/i2c/samx7x/module.lb
new file mode 100644
index 0000000000..f7da155f2b
--- /dev/null
+++ b/test/modm/platform/i2c/samx7x/module.lb
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2023, Christopher Durand
+#
+# 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 = ":test:platform:i2c"
+ module.description = "Tests for SAMx7x I2C"
+
+def prepare(module, options):
+ target = options[":target"]
+
+ identifier = target.identifier
+ if identifier.platform != "sam" or identifier.family != "e7x/s7x/v7x":
+ return False
+
+ module.depends(":platform:i2c:0")
+ module.depends(":driver:at24mac402")
+ return True
+
+def build(env):
+ if not env.has_module(":board:samv71-xplained-ultra"):
+ env.log.warn("The test requires an AT24MAC402 EEPROM to be connected to specific pins."
+ "Only the SAMV71 Xplained Ultra board is supported for now.")
+ return
+
+ env.outbasepath = "modm-test/src/modm-test/platform/i2c_test"
+ env.copy("i2c_platform_test.hpp")
+ env.copy("i2c_platform_test.cpp")