1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-06-07 16:23:38 +03:00

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, )
This commit is contained in:
david gauchard 2019-05-23 22:03:53 +02:00 committed by GitHub
parent 6191fbbd92
commit b55199227b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 132 additions and 255 deletions

View File

@ -213,7 +213,8 @@ uint32_t EspClass::getCycleCount()
__asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount)); __asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
return ccount; return ccount;
} }
#endif
#endif // !defined(CORE_MOCK)
extern EspClass ESP; extern EspClass ESP;

View File

@ -1,7 +1,6 @@
#include <FunctionalInterrupt.h> #include <FunctionalInterrupt.h>
#include <Schedule.h> #include <Schedule.h>
#include "Arduino.h" #include "Arduino.h"
#include <ScheduledFunctions.h>
// Duplicate typedefs from core_esp8266_wiring_digital_c // Duplicate typedefs from core_esp8266_wiring_digital_c
typedef void (*voidFuncPtr)(void); typedef void (*voidFuncPtr)(void);
@ -17,7 +16,6 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg)
if (localArg->functionInfo->reqScheduledFunction) if (localArg->functionInfo->reqScheduledFunction)
{ {
schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo)))); schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))));
// scheduledInterrupts->scheduleFunctionReg(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))), false, true);
} }
if (localArg->functionInfo->reqFunction) if (localArg->functionInfo->reqFunction)
{ {
@ -54,10 +52,6 @@ void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode) void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode)
{ {
if (!scheduledInterrupts)
{
scheduledInterrupts = new ScheduledFunctions(32);
}
InterruptInfo* ii = new InterruptInfo; InterruptInfo* ii = new InterruptInfo;
FunctionInfo* fi = new FunctionInfo; FunctionInfo* fi = new FunctionInfo;

View File

@ -4,7 +4,6 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <functional> #include <functional>
#include <ScheduledFunctions.h>
extern "C" { extern "C" {
#include "c_types.h" #include "c_types.h"
@ -29,7 +28,6 @@ struct ArgStructure {
FunctionInfo* functionInfo = nullptr; FunctionInfo* functionInfo = nullptr;
}; };
static ScheduledFunctions* scheduledInterrupts;
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode); void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode); void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode);

View File

@ -153,7 +153,7 @@ public:
reset(userTimeout); reset(userTimeout);
} }
ICACHE_RAM_ATTR IRAM_ATTR // fast
bool expired() bool expired()
{ {
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
@ -162,7 +162,7 @@ public:
return expiredOneShot(); return expiredOneShot();
} }
ICACHE_RAM_ATTR IRAM_ATTR // fast
operator bool() operator bool()
{ {
return expired(); return expired();
@ -178,6 +178,7 @@ public:
return _timeout != alwaysExpired; return _timeout != alwaysExpired;
} }
IRAM_ATTR // called from ISR
void reset(const timeType newUserTimeout) void reset(const timeType newUserTimeout)
{ {
reset(); reset();
@ -185,6 +186,7 @@ public:
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax()); _neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
} }
IRAM_ATTR // called from ISR
void reset() void reset()
{ {
_start = TimePolicyT::time(); _start = TimePolicyT::time();
@ -208,7 +210,7 @@ public:
private: private:
ICACHE_RAM_ATTR IRAM_ATTR // fast
bool checkExpired(const timeType internalUnit) const bool checkExpired(const timeType internalUnit) const
{ {
// canWait() is not checked here // canWait() is not checked here
@ -218,7 +220,7 @@ private:
protected: protected:
ICACHE_RAM_ATTR IRAM_ATTR // fast
bool expiredRetrigger() bool expiredRetrigger()
{ {
if (!canWait()) if (!canWait())
@ -234,7 +236,7 @@ protected:
return false; return false;
} }
ICACHE_RAM_ATTR IRAM_ATTR // fast
bool expiredOneShot() const bool expiredOneShot() const
{ {
// returns "always expired" or "has expired" // returns "always expired" or "has expired"

View File

@ -1,78 +1,131 @@
#include <assert.h>
#include "Schedule.h" #include "Schedule.h"
#include "PolledTimeout.h"
#include "interrupts.h"
typedef std::function<bool(void)> mFuncT;
struct scheduled_fn_t struct scheduled_fn_t
{ {
scheduled_fn_t* mNext; scheduled_fn_t* mNext = nullptr;
std::function<void(void)> mFunc; mFuncT mFunc;
esp8266::polledTimeout::periodicFastUs callNow;
scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { }
}; };
static scheduled_fn_t* sFirst = 0; static scheduled_fn_t* sFirst = nullptr;
static scheduled_fn_t* sLast = 0; static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sFirstUnused = 0; static scheduled_fn_t* sUnused = nullptr;
static scheduled_fn_t* sLastUnused = 0;
static int sCount = 0; static int sCount = 0;
static scheduled_fn_t* get_fn() { IRAM_ATTR // called from ISR
scheduled_fn_t* result = NULL; static scheduled_fn_t* get_fn_unsafe()
{
scheduled_fn_t* result = nullptr;
// try to get an item from unused items list // try to get an item from unused items list
if (sFirstUnused) { if (sUnused)
result = sFirstUnused; {
sFirstUnused = result->mNext; result = sUnused;
if (sFirstUnused == NULL) { sUnused = sUnused->mNext;
sLastUnused = NULL; result->mNext = nullptr;
}
} }
// if no unused items, and count not too high, allocate a new one // if no unused items, and count not too high, allocate a new one
else if (sCount != SCHEDULED_FN_MAX_COUNT) { else if (sCount < SCHEDULED_FN_MAX_COUNT)
{
result = new scheduled_fn_t; result = new scheduled_fn_t;
result->mNext = NULL;
++sCount; ++sCount;
} }
return result; return result;
} }
static void recycle_fn(scheduled_fn_t* fn) static void recycle_fn_unsafe(scheduled_fn_t* fn)
{ {
if (!sLastUnused) { fn->mFunc = nullptr; // special overload in c++ std lib
sFirstUnused = fn; fn->mNext = sUnused;
} sUnused = fn;
else {
sLastUnused->mNext = fn;
}
fn->mNext = NULL;
sLastUnused = fn;
} }
bool schedule_function(std::function<void(void)> fn) IRAM_ATTR // (not only) called from ISR
bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us)
{ {
scheduled_fn_t* item = get_fn(); assert(repeat_us < decltype(scheduled_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
if (!item) {
InterruptLock lockAllInterruptsInThisScope;
scheduled_fn_t* item = get_fn_unsafe();
if (!item)
return false; return false;
}
if (repeat_us)
item->callNow.reset(repeat_us);
item->mFunc = fn; item->mFunc = fn;
item->mNext = NULL; if (sFirst)
if (!sFirst) {
sFirst = item;
}
else {
sLast->mNext = item; sLast->mNext = item;
} else
sFirst = item;
sLast = item; sLast = item;
return true; return true;
} }
IRAM_ATTR // (not only) called from ISR
bool schedule_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us)
{
return schedule_function_us(std::function<bool(void)>(fn), repeat_us);
}
IRAM_ATTR // called from ISR
bool schedule_function(std::function<void(void)>&& fn)
{
return schedule_function_us([fn]() { fn(); return false; }, 0);
}
IRAM_ATTR // called from ISR
bool schedule_function(const std::function<void(void)>& fn)
{
return schedule_function(std::function<void(void)>(fn));
}
void run_scheduled_functions() void run_scheduled_functions()
{ {
scheduled_fn_t* rFirst = sFirst; // Note to the reader:
sFirst = NULL; // There is no exposed API to remove a scheduled function:
sLast = NULL; // Scheduled functions are removed only from this function, and
while (rFirst) { // its purpose is that it is never called from an interrupt
scheduled_fn_t* item = rFirst; // (always on cont stack).
rFirst = item->mNext;
item->mFunc(); scheduled_fn_t* lastRecurring = nullptr;
item->mFunc = std::function<void(void)>(); scheduled_fn_t* toCall = sFirst;
recycle_fn(item); while (toCall)
{
scheduled_fn_t* item = toCall;
toCall = toCall->mNext;
if (item->callNow)
{
if (item->mFunc())
{
lastRecurring = item;
}
else
{
InterruptLock lockAllInterruptsInThisScope;
if (sFirst == item)
sFirst = sFirst->mNext;
else if (lastRecurring)
lastRecurring->mNext = item->mNext;
if (sLast == item)
sLast = lastRecurring;
recycle_fn_unsafe(item);
}
}
} }
} }

View File

@ -4,20 +4,26 @@
#include <functional> #include <functional>
#define SCHEDULED_FN_MAX_COUNT 32 #define SCHEDULED_FN_MAX_COUNT 32
#define SCHEDULED_FN_INITIAL_COUNT 4
// Warning // This API was not considered stable but is now stabilizing.
// This API is not considered stable. // Function signatures may change, queue must stay FIFO.
// Function signatures will change.
// You have been warned. // You have been warned.
// Run given function next time `loop` function returns, // Run given function ONCE next time `loop` function returns,
// or `yield` is called,
// or `run_scheduled_functions` is called. // or `run_scheduled_functions` is called.
// Use std::bind to pass arguments to a function, or call a class member function. // Use std::bind to pass arguments to a function, or call a class member function.
// Note: there is no mechanism for cancelling scheduled functions. // Note: there is no mechanism for cancelling scheduled functions.
// Keep that in mind when binding functions to objects which may have short lifetime. // Keep that in mind when binding functions to objects which may have short lifetime.
// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. // Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT.
bool schedule_function(std::function<void(void)> fn); //bool schedule_function(std::function<void(void)>&& fn);
bool schedule_function(const std::function<void(void)>& fn);
// Run given function periodically about every <repeat_us> microseconds until it returns false.
// Note that it may be more than <repeat_us> microseconds between calls if `yield` is not called
// frequently, and therefore should not be used for timing critical operations.
//bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us);
bool schedule_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us);
// Run all scheduled functions. // Run all scheduled functions.
// Use this function if your are not using `loop`, or `loop` does not return // Use this function if your are not using `loop`, or `loop` does not return

View File

@ -1,117 +0,0 @@
/*
* ScheduledFunctions.cpp
*
* Created on: 27 apr. 2018
* Author: Herman
*/
#include "ScheduledFunctions.h"
std::list<ScheduledFunctions::ScheduledElement> ScheduledFunctions::scheduledFunctions;
ScheduledFunctions::ScheduledFunctions()
:ScheduledFunctions(UINT_MAX)
{
}
ScheduledFunctions::ScheduledFunctions(unsigned int reqMax)
{
maxElements = reqMax;
}
ScheduledFunctions::~ScheduledFunctions() {
}
ScheduledRegistration ScheduledFunctions::insertElement(ScheduledElement se, bool front)
{
if (countElements >= maxElements)
{
return nullptr;
}
else
{
countElements++;
if (front)
{
scheduledFunctions.push_front(se);
return scheduledFunctions.begin()->registration;
}
else
{
scheduledFunctions.push_back(se);
return scheduledFunctions.rbegin()->registration;
}
}
}
std::list<ScheduledFunctions::ScheduledElement>::iterator ScheduledFunctions::eraseElement(std::list<ScheduledFunctions::ScheduledElement>::iterator it)
{
countElements--;
return scheduledFunctions.erase(it);
}
bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf, bool continuous, bool front)
{
return (insertElement({this,continuous,nullptr,sf}, front) == nullptr);
}
bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf)
{
return scheduleFunction(sf, false, false);
}
ScheduledRegistration ScheduledFunctions::scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front)
{
return insertElement({this,continuous,std::make_shared<int>(1),sf},front);
}
void ScheduledFunctions::runScheduledFunctions()
{
auto lastElement = scheduledFunctions.end(); // do not execute elements added during runScheduledFunctions
auto it = scheduledFunctions.begin();
while (it != lastElement)
{
bool erase = false;
if (it->registration == nullptr)
{
it->function();
}
else
{
if (it->registration.use_count() > 1)
{
it->function();
}
else
{
erase = true;
}
}
if ((!it->continuous) || (erase))
{
it = it->_this->eraseElement(it);
}
else
{
it++;
}
}
}
void ScheduledFunctions::removeFunction(ScheduledRegistration sr)
{
auto it = scheduledFunctions.begin();
bool removed = false;
while ((!removed) && (it != scheduledFunctions.end()))
{
if (it->registration == sr)
{
it = eraseElement(it);
removed = true;
}
else
{
it++;
}
}
}

View File

@ -1,51 +0,0 @@
/*
* ScheduledFunctions.h
*
* Created on: 27 apr. 2018
* Author: Herman
*/
#include "Arduino.h"
#include "Schedule.h"
#include <functional>
#include <memory>
#include <list>
#include <climits>
#ifndef SCHEDULEDFUNCTIONS_H_
#define SCHEDULEDFUNCTIONS_H_
typedef std::function<void(void)> ScheduledFunction;
typedef std::shared_ptr<void> ScheduledRegistration;
class ScheduledFunctions {
public:
ScheduledFunctions();
ScheduledFunctions(unsigned int reqMax);
virtual ~ScheduledFunctions();
struct ScheduledElement
{
ScheduledFunctions* _this;
bool continuous;
ScheduledRegistration registration;
ScheduledFunction function;
};
ScheduledRegistration insertElement(ScheduledElement se, bool front);
std::list<ScheduledElement>::iterator eraseElement(std::list<ScheduledElement>::iterator);
bool scheduleFunction(ScheduledFunction sf, bool continuous, bool front);
bool scheduleFunction(ScheduledFunction sf);
ScheduledRegistration scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front);
static void runScheduledFunctions();
void removeFunction(ScheduledRegistration sr);
static std::list<ScheduledElement> scheduledFunctions;
unsigned int maxElements;
unsigned int countElements = 0;
};
#endif /* SCHEDULEDFUNCTIONS_H_ */

View File

@ -91,13 +91,15 @@ extern "C" void esp_yield() {
} }
extern "C" void esp_schedule() { extern "C" void esp_schedule() {
// always on CONT stack here
run_scheduled_functions();
ets_post(LOOP_TASK_PRIORITY, 0, 0); ets_post(LOOP_TASK_PRIORITY, 0, 0);
} }
extern "C" void __yield() { extern "C" void __yield() {
if (cont_can_yield(g_pcont)) { if (cont_can_yield(g_pcont)) {
esp_schedule(); esp_schedule();
esp_yield(); cont_yield(g_pcont); //esp_yield();
} }
else { else {
panic(); panic();
@ -122,7 +124,6 @@ static void loop_wrapper() {
setup_done = true; setup_done = true;
} }
loop(); loop();
run_scheduled_functions();
esp_schedule(); esp_schedule();
} }

View File

@ -143,26 +143,12 @@ extern "C" {
#define __STRINGIFY(a) #a #define __STRINGIFY(a) #a
#endif #endif
// these low level routines provide a replacement for SREG interrupt save that AVR uses #define xt_rsil(level) (level)
// but are esp8266 specific. A normal use pattern is like #define xt_wsr_ps(state) do { (void)(state); } while (0)
//
//{
// uint32_t savedPS = xt_rsil(1); // this routine will allow level 2 and above
// // do work here
// xt_wsr_ps(savedPS); // restore the state
//}
//
// level (0-15), interrupts of the given level and above will be active
// level 15 will disable ALL interrupts,
// level 0 will enable ALL interrupts,
//
#define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)); state;}))
#define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory")
#define interrupts() xt_rsil(0) #define interrupts() xt_rsil(0)
#define noInterrupts() xt_rsil(15) #define noInterrupts() xt_rsil(15)
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L ) #define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() ) #define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() ) #define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

View File

@ -102,6 +102,9 @@ typedef enum {
#define ICACHE_RODATA_ATTR #define ICACHE_RODATA_ATTR
#endif /* ICACHE_FLASH */ #endif /* ICACHE_FLASH */
// counterpart https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp8266-compat.h
#define IRAM_ATTR ICACHE_RAM_ATTR
#define STORE_ATTR __attribute__((aligned(4))) #define STORE_ATTR __attribute__((aligned(4)))
#ifndef __cplusplus #ifndef __cplusplus

View File

@ -131,8 +131,6 @@ void mockUDPSwallow (size_t copied, char* ccinbuf, size_t& ccinbufsize);
class UdpContext; class UdpContext;
void register_udp (int sock, UdpContext* udp = nullptr); void register_udp (int sock, UdpContext* udp = nullptr);
class InterruptLock { };
// //
void mock_start_spiffs (const String& fname, size_t size_kb, size_t block_kb = 8, size_t page_b = 512); void mock_start_spiffs (const String& fname, size_t size_kb, size_t block_kb = 8, size_t page_b = 512);

View File

@ -93,6 +93,9 @@ typedef enum {
#define ICACHE_RAM_ATTR #define ICACHE_RAM_ATTR
#endif /* ICACHE_FLASH */ #endif /* ICACHE_FLASH */
// counterpart https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp8266-compat.h
#define IRAM_ATTR ICACHE_RAM_ATTR
#define STORE_ATTR __attribute__((aligned(4))) #define STORE_ATTR __attribute__((aligned(4)))
#ifndef __cplusplus #ifndef __cplusplus