From f6d2e00952d673994c40dacbebe65a597224873c Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 6 Feb 2023 23:13:10 +0100 Subject: [PATCH] Fixup driver --- .../platform/i2c/sam_x7x/i2c_master.hpp.in | 91 ++++++++++++------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in index 32376af91b..2fd036b0a7 100644 --- a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in +++ b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace modm @@ -39,17 +40,41 @@ public: static constexpr size_t TransactionBufferSize = {{ options["buffer.transaction"] }}; private: - template + // 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() { - const auto clock = SystemClock::Twihs{{ id }}; + 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; - const float holdIdeal = holdTime * clock - 3.f; - const uint8_t hold = static_cast(std::clamp(std::ceil(holdIdeal), 0.f, 63.f)); + 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 @@ -57,38 +82,37 @@ private: constexpr auto minLowTime = fastMode ? 1.3e-6 : 4.7e-6; constexpr auto minLowTimeLimit = 1.f / (2*minLowTime); - constexpr uint32_t maxDiv = 255; // max value of CHDIV, CLDIV - constexpr uint32_t maxPreDiv = 7; // max exponent of 2^N divider - // 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) - auto cldiv = uint32_t(std::round((minLowTime * clock) - 3)); - const auto tHigh = 1.f / (baudrate + (baudrate - minLowTimeLimit)); - auto chdiv = uint32_t(std::round((tHigh * clock) - 3)); - uint32_t ckdiv = 0; - - // increase 2^N pre-divider until prescalers fit into 8 bit - while ((chdiv > maxDiv || cldiv > maxDiv) && (ckdiv < maxPreDiv)) { - ++ckdiv; - chdiv /= 2; - cldiv /= 2; - } - return TWIHS_CWGR_HOLD(hold) | TWIHS_CWGR_CLDIV(cldiv) | - TWIHS_CWGR_CHDIV(chdiv) | TWIHS_CWGR_CKDIV(ckdiv); + 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 { - uint32_t ckdiv = 0; - auto div = uint32_t(std::round(clock / (baudrate * 2.f) - 3)); - - while ((div > maxDiv) && (ckdiv < maxPreDiv)) { - ++ckdiv; - div /= 2; - } - return TWIHS_CWGR_HOLD(hold) | TWIHS_CWGR_CLDIV(div) | - TWIHS_CWGR_CHDIV(div) | TWIHS_CWGR_CKDIV(ckdiv); + 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); } - // TODO: tolerance check } public: @@ -120,13 +144,14 @@ public: * @param rate * `Standard` (100 kHz) or `Fast` (400 kHz) */ - template + 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"); + 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()); }