arduino-pico/cores/rp2040/PolledTimeout.h
2022-09-07 15:19:12 -07:00

290 lines
10 KiB
C++

/*
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
*/
#pragma once
#include <limits> // std::numeric_limits
#include <type_traits> // std::is_unsigned
#define IRAM_ATTR
namespace esp8266 {
namespace polledTimeout {
namespace YieldPolicy {
struct DoNothing {
static void execute() {}
};
struct YieldOrSkip {
static void execute() {} //{esp_yield();}
};
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_get_cycle_count()
// this particular time measurement is intended to be called very often
// (every loop, every yield)
using timeType = decltype(rp2040.getCycleCount());
static timeType time() {
return rp2040.getCycleCount();
}
static constexpr timeType ticksPerSecond = F_CPU;
static constexpr timeType ticksPerSecondMax = F_CPU;
};
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, 1'000 >;
using TimeFastMillis = TimeUnit < TimeSourceCycles, 1'000 >;
using TimeFastMicros = TimeUnit < TimeSourceCycles, 1'000'000 >;
using TimeFastNanos = TimeUnit < TimeSourceCycles, 1'000'000'000 >;
} //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;
}
// Resets, will trigger after this new timeout.
IRAM_ATTR // called from ISR
void reset(const timeType newUserTimeout) {
reset();
_timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout);
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
}
// Resets, will trigger after the timeout previously set.
IRAM_ATTR // called from ISR
void reset() {
_start = TimePolicyT::time();
}
// Resets to just expired so that on next poll the check will immediately trigger for the user,
// also change timeout (after next immediate trigger).
IRAM_ATTR // called from ISR
void resetAndSetExpired(const timeType newUserTimeout) {
reset(newUserTimeout);
_start -= _timeout;
}
// Resets to just expired so that on next poll the check will immediately trigger for the user.
IRAM_ATTR // called from ISR
void resetAndSetExpired() {
reset();
_start -= _timeout;
}
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_get_cycle_count(), 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_get_cycle_count()))
// 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_get_cycle_count() 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