diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index 6ec94500db..5d4b8ff974 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include "Arduino.h" #include "HardwareSerial.h" #include "Esp.h" @@ -139,9 +138,9 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size) while (got < size) { - esp8266::polledTimeout::oneShotFastMs timeOut(_timeout); + unsigned long startMillis = millis(); size_t avail; - while ((avail = available()) == 0 && !timeOut); + while ((avail = available()) == 0 && (millis() - startMillis) < _timeout); if (avail == 0) break; got += read(buffer + got, std::min(size - got, avail)); diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 588019f0dd..48050209d3 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -23,7 +23,6 @@ //This may be used to change user task stack size: //#define CONT_STACKSIZE 4096 #include -#include "Schedule.h" extern "C" { #include "ets_sys.h" #include "os_type.h" @@ -40,7 +39,6 @@ extern "C" { #define OPTIMISTIC_YIELD_TIME_US 16000 extern "C" void call_user_start(); -extern void loop(); extern void setup(); extern void (*__init_array_start)(void); extern void (*__init_array_end)(void); @@ -83,19 +81,15 @@ void preloop_update_frequency() { #endif } - -static inline void esp_yield_within_cont() __attribute__((always_inline)); -static void esp_yield_within_cont() { - cont_yield(g_pcont); - run_scheduled_recurrent_functions(); -} - -extern "C" void esp_yield() { +extern "C" void __esp_yield() __attribute__((weak)); +extern "C" void __esp_yield() { if (cont_can_yield(g_pcont)) { - esp_yield_within_cont(); + cont_yield(g_pcont); } } +extern "C" void esp_yield(void) __attribute__ ((weak, alias("__esp_yield"))); + extern "C" void esp_schedule() { // always on CONT stack here ets_post(LOOP_TASK_PRIORITY, 0, 0); @@ -104,7 +98,7 @@ extern "C" void esp_schedule() { extern "C" void __yield() { if (cont_can_yield(g_pcont)) { esp_schedule(); - esp_yield_within_cont(); + esp_yield(); } else { panic(); @@ -121,14 +115,11 @@ extern "C" void optimistic_yield(uint32_t interval_us) { } } -extern "C" void __loop_end (void) -{ - run_scheduled_functions(); - run_scheduled_recurrent_functions(); +extern "C" void esp_loop(void) __attribute__((weak)); +extern "C" void esp_loop(void) { + loop(); } -extern "C" void loop_end (void) __attribute__ ((weak, alias("__loop_end"))); - static void loop_wrapper() { static bool setup_done = false; preloop_update_frequency(); @@ -136,8 +127,7 @@ static void loop_wrapper() { setup(); setup_done = true; } - loop(); - loop_end(); + esp_loop(); esp_schedule(); } diff --git a/cores/esp8266/core_esp8266_wiring_digital.cpp b/cores/esp8266/core_esp8266_wiring_digital.cpp index 5ed0a40ab7..21568879e0 100644 --- a/cores/esp8266/core_esp8266_wiring_digital.cpp +++ b/cores/esp8266/core_esp8266_wiring_digital.cpp @@ -115,16 +115,21 @@ typedef struct { bool functional; } interrupt_handler_t; -//duplicate from functionalInterrupt.h keep in sync +//duplicates from functionalInterrupt.h keep in sync typedef struct InterruptInfo { uint8_t pin; uint8_t value; uint32_t micro; } InterruptInfo; -typedef struct { +typedef struct FunctionInfo { + std::function reqFunction; + std::function reqScheduledFunction; +} FunctionInfo; + +typedef struct ArgStructure { InterruptInfo* interruptInfo; - void* functionInfo; + FunctionInfo* functionInfo; } ArgStructure; static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, }; @@ -172,7 +177,13 @@ void ICACHE_RAM_ATTR interrupt_handler(void*) ETS_GPIO_INTR_ENABLE(); } -extern void cleanupFunctional(void* arg); +static void cleanupFunctional(void* arg) +{ + ArgStructure* localArg = (ArgStructure*)arg; + delete localArg->interruptInfo; + delete localArg->functionInfo; + delete localArg; +} static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional) { diff --git a/libraries/ESP8266mDNS/src/LEAmDNS.cpp b/libraries/ESP8266mDNS/src/LEAmDNS.cpp index 07e385a530..cb2c77cca8 100644 --- a/libraries/ESP8266mDNS/src/LEAmDNS.cpp +++ b/libraries/ESP8266mDNS/src/LEAmDNS.cpp @@ -22,7 +22,7 @@ * */ -#include +#include #include "LEAmDNS_Priv.h" diff --git a/libraries/FastScheduler/library.properties b/libraries/FastScheduler/library.properties new file mode 100644 index 0000000000..6b07ae5564 --- /dev/null +++ b/libraries/FastScheduler/library.properties @@ -0,0 +1,10 @@ +name=FastScheduler +version=1.0 +author=Dirk O. Kaar +maintainer=Dirk O. Kaar +sentence= +paragraph= +category=Other +url=https://github.com/esp8266/Arduino +architectures=esp8266 +dot_a_linkage=true diff --git a/libraries/FastScheduler/src/FastScheduler.cpp b/libraries/FastScheduler/src/FastScheduler.cpp new file mode 100644 index 0000000000..aa1e598139 --- /dev/null +++ b/libraries/FastScheduler/src/FastScheduler.cpp @@ -0,0 +1,198 @@ +#include "FastScheduler.h" +#include +#include +#ifdef ESP8266 +#include +#include +#else +#include +#endif +#include "circular_queue/circular_queue_mp.h" + +extern "C" +{ + void esp_loop() + { + loop(); + run_scheduled_functions(SCHEDULE_FUNCTION_FROM_LOOP); + } + +#ifdef ESP8266 + void __esp_yield(); + + extern "C" void esp_yield() + { + __esp_yield(); + run_scheduled_functions(SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS); + } +#endif +} + +typedef std::function mFuncT; + +struct scheduled_fn_t +{ + mFuncT mFunc = nullptr; + esp8266::polledTimeout::periodicFastUs callNow; + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP; + scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { } +}; + +// anonymous namespace provides compilation-unit internal linkage +namespace { + static circular_queue_mp schedule_queue(SCHEDULED_FN_MAX_COUNT); + static esp8266::polledTimeout::periodicFastMs yieldSchedulerNow(100); // yield every 100ms + static schedule_e activePolicy; + + class FastSchedulerTicker; + // A local heap for Ticker instances to prevent global heap exhaustion + class TickerHeap : public circular_queue_mp { + public: + TickerHeap(const size_t capacity) : circular_queue_mp(capacity) + { + heap = new char[capacity * sizeof(Ticker)]; + for (size_t i = 0; i < capacity; ++i) push(reinterpret_cast(heap + i * sizeof(Ticker))); + } + ~TickerHeap() + { + delete heap; + } + protected: + char* heap; + }; + static TickerHeap tickerHeap(SCHEDULED_FN_MAX_COUNT); + + class FastSchedulerTicker : public Ticker + { + public: + static void operator delete(void* ptr) { + tickerHeap.push(static_cast(ptr)); + } + }; + static_assert(sizeof(FastSchedulerTicker) == sizeof(Ticker), "sizeof(FastSchedulerTicker) != sizeof(Ticker)"); + + void ticker_scheduled(FastSchedulerTicker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) + { +#ifdef ESP8266 + auto repeat_ms = (repeat_us + 500) / 1000; + ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() +#else + ticker->once_us(repeat_us, [ticker, fn, repeat_us, policy]() +#endif + { + if (!schedule_function([ticker, fn, repeat_us, policy]() + { + if (fn()) ticker_scheduled(ticker, fn, repeat_us, policy); + else delete ticker; + return false; + }, policy)) + { + ticker_scheduled(ticker, fn, repeat_us, policy); + } + }); + } + +#ifdef ESP8266 + constexpr uint32_t TICKER_MIN_US = 5000; +#else + constexpr uint32_t TICKER_MIN_US = 5000; +#endif +}; + +bool IRAM_ATTR schedule_recurrent_function_us(std::function&& fn, uint32_t repeat_us, schedule_e policy) +{ + if (repeat_us >= TICKER_MIN_US) + { + // failure to aquire a Ticker must be returned to caller now. Specifically, allocating + // Tickers from inside the scheduled function doesn't work, any exhaustion of the scheduler + // can dead-lock it forever. + auto tickerPlace = tickerHeap.pop(); + if (!tickerPlace) return false; + auto ticker = new(tickerPlace) FastSchedulerTicker; + if (!schedule_function([ticker, fn, repeat_us, policy]() + { + ticker_scheduled(ticker, std::move(fn), repeat_us, policy); + return false; + }, SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS)) + { + delete ticker; + return false; + } + return true; + } + else + { + scheduled_fn_t item; + item.mFunc = std::move(fn); + if (repeat_us) item.callNow.reset(repeat_us); + item.policy = policy; + return schedule_queue.push(std::move(item)); + } +} + +bool IRAM_ATTR schedule_recurrent_function_us(const std::function& fn, uint32_t repeat_us, schedule_e policy) +{ + return schedule_recurrent_function_us(std::function(fn), repeat_us, policy); +} + +bool IRAM_ATTR schedule_function(std::function&& fn, schedule_e policy) +{ + return schedule_recurrent_function_us([fn]() { fn(); return false; }, 0, policy); +} + +bool IRAM_ATTR schedule_function(const std::function& fn, schedule_e policy) +{ + return schedule_function(std::function(fn), policy); +} + +bool run_function(scheduled_fn_t& func) +{ + if (yieldSchedulerNow) { +#if defined(ESP8266) + esp_schedule(); + cont_yield(g_pcont); +#elif defined(ESP32) + vPortYield(); +#else + yield(); +#endif + } + return + (func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && activePolicy != SCHEDULE_FUNCTION_FROM_LOOP) + || !func.callNow + || func.mFunc(); +} + +void run_scheduled_functions(schedule_e policy) +{ + // Note to the reader: + // There is no exposed API to remove a scheduled function: + // Scheduled functions are removed only from this function, and + // its purpose is that it is never called from an interrupt + // (always on cont stack). + + static std::atomic fence(false); +#ifdef ESP8266 + { + esp8266::InterruptLock lockAllInterruptsInThisScope; + if (fence.load()) { + // prevent any recursive calls from yield() + return; + } + fence.store(true); + } +#else + if (fence.exchange(true)) return; +#endif + + yieldSchedulerNow.reset(100); + activePolicy = policy; + + // run scheduled function: + // - when its schedule policy allows it anytime + // - or if we are called at loop() time + // and + // - its time policy allows it + schedule_queue.for_each_rev_requeue(run_function); + fence.store(false); +} diff --git a/libraries/FastScheduler/src/FastScheduler.h b/libraries/FastScheduler/src/FastScheduler.h new file mode 100644 index 0000000000..4ef3a97b2e --- /dev/null +++ b/libraries/FastScheduler/src/FastScheduler.h @@ -0,0 +1,73 @@ +#ifndef ESP_FASTSCHEDULER_H +#define ESP_FASTSCHEDULER_H + +// This API is stabilizing +// Function signatures may change, internal queue will remain FIFO. +// +// * Add the given lambda to a fifo list of lambdas, which is run when +// - `loop` function returns, +// - or `yield` is called, +// - or `run_scheduled_functions` is called. +// +// * Use lambdas to pass arguments to a function, or call a class/static +// member function. +// +// * Please ensure variables or instances used from inside lambda will exist +// when lambda is later called +// +// * There is no mechanism for cancelling scheduled functions. +// +// * `yield` can be called from inside lambdas +// +// * Returns false if the number of scheduled functions exceeds +// SCHEDULED_FN_MAX_COUNT. + +#ifdef __cplusplus +#include +extern "C" { +#endif + +#define SCHEDULED_FN_MAX_COUNT 32 + + typedef enum schedule_e_t + { + SCHEDULE_FUNCTION_FROM_LOOP, + SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS + } schedule_e; + +#ifdef __cplusplus +} + +// * Run the lambda only once next time +bool schedule_function(std::function&& fn, + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); +bool schedule_function(const std::function& fn, + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); + +// * Run the lambda periodically about every microseconds until +// it returns false. +// * Note that it may be more than microseconds between calls if +// `yield` is not called frequently, and therefore should not be used for +// timing critical operations. +bool schedule_recurrent_function_us(std::function&& fn, + uint32_t repeat_us, + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); +bool schedule_recurrent_function_us(const std::function& fn, + uint32_t repeat_us, + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); + +extern "C" { +#endif /* __cplusplus */ + + // Run all scheduled functions. + // Use this function if your are not using `loop`, or `loop` does not return + // on a regular basis. + +#ifndef __cplusplus + void run_scheduled_functions(schedule_e policy); +#else + void run_scheduled_functions(schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); +} +#endif + +#endif //ESP_FASTSCHEDULER_H diff --git a/libraries/FastScheduler/src/circular_queue/circular_queue.h b/libraries/FastScheduler/src/circular_queue/circular_queue.h new file mode 100644 index 0000000000..130877a264 --- /dev/null +++ b/libraries/FastScheduler/src/circular_queue/circular_queue.h @@ -0,0 +1,367 @@ +/* +circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __circular_queue_h +#define __circular_queue_h + +#ifdef ARDUINO +#include +#endif +#include +#include +#include +#include + +#if !defined(ESP32) && !defined(ESP8266) +#define ICACHE_RAM_ATTR +#define IRAM_ATTR +#endif + +using std::min; + +/*! + @brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producer and consumer for the available(), peek(), + pop(), and push() type functions. +*/ +template< typename T > +class circular_queue +{ +public: + /*! + @brief Constructs a valid, but zero-capacity dummy queue. + */ + circular_queue() : m_bufSize(1) + { + m_inPos.store(0); + m_outPos.store(0); + } + /*! + @brief Constructs a queue of the given maximum capacity. + */ + circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize]) + { + m_inPos.store(0); + m_outPos.store(0); + } + circular_queue(circular_queue&& cq) : + m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load()) + {} + ~circular_queue() + { + m_buffer.reset(); + } + circular_queue(const circular_queue&) = delete; + circular_queue& operator=(circular_queue&& cq) + { + m_bufSize = cq.m_bufSize; + m_buffer = cq.m_buffer; + m_inPos.store(cq.m_inPos.load()); + m_outPos.store(cq.m_outPos.load()); + } + circular_queue& operator=(const circular_queue&) = delete; + + /*! + @brief Get the numer of elements the queue can hold at most. + */ + size_t capacity() const + { + return m_bufSize - 1; + } + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free and concurrent producer or consumer access + will lead to corruption. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap); + + /*! + @brief Discard all data in the queue. + */ + void flush() + { + m_outPos.store(m_inPos.load()); + } + + /*! + @brief Get a snapshot number of elements that can be retrieved by pop. + */ + size_t available() const + { + int avail = static_cast(m_inPos.load() - m_outPos.load()); + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Get the remaining free elementes for pushing. + */ + size_t available_for_push() const + { + int avail = static_cast(m_outPos.load() - m_inPos.load()) - 1; + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Peek at the next element pop will return without removing it from the queue. + @return An rvalue copy of the next element that can be popped. If the queue is empty, + return an rvalue copy of the element that is pending the next push. + */ + T peek() const + { + const auto outPos = m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[outPos]; + } + + /*! + @brief Peek at the next pending input value. + @return A reference to the next element that can be pushed. + */ + T& IRAM_ATTR pushpeek() + { + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[inPos]; + } + + /*! + @brief Release the next pending input value, accessible by pushpeek(), into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(); + + /*! + @brief Move the rvalue parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val); + + /*! + @brief Push a copy of the parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { + return push(T(val)); + } + + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size); + + /*! + @brief Pop the next available element from the queue. + @return An rvalue copy of the popped element, or a default + value of type T if the queue is empty. + */ + T pop(); + + /*! + @brief Pop multiple elements in ordered sequence from the queue to a buffer. + If buffer is nullptr, simply discards up to size elements from the queue. + @return The number of elements actually popped from the queue to + buffer. + */ + size_t pop_n(T* buffer, size_t size); + + /*! + @brief Iterate over and remove each available element from queue, + calling back fun with an rvalue reference of every single element. + */ + void for_each(const std::function& fun); + + /*! + @brief In reverse order, iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ + bool for_each_rev_requeue(const std::function& fun); + +protected: + const T defaultValue = {}; + unsigned m_bufSize; + std::unique_ptr m_buffer; + std::atomic m_inPos; + std::atomic m_outPos; +}; + +template< typename T > +bool circular_queue::capacity(const size_t cap) +{ + if (cap + 1 == m_bufSize) return true; + else if (available() > cap) return false; + std::unique_ptr buffer(new T[cap + 1]); + const auto available = pop_n(buffer, cap); + m_buffer.reset(buffer); + m_bufSize = cap + 1; + std::atomic_thread_fence(std::memory_order_release); + m_inPos.store(available, std::memory_order_relaxed); + m_outPos.store(0, std::memory_order_release); + return true; +} + +template< typename T > +bool IRAM_ATTR circular_queue::push() +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const unsigned next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_inPos.store(next, std::memory_order_release); + return true; +} + +template< typename T > +bool IRAM_ATTR circular_queue::push(T&& val) +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const unsigned next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_buffer[inPos] = std::move(val); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return true; +} + +template< typename T > +size_t circular_queue::push_n(const T* buffer, size_t size) +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const auto outPos = m_outPos.load(std::memory_order_relaxed); + + size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos; + blockSize = min(size, blockSize); + if (!blockSize) return 0; + int next = (inPos + blockSize) % m_bufSize; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto dest = m_buffer.get() + inPos; + std::copy_n(std::make_move_iterator(buffer), blockSize, dest); + size = min(size - blockSize, outPos > 1 ? static_cast(outPos - next - 1) : 0); + next += size; + dest = m_buffer.get(); + std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return blockSize + size; +} + +template< typename T > +T circular_queue::pop() +{ + const auto outPos = m_outPos.load(std::memory_order_acquire); + if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto val = std::move(m_buffer[outPos]); + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release); + return val; +} + +template< typename T > +size_t circular_queue::pop_n(T* buffer, size_t size) { + size_t avail = size = min(size, available()); + if (!avail) return 0; + const auto outPos = m_outPos.load(std::memory_order_acquire); + size_t n = min(avail, static_cast(m_bufSize - outPos)); + + std::atomic_thread_fence(std::memory_order_acquire); + + if (buffer) { + buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer); + avail -= n; + std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer); + } + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release); + return size; +} + +template< typename T > +void circular_queue::for_each(const std::function& fun) +{ + auto outPos = m_outPos.load(std::memory_order_acquire); + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + while (outPos != inPos) + { + fun(std::move(m_buffer[outPos])); + std::atomic_thread_fence(std::memory_order_release); + outPos = (outPos + 1) % m_bufSize; + m_outPos.store(outPos, std::memory_order_release); + } +} + +template< typename T > +bool circular_queue::for_each_rev_requeue(const std::function& fun) +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + auto pos = inPos0; + auto outPos1 = inPos0; + const auto posDecr = circular_queue::m_bufSize - 1; + do { + pos = (pos + posDecr) % circular_queue::m_bufSize; + T&& val = std::move(circular_queue::m_buffer[pos]); + if (fun(val)) + { + outPos1 = (outPos1 + posDecr) % circular_queue::m_bufSize; + if (outPos1 != pos) circular_queue::m_buffer[outPos1] = std::move(val); + } + } while (pos != outPos); + circular_queue::m_outPos.store(outPos1, std::memory_order_release); + return true; +} + +#endif // __circular_queue_h diff --git a/libraries/FastScheduler/src/circular_queue/circular_queue_mp.h b/libraries/FastScheduler/src/circular_queue/circular_queue_mp.h new file mode 100644 index 0000000000..1f8d301899 --- /dev/null +++ b/libraries/FastScheduler/src/circular_queue/circular_queue_mp.h @@ -0,0 +1,200 @@ +/* +circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __circular_queue_mp_h +#define __circular_queue_mp_h + +#include "circular_queue.h" + +#ifdef ESP8266 +#include "interrupts.h" +#else +#include +#endif + +/*! + @brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producers and consumer for the available(), peek(), + pop(), and push() type functions, but is guarded to safely allow only a single producer + at any instant. +*/ +template< typename T > +class circular_queue_mp : protected circular_queue +{ +public: + circular_queue_mp() = default; + circular_queue_mp(const size_t capacity) : circular_queue(capacity) + {} + circular_queue_mp(circular_queue&& cq) : circular_queue(std::move(cq)) + {} + using circular_queue::operator=; + using circular_queue::capacity; + using circular_queue::flush; + using circular_queue::available; + using circular_queue::available_for_push; + using circular_queue::peek; + using circular_queue::pop; + using circular_queue::pop_n; + using circular_queue::for_each; + using circular_queue::for_each_rev_requeue; + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free, but safe, concurrent producer or consumer access + is guarded. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::capacity(cap); + } + + bool IRAM_ATTR push() = delete; + + /*! + @brief Move the rvalue parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(std::move(val)); + } + + /*! + @brief Push a copy of the parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(val); + } + + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. This is guarded for + multiple producers, push_n() is atomic. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push_n(buffer, size); + } + + /*! + @brief Pops the next available element from the queue, requeues + it immediately. + @return A reference to the just requeued element, or the default + value of type T if the queue is empty. + */ + T& pop_requeue(); + + /*! + @brief Iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ + bool for_each_requeue(const std::function& fun); + +#ifndef ESP8266 +protected: + std::mutex m_pushMtx; +#endif +}; + +template< typename T > +T& circular_queue_mp::pop_requeue() +{ +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + const auto outPos = circular_queue::m_outPos.load(std::memory_order_acquire); + const auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (inPos == outPos) return circular_queue::defaultValue; + T& val = circular_queue::m_buffer[inPos] = std::move(circular_queue::m_buffer[outPos]); + const auto bufSize = circular_queue::m_bufSize; + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_outPos.store((outPos + 1) % bufSize, std::memory_order_relaxed); + circular_queue::m_inPos.store((inPos + 1) % bufSize, std::memory_order_release); + return val; +} + +template< typename T > +bool circular_queue_mp::for_each_requeue(const std::function& fun) +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + do { + T&& val = std::move(circular_queue::m_buffer[outPos]); + if (fun(val)) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + std::atomic_thread_fence(std::memory_order_release); + auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + circular_queue::m_buffer[inPos] = std::move(val); + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_inPos.store((inPos + 1) % circular_queue::m_bufSize, std::memory_order_release); + } + else + { + std::atomic_thread_fence(std::memory_order_release); + } + outPos = (outPos + 1) % circular_queue::m_bufSize; + circular_queue::m_outPos.store(outPos, std::memory_order_release); + } while (outPos != inPos0); + return true; +} + +#endif // __circular_queue_mp_h diff --git a/libraries/PolledTimeout/library.properties b/libraries/PolledTimeout/library.properties new file mode 100644 index 0000000000..02e13294cc --- /dev/null +++ b/libraries/PolledTimeout/library.properties @@ -0,0 +1,9 @@ +name=PolledTimeout +version=1.0 +author= +maintainer= +sentence= +paragraph= +category=Timing +url= +architectures=esp8266,esp32 diff --git a/cores/esp8266/PolledTimeout.h b/libraries/PolledTimeout/src/PolledTimeout.h similarity index 100% rename from cores/esp8266/PolledTimeout.h rename to libraries/PolledTimeout/src/PolledTimeout.h diff --git a/libraries/Schedule/library.properties b/libraries/Schedule/library.properties new file mode 100644 index 0000000000..b9153597be --- /dev/null +++ b/libraries/Schedule/library.properties @@ -0,0 +1,10 @@ +name=Schedule +version=1.0 +author=Ivan Grokhotkov (ivan@esp8266.com) +maintainer=Ivan Grokhotkov (ivan@esp8266.com) +sentence= +paragraph= +category=Other +url=https://github.com/esp8266/Arduino +architectures=esp8266 +dot_a_linkage=true diff --git a/cores/esp8266/Schedule.cpp b/libraries/Schedule/src/Schedule.cpp similarity index 92% rename from cores/esp8266/Schedule.cpp rename to libraries/Schedule/src/Schedule.cpp index ed39736e55..6be96d4816 100644 --- a/cores/esp8266/Schedule.cpp +++ b/libraries/Schedule/src/Schedule.cpp @@ -2,9 +2,27 @@ #include #include "Schedule.h" -#include "PolledTimeout.h" -#include "interrupts.h" -#include "coredecls.h" +#include +#include +#include + +extern "C" +{ + void esp_loop() + { + loop(); + run_scheduled_functions(); + run_scheduled_recurrent_functions(); + } + + void __esp_yield(); + + extern "C" void esp_yield() + { + __esp_yield(); + run_scheduled_recurrent_functions(); + } +} typedef std::function mSchedFuncT; struct scheduled_fn_t diff --git a/cores/esp8266/Schedule.h b/libraries/Schedule/src/Schedule.h similarity index 100% rename from cores/esp8266/Schedule.h rename to libraries/Schedule/src/Schedule.h diff --git a/libraries/ScheduledInterrupts/examples/Functional/Functional.ino b/libraries/ScheduledInterrupts/examples/Functional/Functional.ino new file mode 100644 index 0000000000..0461ef2879 --- /dev/null +++ b/libraries/ScheduledInterrupts/examples/Functional/Functional.ino @@ -0,0 +1,69 @@ +#include + +#ifndef IRAM_ATTR +#define IRAM_ATTR ICACHE_RAM_ATTR +#endif + +#if defined(ESP32) +#define BUTTON1 16 +#define BUTTON2 17 +#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI) +#define BUTTON1 D4 +#define BUTTON2 D3 +#else +#define BUTTON1 2 +#define BUTTON2 0 +#endif + +class Button { + public: + Button(const uint8_t reqPin) : _PIN(reqPin) { + pinMode(_PIN, INPUT_PULLUP); + attachInterrupt(_PIN, std::bind(&Button::buttonIsr, this), FALLING); + }; + ~Button() { + detachInterrupt(_PIN); + } + + void IRAM_ATTR buttonIsr() { + _numberKeyPresses += 1; + _pressed = true; + } + + uint32_t testResetPressed() { + if (_pressed) { + Serial.printf("Button on pin %u has been pressed %u times\n", _PIN, _numberKeyPresses); + _pressed = false; + } + return _numberKeyPresses; + } + + private: + const uint8_t _PIN; + volatile uint32_t _numberKeyPresses = 0; + volatile bool _pressed = false; +}; + +// Pointers and "new" in setup() are used in this example to simply test +// and demonstrate how an ISR object can be constructed and destructed at runtime, +// including the detach of the ISR from the GPIO. +Button* button1 = nullptr; +Button* button2 = nullptr; + +void setup() { + Serial.begin(115200); + Serial.println("ScheduledInterrupts test/example"); + + button1 = new Button(BUTTON1); + button2 = new Button(BUTTON2); + + Serial.println("setup() complete"); +} + +void loop() { + button1->testResetPressed(); + if (nullptr != button2 && 10 < button2->testResetPressed()) { + delete button2; + button2 = nullptr; + } +} diff --git a/libraries/ScheduledInterrupts/examples/ScheduledFunctional/ScheduledFunctional.ino b/libraries/ScheduledInterrupts/examples/ScheduledFunctional/ScheduledFunctional.ino new file mode 100644 index 0000000000..6d21a01c1e --- /dev/null +++ b/libraries/ScheduledInterrupts/examples/ScheduledFunctional/ScheduledFunctional.ino @@ -0,0 +1,74 @@ +#include + +#ifndef IRAM_ATTR +#define IRAM_ATTR ICACHE_RAM_ATTR +#endif + +#if defined(ESP32) +#define BUTTON1 16 +#define BUTTON2 17 +#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI) +#define BUTTON1 D4 +#define BUTTON2 D3 +#else +#define BUTTON1 2 +#define BUTTON2 0 +#endif + +class Button { + public: + Button(const uint8_t reqPin) : _PIN(reqPin) { + pinMode(_PIN, INPUT_PULLUP); + attachScheduledInterrupt(_PIN, [this](const InterruptInfo & ii) { + Serial.print("Pin "); + Serial.println(ii.pin); + buttonIsr(); + }, FALLING); // works on ESP8266 + }; + ~Button() { + detachInterrupt(_PIN); + } + + void IRAM_ATTR buttonIsr() { + _numberKeyPresses += 1; + _pressed = true; + } + + uint32_t testResetPressed() { + if (_pressed) { + Serial.printf("Button on pin %u has been pressed %u times\n", _PIN, _numberKeyPresses); + _pressed = false; + } + return _numberKeyPresses; + } + + private: + const uint8_t _PIN; + volatile uint32_t _numberKeyPresses = 0; + volatile bool _pressed = false; +}; + +// Pointers and "new" in setup() are used in this example to simply test +// and demonstrate how an ISR object can be constructed and destructed at runtime, +// including the detach of the ISR from the GPIO. +Button* button1; +Button* button2; + + +void setup() { + Serial.begin(115200); + Serial.println("ScheduledInterrupts test/example"); + + button1 = new Button(BUTTON1); + button2 = new Button(BUTTON2); + + Serial.println("setup() complete"); +} + +void loop() { + button1->testResetPressed(); + if (nullptr != button2 && 10 < button2->testResetPressed()) { + delete button2; + button2 = nullptr; + } +} diff --git a/libraries/ScheduledInterrupts/keywords.txt b/libraries/ScheduledInterrupts/keywords.txt new file mode 100644 index 0000000000..5e6571998c --- /dev/null +++ b/libraries/ScheduledInterrupts/keywords.txt @@ -0,0 +1,12 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### + +InterruptInfo KEYWORD1 +ArgStructure KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +attachScheduledInterrupt KEYWORD2 diff --git a/libraries/ScheduledInterrupts/library.properties b/libraries/ScheduledInterrupts/library.properties new file mode 100644 index 0000000000..e47c0faa4a --- /dev/null +++ b/libraries/ScheduledInterrupts/library.properties @@ -0,0 +1,10 @@ +name=ScheduledInterrupts +version=1.0 +author=hreintke +maintainer=hreintke +sentence=C++ functional, scheduled interrupt handling +paragraph= +category=Other +url=https://github.com/esp8266/Arduino +architectures=esp8266 +dot_a_linkage=true diff --git a/cores/esp8266/FunctionalInterrupt.cpp b/libraries/ScheduledInterrupts/src/ScheduledInterrupts.cpp similarity index 83% rename from cores/esp8266/FunctionalInterrupt.cpp rename to libraries/ScheduledInterrupts/src/ScheduledInterrupts.cpp index 665d8043b3..6c3c0b6c98 100644 --- a/cores/esp8266/FunctionalInterrupt.cpp +++ b/libraries/ScheduledInterrupts/src/ScheduledInterrupts.cpp @@ -1,6 +1,6 @@ -#include -#include -#include "Arduino.h" +#include "ScheduledInterrupts.h" +#include +#include // Duplicate typedefs from core_esp8266_wiring_digital_c typedef void (*voidFuncPtr)(void); @@ -23,17 +23,6 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg) } } -extern "C" -{ - void cleanupFunctional(void* arg) - { - ArgStructure* localArg = (ArgStructure*)arg; - delete (FunctionInfo*)localArg->functionInfo; - delete (InterruptInfo*)localArg->interruptInfo; - delete localArg; - } -} - void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) { // use the local interrupt routine which takes the ArgStructure as argument diff --git a/cores/esp8266/FunctionalInterrupt.h b/libraries/ScheduledInterrupts/src/ScheduledInterrupts.h similarity index 88% rename from cores/esp8266/FunctionalInterrupt.h rename to libraries/ScheduledInterrupts/src/ScheduledInterrupts.h index 968793e499..c192fd212e 100644 --- a/cores/esp8266/FunctionalInterrupt.h +++ b/libraries/ScheduledInterrupts/src/ScheduledInterrupts.h @@ -1,5 +1,5 @@ -#ifndef FUNCTIONALINTERRUPT_H -#define FUNCTIONALINTERRUPT_H +#ifndef SCHEDULEDINTERRUPTS_H +#define SCHEDULEDINTERRUPTS_H #include #include @@ -31,5 +31,4 @@ struct ArgStructure { void attachInterrupt(uint8_t pin, std::function intRoutine, int mode); void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode); - -#endif //INTERRUPTS_H +#endif // SCHEDULEDINTERRUPTS_H diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index 4abc14f429..343702048b 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit 4abc14f4295f3d2dd296f535c48740339edc6d4d +Subproject commit 343702048b1dca715b30ad6e189805c055d01d69 diff --git a/libraries/Ticker/examples/Blinker/Blinker.ino b/libraries/Ticker/examples/Blinker/Blinker.ino new file mode 100644 index 0000000000..ce5a18a316 --- /dev/null +++ b/libraries/Ticker/examples/Blinker/Blinker.ino @@ -0,0 +1,41 @@ +#include +#include + +// attach a LED to pPIO 21 +#define LED_PIN 21 + +Ticker blinker; +Ticker toggler; +Ticker changer; +float blinkerPace = 0.1; //seconds +const float togglePeriod = 5; //seconds + +void change() { + blinkerPace = 0.5; +} + +void blink() { + digitalWrite(LED_PIN, !digitalRead(LED_PIN)); +} + +void toggle() { + static bool isBlinking = false; + if (isBlinking) { + blinker.detach(); + isBlinking = false; + } else { + blinker.attach(blinkerPace, blink); + isBlinking = true; + } + digitalWrite(LED_PIN, LOW); //make sure LED on on after toggling (pin LOW = led ON) +} + +void setup() { + pinMode(LED_PIN, OUTPUT); + toggler.attach(togglePeriod, toggle); + changer.once(30, change); +} + +void loop() { + +} diff --git a/libraries/Ticker/examples/TickerBasic/TickerBasic.ino b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino index 2fabd98315..a387b554b7 100644 --- a/libraries/Ticker/examples/TickerBasic/TickerBasic.ino +++ b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino @@ -14,6 +14,10 @@ #include +#ifndef LED_BUILTIN +#define LED_BUILTIN 13 +#endif + Ticker flipper; int count = 0; diff --git a/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino index 33c9435982..387e6d6bcb 100644 --- a/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino +++ b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino @@ -1,5 +1,5 @@ -#include "Arduino.h" -#include "Ticker.h" +#include +#include #define LED1 2 #define LED2 4 diff --git a/libraries/Ticker/examples/TickerParameter/TickerParameter.ino b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino index 80734a1a6f..9267c95772 100644 --- a/libraries/Ticker/examples/TickerParameter/TickerParameter.ino +++ b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino @@ -13,6 +13,10 @@ #include +#ifndef LED_BUILTIN +#define LED_BUILTIN 13 +#endif + Ticker tickerSetHigh; Ticker tickerSetAnalog; Ticker tickerSetLow; diff --git a/libraries/Ticker/keywords.txt b/libraries/Ticker/keywords.txt index 1ecd8d0eda..b1020c4e59 100644 --- a/libraries/Ticker/keywords.txt +++ b/libraries/Ticker/keywords.txt @@ -1,29 +1,21 @@ -####################################### -# Syntax Coloring Map For Wire -####################################### - ####################################### # Datatypes (KEYWORD1) ####################################### +Ticker KEYWORD1 + ####################################### # Methods and Functions (KEYWORD2) ####################################### +attach_scheduled KEYWORD2 attach KEYWORD2 +attach_ms_scheduled KEYWORD2 attach_ms KEYWORD2 +once_scheduled KEYWORD2 once KEYWORD2 +once_ms_scheduled KEYWORD2 once_ms KEYWORD2 detach KEYWORD2 active KEYWORD2 -####################################### -# Instances (KEYWORD2) -####################################### - -Ticker KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### - diff --git a/libraries/Ticker/Ticker.cpp b/libraries/Ticker/src/Ticker.cpp similarity index 100% rename from libraries/Ticker/Ticker.cpp rename to libraries/Ticker/src/Ticker.cpp diff --git a/libraries/Ticker/Ticker.h b/libraries/Ticker/src/Ticker.h similarity index 99% rename from libraries/Ticker/Ticker.h rename to libraries/Ticker/src/Ticker.h index 074e5ba32c..6800ebab9b 100644 --- a/libraries/Ticker/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -23,7 +23,7 @@ #define TICKER_H #include -#include +#include #include class Ticker diff --git a/tests/device/test_schedule/test_schedule.ino b/tests/device/test_schedule/test_schedule.ino index 26c7d114db..61b91e68c1 100644 --- a/tests/device/test_schedule/test_schedule.ino +++ b/tests/device/test_schedule/test_schedule.ino @@ -1,5 +1,5 @@ #include -#include +#include BS_ENV_DECLARE(); diff --git a/tests/host/Makefile b/tests/host/Makefile index 3768c11b5e..0b9148a2e2 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -67,7 +67,8 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\ spiffs/spiffs_nucleus.cpp \ libb64/cencode.cpp \ libb64/cdecode.cpp \ - Schedule.cpp \ + ../../libraries/Ticker/src/Ticker.cpp \ + ../../libraries/FastScheduler/src/FastScheduler.cpp \ HardwareSerial.cpp \ ) \ $(addprefix $(LIBRARIES_PATH)/ESP8266SdFat/src/, \ diff --git a/tests/host/common/Arduino.cpp b/tests/host/common/Arduino.cpp index 5192028e73..93aa923264 100644 --- a/tests/host/common/Arduino.cpp +++ b/tests/host/common/Arduino.cpp @@ -42,7 +42,7 @@ extern "C" void optimistic_yield (uint32_t interval_us) usleep(interval_us); } -extern "C" void esp_yield() +extern "C" void __esp_yield() { }