Skip to content

Commit 9a2ed27

Browse files
d-a-vdevyte
authored andcommitted
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
1 parent f0eb550 commit 9a2ed27

File tree

15 files changed

+344
-92
lines changed

15 files changed

+344
-92
lines changed

cores/esp8266/Esp.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -200,15 +200,20 @@ class EspClass {
200200

201201
bool eraseConfig();
202202

203-
inline uint32_t getCycleCount();
203+
#ifndef CORE_MOCK
204+
inline
205+
#endif
206+
uint32_t getCycleCount();
204207
};
205208

209+
#ifndef CORE_MOCK
206210
uint32_t EspClass::getCycleCount()
207211
{
208212
uint32_t ccount;
209213
__asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
210214
return ccount;
211215
}
216+
#endif
212217

213218
extern EspClass ESP;
214219

cores/esp8266/HardwareSerial.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size)
139139

140140
while (got < size)
141141
{
142-
esp8266::polledTimeout::oneShot timeOut(_timeout);
142+
esp8266::polledTimeout::oneShotFastMs timeOut(_timeout);
143143
size_t avail;
144144
while ((avail = available()) == 0 && !timeOut);
145145
if (avail == 0)

cores/esp8266/PolledTimeout.h

+173-19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2424
*/
2525

26+
#include <limits>
27+
2628
#include <Arduino.h>
2729

2830
namespace esp8266
@@ -45,19 +47,112 @@ struct YieldOrSkip
4547
static void execute() {delay(0);}
4648
};
4749

50+
template <unsigned long delayMs>
51+
struct YieldAndDelayMs
52+
{
53+
static void execute() {delay(delayMs);}
54+
};
55+
4856
} //YieldPolicy
4957

58+
namespace TimePolicy
59+
{
60+
61+
struct TimeSourceMillis
62+
{
63+
// time policy in milli-seconds based on millis()
64+
65+
using timeType = decltype(millis());
66+
static timeType time() {return millis();}
67+
static constexpr timeType ticksPerSecond = 1000;
68+
static constexpr timeType ticksPerSecondMax = 1000;
69+
};
70+
71+
struct TimeSourceCycles
72+
{
73+
// time policy based on ESP.getCycleCount()
74+
// this particular time measurement is intended to be called very often
75+
// (every loop, every yield)
76+
77+
using timeType = decltype(ESP.getCycleCount());
78+
static timeType time() {return ESP.getCycleCount();}
79+
static constexpr timeType ticksPerSecond = F_CPU; // 80'000'000 or 160'000'000 Hz
80+
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
81+
};
82+
83+
template <typename TimeSourceType, unsigned long long second_th>
84+
// "second_th" units of timeType for one second
85+
struct TimeUnit
86+
{
87+
using timeType = typename TimeSourceType::timeType;
88+
89+
#if __GNUC__ < 5
90+
// gcc-4.8 cannot compile the constexpr-only version of this function
91+
// using #defines instead luckily works
92+
static constexpr timeType computeRangeCompensation ()
93+
{
94+
#define number_of_secondTh_in_one_tick ((1.0 * second_th) / ticksPerSecond)
95+
#define fractional (number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick)
96+
97+
return ({
98+
fractional == 0?
99+
1: // no need for compensation
100+
(number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division
101+
});
50102

51-
template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing>
103+
#undef number_of_secondTh_in_one_tick
104+
#undef fractional
105+
}
106+
#else
107+
static constexpr timeType computeRangeCompensation ()
108+
{
109+
return ({
110+
constexpr double number_of_secondTh_in_one_tick = (1.0 * second_th) / ticksPerSecond;
111+
constexpr double fractional = number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick;
112+
fractional == 0?
113+
1: // no need for compensation
114+
(number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division
115+
});
116+
}
117+
#endif
118+
119+
static constexpr timeType ticksPerSecond = TimeSourceType::ticksPerSecond;
120+
static constexpr timeType ticksPerSecondMax = TimeSourceType::ticksPerSecondMax;
121+
static constexpr timeType rangeCompensate = computeRangeCompensation();
122+
static constexpr timeType user2UnitMultiplierMax = (ticksPerSecondMax * rangeCompensate) / second_th;
123+
static constexpr timeType user2UnitMultiplier = (ticksPerSecond * rangeCompensate) / second_th;
124+
static constexpr timeType user2UnitDivider = rangeCompensate;
125+
// std::numeric_limits<timeType>::max() is reserved
126+
static constexpr timeType timeMax = (std::numeric_limits<timeType>::max() - 1) / user2UnitMultiplierMax;
127+
128+
static timeType toTimeTypeUnit (const timeType userUnit) {return (userUnit * user2UnitMultiplier) / user2UnitDivider;}
129+
static timeType toUserUnit (const timeType internalUnit) {return (internalUnit * user2UnitDivider) / user2UnitMultiplier;}
130+
static timeType time () {return TimeSourceType::time();}
131+
};
132+
133+
using TimeMillis = TimeUnit< TimeSourceMillis, 1000 >;
134+
using TimeFastMillis = TimeUnit< TimeSourceCycles, 1000 >;
135+
using TimeFastMicros = TimeUnit< TimeSourceCycles, 1000000 >;
136+
using TimeFastNanos = TimeUnit< TimeSourceCycles, 1000000000 >;
137+
138+
} //TimePolicy
139+
140+
template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing, typename TimePolicyT = TimePolicy::TimeMillis>
52141
class timeoutTemplate
53142
{
54143
public:
55-
using timeType = decltype(millis());
56-
57-
timeoutTemplate(timeType timeout)
58-
: _timeout(timeout), _start(millis())
59-
{}
144+
using timeType = typename TimePolicyT::timeType;
145+
146+
static constexpr timeType alwaysExpired = 0;
147+
static constexpr timeType neverExpires = std::numeric_limits<timeType>::max();
148+
static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug
149+
150+
timeoutTemplate(const timeType userTimeout)
151+
{
152+
reset(userTimeout);
153+
}
60154

155+
ICACHE_RAM_ATTR
61156
bool expired()
62157
{
63158
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
@@ -66,37 +161,69 @@ class timeoutTemplate
66161
return expiredOneShot();
67162
}
68163

164+
ICACHE_RAM_ATTR
69165
operator bool()
70166
{
71167
return expired();
72168
}
73169

74-
void reset(const timeType newTimeout)
170+
bool canExpire () const
171+
{
172+
return !_neverExpires;
173+
}
174+
175+
bool canWait () const
176+
{
177+
return _timeout != alwaysExpired;
178+
}
179+
180+
void reset(const timeType newUserTimeout)
75181
{
76-
_timeout = newTimeout;
77182
reset();
183+
_timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout);
184+
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
78185
}
79186

80187
void reset()
81188
{
82-
_start = millis();
189+
_start = TimePolicyT::time();
190+
}
191+
192+
void resetToNeverExpires ()
193+
{
194+
_timeout = alwaysExpired + 1; // because canWait() has precedence
195+
_neverExpires = true;
83196
}
84197

85198
timeType getTimeout() const
86199
{
87-
return _timeout;
200+
return TimePolicyT::toUserUnit(_timeout);
88201
}
89202

90-
bool checkExpired(const timeType t) const
203+
static constexpr timeType timeMax()
91204
{
92-
return (t - _start) >= _timeout;
205+
return TimePolicyT::timeMax;
93206
}
94-
207+
208+
private:
209+
210+
ICACHE_RAM_ATTR
211+
bool checkExpired(const timeType internalUnit) const
212+
{
213+
// canWait() is not checked here
214+
// returns "can expire" and "time expired"
215+
return (!_neverExpires) && ((internalUnit - _start) >= _timeout);
216+
}
217+
95218
protected:
96-
219+
220+
ICACHE_RAM_ATTR
97221
bool expiredRetrigger()
98222
{
99-
timeType current = millis();
223+
if (!canWait())
224+
return true;
225+
226+
timeType current = TimePolicyT::time();
100227
if(checkExpired(current))
101228
{
102229
unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout)
@@ -106,23 +233,50 @@ class timeoutTemplate
106233
return false;
107234
}
108235

236+
ICACHE_RAM_ATTR
109237
bool expiredOneShot() const
110238
{
111-
return checkExpired(millis());
239+
// returns "always expired" or "has expired"
240+
return !canWait() || checkExpired(TimePolicyT::time());
112241
}
113242

114243
timeType _timeout;
115244
timeType _start;
245+
bool _neverExpires;
116246
};
117247

118-
using oneShot = polledTimeout::timeoutTemplate<false>;
119-
using periodic = polledTimeout::timeoutTemplate<true>;
248+
// legacy type names, deprecated (unit is milliseconds)
249+
250+
using oneShot = polledTimeout::timeoutTemplate<false> /*__attribute__((deprecated("use oneShotMs")))*/;
251+
using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecated("use periodicMs")))*/;
252+
253+
// standard versions (based on millis())
254+
// timeMax() is 49.7 days ((2^32)-2 ms)
255+
256+
using oneShotMs = polledTimeout::timeoutTemplate<false>;
257+
using periodicMs = polledTimeout::timeoutTemplate<true>;
258+
259+
// Time policy based on ESP.getCycleCount(), and intended to be called very often:
260+
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
261+
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount()))
262+
// timeMax() values:
263+
// Ms: max is 26843 ms (26.8 s)
264+
// Us: max is 26843545 us (26.8 s)
265+
// Ns: max is 1073741823 ns ( 1.07 s)
266+
// (time policy based on ESP.getCycleCount() is intended to be called very often)
267+
268+
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
269+
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
270+
using oneShotFastUs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMicros>;
271+
using periodicFastUs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMicros>;
272+
using oneShotFastNs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastNanos>;
273+
using periodicFastNs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastNanos>;
120274

121275
} //polledTimeout
122276

123277

124278
/* A 1-shot timeout that auto-yields when in CONT can be built as follows:
125-
* using oneShotYield = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>;
279+
* using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>;
126280
*
127281
* 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.
128282
*/

libraries/ESP8266WiFi/examples/IPv6/IPv6.ino

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
WiFiServer statusServer(TCP_PORT);
3737
WiFiUDP udp;
38-
esp8266::polledTimeout::periodic statusPeriod(STATUSDELAY_MS);
38+
esp8266::polledTimeout::periodicMs showStatusOnSerialNow(STATUSDELAY_MS);
3939

4040
void fqdn(Print& out, const String& fqdn) {
4141
out.print(F("resolving "));
@@ -149,7 +149,7 @@ void setup() {
149149
Serial.print(F(" - UDP server on port "));
150150
Serial.println(UDP_PORT);
151151

152-
statusPeriod.reset();
152+
showStatusOnSerialNow.reset();
153153
}
154154

155155
unsigned long statusTimeMs = 0;
@@ -182,7 +182,7 @@ void loop() {
182182
}
183183

184184

185-
if (statusPeriod) {
185+
if (showStatusOnSerialNow) {
186186
status(Serial);
187187
}
188188

libraries/ESP8266mDNS/examples/LEAmDNS/mDNS_Clock/mDNS_Clock.ino

+1-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ void loop(void) {
268268
// Allow MDNS processing
269269
MDNS.update();
270270

271-
static esp8266::polledTimeout::periodic timeout(UPDATE_CYCLE);
271+
static esp8266::polledTimeout::periodicMs timeout(UPDATE_CYCLE);
272272
if (timeout.expired()) {
273273

274274
if (hMDNSService) {

libraries/ESP8266mDNS/src/LEAmDNS.h

+18-18
Original file line numberDiff line numberDiff line change
@@ -905,12 +905,12 @@ class MDNSResponder {
905905
* stcProbeInformation
906906
*/
907907
struct stcProbeInformation {
908-
enuProbingStatus m_ProbingStatus;
909-
uint8_t m_u8SentCount; // Used for probes and announcements
910-
esp8266::polledTimeout::oneShot m_Timeout; // Used for probes and announcements
911-
//clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements
912-
bool m_bConflict;
913-
bool m_bTiebreakNeeded;
908+
enuProbingStatus m_ProbingStatus;
909+
uint8_t m_u8SentCount; // Used for probes and announcements
910+
esp8266::polledTimeout::oneShotMs m_Timeout; // Used for probes and announcements
911+
//clsMDNSTimeFlag m_TimeFlag; // Used for probes and announcements
912+
bool m_bConflict;
913+
bool m_bTiebreakNeeded;
914914
MDNSHostProbeFn m_fnHostProbeResultCallback;
915915
MDNSServiceProbeFn m_fnServiceProbeResultCallback;
916916

@@ -974,14 +974,14 @@ class MDNSResponder {
974974
const timeoutLevel_t TIMEOUTLEVEL_INTERVAL = 5;
975975
const timeoutLevel_t TIMEOUTLEVEL_FINAL = 100;
976976

977-
uint32_t m_u32TTL;
978-
esp8266::polledTimeout::oneShot m_TTLTimeout;
979-
timeoutLevel_t m_timeoutLevel;
977+
uint32_t m_u32TTL;
978+
esp8266::polledTimeout::oneShotMs m_TTLTimeout;
979+
timeoutLevel_t m_timeoutLevel;
980980

981981
stcTTL(void);
982982
bool set(uint32_t p_u32TTL);
983983

984-
bool flagged(void) const;
984+
bool flagged(void);
985985
bool restart(void);
986986

987987
bool prepareDeletion(void);
@@ -1073,14 +1073,14 @@ class MDNSResponder {
10731073
#endif
10741074
};
10751075

1076-
stcMDNSServiceQuery* m_pNext;
1077-
stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local
1078-
MDNSServiceQueryCallbackFunc m_fnCallback;
1079-
bool m_bLegacyQuery;
1080-
uint8_t m_u8SentCount;
1081-
esp8266::polledTimeout::oneShot m_ResendTimeout;
1082-
bool m_bAwaitingAnswers;
1083-
stcAnswer* m_pAnswers;
1076+
stcMDNSServiceQuery* m_pNext;
1077+
stcMDNS_RRDomain m_ServiceTypeDomain; // eg. _http._tcp.local
1078+
MDNSServiceQueryCallbackFunc m_fnCallback;
1079+
bool m_bLegacyQuery;
1080+
uint8_t m_u8SentCount;
1081+
esp8266::polledTimeout::oneShotMs m_ResendTimeout;
1082+
bool m_bAwaitingAnswers;
1083+
stcAnswer* m_pAnswers;
10841084

10851085
stcMDNSServiceQuery(void);
10861086
~stcMDNSServiceQuery(void);

0 commit comments

Comments
 (0)