|
| 1 | +#ifndef __POLLEDTIMING_H__ |
| 2 | +#define __POLLEDTIMING_H__ |
| 3 | + |
| 4 | + |
| 5 | +/* |
| 6 | + PolledTimeout.h - Encapsulation of a polled Timeout |
| 7 | + |
| 8 | + Copyright (c) 2018 Daniel Salazar. All rights reserved. |
| 9 | + This file is part of the esp8266 core for Arduino environment. |
| 10 | +
|
| 11 | + This library is free software; you can redistribute it and/or |
| 12 | + modify it under the terms of the GNU Lesser General Public |
| 13 | + License as published by the Free Software Foundation; either |
| 14 | + version 2.1 of the License, or (at your option) any later version. |
| 15 | +
|
| 16 | + This library is distributed in the hope that it will be useful, |
| 17 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 19 | + Lesser General Public License for more details. |
| 20 | +
|
| 21 | + You should have received a copy of the GNU Lesser General Public |
| 22 | + License along with this library; if not, write to the Free Software |
| 23 | + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 24 | + */ |
| 25 | + |
| 26 | +#include <limits> |
| 27 | + |
| 28 | +#include <Arduino.h> |
| 29 | + |
| 30 | +namespace esp8266 |
| 31 | +{ |
| 32 | + |
| 33 | + |
| 34 | +namespace polledTimeout |
| 35 | +{ |
| 36 | + |
| 37 | +namespace YieldPolicy |
| 38 | +{ |
| 39 | + |
| 40 | +struct DoNothing |
| 41 | +{ |
| 42 | + static void execute() {} |
| 43 | +}; |
| 44 | + |
| 45 | +struct YieldOrSkip |
| 46 | +{ |
| 47 | + static void execute() {delay(0);} |
| 48 | +}; |
| 49 | + |
| 50 | +template <unsigned long delayMs> |
| 51 | +struct YieldAndDelayMs |
| 52 | +{ |
| 53 | + static void execute() {delay(delayMs);} |
| 54 | +}; |
| 55 | + |
| 56 | +} //YieldPolicy |
| 57 | + |
| 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 | + }); |
| 102 | + |
| 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> |
| 141 | +class timeoutTemplate |
| 142 | +{ |
| 143 | +public: |
| 144 | + using timeType = typename TimePolicyT::timeType; |
| 145 | + static_assert(std::is_unsigned<timeType>::value == true, "timeType must be unsigned"); |
| 146 | + |
| 147 | + static constexpr timeType alwaysExpired = 0; |
| 148 | + static constexpr timeType neverExpires = std::numeric_limits<timeType>::max(); |
| 149 | + static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug |
| 150 | + |
| 151 | + timeoutTemplate(const timeType userTimeout) |
| 152 | + { |
| 153 | + reset(userTimeout); |
| 154 | + } |
| 155 | + |
| 156 | + IRAM_ATTR // fast |
| 157 | + bool expired() |
| 158 | + { |
| 159 | + YieldPolicyT::execute(); //in case of DoNothing: gets optimized away |
| 160 | + if(PeriodicT) //in case of false: gets optimized away |
| 161 | + return expiredRetrigger(); |
| 162 | + return expiredOneShot(); |
| 163 | + } |
| 164 | + |
| 165 | + IRAM_ATTR // fast |
| 166 | + operator bool() |
| 167 | + { |
| 168 | + return expired(); |
| 169 | + } |
| 170 | + |
| 171 | + bool canExpire () const |
| 172 | + { |
| 173 | + return !_neverExpires; |
| 174 | + } |
| 175 | + |
| 176 | + bool canWait () const |
| 177 | + { |
| 178 | + return _timeout != alwaysExpired; |
| 179 | + } |
| 180 | + |
| 181 | + IRAM_ATTR // called from ISR |
| 182 | + void reset(const timeType newUserTimeout) |
| 183 | + { |
| 184 | + reset(); |
| 185 | + _timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout); |
| 186 | + _neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax()); |
| 187 | + } |
| 188 | + |
| 189 | + IRAM_ATTR // called from ISR |
| 190 | + void reset() |
| 191 | + { |
| 192 | + _start = TimePolicyT::time(); |
| 193 | + } |
| 194 | + |
| 195 | + void resetToNeverExpires () |
| 196 | + { |
| 197 | + _timeout = alwaysExpired + 1; // because canWait() has precedence |
| 198 | + _neverExpires = true; |
| 199 | + } |
| 200 | + |
| 201 | + timeType getTimeout() const |
| 202 | + { |
| 203 | + return TimePolicyT::toUserUnit(_timeout); |
| 204 | + } |
| 205 | + |
| 206 | + static constexpr timeType timeMax() |
| 207 | + { |
| 208 | + return TimePolicyT::timeMax; |
| 209 | + } |
| 210 | + |
| 211 | +private: |
| 212 | + |
| 213 | + IRAM_ATTR // fast |
| 214 | + bool checkExpired(const timeType internalUnit) const |
| 215 | + { |
| 216 | + // canWait() is not checked here |
| 217 | + // returns "can expire" and "time expired" |
| 218 | + return (!_neverExpires) && ((internalUnit - _start) >= _timeout); |
| 219 | + } |
| 220 | + |
| 221 | +protected: |
| 222 | + |
| 223 | + IRAM_ATTR // fast |
| 224 | + bool expiredRetrigger() |
| 225 | + { |
| 226 | + if (!canWait()) |
| 227 | + return true; |
| 228 | + |
| 229 | + timeType current = TimePolicyT::time(); |
| 230 | + if(checkExpired(current)) |
| 231 | + { |
| 232 | + unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) |
| 233 | + _start += n * _timeout; |
| 234 | + return true; |
| 235 | + } |
| 236 | + return false; |
| 237 | + } |
| 238 | + |
| 239 | + IRAM_ATTR // fast |
| 240 | + bool expiredOneShot() const |
| 241 | + { |
| 242 | + // returns "always expired" or "has expired" |
| 243 | + return !canWait() || checkExpired(TimePolicyT::time()); |
| 244 | + } |
| 245 | + |
| 246 | + timeType _timeout; |
| 247 | + timeType _start; |
| 248 | + bool _neverExpires; |
| 249 | +}; |
| 250 | + |
| 251 | +// legacy type names, deprecated (unit is milliseconds) |
| 252 | + |
| 253 | +using oneShot = polledTimeout::timeoutTemplate<false> /*__attribute__((deprecated("use oneShotMs")))*/; |
| 254 | +using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecated("use periodicMs")))*/; |
| 255 | + |
| 256 | +// standard versions (based on millis()) |
| 257 | +// timeMax() is 49.7 days ((2^32)-2 ms) |
| 258 | + |
| 259 | +using oneShotMs = polledTimeout::timeoutTemplate<false>; |
| 260 | +using periodicMs = polledTimeout::timeoutTemplate<true>; |
| 261 | + |
| 262 | +// Time policy based on ESP.getCycleCount(), and intended to be called very often: |
| 263 | +// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%) |
| 264 | +// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount())) |
| 265 | +// timeMax() values: |
| 266 | +// Ms: max is 26843 ms (26.8 s) |
| 267 | +// Us: max is 26843545 us (26.8 s) |
| 268 | +// Ns: max is 1073741823 ns ( 1.07 s) |
| 269 | +// (time policy based on ESP.getCycleCount() is intended to be called very often) |
| 270 | + |
| 271 | +using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>; |
| 272 | +using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>; |
| 273 | +using oneShotFastUs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMicros>; |
| 274 | +using periodicFastUs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMicros>; |
| 275 | +using oneShotFastNs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastNanos>; |
| 276 | +using periodicFastNs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastNanos>; |
| 277 | + |
| 278 | +} //polledTimeout |
| 279 | + |
| 280 | + |
| 281 | +/* A 1-shot timeout that auto-yields when in CONT can be built as follows: |
| 282 | + * using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>; |
| 283 | + * |
| 284 | + * 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. |
| 285 | + */ |
| 286 | + |
| 287 | +}//esp8266 |
| 288 | + |
| 289 | +#endif |
0 commit comments