Skip to content

Commit

Permalink
delay / esp_delay: transparently manage recurrent scheduled functions (
Browse files Browse the repository at this point in the history
…#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.
  • Loading branch information
d-a-v authored Jan 14, 2023
1 parent e1c4a6c commit 39080e3
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 30 deletions.
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

0 comments on commit 39080e3

Please sign in to comment.