1
0
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:
david gauchard 2023-01-14 22:25:57 +01:00 committed by GitHub
parent e1c4a6c8e6
commit 39080e317e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 74 additions and 30 deletions

View File

@ -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
{ {

View File

@ -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.

View File

@ -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() {

View File

@ -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")));

View File

@ -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

View File

@ -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()) {

View File

@ -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");

View File

@ -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);