diff --git a/CMakeLists.txt b/CMakeLists.txt index 32979ad4acf..bf53fe91080 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,6 @@ set(CORE_SRCS cores/esp32/esp32-hal-uart.c cores/esp32/esp32-hal-rmt.c cores/esp32/Esp.cpp - cores/esp32/FunctionalInterrupt.cpp cores/esp32/HardwareSerial.cpp cores/esp32/IPAddress.cpp cores/esp32/IPv6Address.cpp @@ -47,10 +46,12 @@ set(LIBRARY_SRCS libraries/FFat/src/FFat.cpp libraries/FS/src/FS.cpp libraries/FS/src/vfs_api.cpp + libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp libraries/HTTPClient/src/HTTPClient.cpp libraries/HTTPUpdate/src/HTTPUpdate.cpp libraries/NetBIOS/src/NetBIOS.cpp libraries/Preferences/src/Preferences.cpp + libraries/Schedule/src/Schedule.cpp libraries/SD_MMC/src/SD_MMC.cpp libraries/SD/src/SD.cpp libraries/SD/src/sd_diskio.cpp @@ -186,10 +187,13 @@ set(COMPONENT_ADD_INCLUDEDIRS libraries/ESPmDNS/src libraries/FFat/src libraries/FS/src + libraries/FunctionalInterrupt/src libraries/HTTPClient/src libraries/HTTPUpdate/src libraries/NetBIOS/src + libraries/PolledTimeout/src libraries/Preferences/src + libraries/Schedule/src libraries/SD_MMC/src libraries/SD/src libraries/SimpleBLE/src diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h index 645b407034f..bc7ab85b779 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -130,6 +130,9 @@ void init(void); void initVariant(void); void initArduino(void); +void yield_completed(void); +void loop_completed(void); + unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout); unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout); diff --git a/cores/esp32/FunctionalInterrupt.cpp b/cores/esp32/FunctionalInterrupt.cpp deleted file mode 100644 index d2e6dfd4236..00000000000 --- a/cores/esp32/FunctionalInterrupt.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * FunctionalInterrupt.cpp - * - * Created on: 8 jul. 2018 - * Author: Herman - */ - -#include "FunctionalInterrupt.h" -#include "Arduino.h" - -typedef void (*voidFuncPtr)(void); -typedef void (*voidFuncPtrArg)(void*); - -extern "C" -{ - extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional); -} - -void IRAM_ATTR interruptFunctional(void* arg) -{ - InterruptArgStructure* localArg = (InterruptArgStructure*)arg; - if (localArg->interruptFunction) - { - localArg->interruptFunction(); - } -} - -void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) -{ - // use the local interrupt routine which takes the ArgStructure as argument - __attachInterruptFunctionalArg (pin, (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true); -} - -extern "C" -{ - void cleanupFunctional(void* arg) - { - delete (InterruptArgStructure*)arg; - } -} - - - - diff --git a/cores/esp32/FunctionalInterrupt.h b/cores/esp32/FunctionalInterrupt.h deleted file mode 100644 index b5e3181f986..00000000000 --- a/cores/esp32/FunctionalInterrupt.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * FunctionalInterrupt.h - * - * Created on: 8 jul. 2018 - * Author: Herman - */ - -#ifndef CORE_CORE_FUNCTIONALINTERRUPT_H_ -#define CORE_CORE_FUNCTIONALINTERRUPT_H_ - -#include - -struct InterruptArgStructure { - std::function interruptFunction; -}; - -void attachInterrupt(uint8_t pin, std::function intRoutine, int mode); - - -#endif /* CORE_CORE_FUNCTIONALINTERRUPT_H_ */ diff --git a/cores/esp32/esp32-hal-gpio.c b/cores/esp32/esp32-hal-gpio.c index d22af774c81..82dbf818638 100644 --- a/cores/esp32/esp32-hal-gpio.c +++ b/cores/esp32/esp32-hal-gpio.c @@ -74,9 +74,8 @@ typedef void (*voidFuncPtrArg)(void*); typedef struct { voidFuncPtr fn; void* arg; - bool functional; } InterruptHandle_t; -static InterruptHandle_t __pinInterruptHandlers[GPIO_PIN_COUNT] = {0,}; +static InterruptHandle_t __pinInterruptHandlers[GPIO_PIN_COUNT] = { {0,0}, }; #include "driver/rtc_io.h" @@ -239,9 +238,7 @@ static void IRAM_ATTR __onPinInterrupt() } } -extern void cleanupFunctional(void* arg); - -extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional) +extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int intr_type) { static bool interrupt_initialized = false; @@ -250,14 +247,8 @@ extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, esp_intr_alloc(ETS_GPIO_INTR_SOURCE, (int)ESP_INTR_FLAG_IRAM, __onPinInterrupt, NULL, &gpio_intr_handle); } - // if new attach without detach remove old info - if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg) - { - cleanupFunctional(__pinInterruptHandlers[pin].arg); - } __pinInterruptHandlers[pin].fn = (voidFuncPtr)userFunc; __pinInterruptHandlers[pin].arg = arg; - __pinInterruptHandlers[pin].functional = functional; esp_intr_disable(gpio_intr_handle); if(esp_intr_get_cpu(gpio_intr_handle)) { //APP_CPU @@ -269,36 +260,31 @@ extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, esp_intr_enable(gpio_intr_handle); } -extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type) -{ - __attachInterruptFunctionalArg(pin, userFunc, arg, intr_type, false); -} - extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int intr_type) { - __attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, NULL, intr_type, false); + __attachInterruptArg(pin, (voidFuncPtrArg)userFunc, NULL, intr_type); } extern void __detachInterrupt(uint8_t pin) { esp_intr_disable(gpio_intr_handle); - if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg) - { - cleanupFunctional(__pinInterruptHandlers[pin].arg); - } __pinInterruptHandlers[pin].fn = NULL; __pinInterruptHandlers[pin].arg = NULL; - __pinInterruptHandlers[pin].functional = false; GPIO.pin[pin].int_ena = 0; GPIO.pin[pin].int_type = 0; esp_intr_enable(gpio_intr_handle); } +extern void* __detachInterruptArg(uint8_t pin) { + void* arg = (pin < GPIO_PIN_COUNT) ? __pinInterruptHandlers[pin].arg : NULL; + __detachInterrupt(pin); + return arg; +} extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode"))); extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite"))); extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead"))); extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt"))); +extern void detachInterrupt(uint8_t pin) __attribute__((weak, alias("__detachInterrupt"))); extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void * arg, int mode) __attribute__ ((weak, alias("__attachInterruptArg"))); -extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt"))); - +extern void* detachInterruptArg(uint8_t pin) __attribute__((weak, alias("__detachInterruptArg"))); diff --git a/cores/esp32/esp32-hal-gpio.h b/cores/esp32/esp32-hal-gpio.h index daa9f6e66fa..ff9ddc56c79 100644 --- a/cores/esp32/esp32-hal-gpio.h +++ b/cores/esp32/esp32-hal-gpio.h @@ -79,8 +79,9 @@ void digitalWrite(uint8_t pin, uint8_t val); int digitalRead(uint8_t pin); void attachInterrupt(uint8_t pin, void (*)(void), int mode); -void attachInterruptArg(uint8_t pin, void (*)(void*), void * arg, int mode); void detachInterrupt(uint8_t pin); +void attachInterruptArg(uint8_t pin, void (*)(void*), void* arg, int mode); +void* detachInterruptArg(uint8_t pin); #ifdef __cplusplus } diff --git a/cores/esp32/esp32-hal-misc.c b/cores/esp32/esp32-hal-misc.c index ae06edc3378..e636ba93eab 100644 --- a/cores/esp32/esp32-hal-misc.c +++ b/cores/esp32/esp32-hal-misc.c @@ -44,11 +44,6 @@ float temperatureRead() return (temprature_sens_read() - 32) / 1.8; } -void yield() -{ - vPortYield(); -} - #if CONFIG_AUTOSTART_ARDUINO extern TaskHandle_t loopTaskHandle; @@ -139,9 +134,34 @@ unsigned long IRAM_ATTR millis() return (unsigned long) (esp_timer_get_time() / 1000ULL); } +void initVariant() __attribute__((weak)); +void initVariant() {} + +void init() __attribute__((weak)); +void init() {} + +void yield_completed() __attribute__((weak)); +void yield_completed() {} + +bool verifyOta() __attribute__((weak)); +bool verifyOta() { return true; } + +#ifdef CONFIG_BT_ENABLED +//overwritten in esp32-hal-bt.c +bool btInUse() __attribute__((weak)); +bool btInUse() { return false; } +#endif + +void yield() +{ + vPortYield(); + yield_completed(); +} + void delay(uint32_t ms) { - vTaskDelay(ms / portTICK_PERIOD_MS); + vTaskDelay(ms / portTICK_PERIOD_MS); + yield_completed(); } void IRAM_ATTR delayMicroseconds(uint32_t us) @@ -160,21 +180,6 @@ void IRAM_ATTR delayMicroseconds(uint32_t us) } } -void initVariant() __attribute__((weak)); -void initVariant() {} - -void init() __attribute__((weak)); -void init() {} - -bool verifyOta() __attribute__((weak)); -bool verifyOta() { return true; } - -#ifdef CONFIG_BT_ENABLED -//overwritten in esp32-hal-bt.c -bool btInUse() __attribute__((weak)); -bool btInUse(){ return false; } -#endif - void initArduino() { #ifdef CONFIG_APP_ROLLBACK_ENABLE diff --git a/cores/esp32/main.cpp b/cores/esp32/main.cpp index 3a455c8a878..bcdeb8ec8d0 100644 --- a/cores/esp32/main.cpp +++ b/cores/esp32/main.cpp @@ -17,6 +17,7 @@ void loopTask(void *pvParameters) esp_task_wdt_reset(); } loop(); + loop_completed(); } } @@ -27,4 +28,7 @@ extern "C" void app_main() xTaskCreateUniversal(loopTask, "loopTask", 8192, NULL, 1, &loopTaskHandle, CONFIG_ARDUINO_RUNNING_CORE); } +extern "C" void loop_completed() __attribute__((weak)); +extern "C" void loop_completed() {} + #endif diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino deleted file mode 100644 index 0e9f97414bb..00000000000 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include - -#define BUTTON1 16 -#define BUTTON2 17 - -class Button -{ -public: - Button(uint8_t reqPin) : PIN(reqPin){ - pinMode(PIN, INPUT_PULLUP); - attachInterrupt(PIN, std::bind(&Button::isr,this), FALLING); - }; - ~Button() { - detachInterrupt(PIN); - } - - void IRAM_ATTR isr() { - numberKeyPresses += 1; - pressed = true; - } - - void checkPressed() { - if (pressed) { - Serial.printf("Button on pin %u has been pressed %u times\n", PIN, numberKeyPresses); - pressed = false; - } - } - -private: - const uint8_t PIN; - volatile uint32_t numberKeyPresses; - volatile bool pressed; -}; - -Button button1(BUTTON1); -Button button2(BUTTON2); - - -void setup() { - Serial.begin(115200); -} - -void loop() { - button1.checkPressed(); - button2.checkPressed(); -} diff --git a/libraries/FunctionalInterrupt/examples/Functional/Functional.ino b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino new file mode 100644 index 00000000000..cc6bc9d6131 --- /dev/null +++ b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino @@ -0,0 +1,83 @@ +#include +#include + +#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(uint8_t reqPin) : PIN(reqPin) { + pinMode(PIN, INPUT_PULLUP); + // Arduino C API: + //attachInterruptArg(PIN, [](void* self) { + // static_cast(self)->isr(); + //}, this, FALLING); // works on ESP32; fails on ESP8266: "ISR not in IRAM" + //attachInterruptArg(PIN, reinterpret_cast(&isr_static), this, FALLING); // works on ESP32; works on ESP8266 + // FunctionalInterrupts API: + attachScheduledInterrupt(PIN, [this](InterruptInfo ii) { Serial.print("Pin "); Serial.println(ii.pin); isr(); }, FALLING); // works on ESP32; works on ESP8266 + }; + ~Button() { + // Arduino C API: + //detachInterrupt(PIN); + // FunctionalInterrupt API: + detachFunctionalInterrupt(PIN); + } + +#if defined(ESP8266) + void ICACHE_RAM_ATTR isr() +#elif defined(ESP32) + void IRAM_ATTR isr() +#endif + { + numberKeyPresses += 1; + pressed = true; + } + +#if defined(ESP8266) + static void ICACHE_RAM_ATTR isr_static(Button* const self) +#elif defined(ESP32) + static void IRAM_ATTR isr_static(Button* const self) +#endif + { + self->isr(); + } + + void checkPressed() { + if (pressed) { + Serial.printf("Button on pin %u has been pressed %u times\n", PIN, numberKeyPresses); + pressed = false; + } + } + +private: + const uint8_t PIN; + volatile uint32_t numberKeyPresses = 0; + volatile bool pressed = false; +}; + +Button* button1; +Button* button2; + + +void setup() { + Serial.begin(115200); + Serial.println("FunctionalInterrupt test/example"); + + button1 = new Button(BUTTON1); + button2 = new Button(BUTTON2); + + Serial.println("setup() complete"); +} + +void loop() { + button1->checkPressed(); + button2->checkPressed(); +} diff --git a/libraries/FunctionalInterrupt/keywords.txt b/libraries/FunctionalInterrupt/keywords.txt new file mode 100644 index 00000000000..52423e5f954 --- /dev/null +++ b/libraries/FunctionalInterrupt/keywords.txt @@ -0,0 +1,13 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### + +InterruptInfo KEYWORD1 +ArgStructure KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +attachScheduledInterrupt KEYWORD2 +detachFunctionalInterrupt KEYWORD2 diff --git a/libraries/FunctionalInterrupt/library.properties b/libraries/FunctionalInterrupt/library.properties new file mode 100644 index 00000000000..09987009e80 --- /dev/null +++ b/libraries/FunctionalInterrupt/library.properties @@ -0,0 +1,9 @@ +name=FunctionalInterrupt +version=1.0 +author=hreintke +maintainer=hreintke +sentence=C++ functional and scheduled interrupt handling +paragraph= +category=Other +url= +architectures=esp32 diff --git a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp new file mode 100644 index 00000000000..fd3fddfb0d3 --- /dev/null +++ b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp @@ -0,0 +1,73 @@ +#include "FunctionalInterrupt.h" +#include +#include + +namespace { + + struct ArgStructure + { + std::function function = nullptr; + }; + + void ICACHE_RAM_ATTR interruptFunctional(void* arg) + { + ArgStructure* localArg = static_cast(arg); + localArg->function(); + } + + void cleanupFunctional(void* arg) + { + ArgStructure* localArg = static_cast(arg); + delete localArg; + } + +} + +void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) +{ + void* localArg = detachInterruptArg(pin); + if (localArg) + { + cleanupFunctional(localArg); + } + + if (intRoutine) + { + ArgStructure* arg = new ArgStructure; + arg->function = std::move(intRoutine); + + attachInterruptArg(pin, interruptFunctional, arg, mode); + } +} + +void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode) +{ + void* localArg = detachInterruptArg(pin); + if (localArg) + { + cleanupFunctional(localArg); + } + + if (scheduledIntRoutine) + { + ArgStructure* arg = new ArgStructure; + arg->function = [scheduledIntRoutine = std::move(scheduledIntRoutine), pin]() + { + InterruptInfo interruptInfo(pin); + interruptInfo.value = digitalRead(pin); + interruptInfo.micro = micros(); + schedule_function([scheduledIntRoutine, interruptInfo]() { scheduledIntRoutine(std::move(interruptInfo)); }); + }; + + attachInterruptArg(pin, interruptFunctional, arg, mode); + } +} + +void detachFunctionalInterrupt(uint8_t pin) +{ + void* localArg = detachInterruptArg(pin); + if (localArg) + { + cleanupFunctional(localArg); + } +} diff --git a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h new file mode 100644 index 00000000000..b51ef93f53d --- /dev/null +++ b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h @@ -0,0 +1,20 @@ +#ifndef FUNCTIONALINTERRUPT_H +#define FUNCTIONALINTERRUPT_H + +#include + +// Structures for communication + +struct InterruptInfo +{ + InterruptInfo(uint8_t _pin) : pin(_pin) {} + const uint8_t pin; + uint8_t value = 0; + uint32_t micro = 0; +}; + +void attachInterrupt(uint8_t pin, std::function intRoutine, int mode); +void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode); +void detachFunctionalInterrupt(uint8_t pin); + +#endif //FUNCTIONALINTERRUPT_H diff --git a/libraries/PolledTimeout/library.properties b/libraries/PolledTimeout/library.properties new file mode 100644 index 00000000000..92e4fb041b8 --- /dev/null +++ b/libraries/PolledTimeout/library.properties @@ -0,0 +1,9 @@ +name=PolledTimeout +version=1.0 +author= +maintainer= +sentence= +paragraph= +category=Other +url= +architectures=esp32 diff --git a/libraries/PolledTimeout/src/PolledTimeout.h b/libraries/PolledTimeout/src/PolledTimeout.h new file mode 100644 index 00000000000..fe9c9ca4bcd --- /dev/null +++ b/libraries/PolledTimeout/src/PolledTimeout.h @@ -0,0 +1,289 @@ +#ifndef __POLLEDTIMING_H__ +#define __POLLEDTIMING_H__ + + +/* + PolledTimeout.h - Encapsulation of a polled Timeout + + Copyright (c) 2018 Daniel Salazar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + 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 + */ + +#include + +#include + +namespace esp8266 +{ + + +namespace polledTimeout +{ + +namespace YieldPolicy +{ + +struct DoNothing +{ + static void execute() {} +}; + +struct YieldOrSkip +{ + static void execute() {delay(0);} +}; + +template +struct YieldAndDelayMs +{ + static void execute() {delay(delayMs);} +}; + +} //YieldPolicy + +namespace TimePolicy +{ + +struct TimeSourceMillis +{ + // time policy in milli-seconds based on millis() + + using timeType = decltype(millis()); + static timeType time() {return millis();} + static constexpr timeType ticksPerSecond = 1000; + static constexpr timeType ticksPerSecondMax = 1000; +}; + +struct TimeSourceCycles +{ + // time policy based on ESP.getCycleCount() + // this particular time measurement is intended to be called very often + // (every loop, every yield) + + using timeType = decltype(ESP.getCycleCount()); + static timeType time() {return ESP.getCycleCount();} + static constexpr timeType ticksPerSecond = F_CPU; // 80'000'000 or 160'000'000 Hz + static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz +}; + +template + // "second_th" units of timeType for one second +struct TimeUnit +{ + using timeType = typename TimeSourceType::timeType; + +#if __GNUC__ < 5 + // gcc-4.8 cannot compile the constexpr-only version of this function + // using #defines instead luckily works + static constexpr timeType computeRangeCompensation () + { + #define number_of_secondTh_in_one_tick ((1.0 * second_th) / ticksPerSecond) + #define fractional (number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick) + + return ({ + fractional == 0? + 1: // no need for compensation + (number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division + }); + + #undef number_of_secondTh_in_one_tick + #undef fractional + } +#else + static constexpr timeType computeRangeCompensation () + { + return ({ + constexpr double number_of_secondTh_in_one_tick = (1.0 * second_th) / ticksPerSecond; + constexpr double fractional = number_of_secondTh_in_one_tick - (long)number_of_secondTh_in_one_tick; + fractional == 0? + 1: // no need for compensation + (number_of_secondTh_in_one_tick / fractional) + 0.5; // scalar multiplier allowing exact division + }); + } +#endif + + static constexpr timeType ticksPerSecond = TimeSourceType::ticksPerSecond; + static constexpr timeType ticksPerSecondMax = TimeSourceType::ticksPerSecondMax; + static constexpr timeType rangeCompensate = computeRangeCompensation(); + static constexpr timeType user2UnitMultiplierMax = (ticksPerSecondMax * rangeCompensate) / second_th; + static constexpr timeType user2UnitMultiplier = (ticksPerSecond * rangeCompensate) / second_th; + static constexpr timeType user2UnitDivider = rangeCompensate; + // std::numeric_limits::max() is reserved + static constexpr timeType timeMax = (std::numeric_limits::max() - 1) / user2UnitMultiplierMax; + + static timeType toTimeTypeUnit (const timeType userUnit) {return (userUnit * user2UnitMultiplier) / user2UnitDivider;} + static timeType toUserUnit (const timeType internalUnit) {return (internalUnit * user2UnitDivider) / user2UnitMultiplier;} + static timeType time () {return TimeSourceType::time();} +}; + +using TimeMillis = TimeUnit< TimeSourceMillis, 1000 >; +using TimeFastMillis = TimeUnit< TimeSourceCycles, 1000 >; +using TimeFastMicros = TimeUnit< TimeSourceCycles, 1000000 >; +using TimeFastNanos = TimeUnit< TimeSourceCycles, 1000000000 >; + +} //TimePolicy + +template +class timeoutTemplate +{ +public: + using timeType = typename TimePolicyT::timeType; + static_assert(std::is_unsigned::value == true, "timeType must be unsigned"); + + static constexpr timeType alwaysExpired = 0; + static constexpr timeType neverExpires = std::numeric_limits::max(); + static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug + + timeoutTemplate(const timeType userTimeout) + { + reset(userTimeout); + } + + IRAM_ATTR // fast + bool expired() + { + YieldPolicyT::execute(); //in case of DoNothing: gets optimized away + if(PeriodicT) //in case of false: gets optimized away + return expiredRetrigger(); + return expiredOneShot(); + } + + IRAM_ATTR // fast + operator bool() + { + return expired(); + } + + bool canExpire () const + { + return !_neverExpires; + } + + bool canWait () const + { + return _timeout != alwaysExpired; + } + + IRAM_ATTR // called from ISR + void reset(const timeType newUserTimeout) + { + reset(); + _timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout); + _neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax()); + } + + IRAM_ATTR // called from ISR + void reset() + { + _start = TimePolicyT::time(); + } + + void resetToNeverExpires () + { + _timeout = alwaysExpired + 1; // because canWait() has precedence + _neverExpires = true; + } + + timeType getTimeout() const + { + return TimePolicyT::toUserUnit(_timeout); + } + + static constexpr timeType timeMax() + { + return TimePolicyT::timeMax; + } + +private: + + IRAM_ATTR // fast + bool checkExpired(const timeType internalUnit) const + { + // canWait() is not checked here + // returns "can expire" and "time expired" + return (!_neverExpires) && ((internalUnit - _start) >= _timeout); + } + +protected: + + IRAM_ATTR // fast + bool expiredRetrigger() + { + if (!canWait()) + return true; + + timeType current = TimePolicyT::time(); + if(checkExpired(current)) + { + unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) + _start += n * _timeout; + return true; + } + return false; + } + + IRAM_ATTR // fast + bool expiredOneShot() const + { + // returns "always expired" or "has expired" + return !canWait() || checkExpired(TimePolicyT::time()); + } + + timeType _timeout; + timeType _start; + bool _neverExpires; +}; + +// legacy type names, deprecated (unit is milliseconds) + +using oneShot = polledTimeout::timeoutTemplate /*__attribute__((deprecated("use oneShotMs")))*/; +using periodic = polledTimeout::timeoutTemplate /*__attribute__((deprecated("use periodicMs")))*/; + +// standard versions (based on millis()) +// timeMax() is 49.7 days ((2^32)-2 ms) + +using oneShotMs = polledTimeout::timeoutTemplate; +using periodicMs = polledTimeout::timeoutTemplate; + +// Time policy based on ESP.getCycleCount(), and intended to be called very often: +// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%) +// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount())) +// timeMax() values: +// Ms: max is 26843 ms (26.8 s) +// Us: max is 26843545 us (26.8 s) +// Ns: max is 1073741823 ns ( 1.07 s) +// (time policy based on ESP.getCycleCount() is intended to be called very often) + +using oneShotFastMs = polledTimeout::timeoutTemplate; +using periodicFastMs = polledTimeout::timeoutTemplate; +using oneShotFastUs = polledTimeout::timeoutTemplate; +using periodicFastUs = polledTimeout::timeoutTemplate; +using oneShotFastNs = polledTimeout::timeoutTemplate; +using periodicFastNs = polledTimeout::timeoutTemplate; + +} //polledTimeout + + +/* A 1-shot timeout that auto-yields when in CONT can be built as follows: + * using oneShotYieldMs = esp8266::polledTimeout::timeoutTemplate; + * + * Other policies can be implemented by the user, e.g.: simple yield that panics in SYS, and the polledTimeout types built as needed as shown above, without modifying this file. + */ + +}//esp8266 + +#endif diff --git a/libraries/Schedule/library.properties b/libraries/Schedule/library.properties new file mode 100644 index 00000000000..9c7563dbed7 --- /dev/null +++ b/libraries/Schedule/library.properties @@ -0,0 +1,9 @@ +name=Schedule +version=1.0 +author= +maintainer= +sentence= +paragraph= +category=Other +url= +architectures=esp32 diff --git a/libraries/Schedule/src/Schedule.cpp b/libraries/Schedule/src/Schedule.cpp new file mode 100644 index 00000000000..8a36849ef5a --- /dev/null +++ b/libraries/Schedule/src/Schedule.cpp @@ -0,0 +1,187 @@ +#include "Schedule.h" +#include +#include +#ifdef ESP8266 +#include +#include +#else +#include +#endif +#include "circular_queue/circular_queue.h" + +void loop_completed() +{ + run_scheduled_functions(SCHEDULE_FUNCTION_FROM_LOOP); +} + +void yield_completed() +{ + run_scheduled_functions(SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS); +} + +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); + + class SchedulerTicker; + // 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 SchedulerTicker : public Ticker + { + public: + static void operator delete(void* ptr) { + tickerHeap.push(static_cast(ptr)); + } + }; + static_assert(sizeof(SchedulerTicker) == sizeof(Ticker), "sizeof(SchedulerTicker) != sizeof(Ticker)"); + +#ifndef ESP8266 + std::mutex schedulerMutex; +#endif + + void ticker_scheduled(SchedulerTicker* 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_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) SchedulerTicker; + if (!schedule_function([ticker, fn = std::move(fn), repeat_us, policy]() + { + ticker_scheduled(ticker, 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_function_us(const std::function& fn, uint32_t repeat_us, schedule_e policy) +{ + return schedule_function_us(std::function(fn), repeat_us, policy); +} + +bool IRAM_ATTR schedule_function(std::function&& fn, schedule_e policy) +{ + return schedule_function_us([fn = std::move(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); +} + +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 bool fence = false; + { +#ifdef ESP8266 + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(schedulerMutex); +#endif + if (fence) { + // prevent recursive calls from yield() + return; + } + fence = true; + } + + esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms + + // 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_requeue([policy, &yieldNow](scheduled_fn_t& func) + { + if (yieldNow) { +#if defined(ESP8266) + cont_yield(g_pcont); +#elif defined(ESP32) + vPortYield(); +#else + yield(); +#endif + } + return + (func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && policy != SCHEDULE_FUNCTION_FROM_LOOP) + || !func.callNow + || func.mFunc(); + }); + fence = false; +} diff --git a/libraries/Schedule/src/Schedule.h b/libraries/Schedule/src/Schedule.h new file mode 100644 index 00000000000..eeb1dac06fa --- /dev/null +++ b/libraries/Schedule/src/Schedule.h @@ -0,0 +1,73 @@ +#ifndef ESP_SCHEDULE_H +#define ESP_SCHEDULE_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_function_us(std::function&& fn, + uint32_t repeat_us, + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); +bool schedule_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_SCHEDULE_H diff --git a/libraries/Schedule/src/circular_queue/circular_queue.h b/libraries/Schedule/src/circular_queue/circular_queue.h new file mode 100644 index 00000000000..83c01b968b3 --- /dev/null +++ b/libraries/Schedule/src/circular_queue/circular_queue.h @@ -0,0 +1,439 @@ +/* +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 + +#include +#include +#include +#include +#ifdef ESP8266 +#include "interrupts.h" +#else +#include +#endif + +#ifdef ESP32 +#include +#elif !defined(ESP8266) +#define ICACHE_RAM_ATTR +#define IRAM_ATTR +#endif + +/*! + @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) + { + 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_relaxed); + return true; + } + + /*! + @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 returns without removing it from the queue. + @return An rvalue copy of the next element that can be popped, or a default + value of type T if the queue is empty. + */ + T peek() const + { + const auto outPos = m_outPos.load(std::memory_order_relaxed); + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (inPos == outPos) return defaultValue; + else return m_buffer[outPos]; + } + + /*! + @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) + { + const auto inPos = m_inPos.load(std::memory_order_relaxed); + 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_relaxed); + return true; + } + + /*! + @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) + { + const auto inPos = m_inPos.load(std::memory_order_relaxed); + 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 = std::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 = std::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_relaxed); + return blockSize + 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() + { + const auto outPos = m_outPos.load(std::memory_order_relaxed); + 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_relaxed); + return val; + } + + /*! + @brief Pop multiple elements in ordered sequence from the queue to a buffer. + @return The number of elements actually popped from the queue to + buffer. + */ + size_t pop_n(T* buffer, size_t size) { + size_t avail = size = std::min(size, available()); + if (!avail) return 0; + const auto outPos = m_outPos.load(std::memory_order_relaxed); + size_t n = std::min(avail, static_cast(m_bufSize - outPos)); + + std::atomic_thread_fence(std::memory_order_acquire); + + 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_relaxed); + return 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(std::function fun) + { + auto outPos = m_outPos.load(std::memory_order_relaxed); + 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_relaxed); + } + } + +protected: + const T defaultValue = {}; + unsigned m_bufSize; + std::unique_ptr m_buffer; + std::atomic m_inPos; + std::atomic m_outPos; +}; + +/*! + @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; + + /*! + @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 + InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::capacity(cap); + } + + bool IRAM_ATTR push(T&& val) + { +#ifdef ESP8266 + InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(std::move(val)); + } + + /*! + @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(const T& val) + { +#ifdef 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 + 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() + { +#ifdef ESP8266 + InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + std::atomic_thread_fence(std::memory_order_release); + const auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + 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_relaxed); + return val; + } + + /*! + @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(std::function fun) + { + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_relaxed); + 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 = circular_queue::m_buffer[outPos]; + if (fun(val)) + { +#ifdef 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_relaxed); + } + 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_relaxed); + } while (outPos != inPos0); + return true; + } + +#ifndef ESP8266 +protected: + std::mutex m_pushMtx; +#endif +}; + +#endif // __circular_queue_h diff --git a/libraries/Ticker/examples/Arguments/Arguments.ino b/libraries/Ticker/examples/Arguments/Arguments.ino deleted file mode 100644 index cde8acbfa09..00000000000 --- a/libraries/Ticker/examples/Arguments/Arguments.ino +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -// attach a LED to GPIO 21 -#define LED_PIN 21 - -Ticker tickerSetHigh; -Ticker tickerSetLow; - -void setPin(int state) { - digitalWrite(LED_PIN, state); -} - -void setup() { - pinMode(LED_PIN, OUTPUT); - digitalWrite(1, LOW); - - // every 25 ms, call setPin(0) - tickerSetLow.attach_ms(25, setPin, 0); - - // every 26 ms, call setPin(1) - tickerSetHigh.attach_ms(26, setPin, 1); -} - -void loop() { - -} diff --git a/libraries/Ticker/examples/Blinker/Blinker.ino b/libraries/Ticker/examples/Blinker/Blinker.ino index aca1f450cca..ce5a18a3168 100644 --- a/libraries/Ticker/examples/Blinker/Blinker.ino +++ b/libraries/Ticker/examples/Blinker/Blinker.ino @@ -23,8 +23,7 @@ void toggle() { if (isBlinking) { blinker.detach(); isBlinking = false; - } - else { + } else { blinker.attach(blinkerPace, blink); isBlinking = true; } @@ -38,5 +37,5 @@ void setup() { } void loop() { - + } diff --git a/libraries/Ticker/examples/TickerBasic/TickerBasic.ino b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino new file mode 100644 index 00000000000..a387b554b71 --- /dev/null +++ b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino @@ -0,0 +1,49 @@ +/* + Basic Ticker usage + + Ticker is an object that will call a given function with a certain period. + Each Ticker calls one function. You can have as many Tickers as you like, + memory being the only limitation. + + A function may be attached to a ticker and detached from the ticker. + There are two variants of the attach function: attach and attach_ms. + The first one takes period in seconds, the second one in milliseconds. + + The built-in LED will be blinking. +*/ + +#include + +#ifndef LED_BUILTIN +#define LED_BUILTIN 13 +#endif + +Ticker flipper; + +int count = 0; + +void flip() { + int state = digitalRead(LED_BUILTIN); // get the current state of GPIO1 pin + digitalWrite(LED_BUILTIN, !state); // set pin to the opposite state + + ++count; + // when the counter reaches a certain value, start blinking like crazy + if (count == 20) { + flipper.attach(0.1, flip); + } + // when the counter reaches yet another value, stop blinking + else if (count == 120) { + flipper.detach(); + } +} + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + + // flip the pin every 0.3s + flipper.attach(0.3, flip); +} + +void loop() { +} diff --git a/libraries/Ticker/examples/TickerParameter/TickerParameter.ino b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino new file mode 100644 index 00000000000..99c69c62567 --- /dev/null +++ b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino @@ -0,0 +1,39 @@ +/* + Passing paramters to Ticker callbacks + + Apart from void(void) functions, the Ticker library supports + functions taking one argument. This argument's size has to be less or + equal to 4 bytes (so char, short, int, float, void*, char* types will do). + + This sample runs two tickers that both call one callback function, + but with different arguments. + + The built-in LED will be pulsing. +*/ + +#include + +#ifndef LED_BUILTIN +#define LED_BUILTIN 13 +#endif + +Ticker tickerSetHigh; +Ticker tickerSetLow; + +void setPin(int state) { + digitalWrite(LED_BUILTIN, state); +} + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(1, LOW); + + // every 25 ms, call setPin(0) + tickerSetLow.attach_ms(25, setPin, 0); + + // every 26 ms, call setPin(1) + tickerSetHigh.attach_ms(26, setPin, 1); +} + +void loop() { +} diff --git a/libraries/Ticker/keywords.txt b/libraries/Ticker/keywords.txt index 81cce2c8ea5..b1020c4e59e 100644 --- a/libraries/Ticker/keywords.txt +++ b/libraries/Ticker/keywords.txt @@ -8,7 +8,14 @@ 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 + diff --git a/libraries/Ticker/src/Ticker.cpp b/libraries/Ticker/src/Ticker.cpp index ce5cf69332c..6a9193e6ee8 100644 --- a/libraries/Ticker/src/Ticker.cpp +++ b/libraries/Ticker/src/Ticker.cpp @@ -1,8 +1,8 @@ -/* +/* Ticker.cpp - esp32 library that calls functions periodically Copyright (c) 2017 Bert Melis. All rights reserved. - + Based on the original work of: Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. The original version is part of the esp8266 core for Arduino environment. @@ -24,35 +24,63 @@ #include "Ticker.h" -Ticker::Ticker() : - _timer(nullptr) {} +Ticker::Ticker() + : _timer(nullptr) +{ +} + +Ticker::~Ticker() +{ + detach(); +} + +void Ticker::_attach_s(float seconds, bool repeat, callback_with_arg_t callback, void* arg) +{ + _attach_us(1000000 * seconds, repeat, callback, arg); +} -Ticker::~Ticker() { - detach(); +void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg) +{ + _attach_us(1000 * milliseconds, repeat, callback, arg); } -void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, uint32_t arg) { - esp_timer_create_args_t _timerConfig; - _timerConfig.arg = reinterpret_cast(arg); - _timerConfig.callback = callback; - _timerConfig.dispatch_method = ESP_TIMER_TASK; - _timerConfig.name = "Ticker"; - if (_timer) { - esp_timer_stop(_timer); - esp_timer_delete(_timer); - } - esp_timer_create(&_timerConfig, &_timer); - if (repeat) { - esp_timer_start_periodic(_timer, milliseconds * 1000); - } else { - esp_timer_start_once(_timer, milliseconds * 1000); - } +void Ticker::_attach_us(uint32_t micros, bool repeat, callback_with_arg_t callback, void* arg) +{ + esp_timer_create_args_t _timerConfig; + _timerConfig.arg = reinterpret_cast(arg); + _timerConfig.callback = callback; + _timerConfig.dispatch_method = ESP_TIMER_TASK; + _timerConfig.name = "Ticker"; + if (_timer) { + esp_timer_stop(_timer); + esp_timer_delete(_timer); + } + esp_timer_create(&_timerConfig, &_timer); + if (repeat) { + esp_timer_start_periodic(_timer, micros); + } + else { + esp_timer_start_once(_timer, micros); + } } void Ticker::detach() { - if (_timer) { - esp_timer_stop(_timer); - esp_timer_delete(_timer); - _timer = nullptr; - } + if (_timer) { + esp_timer_stop(_timer); + esp_timer_delete(_timer); + _timer = nullptr; + _callback_function = nullptr; + } +} + +bool Ticker::active() const +{ + return _timer; +} + +void Ticker::_static_callback(void* arg) +{ + Ticker* _this = reinterpret_cast(arg); + if (_this && _this->_callback_function) + _this->_callback_function(); } diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index 82804e0f37d..771bb6478b3 100644 --- a/libraries/Ticker/src/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -1,8 +1,8 @@ -/* +/* Ticker.h - esp32 library that calls functions periodically Copyright (c) 2017 Bert Melis. All rights reserved. - + Based on the original work of: Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. The original version is part of the esp8266 core for Arduino environment. @@ -26,82 +26,146 @@ #define TICKER_H extern "C" { - #include "esp_timer.h" +#include "esp_timer.h" } +#include +#include class Ticker { public: - Ticker(); - ~Ticker(); - typedef void (*callback_t)(void); - typedef void (*callback_with_arg_t)(void*); - - void attach(float seconds, callback_t callback) - { - _attach_ms(seconds * 1000, true, reinterpret_cast(callback), 0); - } - - void attach_ms(uint32_t milliseconds, callback_t callback) - { - _attach_ms(milliseconds, true, reinterpret_cast(callback), 0); - } - - template - void attach(float seconds, void (*callback)(TArg), TArg arg) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "attach() callback argument size must be <= 4 bytes"); - // C-cast serves two purposes: - // static_cast for smaller integer types, - // reinterpret_cast + const_cast for pointer types - uint32_t arg32 = (uint32_t)arg; - _attach_ms(seconds * 1000, true, reinterpret_cast(callback), arg32); - } - - template - void attach_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "attach_ms() callback argument size must be <= 4 bytes"); - uint32_t arg32 = (uint32_t)arg; - _attach_ms(milliseconds, true, reinterpret_cast(callback), arg32); - } - - void once(float seconds, callback_t callback) - { - _attach_ms(seconds * 1000, false, reinterpret_cast(callback), 0); - } - - void once_ms(uint32_t milliseconds, callback_t callback) - { - _attach_ms(milliseconds, false, reinterpret_cast(callback), 0); - } - - template - void once(float seconds, void (*callback)(TArg), TArg arg) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "attach() callback argument size must be <= 4 bytes"); - uint32_t arg32 = (uint32_t)(arg); - _attach_ms(seconds * 1000, false, reinterpret_cast(callback), arg32); - } - - template - void once_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "attach_ms() callback argument size must be <= 4 bytes"); - uint32_t arg32 = (uint32_t)(arg); - _attach_ms(milliseconds, false, reinterpret_cast(callback), arg32); - } - - void detach(); - bool active(); - -protected: - void _attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, uint32_t arg); - + Ticker(); + ~Ticker(); + + typedef void (*callback_with_arg_t)(void*); + typedef std::function callback_function_t; + + void attach_scheduled(float seconds, callback_function_t callback) + { + attach(seconds, [callback]() { schedule_function(callback); }); + } + + void attach(float seconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_s(seconds, true, _static_callback, this); + } + + void attach_ms_scheduled(uint32_t milliseconds, callback_function_t callback) + { + attach_ms(milliseconds, [callback]() { schedule_function(callback); }); + } + + void attach_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, true, _static_callback, this); + } + + void attach_us_scheduled(uint32_t micros, callback_function_t callback) + { + attach_us(micros, [callback]() { schedule_function(callback); }); + } + + void attach_us(uint32_t micros, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_us(micros, true, _static_callback, this); + } + + template + void attach(float seconds, void (*callback)(TArg), TArg arg) + { + static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); + // C-cast serves two purposes: + // static_cast for smaller integer types, + // reinterpret_cast + const_cast for pointer types + _attach_s(seconds, true, reinterpret_cast(callback), (void*)arg); + } + + template + void attach_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) + { + static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); + _attach_ms(milliseconds, true, reinterpret_cast(callback), (void*)arg); + } + + template + void attach_us(uint32_t micros, void (*callback)(TArg), TArg arg) + { + static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); + _attach_us(micros, true, reinterpret_cast(callback), (void*)arg); + } + + void once_scheduled(float seconds, callback_function_t callback) + { + once(seconds, [callback]() { schedule_function(callback); }); + } + + void once(float seconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_s(seconds, false, _static_callback, this); + } + + void once_ms_scheduled(uint32_t milliseconds, callback_function_t callback) + { + once_ms(milliseconds, [callback]() { schedule_function(callback); }); + } + + void once_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, false, _static_callback, this); + } + + void once_us_scheduled(uint32_t micros, callback_function_t callback) + { + once_us(micros, [callback]() { schedule_function(callback); }); + } + + void once_us(uint32_t micros, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_us(micros, false, _static_callback, this); + } + + template + void once(float seconds, void (*callback)(TArg), TArg arg) + { + static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); + _attach_s(seconds, false, reinterpret_cast(callback), (void*)arg); + } + + template + void once_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) + { + static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); + _attach_ms(milliseconds, false, reinterpret_cast(callback), (void*)arg); + } + + template + void once_us(uint32_t micros, void (*callback)(TArg), TArg arg) + { + static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); + _attach_us(micros, false, reinterpret_cast(callback), (void*)arg); + } + + void detach(); + bool active() const; protected: - esp_timer_handle_t _timer; + void _attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg); + static void _static_callback(void* arg); + + callback_function_t _callback_function = nullptr; + + esp_timer_handle_t _timer; + +private: + void _attach_us(uint32_t micros, bool repeat, callback_with_arg_t callback, void* arg); + void _attach_s(float seconds, bool repeat, callback_with_arg_t callback, void* arg); }; -#endif // TICKER_H +#endif//TICKER_H