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 all 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
35 changes: 35 additions & 0 deletions cores/esp8266/Schedule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

#include <assert.h>
#include <numeric>

#include "Schedule.h"
#include "PolledTimeout.h"
Expand All @@ -34,6 +35,7 @@ static scheduled_fn_t* sFirst = nullptr;
static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sUnused = nullptr;
static int sCount = 0;
static uint32_t recurrent_max_grain_mS = 0;

typedef std::function<bool(void)> mRecFuncT;
struct recurrent_fn_t
Expand Down Expand Up @@ -130,9 +132,39 @@ 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;
}

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()
{
// prevent scheduling of new functions during this run
Expand Down Expand Up @@ -226,6 +258,9 @@ void run_scheduled_recurrent_functions()
}

delete(to_ditch);

// grain needs to be recomputed
recurrent_max_grain_mS = 0;
}
else
{
Expand Down
5 changes: 5 additions & 0 deletions cores/esp8266/Schedule.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
// scheduled function happen more often: every yield() (vs every loop()),
// 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:
//
// * internal queue is FIFO.
Expand Down
17 changes: 14 additions & 3 deletions cores/esp8266/core_esp8266_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

//This may be used to change user task stack size:
//#define CONT_STACKSIZE 4096

#include <numeric>

#include <Arduino.h>
#include "Schedule.h"
extern "C" {
Expand Down Expand Up @@ -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) {
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;

// 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() {
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
14 changes: 6 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 @@ -79,5 +79,3 @@ inline void esp_delay(const uint32_t timeout_ms, T&& blocked) {
}

#endif // __cplusplus

#endif // __COREDECLS_H
12 changes: 4 additions & 8 deletions libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,8 @@ 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);
// check opmode every 100ms or give up after timeout
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 +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
// (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);
// sleep until dns_found_callback is called or timeout is reached
esp_delay(timeout_ms, [&]() { return !pending->done; });

if (pending->done) {
if ((pending->addr).isSet()) {
Expand Down
11 changes: 5 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.
// Wait for WiFi to connect
// stop waiting upon status checked every 100ms or when timeout is reached
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,12 @@ 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.
// stop waiting upon status checked every 100ms or when timeout is reached
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