1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
#ifndef __POLLEDTIMING_H__
#define __POLLEDTIMING_H__
/*
PolledTimeout.h - Encapsulation of a polled Timeout
Copyright (c) 2018 Daniel Salazar. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <limits>
#include <Arduino.h>
namespace esp8266
{
namespace polledTimeout
{
namespace YieldPolicy
{
struct DoNothing
{
static void execute() {}
};
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
});
#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 = typename TimePolicyT::timeType;
static_assert(std::is_unsigned<timeType>::value == true, "timeType must be unsigned");
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);
}
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();
}
IRAM_ATTR // fast
operator bool()
{
return expired();
}
bool canExpire () const
{
return !_neverExpires;
}
bool canWait () const
{
return _timeout != alwaysExpired;
}
IRAM_ATTR // called from ISR
void reset(const timeType newUserTimeout)
{
reset();
_timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout);
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
}
IRAM_ATTR // called from ISR
void reset()
{
_start = TimePolicyT::time();
}
void resetToNeverExpires ()
{
_timeout = alwaysExpired + 1; // because canWait() has precedence
_neverExpires = true;
}
timeType getTimeout() const
{
return TimePolicyT::toUserUnit(_timeout);
}
static constexpr timeType timeMax()
{
return TimePolicyT::timeMax;
}
private:
IRAM_ATTR // fast
bool checkExpired(const timeType internalUnit) const
{
// canWait() is not checked here
// returns "can expire" and "time expired"
return (!_neverExpires) && ((internalUnit - _start) >= _timeout);
}
protected:
IRAM_ATTR // fast
bool expiredRetrigger()
{
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)
_start += n * _timeout;
return true;
}
return false;
}
IRAM_ATTR // fast
bool expiredOneShot() const
{
// returns "always expired" or "has expired"
return !canWait() || checkExpired(TimePolicyT::time());
}
timeType _timeout;
timeType _start;
bool _neverExpires;
};
// 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 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.
*/
}//esp8266
#endif
|