Skip to content

Commit 39080e3

Browse files
authored
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.
1 parent e1c4a6c commit 39080e3

File tree

8 files changed

+74
-30
lines changed

8 files changed

+74
-30
lines changed

cores/esp8266/Schedule.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
#include <assert.h>
20+
#include <numeric>
2021

2122
#include "Schedule.h"
2223
#include "PolledTimeout.h"
@@ -34,6 +35,7 @@ static scheduled_fn_t* sFirst = nullptr;
3435
static scheduled_fn_t* sLast = nullptr;
3536
static scheduled_fn_t* sUnused = nullptr;
3637
static int sCount = 0;
38+
static uint32_t recurrent_max_grain_mS = 0;
3739

3840
typedef std::function<bool(void)> mRecFuncT;
3941
struct recurrent_fn_t
@@ -130,9 +132,39 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
130132
}
131133
rLast = item;
132134

135+
// grain needs to be recomputed
136+
recurrent_max_grain_mS = 0;
137+
133138
return true;
134139
}
135140

141+
uint32_t compute_scheduled_recurrent_grain ()
142+
{
143+
if (recurrent_max_grain_mS == 0)
144+
{
145+
if (rFirst)
146+
{
147+
uint32_t recurrent_max_grain_uS = rFirst->callNow.getTimeout();
148+
for (auto it = rFirst->mNext; it; it = it->mNext)
149+
recurrent_max_grain_uS = std::gcd(recurrent_max_grain_uS, it->callNow.getTimeout());
150+
if (recurrent_max_grain_uS)
151+
// round to the upper millis
152+
recurrent_max_grain_mS = recurrent_max_grain_uS <= 1000? 1: (recurrent_max_grain_uS + 999) / 1000;
153+
}
154+
155+
#ifdef DEBUG_ESP_CORE
156+
static uint32_t last_grain = 0;
157+
if (recurrent_max_grain_mS != last_grain)
158+
{
159+
::printf(":rsf %u->%u\n", last_grain, recurrent_max_grain_mS);
160+
last_grain = recurrent_max_grain_mS;
161+
}
162+
#endif
163+
}
164+
165+
return recurrent_max_grain_mS;
166+
}
167+
136168
void run_scheduled_functions()
137169
{
138170
// prevent scheduling of new functions during this run
@@ -226,6 +258,9 @@ void run_scheduled_recurrent_functions()
226258
}
227259

228260
delete(to_ditch);
261+
262+
// grain needs to be recomputed
263+
recurrent_max_grain_mS = 0;
229264
}
230265
else
231266
{

cores/esp8266/Schedule.h

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
// scheduled function happen more often: every yield() (vs every loop()),
4040
// and time resolution is microsecond (vs millisecond). Details are below.
4141

42+
// compute_scheduled_recurrent_grain() is used by delay() to give a chance to
43+
// all recurrent functions to run per their timing requirement.
44+
45+
uint32_t compute_scheduled_recurrent_grain ();
46+
4247
// scheduled functions called once:
4348
//
4449
// * internal queue is FIFO.

cores/esp8266/core_esp8266_main.cpp

+14-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
//This may be used to change user task stack size:
2424
//#define CONT_STACKSIZE 4096
25+
26+
#include <numeric>
27+
2528
#include <Arduino.h>
2629
#include "Schedule.h"
2730
extern "C" {
@@ -165,10 +168,18 @@ extern "C" void esp_delay(unsigned long ms) __attribute__((weak, alias("__esp_de
165168
bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uint32_t intvl_ms) {
166169
uint32_t expired = millis() - start_ms;
167170
if (expired >= timeout_ms) {
168-
return true;
171+
return true; // expired
169172
}
170-
esp_delay(std::min((timeout_ms - expired), intvl_ms));
171-
return false;
173+
174+
// compute greatest chunked delay with respect to scheduled recurrent functions
175+
uint32_t grain_ms = std::gcd(intvl_ms, compute_scheduled_recurrent_grain());
176+
177+
// recurrent scheduled functions will be called from esp_delay()->esp_suspend()
178+
esp_delay(grain_ms > 0 ?
179+
std::min((timeout_ms - expired), grain_ms):
180+
(timeout_ms - expired));
181+
182+
return false; // expiration must be checked again
172183
}
173184

174185
extern "C" void __yield() {

cores/esp8266/core_esp8266_wiring.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ static uint32_t micros_overflow_count = 0;
3434
#define REPEAT 1
3535

3636
void __delay(unsigned long ms) {
37-
esp_delay(ms);
37+
// Use API letting recurrent scheduled functions run in background
38+
// but stay blocked in delay until ms is expired.
39+
esp_delay(ms, [](){ return true; });
3840
}
3941

4042
void delay(unsigned long ms) __attribute__ ((weak, alias("__delay")));

cores/esp8266/coredecls.h

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

2-
#ifndef __COREDECLS_H
3-
#define __COREDECLS_H
2+
#pragma once
43

54
#include "core_esp8266_features.h"
65

@@ -55,14 +54,15 @@ inline void esp_suspend(T&& blocked) {
5554
// Try to delay until timeout_ms has expired since start_ms.
5655
// Returns true if timeout_ms has completely expired on entry.
5756
// Otherwise returns false after delaying for the relative
58-
// remainder of timeout_ms, or an absolute intvl_ms, whichever is shorter.
57+
// remainder of timeout_ms, or an absolute intvl_ms, whichever is shorter
58+
// and possibly amended by recurrent scheduled functions timing grain.
5959
// The delay may be asynchronously cancelled, before that timeout is reached.
6060
bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uint32_t intvl_ms);
6161

6262
// This overload of esp_delay() delays for a duration of at most timeout_ms milliseconds.
63-
// Whenever it is resumed, as well as every intvl_ms millisconds, it performs
64-
// the blocked callback, and if that returns true, it keeps delaying for the remainder
65-
// of the original timeout_ms period.
63+
// Whenever it is resumed, as well as at most every intvl_ms millisconds and depending on
64+
// recurrent scheduled functions, it performs the blocked callback, and if that returns true,
65+
// it keeps delaying for the remainder of the original timeout_ms period.
6666
template <typename T>
6767
inline void esp_delay(const uint32_t timeout_ms, T&& blocked, const uint32_t intvl_ms) {
6868
const auto start_ms = millis();
@@ -79,5 +79,3 @@ inline void esp_delay(const uint32_t timeout_ms, T&& blocked) {
7979
}
8080

8181
#endif // __cplusplus
82-
83-
#endif // __COREDECLS_H

libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp

+4-8
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,8 @@ bool ESP8266WiFiGenericClass::mode(WiFiMode_t m) {
451451
//tasks to wait correctly.
452452
constexpr unsigned int timeoutValue = 1000; //1 second
453453
if(can_yield()) {
454-
// The final argument, intvl_ms, to esp_delay influences how frequently
455-
// the scheduled recurrent functions (Schedule.h) are probed.
456-
esp_delay(timeoutValue, [m]() { return wifi_get_opmode() != m; }, 5);
454+
// check opmode every 100ms or give up after timeout
455+
esp_delay(timeoutValue, [m]() { return wifi_get_opmode() != m; }, 100);
457456

458457
//if at this point mode still hasn't been reached, give up
459458
if(wifi_get_opmode() != (uint8) m) {
@@ -642,11 +641,8 @@ static int hostByNameImpl(const char* aHostname, IPAddress& aResult, uint32_t ti
642641
// We need to wait for c/b to fire *or* we exit on our own timeout
643642
// (which also requires us to notify the c/b that it is supposed to delete the pending obj)
644643
case ERR_INPROGRESS:
645-
// Re-check every 10ms, we expect this to happen fast
646-
esp_delay(timeout_ms,
647-
[&]() {
648-
return !pending->done;
649-
}, 10);
644+
// sleep until dns_found_callback is called or timeout is reached
645+
esp_delay(timeout_ms, [&]() { return !pending->done; });
650646

651647
if (pending->done) {
652648
if ((pending->addr).isSet()) {

libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp

+5-6
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ static void printWiFiStatus(wl_status_t status)
8484
static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
8585
{
8686
wl_status_t status = WL_CONNECT_FAILED;
87-
// The final argument, intvl_ms, to esp_delay influences how frequently
88-
// the scheduled recurrent functions (Schedule.h) are probed.
87+
// Wait for WiFi to connect
88+
// stop waiting upon status checked every 100ms or when timeout is reached
8989
esp_delay(connectTimeoutMs,
9090
[&status]() {
9191
status = WiFi.status();
9292
return status != WL_CONNECTED && status != WL_CONNECT_FAILED;
93-
}, 0);
93+
}, 100);
9494

9595
// Check status
9696
if (status == WL_CONNECTED) {
@@ -236,13 +236,12 @@ int8_t ESP8266WiFiMulti::startScan()
236236
WiFi.scanNetworks(true);
237237

238238
// Wait for WiFi scan change or timeout
239-
// The final argument, intvl_ms, to esp_delay influences how frequently
240-
// the scheduled recurrent functions (Schedule.h) are probed.
239+
// stop waiting upon status checked every 100ms or when timeout is reached
241240
esp_delay(WIFI_SCAN_TIMEOUT_MS,
242241
[&scanResult]() {
243242
scanResult = WiFi.scanComplete();
244243
return scanResult < 0;
245-
}, 0);
244+
}, 100);
246245
// Check for scan timeout which may occur when scan does not report completion
247246
if (scanResult < 0) {
248247
DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");

libraries/ESP8266WiFi/src/include/ClientContext.h

+2-4
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,7 @@ class ClientContext
144144
_connect_pending = true;
145145
_op_start_time = millis();
146146
// will resume on timeout or when _connected or _notify_error fires
147-
// give scheduled functions a chance to run (e.g. Ethernet uses recurrent)
148-
esp_delay(_timeout_ms, [this]() { return this->_connect_pending; }, 1);
147+
esp_delay(_timeout_ms, [this]() { return this->_connect_pending; });
149148
_connect_pending = false;
150149
if (!_pcb) {
151150
DEBUGV(":cabrt\r\n");
@@ -485,8 +484,7 @@ class ClientContext
485484

486485
_send_waiting = true;
487486
// will resume on timeout or when _write_some_from_cb or _notify_error fires
488-
// give scheduled functions a chance to run (e.g. Ethernet uses recurrent)
489-
esp_delay(_timeout_ms, [this]() { return this->_send_waiting; }, 1);
487+
esp_delay(_timeout_ms, [this]() { return this->_send_waiting; });
490488
_send_waiting = false;
491489
} while(true);
492490

0 commit comments

Comments
 (0)