diff --git a/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp b/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp index bb332c0c..4525ab3c 100644 --- a/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp +++ b/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp @@ -3,7 +3,7 @@ * @author Frédéric Metrich (frederic.metrich@live.fr) * @brief This file implements an Exponentially Weighted Moving Average template class * @version 0.1 - * @date 2024-02-27 + * @date 2026-10-01 * * @section description Description * The Exponentially Weighted Moving Average (EWMA) is a quantitative or statistical measure used to model or describe a time series. @@ -27,7 +27,7 @@ * This comes with some restrictions on the alpha parameter, but the benefit of full integer math wins * on the side-drawback. * - * @copyright Copyright (c) 2024 + * @copyright Copyright (c) 2024-2026 * */ @@ -114,8 +114,12 @@ class EWMA_average * Moving Average (EMA), Double EMA (DEMA), and Triple EMA (TEMA). * * @param input The new input value to process. + * + * @note Not inlined, even with a single caller: on AVR, inlined code reaches a static + * instance's six int32 with absolute lds/sts (4 bytes per byte accessed), the + * function through its this pointer with ld/st/ldd/std (2 bytes) - 78 bytes less. */ - void addValue(int32_t input) + __attribute__((noinline)) void addValue(int32_t input) { ema_raw = ema_raw - ema + input; ema = ema_raw >> round_up_to_power_of_2(A); @@ -151,8 +155,12 @@ class EWMA_average * @brief Get the Triple Exponentially Weighted Moving Average (TEMA). * * @return auto The TEMA value. + * + * @note Not inlined: it is read in several places (relay decisions, datalog output), and + * each inlined copy would read a static instance's averages with absolute lds + * (4 bytes per byte), against ld/ldd through this (2 bytes) - 70 bytes less. */ - auto getAverageT() const + __attribute__((noinline)) auto getAverageT() const { return 3 * (ema - ema_ema) + ema_ema_ema; } diff --git a/Mk2_3phase_RFdatalog_temp/utils_pins.h b/Mk2_3phase_RFdatalog_temp/utils_pins.h index 4efa4400..51d8cf46 100644 --- a/Mk2_3phase_RFdatalog_temp/utils_pins.h +++ b/Mk2_3phase_RFdatalog_temp/utils_pins.h @@ -3,7 +3,7 @@ * @author Frédéric Metrich (frederic.metrich@live.fr) * @brief Some utility functions for pins manipulation * @version 0.1 - * @date 2026-01-29 + * @date 2026-10-01 * * @copyright Copyright (c) 2023-2026 * @@ -71,16 +71,22 @@ void constexpr togglePin(const uint8_t pin) * * @param pin pin to change [2..13] * @param bState state to be set + * + * @details For a pin only known at run time (a relay). The ADC ISR writes the load pins, + * possibly on the same port, so the port must not be read-modified-written here: + * the pin is toggled through PINx instead, where writing a 1 toggles that bit of + * PORTx and writing a 0 leaves the others alone. Only the caller changes this pin, + * so reading its state first is safe. */ -inline constexpr void setPinState(const uint8_t pin, const bool bState) +inline void setPinState(const uint8_t pin, const bool bState) { - if (bState) - { - setPinON(pin); - } - else + const volatile uint8_t &port{ (pin < 8) ? PORTD : ((pin < 14) ? PORTB : PORTC) }; + volatile uint8_t &toggle{ (pin < 8) ? PIND : ((pin < 14) ? PINB : PINC) }; + const uint8_t mask{ static_cast< uint8_t >(1U << ((pin < 8) ? pin : ((pin < 14) ? pin - 8 : pin - 14))) }; + + if (static_cast< bool >(port & mask) != bState) { - setPinOFF(pin); + toggle = mask; } } @@ -88,6 +94,9 @@ inline constexpr void setPinState(const uint8_t pin, const bool bState) * @brief Set the Pin state to ON for the specified pin * * @param pin pin to change [2..13] + * + * @warning Atomic (a single sbi/cbi) only for a pin known at compile time. For a pin known at + * run time it is a read-modify-write of the port: from loop(), use setPinState(). */ inline constexpr void setPinON(const uint8_t pin) { @@ -109,6 +118,10 @@ inline constexpr void setPinON(const uint8_t pin) * @brief Set the Pins state to ON * * @param pins The pins to change + * + * @warning Read-modify-write of PORTD and PORTB: only safe from the ADC ISR (its sole caller), + * which cannot be interrupted. From loop(), use setPinState() or togglePin(): an ISR + * landing in the middle would have its load pin changes written back to the old state. */ inline void setPinsON(const uint16_t pins) { @@ -120,6 +133,9 @@ inline void setPinsON(const uint16_t pins) * @brief Set the Pin state to OFF for the specified pin * * @param pin pin to change [2..13] + * + * @warning Atomic (a single sbi/cbi) only for a pin known at compile time. For a pin known at + * run time it is a read-modify-write of the port: from loop(), use setPinState(). */ inline constexpr void setPinOFF(const uint8_t pin) { @@ -141,6 +157,10 @@ inline constexpr void setPinOFF(const uint8_t pin) * @brief Set the Pins state to OFF * * @param pins The pins to change + * + * @warning Read-modify-write of PORTD and PORTB: only safe from the ADC ISR (its sole caller), + * which cannot be interrupted. From loop(), use setPinState() or togglePin(): an ISR + * landing in the middle would have its load pin changes written back to the old state. */ inline void setPinsOFF(const uint16_t pins) { diff --git a/Mk2_3phase_RFdatalog_temp/utils_relay.h b/Mk2_3phase_RFdatalog_temp/utils_relay.h index cbff261f..d281655f 100644 --- a/Mk2_3phase_RFdatalog_temp/utils_relay.h +++ b/Mk2_3phase_RFdatalog_temp/utils_relay.h @@ -3,7 +3,7 @@ * @author Frédéric Metrich (frederic.metrich@live.fr) * @brief Some utility functions for the relay output feature * @version 0.1 - * @date 2026-01-29 + * @date 2026-10-01 * * @copyright Copyright (c) 2023-2026 * @@ -173,25 +173,14 @@ class relayOutput if (currentAvgPower < surplusThreshold || isOverrideActive) { bit_clear(overrideBitmask, relay_pin); // Clear override bit if it was set - return try_turnON(); + return try_switch(true); } - // Handle both positive and negative import thresholds - if (importThreshold >= 0) + // Positive threshold: turn OFF when importing > threshold (normal mode). + // Negative threshold: turn OFF when surplus < abs(threshold) (battery mode). + if (currentAvgPower > importThreshold) { - // Positive threshold: turn OFF when importing > threshold (normal mode) - if (currentAvgPower > importThreshold) - { - return try_turnOFF(); - } - } - else - { - // Negative threshold: turn OFF when surplus < abs(threshold) (battery mode) - if (currentAvgPower > importThreshold) // importThreshold is negative, so this checks surplus < abs(threshold) - { - return try_turnOFF(); - } + return try_switch(false); } return false; @@ -234,55 +223,34 @@ class relayOutput /** * @brief Force the relay OFF when diversion is disabled - * + * * @return bool True if state has changed * @details This method attempts to turn OFF the relay, respecting the minimum ON time constraint */ bool forceOFF() const { - return try_turnOFF(); + return try_switch(false); } private: /** - * @brief Turn ON the relay if the 'time' condition is met + * @brief Turn the relay ON or OFF if the 'time' condition is met * + * @param on True to turn the relay ON, false to turn it OFF * @return bool True if state has changed */ - bool try_turnON() const + bool try_switch(const bool on) const { - if (relayIsON || duration < minOFF) + if ((relayIsON == on) || (duration < (on ? minOFF : minON))) { return false; } - setPinON(relay_pin); + setPinState(relay_pin, on); - DBUGLN(F("Relay turned ON!")); + DBUGLN(on ? F("Relay turned ON!") : F("Relay turned OFF!")); - relayIsON = true; - duration = 0; - - return true; - } - - /** - * @brief Turn OFF the relay if the 'time' condition is met - * - * @return bool True if state has changed - */ - bool try_turnOFF() const - { - if (!relayIsON || duration < minON) - { - return false; - } - - setPinOFF(relay_pin); - - DBUGLN(F("Relay turned OFF!")); - - relayIsON = false; + relayIsON = on; duration = 0; return true; @@ -434,13 +402,15 @@ class RelayEngine return; } - if (ewma_average.getAverageT() > 0) + const auto currentAvgPower{ ewma_average.getAverageT() }; + + if (currentAvgPower > 0) { // Currently importing, try to turn OFF some relays uint8_t idx{ N }; do { - if (relay[--idx].proceed_relay(ewma_average.getAverageT(), overrideBitmask)) + if (relay[--idx].proceed_relay(currentAvgPower, overrideBitmask)) { settle_change = 60; return; @@ -453,7 +423,7 @@ class RelayEngine uint8_t idx{ 0 }; do { - if (relay[idx].proceed_relay(ewma_average.getAverageT(), overrideBitmask)) + if (relay[idx].proceed_relay(currentAvgPower, overrideBitmask)) { settle_change = 60; return;