Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

delay / esp_delay: transparently manage recurrent scheduled functions #8802

Merged
merged 10 commits into from
Jan 14, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions cores/esp8266/Schedule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sUnused = nullptr;
static int sCount = 0;

uint32_t recurrent_max_grain_mS = 0;
d-a-v marked this conversation as resolved.
Show resolved Hide resolved

typedef std::function<bool(void)> mRecFuncT;
struct recurrent_fn_t
{
Expand Down Expand Up @@ -130,9 +132,37 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
}
rLast = item;

// grain needs to be recomputed
recurrent_max_grain_mS = 0;

return true;
}

void update_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 = compute_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
}
}

void run_scheduled_functions()
{
// prevent scheduling of new functions during this run
Expand Down Expand Up @@ -226,6 +256,9 @@ void run_scheduled_recurrent_functions()
}

delete(to_ditch);

// grain needs to be recomputed
recurrent_max_grain_mS = 0;
}
else
{
Expand Down
7 changes: 7 additions & 0 deletions cores/esp8266/Schedule.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
// scheduled function happen more often: every yield() (vs every loop()),
// and time resolution is microsecond (vs millisecond). Details are below.

// recurrent_max_grain_mS is used by delay() to let a chance to all
// recurrent functions to accomplish their duty per their timing
// requirement.
// When it is 0, update_recurrent_grain() can be called to recalculate it.
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
extern uint32_t recurrent_max_grain_mS;
void update_recurrent_grain ();
d-a-v marked this conversation as resolved.
Show resolved Hide resolved

// scheduled functions called once:
//
// * internal queue is FIFO.
Expand Down
15 changes: 12 additions & 3 deletions cores/esp8266/core_esp8266_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,19 @@ 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) {
uint32_t expired = millis() - start_ms;
if (expired >= timeout_ms) {
return true;
return true; // expired
}
esp_delay(std::min((timeout_ms - expired), intvl_ms));
return false;

// possibly recompute recurrent scheduled functions common timing grain
update_recurrent_grain();

// update intvl_ms according to this grain
uint32_t ms = compute_gcd(intvl_ms, recurrent_max_grain_mS);

// recurrent scheduled functions will be called from esp_delay()->esp_suspend()
esp_delay(ms? std::min((timeout_ms - expired), ms): (timeout_ms - expired));
d-a-v marked this conversation as resolved.
Show resolved Hide resolved

return false; // expiration must be checked again
}

extern "C" void __yield() {
Expand Down
4 changes: 3 additions & 1 deletion cores/esp8266/core_esp8266_wiring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ static uint32_t micros_overflow_count = 0;
#define REPEAT 1

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")));
Expand Down
28 changes: 20 additions & 8 deletions cores/esp8266/coredecls.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

#ifndef __COREDECLS_H
#define __COREDECLS_H
#pragma once

#include "core_esp8266_features.h"

Expand Down Expand Up @@ -55,14 +54,15 @@ inline void esp_suspend(T&& blocked) {
// Try to delay until timeout_ms has expired since start_ms.
// Returns true if timeout_ms has completely expired on entry.
// 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.
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.
// Whenever it is resumed, as well as every intvl_ms millisconds, it performs
// the blocked callback, and if that returns true, it keeps delaying for the remainder
// of the original timeout_ms period.
// Whenever it is resumed, as well as at most every intvl_ms millisconds and depending on
// recurrent scheduled functions, it performs the blocked callback, and if that returns true,
// it keeps delaying for the remainder of the original timeout_ms period.
template <typename T>
inline void esp_delay(const uint32_t timeout_ms, T&& blocked, const uint32_t intvl_ms) {
const auto start_ms = millis();
Expand All @@ -78,6 +78,18 @@ inline void esp_delay(const uint32_t timeout_ms, T&& blocked) {
esp_delay(timeout_ms, std::forward<T>(blocked), timeout_ms);
}

#endif // __cplusplus
// Greatest Common Divisor Euclidian algorithm
// one entry may be 0, the other is returned
template <typename T>
auto compute_gcd (T a, T b)
{
while (b)
{
auto t = b;
b = a % b;
a = t;
}
return a;
}
d-a-v marked this conversation as resolved.
Show resolved Hide resolved

#endif // __COREDECLS_H
#endif // __cplusplus
13 changes: 5 additions & 8 deletions libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,9 @@ bool ESP8266WiFiGenericClass::mode(WiFiMode_t m) {
//tasks to wait correctly.
constexpr unsigned int timeoutValue = 1000; //1 second
if(can_yield()) {
// The final argument, intvl_ms, to esp_delay influences how frequently
// the scheduled recurrent functions (Schedule.h) are probed.
esp_delay(timeoutValue, [m]() { return wifi_get_opmode() != m; }, 5);
// The final argument, intvl_ms, to esp_delay determines at least
// how frequently wifi_get_opmode() is checked
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
esp_delay(timeoutValue, [m]() { return wifi_get_opmode() != m; }, 100);

//if at this point mode still hasn't been reached, give up
if(wifi_get_opmode() != (uint8) m) {
Expand Down Expand Up @@ -642,11 +642,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
// (which also requires us to notify the c/b that it is supposed to delete the pending obj)
case ERR_INPROGRESS:
// Re-check every 10ms, we expect this to happen fast
esp_delay(timeout_ms,
[&]() {
return !pending->done;
}, 10);
// esp_delay will be interrupted by found callback
mcspr marked this conversation as resolved.
Show resolved Hide resolved
esp_delay(timeout_ms, [&]() { return !pending->done; });

if (pending->done) {
if ((pending->addr).isSet()) {
Expand Down
12 changes: 6 additions & 6 deletions libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ static void printWiFiStatus(wl_status_t status)
static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
{
wl_status_t status = WL_CONNECT_FAILED;
// The final argument, intvl_ms, to esp_delay influences how frequently
// the scheduled recurrent functions (Schedule.h) are probed.
// The final argument, intvl_ms, to esp_delay determines
// the max ms interval at which status is checked
esp_delay(connectTimeoutMs,
[&status]() {
status = WiFi.status();
return status != WL_CONNECTED && status != WL_CONNECT_FAILED;
}, 0);
}, 100);

// Check status
if (status == WL_CONNECTED) {
Expand Down Expand Up @@ -236,13 +236,13 @@ int8_t ESP8266WiFiMulti::startScan()
WiFi.scanNetworks(true);

// Wait for WiFi scan change or timeout
// The final argument, intvl_ms, to esp_delay influences how frequently
// the scheduled recurrent functions (Schedule.h) are probed.
// The final argument, intvl_ms, to esp_delay determines
// the max ms interval at which status is checked
esp_delay(WIFI_SCAN_TIMEOUT_MS,
[&scanResult]() {
scanResult = WiFi.scanComplete();
return scanResult < 0;
}, 0);
}, 100);
// Check for scan timeout which may occur when scan does not report completion
if (scanResult < 0) {
DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");
Expand Down
6 changes: 2 additions & 4 deletions libraries/ESP8266WiFi/src/include/ClientContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ class ClientContext
_connect_pending = true;
_op_start_time = millis();
// 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; }, 1);
esp_delay(_timeout_ms, [this]() { return this->_connect_pending; });
_connect_pending = false;
if (!_pcb) {
DEBUGV(":cabrt\r\n");
Expand Down Expand Up @@ -485,8 +484,7 @@ class ClientContext

_send_waiting = true;
// 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; }, 1);
esp_delay(_timeout_ms, [this]() { return this->_send_waiting; });
_send_waiting = false;
} while(true);

Expand Down