mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-25 20:02:37 +03:00
esp_yield() now also calls esp_schedule(), original esp_yield() function renamed to esp_suspend(). Don't use delay(0) in the Core internals, libraries and examples. Use yield() when the code is supposed to be called from CONT, use esp_yield() when the code can be called from either CONT or SYS. Clean-up esp_yield() and esp_schedule() declarations across the code and use coredecls.h instead. Implement helper functions for libraries that were previously using esp_yield(), esp_schedule() and esp_delay() directly to wait for certain SYS context tasks to complete. Correctly use esp_delay() for timeouts, make sure scheduled functions have a chance to run (e.g. LwIP_Ethernet uses recurrent) Related issues: - #6107 - discussion about the esp_yield() and esp_delay() usage in ClientContext - #6212 - discussion about replacing delay() with a blocking loop - #6680 - pull request introducing LwIP-based Ethernet - #7146 - discussion that originated UART code changes - #7969 - proposal to remove delay(0) from the example code - #8291 - discussion related to the run_scheduled_recurrent_functions() usage in LwIP Ethernet - #8317 - yieldUntil() implementation, similar to the esp_delay() overload with a timeout and a 0 interval
311 lines
9.8 KiB
C++
311 lines
9.8 KiB
C++
#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 <c_types.h> // IRAM_ATTR
|
|
#include <limits> // std::numeric_limits
|
|
#include <type_traits> // std::is_unsigned
|
|
#include <core_esp8266_features.h>
|
|
#include <coredecls.h>
|
|
|
|
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(esp_get_cycle_count());
|
|
static timeType time() {return esp_get_cycle_count();}
|
|
static constexpr timeType ticksPerSecond = esp_get_cpu_freq_mhz() * 1000000UL; // 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;
|
|
}
|
|
|
|
// 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
|
|
|
|
#endif
|