From f92e3ba21e84536e79002b850226cd5559db3879 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Sun, 19 Apr 2026 12:33:18 +0200 Subject: [PATCH 1/7] [math] Implement consteval float rounding functions for clang support Clang doesn't provide constexpr built-ins for round, ceil and floor yet. GCC and libstd++ had support for constexpr std::round, std::ceil and std::floor before the feature had been added to C++23. --- src/modm/math/utils/cmath.hpp | 161 +++++++++ test/modm/math/utils/consteval_cmath_test.cpp | 316 ++++++++++++++++++ test/modm/math/utils/consteval_cmath_test.hpp | 35 ++ 3 files changed, 512 insertions(+) create mode 100644 src/modm/math/utils/cmath.hpp create mode 100644 test/modm/math/utils/consteval_cmath_test.cpp create mode 100644 test/modm/math/utils/consteval_cmath_test.hpp diff --git a/src/modm/math/utils/cmath.hpp b/src/modm/math/utils/cmath.hpp new file mode 100644 index 0000000000..f46f4f2cf3 --- /dev/null +++ b/src/modm/math/utils/cmath.hpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2026, 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/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +namespace modm::detail +{ + +template +consteval T +consteval_ceil(T value) +{ + static_assert(std::numeric_limits::is_iec559); + + // handle NaN + if (value != value) return value; + + constexpr auto mantissaDigits = std::numeric_limits::digits; + static_assert(std::numeric_limits::digits >= mantissaDigits); + + // calculate magnitude limit outside which all values must be integral + constexpr auto limit = T{1ll << (mantissaDigits - 1)}; + if (value >= limit || value <= -limit) { + return value; + } + + const auto truncated = static_cast(static_cast(value)); + if (value <= T{0}) { + // preserve sign of zero, e.g. ceil(-0.5f) = -0.0f + if (truncated == 0) { + return value * 0; + } + + return truncated; + } else { + return (value > truncated) ? (truncated + T{1}) : truncated; + } +} + +template +consteval T +consteval_floor(T value) +{ + // handle NaN + if (value != value) return value; + + constexpr auto mantissaDigits = std::numeric_limits::digits; + static_assert(std::numeric_limits::digits >= mantissaDigits); + + // calculate magnitude limit outside which all values must be integral + constexpr auto limit = T{1ll << (mantissaDigits - 1)}; + if (value >= limit || value <= -limit) { + return value; + } + + // preserve sign of zero, floor(-0.0f) = -0.0f + if (value == T{0}) { + return value; + } + + const auto truncated = static_cast(static_cast(value)); + if (value >= T{0}) { + return truncated; + } else { + return (value < truncated) ? (truncated - T{1}) : truncated; + } +} + +template +consteval T +consteval_round(T value) +{ + // handle NaN + if (value != value) return value; + + constexpr auto mantissaDigits = std::numeric_limits::digits; + static_assert(std::numeric_limits::digits >= mantissaDigits); + + // calculate magnitude limit outside which all values must be integral + constexpr auto limit = T{1ll << (mantissaDigits - 1)}; + if (value >= limit || value <= -limit) { + return value; + } + + const auto truncated = static_cast(static_cast(value)); + const auto fraction = value - truncated; + + if (std::signbit(value)) { + if (fraction <= T{-0.5}) return truncated - T{1}; + if (truncated == 0) return value * T{0}; + return truncated; + } else { + if (fraction >= T{0.5}) return truncated + T{1}; + return truncated; + } +} + +} // namespace modm::detail + +namespace modm +{ + +template +constexpr T +ceil(T value) +{ +#ifdef MODM_COMPILER_CLANG + if consteval { + return detail::consteval_ceil(value); + } else { + return std::ceil(value); + } +#else + return std::ceil(value); +#endif +} + +template +constexpr T +floor(T value) +{ +#ifdef MODM_COMPILER_CLANG + if consteval { + return detail::consteval_floor(value); + } else { + return std::floor(value); + } +#else + return std::floor(value); +#endif +} + +template +constexpr T +round(T value) +{ +#ifdef MODM_COMPILER_CLANG + if consteval { + return detail::consteval_round(value); + } else { + return std::round(value); + } +#else + return std::round(value); +#endif +} + +} // namespace modm diff --git a/test/modm/math/utils/consteval_cmath_test.cpp b/test/modm/math/utils/consteval_cmath_test.cpp new file mode 100644 index 0000000000..86226d6dc0 --- /dev/null +++ b/test/modm/math/utils/consteval_cmath_test.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2026, 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 "consteval_cmath_test.hpp" +#include + +namespace { + +template +constexpr bool +is_negative_zero(T value) +{ + if (value != T{0}) return false; + return std::signbit(value); +} + +static_assert(!is_negative_zero(0.1f)); +static_assert(!is_negative_zero(0.0f)); +static_assert(is_negative_zero(-0.0f)); +static_assert(!is_negative_zero(0.1)); +static_assert(!is_negative_zero(0.0)); +static_assert(is_negative_zero(-0.0)); + +// 2^60 + 2^40 +template +constexpr T LargeNumber = T{1152922604118474752.0l}; + +} // namespace + +void +ConstevalCmathTest::testCeilFloat() +{ + using modm::detail::consteval_ceil; + + // Standard rounding + static_assert(consteval_ceil(1.1f) == 2.0f); + static_assert(consteval_ceil(1.9f) == 2.0f); + static_assert(consteval_ceil(-1.1f) == -1.0f); + static_assert(consteval_ceil(-1.9f) == -1.0f); + + // Integers should remain unchanged + static_assert(consteval_ceil(2.0f) == 2.0f); + static_assert(consteval_ceil(-2.0f) == -2.0f); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_ceil(0.0f) == 0.0f); + static_assert(!is_negative_zero(consteval_ceil(0.0f))); + static_assert(is_negative_zero(consteval_ceil(-0.0f))); + static_assert(is_negative_zero(consteval_ceil(-0.5f))); + + // Mantissa Limit (2^23) + static_assert(consteval_ceil(8388607.5f) == 8388608.0f); // 2^23 - 0.5 + static_assert(consteval_ceil(8388608.0f) == 8388608.0f); // 2^23 + static_assert(consteval_ceil(8388609.0f) == 8388609.0f); // 2^23 + 1.0 + + // Large numbers + static_assert(consteval_ceil(LargeNumber) == LargeNumber); + static_assert(consteval_ceil(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_ceil(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_ceil(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_ceil(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_ceil(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testCeilDouble() +{ + using modm::detail::consteval_ceil; + + // Standard rounding + static_assert(consteval_ceil(1.1) == 2.0); + static_assert(consteval_ceil(1.9) == 2.0); + static_assert(consteval_ceil(-1.1) == -1.0); + static_assert(consteval_ceil(-1.9) == -1.0); + + // Integers should remain unchanged + static_assert(consteval_ceil(2.0) == 2.0); + static_assert(consteval_ceil(-2.0) == -2.0); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_ceil(0.0) == 0.0); + static_assert(!is_negative_zero(consteval_ceil(0.0))); + static_assert(is_negative_zero(consteval_ceil(-0.0))); + static_assert(is_negative_zero(consteval_ceil(-0.5))); + + // Mantissa Limit (2^52) + static_assert(consteval_ceil(4503599627370495.5) == 4503599627370496.0); // 2^52 - 0.5 + static_assert(consteval_ceil(4503599627370496.0) == 4503599627370496.0); // 2^52 + static_assert(consteval_ceil(4503599627370497.0) == 4503599627370497.0); // 2^52 + 1.0 + + // Large numbers (2^60 + 2^40) + static_assert(consteval_ceil(LargeNumber) == LargeNumber); + static_assert(consteval_ceil(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_ceil(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_ceil(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_ceil(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_ceil(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testFloorFloat() +{ + using modm::detail::consteval_floor; + + // Standard rounding + static_assert(consteval_floor(1.1f) == 1.0f); + static_assert(consteval_floor(1.9f) == 1.0f); + static_assert(consteval_floor(-1.1f) == -2.0f); + static_assert(consteval_floor(-1.9f) == -2.0f); + + // Integers should remain unchanged + static_assert(consteval_floor(2.0f) == 2.0f); + static_assert(consteval_floor(-2.0f) == -2.0f); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_floor(0.0f) == 0.0f); + static_assert(consteval_floor(-0.0f) == -0.0f); + static_assert(!is_negative_zero(consteval_floor(0.0f))); + static_assert(is_negative_zero(consteval_floor(-0.0f))); + + // Mantissa Limit (2^23) + static_assert(consteval_floor(8388607.5f) == 8388607.0f); // 2^23 - 0.5 + static_assert(consteval_floor(8388608.0f) == 8388608.0f); // 2^23 + static_assert(consteval_floor(8388609.0f) == 8388609.0f); // 2^23 + 1.0 + + // Large numbers + static_assert(consteval_floor(LargeNumber) == LargeNumber); + static_assert(consteval_floor(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_floor(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_floor(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_floor(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_floor(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testFloorDouble() +{ + using modm::detail::consteval_floor; + + // Standard rounding + static_assert(consteval_floor(1.1) == 1.0); + static_assert(consteval_floor(1.9) == 1.0); + static_assert(consteval_floor(-1.1) == -2.0); + static_assert(consteval_floor(-1.9) == -2.0); + + // Integers should remain unchanged + static_assert(consteval_floor(2.0) == 2.0); + static_assert(consteval_floor(-2.0) == -2.0); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_floor(0.0) == 0.0); + static_assert(consteval_floor(-0.0) == -0.0); + static_assert(!is_negative_zero(consteval_floor(0.0))); + static_assert(is_negative_zero(consteval_floor(-0.0))); + + // Mantissa Limit (2^52) + static_assert(consteval_floor(4503599627370495.5) == 4503599627370495.0); // 2^52 - 0.5 + static_assert(consteval_floor(4503599627370496.0) == 4503599627370496.0); // 2^52 + static_assert(consteval_floor(4503599627370497.0) == 4503599627370497.0); // 2^52 + 1.0 + + // Large numbers + static_assert(consteval_floor(LargeNumber) == LargeNumber); + static_assert(consteval_floor(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_floor(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_floor(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_floor(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_floor(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testRoundFloat() +{ + using modm::detail::consteval_round; + + // Standard rounding + static_assert(consteval_round(1.1f) == 1.0f); + static_assert(consteval_round(1.4f) == 1.0f); + static_assert(consteval_round(1.5f) == 2.0f); + static_assert(consteval_round(1.9f) == 2.0f); + static_assert(consteval_round(-1.1f) == -1.0f); + static_assert(consteval_round(-1.4f) == -1.0f); + static_assert(consteval_round(-1.5f) == -2.0f); + static_assert(consteval_round(-1.9f) == -2.0f); + + // Integers should remain unchanged + static_assert(consteval_round(2.0f) == 2.0f); + static_assert(consteval_round(-2.0f) == -2.0f); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_round(0.0f) == 0.0f); + static_assert(consteval_round(-0.0f) == -0.0f); + static_assert(consteval_round(-0.4f) == -0.0f); + static_assert(!is_negative_zero(consteval_round(0.0f))); + static_assert(is_negative_zero(consteval_round(-0.0f))); + static_assert(is_negative_zero(consteval_round(-0.4f))); + + // Mantissa Limit (2^23) + static_assert(consteval_round(8388607.5f) == 8388608.0f); // 2^23 - 0.5 + static_assert(consteval_round(8388608.0f) == 8388608.0f); // 2^23 + static_assert(consteval_round(8388609.0f) == 8388609.0f); // 2^23 + 1.0 + + // Large numbers + static_assert(consteval_round(LargeNumber) == LargeNumber); + static_assert(consteval_round(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_round(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_round(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_round(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_round(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testRoundDouble() +{ + using modm::detail::consteval_round; + + // Standard rounding + static_assert(consteval_round(1.1) == 1.0); + static_assert(consteval_round(1.4) == 1.0); + static_assert(consteval_round(1.5) == 2.0); + static_assert(consteval_round(1.9) == 2.0); + static_assert(consteval_round(-1.1) == -1.0); + static_assert(consteval_round(-1.4) == -1.0); + static_assert(consteval_round(-1.5) == -2.0); + static_assert(consteval_round(-1.9) == -2.0); + + // Integers should remain unchanged + static_assert(consteval_round(2.0) == 2.0); + static_assert(consteval_round(-2.0) == -2.0); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_round(0.0) == 0.0); + static_assert(consteval_round(-0.0) == -0.0); + static_assert(!is_negative_zero(consteval_round(0.0))); + static_assert(is_negative_zero(consteval_round(-0.0))); + + // Mantissa Limit (2^52) + static_assert(consteval_round(4503599627370495.5) == 4503599627370496.0); // 2^52 - 0.5 + static_assert(consteval_round(4503599627370496.0) == 4503599627370496.0); // 2^52 + static_assert(consteval_round(4503599627370497.0) == 4503599627370497.0); // 2^52 + 1.0 + + // Large numbers + static_assert(consteval_round(LargeNumber) == LargeNumber); + static_assert(consteval_round(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_round(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_round(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_round(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_round(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} diff --git a/test/modm/math/utils/consteval_cmath_test.hpp b/test/modm/math/utils/consteval_cmath_test.hpp new file mode 100644 index 0000000000..fca705cb10 --- /dev/null +++ b/test/modm/math/utils/consteval_cmath_test.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026, 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_math +class ConstevalCmathTest : public unittest::TestSuite +{ +public: + void + testCeilFloat(); + + void + testCeilDouble(); + + void + testFloorFloat(); + + void + testFloorDouble(); + + void + testRoundFloat(); + + void + testRoundDouble(); +}; From 750716435587a98d765ffe42a7d0a5c02eeef0d8 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 20 Apr 2026 15:17:29 +0200 Subject: [PATCH 2/7] [ui] Fix circular dependencies in ui:color module Rgb, Rgb565, Hsv and Brightness types all circularly depended on each other. The code is ill-formed, but compiled with gcc because of its non-conforming way of instantiating templates. Especially, the code doesn't compile with Clang. All of those classes implemented converting constructors to convert between each other. The dependency circle is broken up by replacing some of those constructors with conversion operators in the other type. Furthermore, conversions between types of different bit widths were disallowed where they were syntactically possible but yielded nonsensical results. In those cases conversion to a smaller type resulted in bitwise truncation. Conversion to a bigger type was done by simple assignment without scaling. TODO: - strongly consider removing error-prone implicit conversions - validate math for correctness --- src/modm/ui/color/brightness.hpp | 21 +++++++++++--- src/modm/ui/color/hsv.hpp | 33 +++------------------ src/modm/ui/color/hsv_impl.hpp | 33 +++++++++++++++++++-- src/modm/ui/color/rgb.hpp | 45 ---------------------------- src/modm/ui/color/rgb565.hpp | 42 +++++++++------------------ src/modm/ui/color/rgb_impl.hpp | 50 -------------------------------- 6 files changed, 65 insertions(+), 159 deletions(-) delete mode 100644 src/modm/ui/color/rgb_impl.hpp diff --git a/src/modm/ui/color/brightness.hpp b/src/modm/ui/color/brightness.hpp index e82fa98e24..82e1d22f7b 100644 --- a/src/modm/ui/color/brightness.hpp +++ b/src/modm/ui/color/brightness.hpp @@ -73,8 +73,7 @@ class BrightnessT * * @param rgb RGB Color */ - template - constexpr BrightnessT(RgbT rgb) + constexpr BrightnessT(RgbT rgb) : value((0.2125f * float(rgb.red)) + (0.7154f * float(rgb.green)) + (0.0721f * float(rgb.blue))) {} @@ -84,8 +83,7 @@ class BrightnessT * * @param hsv HSV Color */ - template - constexpr BrightnessT(HsvT hsv) : value(hsv.value) + constexpr BrightnessT(HsvT hsv) : value(hsv.value) {} /** @@ -97,6 +95,21 @@ class BrightnessT constexpr bool operator==(const BrightnessT &other) const = default; + + constexpr operator RgbT() const + { + return RgbT{value, value, value}; + } + + constexpr operator HsvT() const + { + return HsvT{0, 0, value}; + } + + constexpr operator Rgb565() const + { + return Rgb565(RgbT(*this)); + } }; /// @ingroup modm_ui_color diff --git a/src/modm/ui/color/hsv.hpp b/src/modm/ui/color/hsv.hpp index 76de4a6b28..06891228f6 100644 --- a/src/modm/ui/color/hsv.hpp +++ b/src/modm/ui/color/hsv.hpp @@ -19,22 +19,11 @@ #include #include -#include "brightness.hpp" #include "rgb.hpp" -#include "rgb565.hpp" namespace modm::color { -// forward declarations for convertion constructors -template -class RgbT; - -template -class BrightnessT; - -class Rgb565; - /** * @brief Color in HSV Colorspace * @@ -76,28 +65,14 @@ class HsvT * * @param rgb RGB Color */ - template - constexpr HsvT(const RgbT& rgb); - - /** - * Convertion Constructor for Brightness - * - * @param brightness Brightness 'Color'-object - */ - template - constexpr HsvT(const BrightnessT gray) : hue(0), saturation(0), value(gray.value) - {} - - /** - * Convertion Constructor for RGB565 Color - * - * @param rgb565 RGB565 Color - */ - constexpr HsvT(const Rgb565& rgb565) : HsvT(RgbT(rgb565)) {} + constexpr HsvT(const RgbT& rgb); constexpr bool operator==(const HsvT& other) const = default; + constexpr operator RgbT() const + requires std::is_same_v; + private: template friend IOStream& diff --git a/src/modm/ui/color/hsv_impl.hpp b/src/modm/ui/color/hsv_impl.hpp index c9851b787f..82b2772484 100644 --- a/src/modm/ui/color/hsv_impl.hpp +++ b/src/modm/ui/color/hsv_impl.hpp @@ -24,8 +24,7 @@ * @param rgb */ template -template -constexpr modm::color::HsvT::HsvT(const modm::color::RgbT &rgb) +constexpr modm::color::HsvT::HsvT(const modm::color::RgbT &rgb) { using CalcType = float; const CalcType maxValue = std::numeric_limits::max(); @@ -69,3 +68,33 @@ constexpr modm::color::HsvT::HsvT(const modm::color::RgbT &rgb) else saturation = _diff / _max * maxValue; } + +template +constexpr modm::color::HsvT::operator RgbT() const + requires std::is_same_v +{ + uint16_t vs = value * saturation; + uint16_t h6 = 6 * hue; + + T p = ((value << 8) - vs) >> 8; + T i = h6 >> 8; + uint16_t f = ((i | 1) << 8) - h6; + if (i & 1) { f = -f; } + T u = (((uint32_t)value << 16) - (uint32_t)vs * f) >> 16; + + uint8_t red = 0; + uint8_t green = 0; + uint8_t blue = 0; + + switch (i) + { + case 0: red = value; green = u; blue = p; break; + case 1: red = u; green = value; blue = p; break; + case 2: red = p; green = value; blue = u; break; + case 3: red = p; green = u; blue = value; break; + case 4: red = u; green = p; blue = value; break; + case 5: red = value; green = p; blue = u; break; + } + + return RgbT(red, green, blue); +} diff --git a/src/modm/ui/color/rgb.hpp b/src/modm/ui/color/rgb.hpp index 788043766a..0b0d1c4792 100644 --- a/src/modm/ui/color/rgb.hpp +++ b/src/modm/ui/color/rgb.hpp @@ -24,22 +24,9 @@ #include #include -#include "brightness.hpp" -#include "hsv.hpp" -#include "rgb565.hpp" - namespace modm::color { -// forward declarations for convertion constructors -template -class HsvT; - -template -class BrightnessT; - -class Rgb565; - /** * Color in HSV Colorspace * @@ -78,36 +65,6 @@ class RgbT : red(rgb_other.red >> 8), green(rgb_other.green >> 8), blue(rgb_other.blue >> 8) {} - /** - * Convertion Constructor for HSV Color - * - * @param hsv HSV Color - */ - template - constexpr RgbT(const HsvT& hsv); - - /** - * Convertion Constructor for Brightness - * - * @param brightness Brightness 'Color'-object - */ - // TODO Plump conversion, implement the right way - template - constexpr RgbT(const BrightnessT brightness) - : red(brightness), green(brightness), blue(brightness) - {} - - /** - * Convertion Constructor for RGB565 Color - * - * @param rgb565 RGB565 Color - */ - constexpr RgbT(const Rgb565& rgb565) - : red((rgb565.color >> 8) & 0xF8), - green((rgb565.color >> 3) & 0xFC), - blue(rgb565.color << 3) - {} - constexpr bool operator==(const RgbT& other) const = default; @@ -160,6 +117,4 @@ operator<<(IOStream& os, const color::RgbT& color) } // namespace modm::color -#include "rgb_impl.hpp" - #endif // MODM_COLOR_RGB_HPP diff --git a/src/modm/ui/color/rgb565.hpp b/src/modm/ui/color/rgb565.hpp index dbb19aba56..01d6838e7c 100644 --- a/src/modm/ui/color/rgb565.hpp +++ b/src/modm/ui/color/rgb565.hpp @@ -13,23 +13,12 @@ #include -#include "brightness.hpp" #include "hsv.hpp" #include "rgb.hpp" namespace modm::color { -// forward declarations for convertion constructors -template -class RgbT; - -template -class HsvT; - -template -class BrightnessT; - /** * Color in RGB Colorspace, 16 bits: RRRR RGGG GGGB BBBB * @@ -72,27 +61,22 @@ class Rgb565 constexpr Rgb565(const RgbT &rgb) : Rgb565(rgb.red, rgb.green, rgb.blue) {} - /** - * Convertion Constructor for HSV Color - * - * @param hsv HSV Color - */ - template - constexpr Rgb565(const HsvT &hsv) : Rgb565(RgbCalcType(hsv)) - {} - - /** - * Convertion Constructor for Brightness - * - * @param brightness Brightness 'Color'-object - */ - template - constexpr Rgb565(const BrightnessT brightness) : Rgb565(RgbCalcType(brightness)) - {} - constexpr bool operator==(const Rgb565 &other) const = default; + constexpr operator RgbCalcType() const + { + const uint8_t red = (color >> 8) & 0xF8; + const uint8_t green = (color >> 3) & 0xFC; + const uint8_t blue = (color << 3); + return RgbCalcType(red, green, blue); + } + + constexpr operator Hsv() const + { + return Hsv(RgbT(*this)); + } + /// Saturated addition ⊕ Rgb565 operator+(const Rgb565 other) const diff --git a/src/modm/ui/color/rgb_impl.hpp b/src/modm/ui/color/rgb_impl.hpp deleted file mode 100644 index b8b9a17841..0000000000 --- a/src/modm/ui/color/rgb_impl.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2009-2010, 2012, Fabian Greif - * Copyright (c) 2010, Martin Rosekeit - * Copyright (c) 2012-2013, Niklas Hauser - * Copyright (c) 2013, David Hebbeker - * Copyright (c) 2021, Thomas Sommer - * - * 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_COLOR_RGB_HPP -#error "Don't include this file directly, use 'rgb.hpp' instead!" -#endif - -#include - -namespace modm::color -{ - -// TODO Finish generalisation for uint16_t -template -template -constexpr RgbT::RgbT(const HsvT &hsv) -{ - uint16_t vs = hsv.value * hsv.saturation; - uint16_t h6 = 6 * hsv.hue; - - T p = ((hsv.value << 8) - vs) >> 8; - T i = h6 >> 8; - uint16_t f = ((i | 1) << 8) - h6; - if (i & 1) { f = -f; } - T u = (((uint32_t)hsv.value << 16) - (uint32_t)vs * f) >> 16; - - switch (i) - { - case 0: red = hsv.value; green = u; blue = p; break; - case 1: red = u; green = hsv.value; blue = p; break; - case 2: red = p; green = hsv.value; blue = u; break; - case 3: red = p; green = u; blue = hsv.value; break; - case 4: red = u; green = p; blue = hsv.value; break; - case 5: red = hsv.value; green = p; blue = u; break; - } -} - -} // namespace modm::color From e4504877f35310ea25123a6f499cfdcb1de5221a Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Sun, 19 Apr 2026 12:47:02 +0200 Subject: [PATCH 3/7] [tools] Allow compilation with Clang version >= 22 --- ext/gcc/modm_atomic.hpp.in | 3 +- src/modm/architecture/interface/interrupt.hpp | 9 ++++++ src/modm/io/iostream.hpp.in | 10 +++++++ src/modm/math/utils/arithmetic_traits.hpp | 30 +++++++++++-------- src/modm/platform/clock/sam/gclk_impl.hpp.in | 6 ++-- src/modm/platform/clock/sam/module.lb | 2 +- .../platform/core/cortex/delay_impl.hpp.in | 4 ++- src/modm/platform/core/cortex/delay_ns.hpp.in | 4 +-- src/modm/platform/core/cortex/module.lb | 1 + .../platform/dac/stm32/dac_dma_impl.hpp.in | 4 +-- .../i2c/at90_tiny_mega/i2c_master.hpp.in | 7 +++-- .../platform/i2c/at90_tiny_mega/module.lb | 1 + .../platform/i2c/sam_x7x/i2c_master.hpp.in | 15 +++++----- src/modm/platform/i2c/sam_x7x/module.lb | 1 + .../stm32-extended/i2c_timing_calculator.hpp | 8 +++-- .../platform/i2c/stm32-extended/module.lb | 1 + src/modm/platform/i2c/stm32/i2c_master.hpp.in | 3 +- src/modm/platform/i2c/stm32/module.lb | 1 + .../uart_spi_master.hpp.in | 5 ++-- .../platform/spi/stm32h7/spi_hal_impl.hpp.in | 10 ++++--- .../timer/stm32/general_purpose_base.hpp.in | 2 +- 21 files changed, 84 insertions(+), 43 deletions(-) diff --git a/ext/gcc/modm_atomic.hpp.in b/ext/gcc/modm_atomic.hpp.in index 7c0bfdf9c2..b9c8bce4c2 100644 --- a/ext/gcc/modm_atomic.hpp.in +++ b/ext/gcc/modm_atomic.hpp.in @@ -153,6 +153,7 @@ __atomic_compare_exchange_{{len//8}}(volatile void *ptr, void *expected, {{len|u %% endmacro // ================================ lock free ================================= +#ifndef __clang__ extern "C" [[gnu::always_inline]] inline bool __atomic_is_lock_free (unsigned int object_size, const volatile void *ptr) { @@ -161,7 +162,7 @@ __atomic_is_lock_free (unsigned int object_size, const volatile void *ptr) return ((uintptr_t)ptr & (object_size - 1)) == 0; return false; } - +#endif // __clang__ %% macro atomic_fetch(len) %% for name, op in [("and", "&"), ("or", "|"), ("xor", "^"), ("nand", "&")] diff --git a/src/modm/architecture/interface/interrupt.hpp b/src/modm/architecture/interface/interrupt.hpp index 77e13d0925..d654aecec8 100644 --- a/src/modm/architecture/interface/interrupt.hpp +++ b/src/modm/architecture/interface/interrupt.hpp @@ -129,11 +129,20 @@ # define MODM_ISR_CALL(vector) \ MODM_ISR_VALIDATE(#vector, vector); \ vector ## _IRQHandler() + +#ifdef MODM_COMPILER_CLANG +# define MODM_ISR(vector, ...) \ + MODM_ISR_VALIDATE(#vector, vector); \ + modm_extern_c void vector ## _IRQHandler(void) \ + __attribute__((used)) __VA_ARGS__; \ + void vector ## _IRQHandler(void) +#else # define MODM_ISR(vector, ...) \ MODM_ISR_VALIDATE(#vector, vector); \ modm_extern_c void vector ## _IRQHandler(void) \ __attribute__((externally_visible)) __VA_ARGS__; \ void vector ## _IRQHandler(void) +#endif // MODM_COMPILER_CLANG #else diff --git a/src/modm/io/iostream.hpp.in b/src/modm/io/iostream.hpp.in index aaf251abac..dc21a4eefc 100644 --- a/src/modm/io/iostream.hpp.in +++ b/src/modm/io/iostream.hpp.in @@ -192,6 +192,15 @@ public: %% endif %% if core.startswith("cortex-m") +#ifdef __clang__ + // For ARM 'int32_t' is of type 'int'. Therefore there is no + // function here for the default type 'long'. As 'long' has the same + // width as 'int32_t' we just use a typedef here. + inline IOStream& operator << (const long& v) + { writeIntegerMode(static_cast(v)); return *this; } + inline IOStream& operator << (const unsigned long& v) + { writeIntegerMode(static_cast(v)); return *this; } +#else // For ARM 'int32_t' is of type 'long'. Therefore there is no // function here for the default type 'int'. As 'int' has the same // width as 'int32_t' we just use a typedef here. @@ -199,6 +208,7 @@ public: { writeIntegerMode(static_cast(v)); return *this; } inline IOStream& operator << (const unsigned int& v) { writeIntegerMode(static_cast(v)); return *this; } +#endif %% endif %% if options.with_float diff --git a/src/modm/math/utils/arithmetic_traits.hpp b/src/modm/math/utils/arithmetic_traits.hpp index bb66b01a9c..67e85f490b 100644 --- a/src/modm/math/utils/arithmetic_traits.hpp +++ b/src/modm/math/utils/arithmetic_traits.hpp @@ -4,7 +4,7 @@ * Copyright (c) 2012, 2014, Niklas Hauser * Copyright (c) 2013, 2015, Sascha Schade * Copyright (c) 2015, Kevin Läufer - * Copyright (c) 2018, Christopher Durand + * Copyright (c) 2018, 2026, Christopher Durand * Copyright (c) 2022, Raphael Lehmann * * This file is part of the modm project. @@ -18,11 +18,14 @@ #ifndef MODM_ARITHMETIC_TRAITS_HPP #define MODM_ARITHMETIC_TRAITS_HPP +#include #include #include #include #include +#include + namespace modm { @@ -97,13 +100,9 @@ namespace detail struct WideType { using type = double; }; - template && !std::is_same_v, bool> - > > - using enable_if_int = T; - - template - struct WideType> + template + requires (!std::is_same_v, bool>) + struct WideType { static constexpr bool isNextIntLarger = std::numeric_limits::type>::max() > std::numeric_limits::max(); @@ -123,8 +122,9 @@ namespace detail using type = T; }; - template - struct MakeSigned> + template + requires (!std::is_same_v, bool>) + struct MakeSigned { using type = std::make_signed_t; }; @@ -135,8 +135,9 @@ namespace detail using type = T; }; - template - struct MakeUnsigned> + template + requires (!std::is_same_v, bool>) + struct MakeUnsigned { using type = std::make_unsigned_t; }; @@ -181,8 +182,11 @@ struct ArithmeticTraits static constexpr bool isInteger = std::is_integral_v && !std::is_same_v, bool>; + // log10(2), not constexpr yet with clang + static constexpr auto log10_2 = 0.3010299956639812; + static constexpr unsigned char decimalDigits = - std::ceil(std::numeric_limits::digits * log10(2)) + (std::is_signed_v ? 1 : 0); + modm::ceil(std::numeric_limits::digits * log10_2) + (std::is_signed_v ? 1 : 0); }; /// @} diff --git a/src/modm/platform/clock/sam/gclk_impl.hpp.in b/src/modm/platform/clock/sam/gclk_impl.hpp.in index 188a5958c7..88ac91193a 100644 --- a/src/modm/platform/clock/sam/gclk_impl.hpp.in +++ b/src/modm/platform/clock/sam/gclk_impl.hpp.in @@ -18,6 +18,8 @@ #include #include +#include + namespace modm::platform { extern "C" uint32_t SystemCoreClock; @@ -97,7 +99,7 @@ GenericClockController::enableDfll48mClosedLoop(uint32_t waitCycles) { static_assert(reference > 732_Hz, "DFLL48 reference frequency must be larger than 732 Hz"); static_assert(reference < 43_kHz, "DFLL48 reference frequency must be less than 33 kHz"); - constexpr auto multiplier = uint16_t(std::round(48_MHz / double(reference))); + constexpr auto multiplier = uint16_t(modm::round(48_MHz / double(reference))); %% if target.family == "d1x/d2x/dax" // Errata 1.2.1: Disable OnDemand mode @@ -389,7 +391,7 @@ findDpllConfig(double inputClock, double target, bool fractional) } // f_pll = f_reference * (1 + N_int + N_frac/(2^frac_bits)) const auto idealMultiplier = (target / inputClock) - 1; - const uint32_t multplier = std::min(maxMultiplier, std::round(idealMultiplier)); + const uint32_t multplier = std::min(maxMultiplier, modm::round(idealMultiplier)); if (fractional) { const auto output = inputClock * (multplier + 1) / (1u << DpllConfig::MultiplierFractionalBits); return DpllConfigCalculation { diff --git a/src/modm/platform/clock/sam/module.lb b/src/modm/platform/clock/sam/module.lb index a710bf3923..72ca21a074 100644 --- a/src/modm/platform/clock/sam/module.lb +++ b/src/modm/platform/clock/sam/module.lb @@ -20,7 +20,7 @@ def prepare(module, options): if not options[":target"].has_driver("gclk:sam"): return False - module.depends(":cmsis:device", ":architecture:delay", ":platform:clock") + module.depends(":cmsis:device", ":architecture:delay", ":math:utils", ":platform:clock") return True def build(env): diff --git a/src/modm/platform/core/cortex/delay_impl.hpp.in b/src/modm/platform/core/cortex/delay_impl.hpp.in index 2f711bccc6..33dfe106c7 100644 --- a/src/modm/platform/core/cortex/delay_impl.hpp.in +++ b/src/modm/platform/core/cortex/delay_impl.hpp.in @@ -22,6 +22,8 @@ #include %% endif +#include + /// @cond #define MODM_DELAY_NS_IS_ACCURATE 1 @@ -34,7 +36,7 @@ extern uint16_t delay_fcpu_MHz; constexpr uint8_t delay_fcpu_MHz_shift({{us_shift}}); constexpr uint16_t computeDelayMhz(uint32_t hz) -{ return std::round(hz / 1'000'000.f * (1ul << delay_fcpu_MHz_shift)); } +{ return modm::round(hz / 1'000'000.f * (1ul << delay_fcpu_MHz_shift)); } } modm_always_inline diff --git a/src/modm/platform/core/cortex/delay_ns.hpp.in b/src/modm/platform/core/cortex/delay_ns.hpp.in index c593088daa..746862b88d 100644 --- a/src/modm/platform/core/cortex/delay_ns.hpp.in +++ b/src/modm/platform/core/cortex/delay_ns.hpp.in @@ -10,7 +10,7 @@ // ---------------------------------------------------------------------------- #pragma once -#include +#include /// @cond namespace modm::platform @@ -21,7 +21,7 @@ void delay_ns(uint32_t ns); constexpr uint16_t computeDelayNsPerLoop(uint32_t hz) { - return std::round({{loop}}'000'000'000.0 / hz); + return modm::round({{loop}}'000'000'000.0 / hz); } } diff --git a/src/modm/platform/core/cortex/module.lb b/src/modm/platform/core/cortex/module.lb index 90a6678a2f..201007ea1a 100644 --- a/src/modm/platform/core/cortex/module.lb +++ b/src/modm/platform/core/cortex/module.lb @@ -227,6 +227,7 @@ def prepare(module, options): module.depends( ":architecture:interrupt", ":cmsis:device", + ":math:utils", ":stdc++") module.add_option( diff --git a/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in b/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in index f306f43e1f..c06c6a80f0 100644 --- a/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in +++ b/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in @@ -111,9 +111,9 @@ void DmaChannel::Priority priority) { %% if target.family in ["f2", "f4", "f7"] - using RequestMapping = typename DmaChannel::RequestMapping; + using RequestMapping = typename DmaChannel::template RequestMapping; %% else - using RequestMapping = typename DmaChannel::RequestMapping; + using RequestMapping = typename DmaChannel::template RequestMapping; %% endif constexpr auto request = RequestMapping::Request; diff --git a/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in b/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in index 28197a30f3..67a43f2825 100644 --- a/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in +++ b/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in @@ -17,6 +17,7 @@ #include "i2c.hpp" #include +#include #include #include #include @@ -79,10 +80,10 @@ public: // calculate the fractional prescaler value constexpr float pre_part_raw = float(SystemClock::I2c) / ( 2 * baudrate ); - constexpr float pre_raw = std::floor(pre_part_raw) < 8 ? 0 : (pre_part_raw - 8) / pre; + constexpr float pre_raw = modm::floor(pre_part_raw) < 8 ? 0 : (pre_part_raw - 8) / pre; // respect the prescaler range of 0 to 255 - constexpr uint32_t pre_ceil = std::min(uint32_t(std::ceil(pre_raw)), 255ul); - constexpr uint32_t pre_floor = std::floor(pre_raw); + constexpr uint32_t pre_ceil = std::min(uint32_t(modm::ceil(pre_raw)), 255ul); + constexpr uint32_t pre_floor = modm::floor(pre_raw); // calculate the possible baudrates above and below the requested baudrate constexpr uint32_t baud_lower = SystemClock::I2c / ( 16 + 2 * pre_ceil * pre ); diff --git a/src/modm/platform/i2c/at90_tiny_mega/module.lb b/src/modm/platform/i2c/at90_tiny_mega/module.lb index 30179ffe4e..f9d6cc16fd 100644 --- a/src/modm/platform/i2c/at90_tiny_mega/module.lb +++ b/src/modm/platform/i2c/at90_tiny_mega/module.lb @@ -28,6 +28,7 @@ def prepare(module, options): ":architecture:accessor", ":architecture:i2c", ":architecture:interrupt", + ":math:utils", ":platform:gpio") return True 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 5fd43208b0..723f69b961 100644 --- a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in +++ b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -74,7 +75,7 @@ private: // => 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 uint8_t hold = static_cast(std::clamp(modm::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 @@ -86,14 +87,14 @@ private: // 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 cldiv = uint32_t(modm::round((minLowTime * clock) - 3)); constexpr auto tHigh = 1.f / ((baudrate + (baudrate - minLowTimeLimit)) * 2.f); - constexpr auto chdiv = uint32_t(std::round((tHigh * clock) - 3)); + constexpr auto chdiv = uint32_t(modm::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)); + constexpr uint32_t cldivScaled = modm::round(float(cldiv) / (1 << ckdiv)); + constexpr uint32_t chdivScaled = modm::round(float(chdiv) / (1 << ckdiv)); if (!checkBaudrate()) return std::nullopt; @@ -101,11 +102,11 @@ private: 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)); + constexpr auto div = uint32_t(modm::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)); + constexpr uint32_t divScaled = modm::round(float(div) / (1 << ckdiv)); if (!checkBaudrate()) return std::nullopt; diff --git a/src/modm/platform/i2c/sam_x7x/module.lb b/src/modm/platform/i2c/sam_x7x/module.lb index 8c572e21e1..6e6ca9184a 100644 --- a/src/modm/platform/i2c/sam_x7x/module.lb +++ b/src/modm/platform/i2c/sam_x7x/module.lb @@ -63,6 +63,7 @@ def prepare(module, options): ":architecture:interrupt", ":cmsis:device", ":container", + ":math:utils", ":platform:gpio") for instance in listify(device.get_driver("twihs")["instance"]): diff --git a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp index be9bac7870..ea9c7d883f 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp +++ b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp @@ -17,6 +17,8 @@ #include #include +#include + namespace modm { @@ -272,8 +274,8 @@ class I2cTimingCalculator ((1.0f / params.peripheralClock) - SyncTime) / clockPeriod - 1 ); - lowMinFloat = std::ceil(lowMinFloat); - highMinFloat = std::ceil(highMinFloat); + lowMinFloat = modm::ceil(lowMinFloat); + highMinFloat = modm::ceil(highMinFloat); if(lowMinFloat > 255 || highMinFloat > 255) { return {false, 255, 255}; @@ -307,7 +309,7 @@ class I2cTimingCalculator auto targetSclHighTime = targetSclTime - sclLowTime - params.riseTime - params.fallTime; - auto targetSclHigh = std::round((targetSclHighTime - SyncTime) / clockPeriod - 1); + auto targetSclHigh = modm::round((targetSclHighTime - SyncTime) / clockPeriod - 1); return (uint8_t) std::max(min, std::min(targetSclHigh, max)); } diff --git a/src/modm/platform/i2c/stm32-extended/module.lb b/src/modm/platform/i2c/stm32-extended/module.lb index 5c2a6124a8..bbc19af566 100644 --- a/src/modm/platform/i2c/stm32-extended/module.lb +++ b/src/modm/platform/i2c/stm32-extended/module.lb @@ -88,6 +88,7 @@ def prepare(module, options): ":architecture:interrupt", ":cmsis:device", ":container", + ":math:utils", ":platform:gpio") global_properties["shared_irqs"] = defaultdict(list) diff --git a/src/modm/platform/i2c/stm32/i2c_master.hpp.in b/src/modm/platform/i2c/stm32/i2c_master.hpp.in index 3422889b63..7252ac1106 100644 --- a/src/modm/platform/i2c/stm32/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32/i2c_master.hpp.in @@ -18,6 +18,7 @@ #include #include #include +#include namespace modm { @@ -93,7 +94,7 @@ public: // => y = x * m + b, with m = -2.3333ns/kHz, b = 1'233.3333ns constexpr float max_rise_time = -2.333333f * (float(baudrate) / 1'000.f) + 1'233.333333f; // calculate trise - constexpr float trise_raw = max_rise_time < 0 ? 0 : std::floor(max_rise_time / (1'000.f / freq)); + constexpr float trise_raw = max_rise_time < 0 ? 0 : modm::floor(max_rise_time / (1'000.f / freq)); constexpr uint8_t trise = trise_raw > 62 ? 63 : (trise_raw + 1); initializeWithPrescaler(freq, trise, prescaler, isrPriority); diff --git a/src/modm/platform/i2c/stm32/module.lb b/src/modm/platform/i2c/stm32/module.lb index eee1cfadda..d180a89e4d 100644 --- a/src/modm/platform/i2c/stm32/module.lb +++ b/src/modm/platform/i2c/stm32/module.lb @@ -61,6 +61,7 @@ def prepare(module, options): ":architecture:i2c", ":architecture:interrupt", ":math:algorithm", + ":math:utils", ":cmsis:device", ":container", ":platform:gpio") diff --git a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in index acedaa2ba9..affb3108cf 100644 --- a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in +++ b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -92,8 +93,8 @@ public: // calculate the fractional prescaler value constexpr float pre_raw = static_cast(SystemClock::UsartSpi) / ( 2 * baudrate ); // respect the prescaler range of 1 to 4096 - constexpr uint32_t pre_ceil = std::ceil(pre_raw) > 4096 ? 4096 : std::ceil(pre_raw); - constexpr uint32_t pre_floor = std::floor(pre_raw) < 1 ? 1 : std::floor(pre_raw); + constexpr uint32_t pre_ceil = modm::ceil(pre_raw) > 4096 ? 4096 : modm::ceil(pre_raw); + constexpr uint32_t pre_floor = modm::floor(pre_raw) < 1 ? 1 : modm::floor(pre_raw); // calculate the possible baudrates above and below the requested baudrate constexpr uint32_t baud_lower = SystemClock::UsartSpi / ( 2 * pre_ceil ); diff --git a/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in index 958dba720b..fa8ba25893 100644 --- a/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in +++ b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in @@ -188,8 +188,9 @@ SpiHal{{ id }}::write16(uint16_t data) { // Write with 16-bit access // SPI{{ id }}->TXDR is of type "volatile uint32_t". - // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. - auto* const [[gnu::may_alias]] ptr = reinterpret_cast<__IO uint16_t*>(&SPI{{ id }}->TXDR); + // may_alias is required to avoid undefined behaviour due to strict aliasing violations. + using Reg16 = __IO uint16_t __attribute__((__may_alias__)); + auto* const ptr = reinterpret_cast(&SPI{{ id }}->TXDR); *ptr = data; } @@ -211,8 +212,9 @@ SpiHal{{ id }}::read16() { // Read with 16-bit access // SPI{{ id }}->RXDR is of type "const volatile uint32_t". - // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. - auto* const [[gnu::may_alias]] ptr = reinterpret_cast(&SPI{{ id }}->RXDR); + // may_alias is required to avoid undefined behaviour due to strict aliasing violations. + using Reg16 = const __IO uint16_t __attribute__((__may_alias__)); + auto* const ptr = reinterpret_cast(&SPI{{ id }}->RXDR); return *ptr; } diff --git a/src/modm/platform/timer/stm32/general_purpose_base.hpp.in b/src/modm/platform/timer/stm32/general_purpose_base.hpp.in index b8ddcaf9c4..f72bed6b45 100644 --- a/src/modm/platform/timer/stm32/general_purpose_base.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose_base.hpp.in @@ -331,7 +331,7 @@ protected: static consteval int signalToChannel() { - modm::platform::detail::SignalConnection{}; + (void) modm::platform::detail::SignalConnection{}; %% for signal, number in signals %% if loop.first if constexpr (Signal::Signal == Gpio::Signal::{{ signal }}) { From e068fd2eb59bb72ea4312f286b4c1f0602c99958 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Sun, 19 Apr 2026 12:49:48 +0200 Subject: [PATCH 4/7] [math] Deprecate ArithmeticTraits, MakeSigned and MakeUnsigned Functionality is provided by the standard library (std::numeric_limits, std::make_signed_t, std::make_unsigned_t) --- src/modm/math/utils/arithmetic_traits.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modm/math/utils/arithmetic_traits.hpp b/src/modm/math/utils/arithmetic_traits.hpp index 67e85f490b..8071e64278 100644 --- a/src/modm/math/utils/arithmetic_traits.hpp +++ b/src/modm/math/utils/arithmetic_traits.hpp @@ -150,10 +150,10 @@ template using WideType = typename detail::WideType::type; template -using SignedType = typename detail::MakeSigned::type; +using SignedType [[deprecated("use std::make_signed_t")]] = typename detail::MakeSigned::type; // DEPRECATED: 2026q3 template -using UnsignedType = typename detail::MakeUnsigned::type; +using UnsignedType [[deprecated("use std::make_unsigned_t")]] = typename detail::MakeUnsigned::type; // DEPRECATED: 2026q3 /** * Arithmetic Traits @@ -177,7 +177,7 @@ using UnsignedType = typename detail::MakeUnsigned::type; * @endcode */ template -struct ArithmeticTraits +struct [[deprecated("use std::numeric_limits instead")]] ArithmeticTraits // DEPRECATED: 2026q3 { static constexpr bool isInteger = std::is_integral_v && !std::is_same_v, bool>; From 635acfef94459d6cee7da68b5946ff077220857a Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Sun, 19 Apr 2026 13:39:50 +0200 Subject: [PATCH 5/7] [utils] Fix aligned_storage_helper for Capacity == 2 The alignment was erroneously 1 instead of 2 --- src/modm/utils/aligned_storage.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modm/utils/aligned_storage.hpp b/src/modm/utils/aligned_storage.hpp index 4c7fdc3312..f87b3db20f 100644 --- a/src/modm/utils/aligned_storage.hpp +++ b/src/modm/utils/aligned_storage.hpp @@ -47,6 +47,7 @@ union aligned_storage_helper maybe f; maybe g; maybe h; + maybe i; }; } // namespace aligned_storage_impl From c650a4d8fa366d1f1cad2c7535896e4102a5a220 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Sun, 19 Apr 2026 13:42:20 +0200 Subject: [PATCH 6/7] [utils] Deprecate modm::aligned_storage_t Deprecate modm::aligned_storage_t which relies on the deprecated type std::aligned_storage. Remove all usages from modm and replace them with a properly aligned std::byte array as recommended by C++ paper P1413R3. --- src/modm/utils/aligned_storage.hpp | 18 ++++++++++----- src/modm/utils/inplace_any.hpp | 2 +- src/modm/utils/inplace_function.hpp | 8 +++---- test/modm/utils/inplace_function_test.cpp | 27 +++++++++++++++++++++++ test/modm/utils/inplace_function_test.hpp | 20 +++++++++++++++++ 5 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 test/modm/utils/inplace_function_test.cpp create mode 100644 test/modm/utils/inplace_function_test.hpp diff --git a/src/modm/utils/aligned_storage.hpp b/src/modm/utils/aligned_storage.hpp index f87b3db20f..fc7c6f741c 100644 --- a/src/modm/utils/aligned_storage.hpp +++ b/src/modm/utils/aligned_storage.hpp @@ -51,6 +51,9 @@ union aligned_storage_helper }; } // namespace aligned_storage_impl +template +constexpr auto default_storage_alignment = alignof(aligned_storage_impl::aligned_storage_helper); + /** * Implementation of std::aligned_storage that avoids GCC bug #61458 which can * cause excessive size for types smaller than the maximum alignment. @@ -59,15 +62,20 @@ union aligned_storage_helper * The implementation is derived from: * https://github.com/WG21-SG14/SG14/blob/master/SG14/inplace_function.h */ -template)> -struct aligned_storage { - using type = std::aligned_storage_t; +template> +struct [[deprecated("see C++ standards paper P1413R3")]] aligned_storage // DEPRECATED: 2026q3 +{ + struct type + { + alignas(Align) unsigned char data[Cap]; + }; }; /// @endcond /// @ingroup modm_utils -template)> -using aligned_storage_t = typename aligned_storage::type; +template> +using aligned_storage_t [[deprecated("see C++ standards paper P1413R3")]] = + typename aligned_storage::type; // DEPRECATED: 2026q3 static_assert(sizeof(aligned_storage_t) == sizeof(void*)); static_assert(alignof(aligned_storage_t) == alignof(void*)); diff --git a/src/modm/utils/inplace_any.hpp b/src/modm/utils/inplace_any.hpp index 80dfa4fb22..165c1c8dd7 100644 --- a/src/modm/utils/inplace_any.hpp +++ b/src/modm/utils/inplace_any.hpp @@ -207,7 +207,7 @@ class inplace_any final void move_from(inplace_any&& other) noexcept; private: - modm::aligned_storage::type storage_; + alignas(default_storage_alignment) std::byte storage_[Size]; inplace_any_impl::HandlerFunc handler_{nullptr}; }; diff --git a/src/modm/utils/inplace_function.hpp b/src/modm/utils/inplace_function.hpp index 793329e303..655da9b398 100644 --- a/src/modm/utils/inplace_function.hpp +++ b/src/modm/utils/inplace_function.hpp @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -118,7 +119,7 @@ struct is_valid_inplace_dst : std::true_type template< class Signature, size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity, - size_t Alignment = alignof(modm::aligned_storage_t) + size_t Alignment = default_storage_alignment > class inplace_function; // unspecified @@ -140,7 +141,6 @@ template< > class inplace_function { - using storage_t = modm::aligned_storage_t; using vtable_t = inplace_function_detail::vtable; using vtable_ptr_t = const vtable_t*; @@ -275,7 +275,7 @@ class inplace_function { if (this == std::addressof(other)) return; - storage_t tmp; + alignas(Alignment) std::byte tmp[Capacity]; vtable_ptr_->relocate_ptr( std::addressof(tmp), std::addressof(storage_) @@ -301,7 +301,7 @@ class inplace_function private: vtable_ptr_t vtable_ptr_; - mutable storage_t storage_; + alignas(Alignment) mutable std::byte storage_[Capacity]; inplace_function( vtable_ptr_t vtable_ptr, diff --git a/test/modm/utils/inplace_function_test.cpp b/test/modm/utils/inplace_function_test.cpp new file mode 100644 index 0000000000..8b8a684bfb --- /dev/null +++ b/test/modm/utils/inplace_function_test.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2026, 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 "inplace_function_test.hpp" +#include + +void +InplaceFunctionTest::testCall() +{ + int value = 0; + + modm::inplace_function func = [&value](int x) { + value = x; + return x * 2; + }; + + TEST_ASSERT_EQUALS(func(42), 84); + TEST_ASSERT_EQUALS(value, 42); +} diff --git a/test/modm/utils/inplace_function_test.hpp b/test/modm/utils/inplace_function_test.hpp new file mode 100644 index 0000000000..75610b8b7d --- /dev/null +++ b/test/modm/utils/inplace_function_test.hpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026, 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_utils +class InplaceFunctionTest : public unittest::TestSuite +{ +public: + void + testCall(); +}; From c0dd51c432af4b48bcfeede5340e44c86060b511 Mon Sep 17 00:00:00 2001 From: Christopher Durand Date: Mon, 20 Apr 2026 15:46:32 +0200 Subject: [PATCH 7/7] fixup --- src/modm/ui/color/rgb565.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/modm/ui/color/rgb565.hpp b/src/modm/ui/color/rgb565.hpp index 01d6838e7c..262729fbf9 100644 --- a/src/modm/ui/color/rgb565.hpp +++ b/src/modm/ui/color/rgb565.hpp @@ -61,6 +61,15 @@ class Rgb565 constexpr Rgb565(const RgbT &rgb) : Rgb565(rgb.red, rgb.green, rgb.blue) {} + /** + * Convertion Constructor for HSV Color + * + * @param hsv HSV Color + */ + template + constexpr Rgb565(const HsvT &hsv) : Rgb565(RgbCalcType(hsv)) + {} + constexpr bool operator==(const Rgb565 &other) const = default;