1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-16 00:43:00 +03:00

polledTimeout: add option to use CPU count instead of millis() (#5870)

* polledTimeout: add option to use CPU count instead of millis()

* use more "using" alias

* more c++/clear code, using typename (thanks @devyte)

* rename class name to include unit, introduce timeMax() and check it with assert()

* remove useless defines

* improve api readability, add micro-second unit

* update example

* mock: emulate getCycleCount, add/fix polledTimeout CI test

* + nano-seconds, assert -> message, comments, host test

* allow 0 for timeout (enables immediate timeout, fix division by 0)

* typo, set member instead of local variable

* unify error message

* slight change on checkExpired() allows "never expired"
also removed printed message, add YieldAndDelay, simplify calculations

* remove traces of debug.h/cpp in this PR

* include missing <limits> header

* back to original expired test, introduce boolean _neverExpires, fix reset(), getTimeout() is invalid

* fix expiredOneShot with _timeout==0 check

* reenable getTimeout()

* expose checkExpired with unit conversion

* fix timing comments, move critical code to iram

* add member ::neverExpires and use it where relevant

* improve clarity

* remove exposed checkExpired(), adapt LEAmDNS with equivalent

* add API ::resetToNeverExpires(), use it in LEAmDNS

* remove offending constness from ::flagged() LEAmDNS (due do API fix in PolledTimeout)

* simplify "Fast" base classes

* minor variable rename

* Fix examples

* compliance with good c++ manners

* minor changes for consistency

* add missing const

* expired() and bool() moved to iram

* constexpr compensation computing

* add/update comments

* move neverExpires and alwaysExpired
This commit is contained in:
david gauchard
2019-04-05 15:50:53 +02:00
committed by Develo
parent f0eb5509a0
commit 9a2ed274f3
15 changed files with 344 additions and 92 deletions

View File

@ -200,15 +200,20 @@ class EspClass {
bool eraseConfig();
inline uint32_t getCycleCount();
#ifndef CORE_MOCK
inline
#endif
uint32_t getCycleCount();
};
#ifndef CORE_MOCK
uint32_t EspClass::getCycleCount()
{
uint32_t ccount;
__asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
return ccount;
}
#endif
extern EspClass ESP;

View File

@ -139,7 +139,7 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size)
while (got < size)
{
esp8266::polledTimeout::oneShot timeOut(_timeout);
esp8266::polledTimeout::oneShotFastMs timeOut(_timeout);
size_t avail;
while ((avail = available()) == 0 && !timeOut);
if (avail == 0)

View File

@ -23,6 +23,8 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <limits>
#include <Arduino.h>
namespace esp8266
@ -45,19 +47,112 @@ struct YieldOrSkip
static void execute() {delay(0);}
};
template <unsigned long delayMs>
struct YieldAndDelayMs
{
static void execute() {delay(delayMs);}
};
} //YieldPolicy
namespace TimePolicy
{
template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing>
struct TimeSourceMillis
{
// time policy in milli-seconds based on millis()
using timeType = decltype(millis());
static timeType time() {return millis();}
static constexpr timeType ticksPerSecond = 1000;
static constexpr timeType ticksPerSecondMax = 1000;
};
struct TimeSourceCycles
{
// time policy based on ESP.getCycleCount()
// this particular time measurement is intended to be called very often
// (every loop, every yield)
using timeType = decltype(ESP.getCycleCount());
static timeType time() {return ESP.getCycleCount();}
static constexpr timeType ticksPerSecond = F_CPU; // 80'000'000 or 160'000'000 Hz
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
};
template <typename TimeSourceType, unsigned long long second_th>
// "second_th" units of timeType for one second
struct TimeUnit
{
using timeType = typename TimeSourceType::timeType;
#if __GNUC__ < 5
// gcc-4.8 cannot compile the constexpr-only version of this function
// using #defines instead luckily works
static constexpr timeType computeRangeCompensation ()
{
#define number_of_secondTh_in_one_tick ((1.0 * second_th) / ticksPerSecond)
#define fractional (number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick)
return ({
fractional == 0?
1: // no need for compensation
(number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division
});
#undef number_of_secondTh_in_one_tick
#undef fractional
}
#else
static constexpr timeType computeRangeCompensation ()
{
return ({
constexpr double number_of_secondTh_in_one_tick = (1.0 * second_th) / ticksPerSecond;
constexpr double fractional = number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick;
fractional == 0?
1: // no need for compensation
(number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division
});
}
#endif
static constexpr timeType ticksPerSecond = TimeSourceType::ticksPerSecond;
static constexpr timeType ticksPerSecondMax = TimeSourceType::ticksPerSecondMax;
static constexpr timeType rangeCompensate = computeRangeCompensation();
static constexpr timeType user2UnitMultiplierMax = (ticksPerSecondMax * rangeCompensate) / second_th;
static constexpr timeType user2UnitMultiplier = (ticksPerSecond * rangeCompensate) / second_th;
static constexpr timeType user2UnitDivider = rangeCompensate;
// std::numeric_limits<timeType>::max() is reserved
static constexpr timeType timeMax = (std::numeric_limits<timeType>::max() - 1) / user2UnitMultiplierMax;
static timeType toTimeTypeUnit (const timeType userUnit) {return (userUnit * user2UnitMultiplier) / user2UnitDivider;}
static timeType toUserUnit (const timeType internalUnit) {return (internalUnit * user2UnitDivider) / user2UnitMultiplier;}
static timeType time () {return TimeSourceType::time();}
};
using TimeMillis = TimeUnit< TimeSourceMillis, 1000 >;
using TimeFastMillis = TimeUnit< TimeSourceCycles, 1000 >;
using TimeFastMicros = TimeUnit< TimeSourceCycles, 1000000 >;
using TimeFastNanos = TimeUnit< TimeSourceCycles, 1000000000 >;
} //TimePolicy
template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing, typename TimePolicyT = TimePolicy::TimeMillis>
class timeoutTemplate
{
public:
using timeType = decltype(millis());
timeoutTemplate(timeType timeout)
: _timeout(timeout), _start(millis())
{}
using timeType = typename TimePolicyT::timeType;
static constexpr timeType alwaysExpired = 0;
static constexpr timeType neverExpires = std::numeric_limits<timeType>::max();
static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug
timeoutTemplate(const timeType userTimeout)
{
reset(userTimeout);
}
ICACHE_RAM_ATTR
bool expired()
{
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
@ -66,37 +161,69 @@ public:
return expiredOneShot();
}
ICACHE_RAM_ATTR
operator bool()
{
return expired();
}
void reset(const timeType newTimeout)
bool canExpire () const
{
return !_neverExpires;
}
bool canWait () const
{
return _timeout != alwaysExpired;
}
void reset(const timeType newUserTimeout)
{
_timeout = newTimeout;
reset();
_timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout);
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
}
void reset()
{
_start = millis();
_start = TimePolicyT::time();
}
void resetToNeverExpires ()
{
_timeout = alwaysExpired + 1; // because canWait() has precedence
_neverExpires = true;
}
timeType getTimeout() const
{
return _timeout;
return TimePolicyT::toUserUnit(_timeout);
}
bool checkExpired(const timeType t) const
static constexpr timeType timeMax()
{
return (t - _start) >= _timeout;
return TimePolicyT::timeMax;
}
private:
ICACHE_RAM_ATTR
bool checkExpired(const timeType internalUnit) const
{
// canWait() is not checked here
// returns "can expire" and "time expired"
return (!_neverExpires) && ((internalUnit - _start) >= _timeout);
}
protected:
ICACHE_RAM_ATTR
bool expiredRetrigger()
{
timeType current = millis();
if (!canWait())
return true;
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)
@ -106,23 +233,50 @@ protected:
return false;
}
ICACHE_RAM_ATTR
bool expiredOneShot() const
{
return checkExpired(millis());
// returns "always expired" or "has expired"
return !canWait() || checkExpired(TimePolicyT::time());
}
timeType _timeout;
timeType _start;
bool _neverExpires;
};
using oneShot = polledTimeout::timeoutTemplate<false>;
using periodic = polledTimeout::timeoutTemplate<true>;
// legacy type names, deprecated (unit is milliseconds)
using oneShot = polledTimeout::timeoutTemplate<false> /*__attribute__((deprecated("use oneShotMs")))*/;
using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecated("use periodicMs")))*/;
// standard versions (based on millis())
// timeMax() is 49.7 days ((2^32)-2 ms)
using oneShotMs = polledTimeout::timeoutTemplate<false>;
using periodicMs = polledTimeout::timeoutTemplate<true>;
// Time policy based on ESP.getCycleCount(), and intended to be called very often:
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount()))
// timeMax() values:
// Ms: max is 26843 ms (26.8 s)
// Us: max is 26843545 us (26.8 s)
// Ns: max is 1073741823 ns ( 1.07 s)
// (time policy based on ESP.getCycleCount() is intended to be called very often)
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
using oneShotFastUs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMicros>;
using periodicFastUs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMicros>;
using oneShotFastNs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastNanos>;
using periodicFastNs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastNanos>;
} //polledTimeout
/* A 1-shot timeout that auto-yields when in CONT can be built as follows:
* using oneShotYield = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>;
* using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>;
*
* Other policies can be implemented by the user, e.g.: simple yield that panics in SYS, and the polledTimeout types built as needed as shown above, without modifying this file.
*/