Skip to content

Commit b551992

Browse files
authored
add regular scheduled functions, now also callable on yield() (#6039)
* add regular scheduled functions, now also callable on `yield()` added bool schedule_function_us(std::function<bool(void)> fn, uint32_t repeat_us) lambda must return true to be not removed from the schedule function list if repeat_us is 0, then the function is called only once. Legacy schedule_function() is preserved This addition allows network drivers like ethernet chips on lwIP to be regularly called - even if some user code loops on receiving data without getting out from main loop (callable from yield()) - without the need to call the driver handling function (transparent) This may be also applicable with common libraries (mDNS, Webserver, )
1 parent 6191fbb commit b551992

13 files changed

+132
-255
lines changed

cores/esp8266/Esp.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ uint32_t EspClass::getCycleCount()
213213
__asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
214214
return ccount;
215215
}
216-
#endif
216+
217+
#endif // !defined(CORE_MOCK)
217218

218219
extern EspClass ESP;
219220

cores/esp8266/FunctionalInterrupt.cpp

-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include <FunctionalInterrupt.h>
22
#include <Schedule.h>
33
#include "Arduino.h"
4-
#include <ScheduledFunctions.h>
54

65
// Duplicate typedefs from core_esp8266_wiring_digital_c
76
typedef void (*voidFuncPtr)(void);
@@ -17,7 +16,6 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg)
1716
if (localArg->functionInfo->reqScheduledFunction)
1817
{
1918
schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))));
20-
// scheduledInterrupts->scheduleFunctionReg(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))), false, true);
2119
}
2220
if (localArg->functionInfo->reqFunction)
2321
{
@@ -54,10 +52,6 @@ void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode
5452

5553
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode)
5654
{
57-
if (!scheduledInterrupts)
58-
{
59-
scheduledInterrupts = new ScheduledFunctions(32);
60-
}
6155
InterruptInfo* ii = new InterruptInfo;
6256

6357
FunctionInfo* fi = new FunctionInfo;

cores/esp8266/FunctionalInterrupt.h

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <stddef.h>
55
#include <stdint.h>
66
#include <functional>
7-
#include <ScheduledFunctions.h>
87

98
extern "C" {
109
#include "c_types.h"
@@ -29,7 +28,6 @@ struct ArgStructure {
2928
FunctionInfo* functionInfo = nullptr;
3029
};
3130

32-
static ScheduledFunctions* scheduledInterrupts;
3331
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);
3432
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode);
3533

cores/esp8266/PolledTimeout.h

+7-5
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class timeoutTemplate
153153
reset(userTimeout);
154154
}
155155

156-
ICACHE_RAM_ATTR
156+
IRAM_ATTR // fast
157157
bool expired()
158158
{
159159
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
@@ -162,7 +162,7 @@ class timeoutTemplate
162162
return expiredOneShot();
163163
}
164164

165-
ICACHE_RAM_ATTR
165+
IRAM_ATTR // fast
166166
operator bool()
167167
{
168168
return expired();
@@ -178,13 +178,15 @@ class timeoutTemplate
178178
return _timeout != alwaysExpired;
179179
}
180180

181+
IRAM_ATTR // called from ISR
181182
void reset(const timeType newUserTimeout)
182183
{
183184
reset();
184185
_timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout);
185186
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
186187
}
187188

189+
IRAM_ATTR // called from ISR
188190
void reset()
189191
{
190192
_start = TimePolicyT::time();
@@ -208,7 +210,7 @@ class timeoutTemplate
208210

209211
private:
210212

211-
ICACHE_RAM_ATTR
213+
IRAM_ATTR // fast
212214
bool checkExpired(const timeType internalUnit) const
213215
{
214216
// canWait() is not checked here
@@ -218,7 +220,7 @@ class timeoutTemplate
218220

219221
protected:
220222

221-
ICACHE_RAM_ATTR
223+
IRAM_ATTR // fast
222224
bool expiredRetrigger()
223225
{
224226
if (!canWait())
@@ -234,7 +236,7 @@ class timeoutTemplate
234236
return false;
235237
}
236238

237-
ICACHE_RAM_ATTR
239+
IRAM_ATTR // fast
238240
bool expiredOneShot() const
239241
{
240242
// returns "always expired" or "has expired"

cores/esp8266/Schedule.cpp

+97-44
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,131 @@
1+
2+
#include <assert.h>
3+
14
#include "Schedule.h"
5+
#include "PolledTimeout.h"
6+
#include "interrupts.h"
7+
8+
typedef std::function<bool(void)> mFuncT;
29

310
struct scheduled_fn_t
411
{
5-
scheduled_fn_t* mNext;
6-
std::function<void(void)> mFunc;
12+
scheduled_fn_t* mNext = nullptr;
13+
mFuncT mFunc;
14+
esp8266::polledTimeout::periodicFastUs callNow;
15+
16+
scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { }
717
};
818

9-
static scheduled_fn_t* sFirst = 0;
10-
static scheduled_fn_t* sLast = 0;
19+
static scheduled_fn_t* sFirst = nullptr;
20+
static scheduled_fn_t* sLast = nullptr;
1121

12-
static scheduled_fn_t* sFirstUnused = 0;
13-
static scheduled_fn_t* sLastUnused = 0;
22+
static scheduled_fn_t* sUnused = nullptr;
1423

1524
static int sCount = 0;
1625

17-
static scheduled_fn_t* get_fn() {
18-
scheduled_fn_t* result = NULL;
26+
IRAM_ATTR // called from ISR
27+
static scheduled_fn_t* get_fn_unsafe()
28+
{
29+
scheduled_fn_t* result = nullptr;
1930
// try to get an item from unused items list
20-
if (sFirstUnused) {
21-
result = sFirstUnused;
22-
sFirstUnused = result->mNext;
23-
if (sFirstUnused == NULL) {
24-
sLastUnused = NULL;
25-
}
31+
if (sUnused)
32+
{
33+
result = sUnused;
34+
sUnused = sUnused->mNext;
35+
result->mNext = nullptr;
2636
}
2737
// if no unused items, and count not too high, allocate a new one
28-
else if (sCount != SCHEDULED_FN_MAX_COUNT) {
38+
else if (sCount < SCHEDULED_FN_MAX_COUNT)
39+
{
2940
result = new scheduled_fn_t;
30-
result->mNext = NULL;
3141
++sCount;
3242
}
3343
return result;
3444
}
3545

36-
static void recycle_fn(scheduled_fn_t* fn)
46+
static void recycle_fn_unsafe(scheduled_fn_t* fn)
3747
{
38-
if (!sLastUnused) {
39-
sFirstUnused = fn;
40-
}
41-
else {
42-
sLastUnused->mNext = fn;
43-
}
44-
fn->mNext = NULL;
45-
sLastUnused = fn;
48+
fn->mFunc = nullptr; // special overload in c++ std lib
49+
fn->mNext = sUnused;
50+
sUnused = fn;
4651
}
4752

48-
bool schedule_function(std::function<void(void)> fn)
53+
IRAM_ATTR // (not only) called from ISR
54+
bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us)
4955
{
50-
scheduled_fn_t* item = get_fn();
51-
if (!item) {
56+
assert(repeat_us < decltype(scheduled_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
57+
58+
InterruptLock lockAllInterruptsInThisScope;
59+
60+
scheduled_fn_t* item = get_fn_unsafe();
61+
if (!item)
5262
return false;
53-
}
63+
64+
if (repeat_us)
65+
item->callNow.reset(repeat_us);
66+
5467
item->mFunc = fn;
55-
item->mNext = NULL;
56-
if (!sFirst) {
57-
sFirst = item;
58-
}
59-
else {
68+
if (sFirst)
6069
sLast->mNext = item;
61-
}
70+
else
71+
sFirst = item;
6272
sLast = item;
73+
6374
return true;
6475
}
6576

77+
IRAM_ATTR // (not only) called from ISR
78+
bool schedule_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us)
79+
{
80+
return schedule_function_us(std::function<bool(void)>(fn), repeat_us);
81+
}
82+
83+
IRAM_ATTR // called from ISR
84+
bool schedule_function(std::function<void(void)>&& fn)
85+
{
86+
return schedule_function_us([fn]() { fn(); return false; }, 0);
87+
}
88+
89+
IRAM_ATTR // called from ISR
90+
bool schedule_function(const std::function<void(void)>& fn)
91+
{
92+
return schedule_function(std::function<void(void)>(fn));
93+
}
94+
6695
void run_scheduled_functions()
6796
{
68-
scheduled_fn_t* rFirst = sFirst;
69-
sFirst = NULL;
70-
sLast = NULL;
71-
while (rFirst) {
72-
scheduled_fn_t* item = rFirst;
73-
rFirst = item->mNext;
74-
item->mFunc();
75-
item->mFunc = std::function<void(void)>();
76-
recycle_fn(item);
97+
// Note to the reader:
98+
// There is no exposed API to remove a scheduled function:
99+
// Scheduled functions are removed only from this function, and
100+
// its purpose is that it is never called from an interrupt
101+
// (always on cont stack).
102+
103+
scheduled_fn_t* lastRecurring = nullptr;
104+
scheduled_fn_t* toCall = sFirst;
105+
while (toCall)
106+
{
107+
scheduled_fn_t* item = toCall;
108+
toCall = toCall->mNext;
109+
if (item->callNow)
110+
{
111+
if (item->mFunc())
112+
{
113+
lastRecurring = item;
114+
}
115+
else
116+
{
117+
InterruptLock lockAllInterruptsInThisScope;
118+
119+
if (sFirst == item)
120+
sFirst = sFirst->mNext;
121+
else if (lastRecurring)
122+
lastRecurring->mNext = item->mNext;
123+
124+
if (sLast == item)
125+
sLast = lastRecurring;
126+
127+
recycle_fn_unsafe(item);
128+
}
129+
}
77130
}
78131
}

cores/esp8266/Schedule.h

+13-7
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@
44
#include <functional>
55

66
#define SCHEDULED_FN_MAX_COUNT 32
7-
#define SCHEDULED_FN_INITIAL_COUNT 4
87

9-
// Warning
10-
// This API is not considered stable.
11-
// Function signatures will change.
8+
// This API was not considered stable but is now stabilizing.
9+
// Function signatures may change, queue must stay FIFO.
1210
// You have been warned.
1311

14-
// Run given function next time `loop` function returns,
12+
// Run given function ONCE next time `loop` function returns,
13+
// or `yield` is called,
1514
// or `run_scheduled_functions` is called.
1615
// Use std::bind to pass arguments to a function, or call a class member function.
1716
// Note: there is no mechanism for cancelling scheduled functions.
1817
// Keep that in mind when binding functions to objects which may have short lifetime.
1918
// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT.
20-
bool schedule_function(std::function<void(void)> fn);
19+
//bool schedule_function(std::function<void(void)>&& fn);
20+
bool schedule_function(const std::function<void(void)>& fn);
2121

22-
// Run all scheduled functions.
22+
// Run given function periodically about every <repeat_us> microseconds until it returns false.
23+
// Note that it may be more than <repeat_us> microseconds between calls if `yield` is not called
24+
// frequently, and therefore should not be used for timing critical operations.
25+
//bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us);
26+
bool schedule_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us);
27+
28+
// Run all scheduled functions.
2329
// Use this function if your are not using `loop`, or `loop` does not return
2430
// on a regular basis.
2531
void run_scheduled_functions();

0 commit comments

Comments
 (0)