diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 1252ce6c29..ae1d7b5e73 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -158,10 +158,10 @@ class timeoutTemplate IRAM_ATTR // fast bool expired() { - YieldPolicyT::execute(); //in case of DoNothing: gets optimized away - if(PeriodicT) //in case of false: gets optimized away - return expiredRetrigger(); - return expiredOneShot(); + bool hasExpired = PeriodicT ? expiredRetrigger() : expiredOneShot(); + if (!hasExpired) //in case of DoNothing: gets optimized away + YieldPolicyT::execute(); + return hasExpired; } IRAM_ATTR // fast @@ -186,7 +186,7 @@ class timeoutTemplate { reset(); _timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout); - _neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax()); + _neverExpires = newUserTimeout > timeMax(); } // Resets, will trigger after the timeout previously set. @@ -219,11 +219,27 @@ class timeoutTemplate _neverExpires = true; } + void stop() + { + resetToNeverExpires(); + } + timeType getTimeout() const { return TimePolicyT::toUserUnit(_timeout); } + IRAM_ATTR // fast + timeType remaining() const + { + if (_neverExpires) + return timeMax(); + timeType current = TimePolicyT::time(); + if (checkExpired(current)) + return TimePolicyT::toUserUnit(0); + return TimePolicyT::toUserUnit(_timeout - (current - _start)); + } + static constexpr timeType timeMax() { return TimePolicyT::timeMax; @@ -235,7 +251,7 @@ class timeoutTemplate bool checkExpired(const timeType internalUnit) const { // canWait() is not checked here - // returns "can expire" and "time expired" + // returns "can expire" and "time has expired" return (!_neverExpires) && ((internalUnit - _start) >= _timeout); } @@ -250,7 +266,7 @@ class timeoutTemplate timeType current = TimePolicyT::time(); if(checkExpired(current)) { - unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) + timeType n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) _start += n * _timeout; return true; } diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index f6c650fcf9..deab9e04eb 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -17,12 +17,11 @@ */ #include -#include #include "Schedule.h" #include "PolledTimeout.h" #include "interrupts.h" -#include "coredecls.h" +#include typedef std::function mSchedFuncT; struct scheduled_fn_t @@ -35,7 +34,6 @@ static scheduled_fn_t* sFirst = nullptr; static scheduled_fn_t* sLast = nullptr; static scheduled_fn_t* sUnused = nullptr; static int sCount = 0; -static uint32_t recurrent_max_grain_mS = 0; typedef std::function mRecFuncT; struct recurrent_fn_t @@ -49,6 +47,20 @@ struct recurrent_fn_t static recurrent_fn_t* rFirst = nullptr; static recurrent_fn_t* rLast = nullptr; +// The target time for scheduling the next timed recurrent function +static std::atomic rTarget; + +// As 32 bit unsigned integer, micros() rolls over every 71.6 minutes. +// For unambiguous earlier/later order between two timestamps, +// despite roll over, there is a limit on the maximum duration +// that can be requested, if full expiration must be observable: +// later - earlier >= 0 for both later >= earlier or (rolled over) later <= earlier +// Also, expiration should remain observable for a useful duration of time: +// now - (start + period) >= 0 for now - start >= 0 despite (start + period) >= now +// A well-balanced solution, not breaking on two's compliment signed arithmetic, +// is limiting durations to the maximum signed value of the same word size +// as the original unsigned word. +constexpr decltype(micros()) HALF_MAX_MICROS = ~static_cast(0) >> 1; // Returns a pointer to an unused sched_fn_t, // or if none are available allocates a new one, @@ -106,7 +118,7 @@ bool schedule_function(const std::function& fn) IRAM_ATTR // (not only) called from ISR bool schedule_recurrent_function_us(const std::function& fn, - uint32_t repeat_us, const std::function& alarm) + decltype(micros()) repeat_us, const std::function& alarm) { assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s) @@ -122,6 +134,19 @@ bool schedule_recurrent_function_us(const std::function& fn, esp8266::InterruptLock lockAllInterruptsInThisScope; + const auto now = micros(); + const auto itemRemaining = item->callNow.remaining(); + for (auto _rTarget = rTarget.load(); ;) + { + const auto remaining = _rTarget - now; + if (!rFirst || (remaining <= HALF_MAX_MICROS && remaining > itemRemaining)) + { + // if (!rTarget.compare_exchange_weak(_rTarget, now + itemRemaining)) continue; + rTarget = now + itemRemaining; // interrupt lock is active, no ABA issue + } + break; + } + if (rLast) { rLast->mNext = item; @@ -132,37 +157,20 @@ bool schedule_recurrent_function_us(const std::function& fn, } rLast = item; - // grain needs to be recomputed - recurrent_max_grain_mS = 0; - return true; } -uint32_t compute_scheduled_recurrent_grain () +decltype(micros()) get_scheduled_recurrent_delay_us() { - if (recurrent_max_grain_mS == 0) - { - if (rFirst) - { - uint32_t recurrent_max_grain_uS = rFirst->callNow.getTimeout(); - for (auto it = rFirst->mNext; it; it = it->mNext) - recurrent_max_grain_uS = std::gcd(recurrent_max_grain_uS, it->callNow.getTimeout()); - if (recurrent_max_grain_uS) - // round to the upper millis - recurrent_max_grain_mS = recurrent_max_grain_uS <= 1000? 1: (recurrent_max_grain_uS + 999) / 1000; - } - -#ifdef DEBUG_ESP_CORE - static uint32_t last_grain = 0; - if (recurrent_max_grain_mS != last_grain) - { - ::printf(":rsf %u->%u\n", last_grain, recurrent_max_grain_mS); - last_grain = recurrent_max_grain_mS; - } -#endif - } + if (!rFirst) return HALF_MAX_MICROS; + const auto now = micros(); + const auto remaining = rTarget.load() - now; + return (remaining <= HALF_MAX_MICROS) ? remaining : 0; +} - return recurrent_max_grain_mS; +decltype(micros()) get_scheduled_delay_us() +{ + return sFirst ? 0 : HALF_MAX_MICROS; } void run_scheduled_functions() @@ -225,11 +233,14 @@ void run_scheduled_recurrent_functions() fence = true; } + decltype(rLast) stop; recurrent_fn_t* prev = nullptr; + bool done; + + rTarget.store(micros() + HALF_MAX_MICROS); // prevent scheduling of new functions during this run - auto stop = rLast; + stop = rLast; - bool done; do { done = current == stop; @@ -258,12 +269,23 @@ void run_scheduled_recurrent_functions() } delete(to_ditch); - - // grain needs to be recomputed - recurrent_max_grain_mS = 0; } else { + const auto now = micros(); + const auto currentRemaining = current->callNow.remaining(); + for (auto _rTarget = rTarget.load(); ;) + { + const auto remaining = _rTarget - now; + if (remaining <= HALF_MAX_MICROS && remaining > currentRemaining) + { + // if (!rTarget.compare_exchange_weak(_rTarget, now + currentRemaining)) continue; + esp8266::InterruptLock lockAllInterruptsInThisScope; + if (rTarget != _rTarget) { _rTarget = rTarget; continue; } + rTarget = now + currentRemaining; + } + break; + } prev = current; current = current->mNext; } diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index 362d15b5f3..730e77d513 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -22,6 +22,8 @@ #include #include +#include "coredecls.h" + #define SCHEDULED_FN_MAX_COUNT 32 // The purpose of scheduled functions is to trigger, from SYS stack (like in @@ -39,10 +41,10 @@ // scheduled function happen more often: every yield() (vs every loop()), // and time resolution is microsecond (vs millisecond). Details are below. -// compute_scheduled_recurrent_grain() is used by delay() to give a chance to +// get_scheduled_recurrent_delay_us() is used by delay() to give a chance to // all recurrent functions to run per their timing requirement. -uint32_t compute_scheduled_recurrent_grain (); +decltype(micros()) get_scheduled_recurrent_delay_us(); // scheduled functions called once: // @@ -60,6 +62,13 @@ uint32_t compute_scheduled_recurrent_grain (); // * Run the lambda only once next time. // * A scheduled function can schedule a function. +// get_scheduled_delay_us() is named for symmetry to get_scheduled_recurrent_delay_us, +// despite the lack of specific delay times. Therefore it can return only one of two +// values, viz. 0 in case of any pending scheduled functions, or a large delay time if +// there is no function in the queue. + +decltype(micros()) get_scheduled_delay_us(); + bool schedule_function (const std::function& fn); // Run all scheduled functions. @@ -86,7 +95,7 @@ void run_scheduled_functions(); // any remaining delay from repeat_us is disregarded, and fn is executed. bool schedule_recurrent_function_us(const std::function& fn, - uint32_t repeat_us, const std::function& alarm = nullptr); + decltype(micros()) repeat_us, const std::function& alarm = nullptr); // Test recurrence and run recurrent scheduled functions. // (internally called at every `yield()` and `loop()`) diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index d0309cc71f..a0cfb8533c 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -22,9 +22,6 @@ //This may be used to change user task stack size: //#define CONT_STACKSIZE 4096 - -#include - #include #include "Schedule.h" extern "C" { @@ -176,13 +173,12 @@ bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uin return true; // expired } - // compute greatest chunked delay with respect to scheduled recurrent functions - uint32_t grain_ms = std::gcd(intvl_ms, compute_scheduled_recurrent_grain()); + // compute greatest delay interval with respect to scheduled recurrent functions + const uint32_t scheduled_recurrent_delay_ms = get_scheduled_recurrent_delay_us() / 1000UL; + const uint32_t max_delay_ms = std::min(intvl_ms, scheduled_recurrent_delay_ms); // recurrent scheduled functions will be called from esp_delay()->esp_suspend() - esp_delay(grain_ms > 0 ? - std::min((timeout_ms - expired), grain_ms): - (timeout_ms - expired)); + esp_delay(std::min((timeout_ms - expired), max_delay_ms)); return false; // expiration must be checked again } diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index bcfd6d10e6..bb133a5a17 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit bcfd6d10e6a45a0d07705d08728f293defe9cc1d +Subproject commit bb133a5a177e64655f900a419f36c2c09b8590be