mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
delay / esp_delay: transparently manage recurrent scheduled functions (#8802)
Recurrent scheduled functions will always be running in background. esp_delay()'s interval (intvl_ms) is internally kept to its highest value allowing to honor recurrent scheduled functions requirements. It transparently allows to keep with the arduino and nonos-sdk trivial programming way and still use background services or drivers running regularly.
This commit is contained in:
parent
e1c4a6c8e6
commit
39080e317e
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include "Schedule.h"
|
#include "Schedule.h"
|
||||||
#include "PolledTimeout.h"
|
#include "PolledTimeout.h"
|
||||||
@ -34,6 +35,7 @@ static scheduled_fn_t* sFirst = nullptr;
|
|||||||
static scheduled_fn_t* sLast = nullptr;
|
static scheduled_fn_t* sLast = nullptr;
|
||||||
static scheduled_fn_t* sUnused = nullptr;
|
static scheduled_fn_t* sUnused = nullptr;
|
||||||
static int sCount = 0;
|
static int sCount = 0;
|
||||||
|
static uint32_t recurrent_max_grain_mS = 0;
|
||||||
|
|
||||||
typedef std::function<bool(void)> mRecFuncT;
|
typedef std::function<bool(void)> mRecFuncT;
|
||||||
struct recurrent_fn_t
|
struct recurrent_fn_t
|
||||||
@ -130,9 +132,39 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
|
|||||||
}
|
}
|
||||||
rLast = item;
|
rLast = item;
|
||||||
|
|
||||||
|
// grain needs to be recomputed
|
||||||
|
recurrent_max_grain_mS = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t compute_scheduled_recurrent_grain ()
|
||||||
|
{
|
||||||
|
if (recurrent_max_grain_mS == 0)
|
||||||
|
{
|
||||||
|
if (rFirst)
|
||||||
|
{
|
||||||
|
uint32_t recurrent_max_grain_uS = rFirst->callNow.getTimeout();
|
||||||
|
for (auto it = rFirst->mNext; it; it = it->mNext)
|
||||||
|
recurrent_max_grain_uS = std::gcd(recurrent_max_grain_uS, it->callNow.getTimeout());
|
||||||
|
if (recurrent_max_grain_uS)
|
||||||
|
// round to the upper millis
|
||||||
|
recurrent_max_grain_mS = recurrent_max_grain_uS <= 1000? 1: (recurrent_max_grain_uS + 999) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_ESP_CORE
|
||||||
|
static uint32_t last_grain = 0;
|
||||||
|
if (recurrent_max_grain_mS != last_grain)
|
||||||
|
{
|
||||||
|
::printf(":rsf %u->%u\n", last_grain, recurrent_max_grain_mS);
|
||||||
|
last_grain = recurrent_max_grain_mS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return recurrent_max_grain_mS;
|
||||||
|
}
|
||||||
|
|
||||||
void run_scheduled_functions()
|
void run_scheduled_functions()
|
||||||
{
|
{
|
||||||
// prevent scheduling of new functions during this run
|
// prevent scheduling of new functions during this run
|
||||||
@ -226,6 +258,9 @@ void run_scheduled_recurrent_functions()
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(to_ditch);
|
delete(to_ditch);
|
||||||
|
|
||||||
|
// grain needs to be recomputed
|
||||||
|
recurrent_max_grain_mS = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -39,6 +39,11 @@
|
|||||||
// scheduled function happen more often: every yield() (vs every loop()),
|
// scheduled function happen more often: every yield() (vs every loop()),
|
||||||
// and time resolution is microsecond (vs millisecond). Details are below.
|
// and time resolution is microsecond (vs millisecond). Details are below.
|
||||||
|
|
||||||
|
// compute_scheduled_recurrent_grain() is used by delay() to give a chance to
|
||||||
|
// all recurrent functions to run per their timing requirement.
|
||||||
|
|
||||||
|
uint32_t compute_scheduled_recurrent_grain ();
|
||||||
|
|
||||||
// scheduled functions called once:
|
// scheduled functions called once:
|
||||||
//
|
//
|
||||||
// * internal queue is FIFO.
|
// * internal queue is FIFO.
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
|
|
||||||
//This may be used to change user task stack size:
|
//This may be used to change user task stack size:
|
||||||
//#define CONT_STACKSIZE 4096
|
//#define CONT_STACKSIZE 4096
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "Schedule.h"
|
#include "Schedule.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -165,10 +168,18 @@ extern "C" void esp_delay(unsigned long ms) __attribute__((weak, alias("__esp_de
|
|||||||
bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uint32_t intvl_ms) {
|
bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uint32_t intvl_ms) {
|
||||||
uint32_t expired = millis() - start_ms;
|
uint32_t expired = millis() - start_ms;
|
||||||
if (expired >= timeout_ms) {
|
if (expired >= timeout_ms) {
|
||||||
return true;
|
return true; // expired
|
||||||
}
|
}
|
||||||
esp_delay(std::min((timeout_ms - expired), intvl_ms));
|
|
||||||
return false;
|
// compute greatest chunked delay with respect to scheduled recurrent functions
|
||||||
|
uint32_t grain_ms = std::gcd(intvl_ms, compute_scheduled_recurrent_grain());
|
||||||
|
|
||||||
|
// recurrent scheduled functions will be called from esp_delay()->esp_suspend()
|
||||||
|
esp_delay(grain_ms > 0 ?
|
||||||
|
std::min((timeout_ms - expired), grain_ms):
|
||||||
|
(timeout_ms - expired));
|
||||||
|
|
||||||
|
return false; // expiration must be checked again
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void __yield() {
|
extern "C" void __yield() {
|
||||||
|
@ -34,7 +34,9 @@ static uint32_t micros_overflow_count = 0;
|
|||||||
#define REPEAT 1
|
#define REPEAT 1
|
||||||
|
|
||||||
void __delay(unsigned long ms) {
|
void __delay(unsigned long ms) {
|
||||||
esp_delay(ms);
|
// Use API letting recurrent scheduled functions run in background
|
||||||
|
// but stay blocked in delay until ms is expired.
|
||||||
|
esp_delay(ms, [](){ return true; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void delay(unsigned long ms) __attribute__ ((weak, alias("__delay")));
|
void delay(unsigned long ms) __attribute__ ((weak, alias("__delay")));
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
#ifndef __COREDECLS_H
|
#pragma once
|
||||||
#define __COREDECLS_H
|
|
||||||
|
|
||||||
#include "core_esp8266_features.h"
|
#include "core_esp8266_features.h"
|
||||||
|
|
||||||
@ -55,14 +54,15 @@ inline void esp_suspend(T&& blocked) {
|
|||||||
// Try to delay until timeout_ms has expired since start_ms.
|
// Try to delay until timeout_ms has expired since start_ms.
|
||||||
// Returns true if timeout_ms has completely expired on entry.
|
// Returns true if timeout_ms has completely expired on entry.
|
||||||
// Otherwise returns false after delaying for the relative
|
// Otherwise returns false after delaying for the relative
|
||||||
// remainder of timeout_ms, or an absolute intvl_ms, whichever is shorter.
|
// remainder of timeout_ms, or an absolute intvl_ms, whichever is shorter
|
||||||
|
// and possibly amended by recurrent scheduled functions timing grain.
|
||||||
// The delay may be asynchronously cancelled, before that timeout is reached.
|
// The delay may be asynchronously cancelled, before that timeout is reached.
|
||||||
bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uint32_t intvl_ms);
|
bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uint32_t intvl_ms);
|
||||||
|
|
||||||
// This overload of esp_delay() delays for a duration of at most timeout_ms milliseconds.
|
// This overload of esp_delay() delays for a duration of at most timeout_ms milliseconds.
|
||||||
// Whenever it is resumed, as well as every intvl_ms millisconds, it performs
|
// Whenever it is resumed, as well as at most every intvl_ms millisconds and depending on
|
||||||
// the blocked callback, and if that returns true, it keeps delaying for the remainder
|
// recurrent scheduled functions, it performs the blocked callback, and if that returns true,
|
||||||
// of the original timeout_ms period.
|
// it keeps delaying for the remainder of the original timeout_ms period.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline void esp_delay(const uint32_t timeout_ms, T&& blocked, const uint32_t intvl_ms) {
|
inline void esp_delay(const uint32_t timeout_ms, T&& blocked, const uint32_t intvl_ms) {
|
||||||
const auto start_ms = millis();
|
const auto start_ms = millis();
|
||||||
@ -79,5 +79,3 @@ inline void esp_delay(const uint32_t timeout_ms, T&& blocked) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
|
||||||
#endif // __COREDECLS_H
|
|
||||||
|
@ -451,9 +451,8 @@ bool ESP8266WiFiGenericClass::mode(WiFiMode_t m) {
|
|||||||
//tasks to wait correctly.
|
//tasks to wait correctly.
|
||||||
constexpr unsigned int timeoutValue = 1000; //1 second
|
constexpr unsigned int timeoutValue = 1000; //1 second
|
||||||
if(can_yield()) {
|
if(can_yield()) {
|
||||||
// The final argument, intvl_ms, to esp_delay influences how frequently
|
// check opmode every 100ms or give up after timeout
|
||||||
// the scheduled recurrent functions (Schedule.h) are probed.
|
esp_delay(timeoutValue, [m]() { return wifi_get_opmode() != m; }, 100);
|
||||||
esp_delay(timeoutValue, [m]() { return wifi_get_opmode() != m; }, 5);
|
|
||||||
|
|
||||||
//if at this point mode still hasn't been reached, give up
|
//if at this point mode still hasn't been reached, give up
|
||||||
if(wifi_get_opmode() != (uint8) m) {
|
if(wifi_get_opmode() != (uint8) m) {
|
||||||
@ -642,11 +641,8 @@ static int hostByNameImpl(const char* aHostname, IPAddress& aResult, uint32_t ti
|
|||||||
// We need to wait for c/b to fire *or* we exit on our own timeout
|
// We need to wait for c/b to fire *or* we exit on our own timeout
|
||||||
// (which also requires us to notify the c/b that it is supposed to delete the pending obj)
|
// (which also requires us to notify the c/b that it is supposed to delete the pending obj)
|
||||||
case ERR_INPROGRESS:
|
case ERR_INPROGRESS:
|
||||||
// Re-check every 10ms, we expect this to happen fast
|
// sleep until dns_found_callback is called or timeout is reached
|
||||||
esp_delay(timeout_ms,
|
esp_delay(timeout_ms, [&]() { return !pending->done; });
|
||||||
[&]() {
|
|
||||||
return !pending->done;
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
if (pending->done) {
|
if (pending->done) {
|
||||||
if ((pending->addr).isSet()) {
|
if ((pending->addr).isSet()) {
|
||||||
|
@ -84,13 +84,13 @@ static void printWiFiStatus(wl_status_t status)
|
|||||||
static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
|
static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
|
||||||
{
|
{
|
||||||
wl_status_t status = WL_CONNECT_FAILED;
|
wl_status_t status = WL_CONNECT_FAILED;
|
||||||
// The final argument, intvl_ms, to esp_delay influences how frequently
|
// Wait for WiFi to connect
|
||||||
// the scheduled recurrent functions (Schedule.h) are probed.
|
// stop waiting upon status checked every 100ms or when timeout is reached
|
||||||
esp_delay(connectTimeoutMs,
|
esp_delay(connectTimeoutMs,
|
||||||
[&status]() {
|
[&status]() {
|
||||||
status = WiFi.status();
|
status = WiFi.status();
|
||||||
return status != WL_CONNECTED && status != WL_CONNECT_FAILED;
|
return status != WL_CONNECTED && status != WL_CONNECT_FAILED;
|
||||||
}, 0);
|
}, 100);
|
||||||
|
|
||||||
// Check status
|
// Check status
|
||||||
if (status == WL_CONNECTED) {
|
if (status == WL_CONNECTED) {
|
||||||
@ -236,13 +236,12 @@ int8_t ESP8266WiFiMulti::startScan()
|
|||||||
WiFi.scanNetworks(true);
|
WiFi.scanNetworks(true);
|
||||||
|
|
||||||
// Wait for WiFi scan change or timeout
|
// Wait for WiFi scan change or timeout
|
||||||
// The final argument, intvl_ms, to esp_delay influences how frequently
|
// stop waiting upon status checked every 100ms or when timeout is reached
|
||||||
// the scheduled recurrent functions (Schedule.h) are probed.
|
|
||||||
esp_delay(WIFI_SCAN_TIMEOUT_MS,
|
esp_delay(WIFI_SCAN_TIMEOUT_MS,
|
||||||
[&scanResult]() {
|
[&scanResult]() {
|
||||||
scanResult = WiFi.scanComplete();
|
scanResult = WiFi.scanComplete();
|
||||||
return scanResult < 0;
|
return scanResult < 0;
|
||||||
}, 0);
|
}, 100);
|
||||||
// Check for scan timeout which may occur when scan does not report completion
|
// Check for scan timeout which may occur when scan does not report completion
|
||||||
if (scanResult < 0) {
|
if (scanResult < 0) {
|
||||||
DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");
|
DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");
|
||||||
|
@ -144,8 +144,7 @@ public:
|
|||||||
_connect_pending = true;
|
_connect_pending = true;
|
||||||
_op_start_time = millis();
|
_op_start_time = millis();
|
||||||
// will resume on timeout or when _connected or _notify_error fires
|
// will resume on timeout or when _connected or _notify_error fires
|
||||||
// give scheduled functions a chance to run (e.g. Ethernet uses recurrent)
|
esp_delay(_timeout_ms, [this]() { return this->_connect_pending; });
|
||||||
esp_delay(_timeout_ms, [this]() { return this->_connect_pending; }, 1);
|
|
||||||
_connect_pending = false;
|
_connect_pending = false;
|
||||||
if (!_pcb) {
|
if (!_pcb) {
|
||||||
DEBUGV(":cabrt\r\n");
|
DEBUGV(":cabrt\r\n");
|
||||||
@ -485,8 +484,7 @@ protected:
|
|||||||
|
|
||||||
_send_waiting = true;
|
_send_waiting = true;
|
||||||
// will resume on timeout or when _write_some_from_cb or _notify_error fires
|
// will resume on timeout or when _write_some_from_cb or _notify_error fires
|
||||||
// give scheduled functions a chance to run (e.g. Ethernet uses recurrent)
|
esp_delay(_timeout_ms, [this]() { return this->_send_waiting; });
|
||||||
esp_delay(_timeout_ms, [this]() { return this->_send_waiting; }, 1);
|
|
||||||
_send_waiting = false;
|
_send_waiting = false;
|
||||||
} while(true);
|
} while(true);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user