Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fca1d5d
polledTimeout: add option to use CPU count instead of millis()
d-a-v Mar 13, 2019
6cd506b
Merge branch 'master' into polledcpu
d-a-v Mar 13, 2019
702cea8
use more "using" alias
d-a-v Mar 13, 2019
2afa570
Merge branch 'master' into polledcpu
d-a-v Mar 14, 2019
133c06f
more c++/clear code, using typename (thanks @devyte)
d-a-v Mar 14, 2019
d675eeb
rename class name to include unit, introduce timeMax() and check it w…
d-a-v Mar 15, 2019
f3f7a2d
remove useless defines
d-a-v Mar 15, 2019
a21514a
improve api readability, add micro-second unit
d-a-v Mar 15, 2019
9b8e40b
update example
d-a-v Mar 15, 2019
ba105b7
mock: emulate getCycleCount, add/fix polledTimeout CI test
d-a-v Mar 18, 2019
02909f8
Merge branch 'master' into polledcpu
d-a-v Mar 18, 2019
d1785c6
Merge branch 'master' into polledcpu
d-a-v Mar 18, 2019
ebda468
+ nano-seconds, assert -> message, comments, host test
d-a-v Mar 19, 2019
7a4c4de
allow 0 for timeout (enables immediate timeout, fix division by 0)
d-a-v Mar 19, 2019
2249315
Merge branch 'master' into polledcpu
d-a-v Mar 19, 2019
aa6b2d9
typo, set member instead of local variable
d-a-v Mar 19, 2019
e98c41c
unify error message
d-a-v Mar 19, 2019
714beb3
slight change on checkExpired() allows "never expired"
d-a-v Mar 19, 2019
925408e
remove traces of debug.h/cpp in this PR
d-a-v Mar 19, 2019
412a241
include missing <limits> header
d-a-v Mar 20, 2019
273306a
back to original expired test, introduce boolean _neverExpires, fix r…
d-a-v Mar 20, 2019
531e08e
fix expiredOneShot with _timeout==0 check
d-a-v Mar 20, 2019
a041a66
reenable getTimeout()
d-a-v Mar 20, 2019
70ac794
expose checkExpired with unit conversion
d-a-v Mar 20, 2019
e25b880
fix timing comments, move critical code to iram
d-a-v Mar 20, 2019
a20f8f6
add member ::neverExpires and use it where relevant
d-a-v Mar 20, 2019
8d4c385
improve clarity
d-a-v Mar 20, 2019
ce7e9b9
remove exposed checkExpired(), adapt LEAmDNS with equivalent
d-a-v Mar 20, 2019
0ca22ce
add API ::resetToNeverExpires(), use it in LEAmDNS
d-a-v Mar 20, 2019
3d4b369
Merge branch 'master' into polledcpu
d-a-v Mar 20, 2019
ff660ae
remove offending constness from ::flagged() LEAmDNS (due do API fix i…
d-a-v Mar 21, 2019
393d40f
simplify "Fast" base classes
d-a-v Mar 21, 2019
1f4f73c
Merge branch 'master' into polledcpu
d-a-v Mar 25, 2019
daa5e85
Merge branch 'polledcpu' of github.com:d-a-v/Arduino into polledcpu
d-a-v Mar 25, 2019
96c7fb7
minor variable rename
d-a-v Mar 25, 2019
45c5da2
Merge branch 'master' into polledcpu
d-a-v Mar 28, 2019
4930ed0
Fix examples
d-a-v Mar 28, 2019
f11c6e6
compliance with good c++ manners
d-a-v Apr 2, 2019
9d73d18
Merge branch 'master' into polledcpu
d-a-v Apr 2, 2019
f971d42
minor changes for consistency
d-a-v Apr 2, 2019
b028ea8
add missing const
d-a-v Apr 2, 2019
a02f889
expired() and bool() moved to iram
d-a-v Apr 2, 2019
40d31a5
constexpr compensation computing
d-a-v Apr 2, 2019
75f36a7
add/update comments
d-a-v Apr 3, 2019
0e72db2
Merge branch 'master' into polledcpu
d-a-v Apr 4, 2019
4470ca2
Merge branch 'polledcpu' of github.com:d-a-v/Arduino into polledcpu
d-a-v Apr 4, 2019
0969fee
move neverExpires and alwaysExpired
d-a-v Apr 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cores/esp8266/Esp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion cores/esp8266/HardwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
192 changes: 173 additions & 19 deletions cores/esp8266/PolledTimeout.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <limits>

#include <Arduino.h>

namespace esp8266
Expand All @@ -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
{

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
});

template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing>
#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
Expand All @@ -66,37 +161,69 @@ class timeoutTemplate
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)
Expand All @@ -106,23 +233,50 @@ class timeoutTemplate
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.
*/
Expand Down
6 changes: 3 additions & 3 deletions libraries/ESP8266WiFi/examples/IPv6/IPv6.ino
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

WiFiServer statusServer(TCP_PORT);
WiFiUDP udp;
esp8266::polledTimeout::periodic statusPeriod(STATUSDELAY_MS);
esp8266::polledTimeout::periodicMs showStatusOnSerialNow(STATUSDELAY_MS);

void fqdn(Print& out, const String& fqdn) {
out.print(F("resolving "));
Expand Down Expand Up @@ -149,7 +149,7 @@ void setup() {
Serial.print(F(" - UDP server on port "));
Serial.println(UDP_PORT);

statusPeriod.reset();
showStatusOnSerialNow.reset();
}

unsigned long statusTimeMs = 0;
Expand Down Expand Up @@ -182,7 +182,7 @@ void loop() {
}


if (statusPeriod) {
if (showStatusOnSerialNow) {
status(Serial);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ void loop(void) {
// Allow MDNS processing
MDNS.update();

static esp8266::polledTimeout::periodic timeout(UPDATE_CYCLE);
static esp8266::polledTimeout::periodicMs timeout(UPDATE_CYCLE);
if (timeout.expired()) {

if (hMDNSService) {
Expand Down
36 changes: 18 additions & 18 deletions libraries/ESP8266mDNS/src/LEAmDNS.h
Original file line number Diff line number Diff line change
Expand Up @@ -905,12 +905,12 @@ class MDNSResponder {
* stcProbeInformation
*/
struct stcProbeInformation {
enuProbingStatus m_ProbingStatus;
uint8_t m_u8SentCount; // Used for probes and announcements
esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements
//clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements
bool m_bConflict;
bool m_bTiebreakNeeded;
enuProbingStatus m_ProbingStatus;
uint8_t m_u8SentCount; // Used for probes and announcements
esp8266::polledTimeout::oneShotMs m_Timeout; // Used for probes and announcements
//clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements
bool m_bConflict;
bool m_bTiebreakNeeded;
MDNSHostProbeFn m_fnHostProbeResultCallback;
MDNSServiceProbeFn m_fnServiceProbeResultCallback;

Expand Down Expand Up @@ -974,14 +974,14 @@ class MDNSResponder {
const timeoutLevel_t TIMEOUTLEVEL_INTERVAL = 5;
const timeoutLevel_t TIMEOUTLEVEL_FINAL = 100;

uint32_t m_u32TTL;
esp8266::polledTimeout::oneShot m_TTLTimeout;
timeoutLevel_t m_timeoutLevel;
uint32_t m_u32TTL;
esp8266::polledTimeout::oneShotMs m_TTLTimeout;
timeoutLevel_t m_timeoutLevel;

stcTTL(void);
bool set(uint32_t p_u32TTL);

bool flagged(void) const;
bool flagged(void);
bool restart(void);

bool prepareDeletion(void);
Expand Down Expand Up @@ -1073,14 +1073,14 @@ class MDNSResponder {
#endif
};

stcMDNSServiceQuery* m_pNext;
stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local
MDNSServiceQueryCallbackFunc m_fnCallback;
bool m_bLegacyQuery;
uint8_t m_u8SentCount;
esp8266::polledTimeout::oneShot m_ResendTimeout;
bool m_bAwaitingAnswers;
stcAnswer* m_pAnswers;
stcMDNSServiceQuery* m_pNext;
stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local
MDNSServiceQueryCallbackFunc m_fnCallback;
bool m_bLegacyQuery;
uint8_t m_u8SentCount;
esp8266::polledTimeout::oneShotMs m_ResendTimeout;
bool m_bAwaitingAnswers;
stcAnswer* m_pAnswers;

stcMDNSServiceQuery(void);
~stcMDNSServiceQuery(void);
Expand Down
Loading