From f938f81389cfef93719612f1c2f1e26f9b90b959 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 11:51:59 +0200 Subject: [PATCH 01/49] Update Ticker API to compatibility with ESP8266, prepares for co-op loop Scheduler --- CMakeLists.txt | 3 +- cores/esp32/Ticker.cpp | 75 +++++++++++++++++ cores/esp32/Ticker.h | 111 ++++++++++++++++++++++++ libraries/Ticker/src/Ticker.cpp | 71 ++++++++++------ libraries/Ticker/src/Ticker.h | 144 ++++++++++++++++---------------- 5 files changed, 305 insertions(+), 99 deletions(-) create mode 100644 cores/esp32/Ticker.cpp create mode 100644 cores/esp32/Ticker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 32979ad4acf..0415e9d6983 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ set(CORE_SRCS cores/esp32/stdlib_noniso.c cores/esp32/Stream.cpp cores/esp32/StreamString.cpp + cores/esp32/Ticker.cpp cores/esp32/wiring_pulse.c cores/esp32/wiring_shift.c cores/esp32/WMath.cpp @@ -58,7 +59,6 @@ set(LIBRARY_SRCS libraries/SimpleBLE/src/SimpleBLE.cpp libraries/SPIFFS/src/SPIFFS.cpp libraries/SPI/src/SPI.cpp - libraries/Ticker/src/Ticker.cpp libraries/Update/src/Updater.cpp libraries/WebServer/src/WebServer.cpp libraries/WebServer/src/Parsing.cpp @@ -195,7 +195,6 @@ set(COMPONENT_ADD_INCLUDEDIRS libraries/SimpleBLE/src libraries/SPIFFS/src libraries/SPI/src - libraries/Ticker/src libraries/Update/src libraries/WebServer/src libraries/WiFiClientSecure/src diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp new file mode 100644 index 00000000000..a92c474ec42 --- /dev/null +++ b/cores/esp32/Ticker.cpp @@ -0,0 +1,75 @@ +/* + 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. + + 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 "Ticker.h" + +Ticker::Ticker() + : _timer(nullptr) +{ +} + +Ticker::~Ticker() +{ + detach(); +} + +void Ticker::_attach_ms(uint32_t milliseconds, 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, milliseconds * 1000); + } + else { + esp_timer_start_once(_timer, milliseconds * 1000); + } +} + +void Ticker::detach() { + if (_timer) { + esp_timer_stop(_timer); + esp_timer_delete(_timer); + _timer = nullptr; + } +} + +bool Ticker::active() const +{ + return _timer; +} + +void Ticker::_static_callback(void* arg) +{ + Ticker* _this = reinterpret_cast(arg); + if (!_this) return; + if (_this->_callback_function) _this->_callback_function(); +} diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h new file mode 100644 index 00000000000..dfb5159ef61 --- /dev/null +++ b/cores/esp32/Ticker.h @@ -0,0 +1,111 @@ +/* + 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. + + 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 TICKER_H +#define TICKER_H + +extern "C" { +#include "esp_timer.h" +} +#include + +class Ticker +{ +public: + Ticker(); + ~Ticker(); + + typedef void (*callback_with_arg_t)(void*); + typedef std::function callback_function_t; + + void attach(float seconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(seconds * 1000, true, _static_callback, this); + } + + void attach_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, 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_ms(seconds * 1000, true, callback, 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, callback, arg); + } + + void once(float seconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(seconds * 1000, false, _static_callback, this); + } + + void once_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, 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_ms(seconds * 1000, false, callback, 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, callback, arg); + } + + void detach(); + bool active() const; + +protected: + 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; + +protected: + esp_timer_handle_t _timer; +}; + + +#endif//TICKER_H diff --git a/libraries/Ticker/src/Ticker.cpp b/libraries/Ticker/src/Ticker.cpp index ce5cf69332c..a92c474ec42 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,52 @@ #include "Ticker.h" -Ticker::Ticker() : - _timer(nullptr) {} +Ticker::Ticker() + : _timer(nullptr) +{ +} -Ticker::~Ticker() { - detach(); +Ticker::~Ticker() +{ + detach(); } -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_ms(uint32_t milliseconds, 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, milliseconds * 1000); + } + else { + esp_timer_start_once(_timer, milliseconds * 1000); + } } 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; + } +} + +bool Ticker::active() const +{ + return _timer; +} + +void Ticker::_static_callback(void* arg) +{ + Ticker* _this = reinterpret_cast(arg); + if (!_this) return; + if (_this->_callback_function) _this->_callback_function(); } diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index 82804e0f37d..dfb5159ef61 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,86 @@ #define TICKER_H extern "C" { - #include "esp_timer.h" +#include "esp_timer.h" } +#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(float seconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(seconds * 1000, true, _static_callback, this); + } + + void attach_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, 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_ms(seconds * 1000, true, callback, 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, callback, arg); + } + + void once(float seconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(seconds * 1000, false, _static_callback, this); + } + + void once_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, 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_ms(seconds * 1000, false, callback, 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, callback, arg); + } + + void detach(); + bool active() const; + +protected: + 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; protected: - esp_timer_handle_t _timer; + esp_timer_handle_t _timer; }; -#endif // TICKER_H +#endif//TICKER_H From 2030ba0430c684eac94c4ff70f51889326476f86 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 12:17:26 +0200 Subject: [PATCH 02/49] Fixing Build server complaints --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0415e9d6983..3bb286c227a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ set(LIBRARY_SRCS libraries/SimpleBLE/src/SimpleBLE.cpp libraries/SPIFFS/src/SPIFFS.cpp libraries/SPI/src/SPI.cpp + libraries/Ticker/src/Ticker.cpp libraries/Update/src/Updater.cpp libraries/WebServer/src/WebServer.cpp libraries/WebServer/src/Parsing.cpp @@ -195,6 +196,7 @@ set(COMPONENT_ADD_INCLUDEDIRS libraries/SimpleBLE/src libraries/SPIFFS/src libraries/SPI/src + libraries/Ticker/src libraries/Update/src libraries/WebServer/src libraries/WiFiClientSecure/src From 108f87fad533c65c633b8e206e8319624b31e3d8 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 12:37:04 +0200 Subject: [PATCH 03/49] Fix omitted casts in template member function --- cores/esp32/Ticker.h | 8 ++++---- libraries/Ticker/src/Ticker.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h index dfb5159ef61..cc816f96815 100644 --- a/cores/esp32/Ticker.h +++ b/cores/esp32/Ticker.h @@ -58,14 +58,14 @@ class Ticker // C-cast serves two purposes: // static_cast for smaller integer types, // reinterpret_cast + const_cast for pointer types - _attach_ms(seconds * 1000, true, callback, arg); + _attach_ms(seconds * 1000, 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, callback, arg); + _attach_ms(milliseconds, true, reinterpret_cast(callback), (void*)arg); } void once(float seconds, callback_function_t callback) @@ -84,14 +84,14 @@ class Ticker void once(float seconds, void (*callback)(TArg), TArg arg) { static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(seconds * 1000, false, callback, arg); + _attach_ms(seconds * 1000, 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, callback, arg); + _attach_ms(milliseconds, false, reinterpret_cast(callback), (void*)arg); } void detach(); diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index dfb5159ef61..cc816f96815 100644 --- a/libraries/Ticker/src/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -58,14 +58,14 @@ class Ticker // C-cast serves two purposes: // static_cast for smaller integer types, // reinterpret_cast + const_cast for pointer types - _attach_ms(seconds * 1000, true, callback, arg); + _attach_ms(seconds * 1000, 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, callback, arg); + _attach_ms(milliseconds, true, reinterpret_cast(callback), (void*)arg); } void once(float seconds, callback_function_t callback) @@ -84,14 +84,14 @@ class Ticker void once(float seconds, void (*callback)(TArg), TArg arg) { static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(seconds * 1000, false, callback, arg); + _attach_ms(seconds * 1000, 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, callback, arg); + _attach_ms(milliseconds, false, reinterpret_cast(callback), (void*)arg); } void detach(); From 01dced2a02e7a3d40bb36e194e92f8baf8866bd2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 4 Jun 2019 23:37:09 +0200 Subject: [PATCH 04/49] Changes after review --- cores/esp32/Ticker.cpp | 19 +++++++++++++++---- cores/esp32/Ticker.h | 13 ++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp index a92c474ec42..6a9193e6ee8 100644 --- a/cores/esp32/Ticker.cpp +++ b/cores/esp32/Ticker.cpp @@ -34,7 +34,17 @@ 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); +} + 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_us(uint32_t micros, bool repeat, callback_with_arg_t callback, void* arg) { esp_timer_create_args_t _timerConfig; _timerConfig.arg = reinterpret_cast(arg); @@ -47,10 +57,10 @@ void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t } esp_timer_create(&_timerConfig, &_timer); if (repeat) { - esp_timer_start_periodic(_timer, milliseconds * 1000); + esp_timer_start_periodic(_timer, micros); } else { - esp_timer_start_once(_timer, milliseconds * 1000); + esp_timer_start_once(_timer, micros); } } @@ -59,6 +69,7 @@ void Ticker::detach() { esp_timer_stop(_timer); esp_timer_delete(_timer); _timer = nullptr; + _callback_function = nullptr; } } @@ -70,6 +81,6 @@ bool Ticker::active() const void Ticker::_static_callback(void* arg) { Ticker* _this = reinterpret_cast(arg); - if (!_this) return; - if (_this->_callback_function) _this->_callback_function(); + if (_this && _this->_callback_function) + _this->_callback_function(); } diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h index cc816f96815..44379841ee9 100644 --- a/cores/esp32/Ticker.h +++ b/cores/esp32/Ticker.h @@ -42,7 +42,7 @@ class Ticker void attach(float seconds, callback_function_t callback) { _callback_function = std::move(callback); - _attach_ms(seconds * 1000, true, _static_callback, this); + _attach_s(seconds, true, _static_callback, this); } void attach_ms(uint32_t milliseconds, callback_function_t callback) @@ -58,7 +58,7 @@ class Ticker // C-cast serves two purposes: // static_cast for smaller integer types, // reinterpret_cast + const_cast for pointer types - _attach_ms(seconds * 1000, true, reinterpret_cast(callback), (void*)arg); + _attach_s(seconds, true, reinterpret_cast(callback), (void*)arg); } template @@ -71,7 +71,7 @@ class Ticker void once(float seconds, callback_function_t callback) { _callback_function = std::move(callback); - _attach_ms(seconds * 1000, false, _static_callback, this); + _attach_s(seconds, false, _static_callback, this); } void once_ms(uint32_t milliseconds, callback_function_t callback) @@ -84,7 +84,7 @@ class Ticker void once(float seconds, void (*callback)(TArg), TArg arg) { static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); - _attach_ms(seconds * 1000, false, reinterpret_cast(callback), (void*)arg); + _attach_s(seconds, false, reinterpret_cast(callback), (void*)arg); } template @@ -103,8 +103,11 @@ class Ticker callback_function_t _callback_function = nullptr; -protected: 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); }; From aa5d1c0b366fb74d0856abc126512fda55bb99c5 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 4 Jun 2019 23:44:41 +0200 Subject: [PATCH 05/49] =?UTF-8?q?Expose=20=C2=B5s=20resolution=20of=20OS?= =?UTF-8?q?=20API=20in=20Ticker=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp32/Ticker.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h index 44379841ee9..f7db7b75e3e 100644 --- a/cores/esp32/Ticker.h +++ b/cores/esp32/Ticker.h @@ -29,6 +29,7 @@ extern "C" { #include "esp_timer.h" } #include +#include class Ticker { @@ -51,6 +52,17 @@ class Ticker _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) { @@ -68,6 +80,13 @@ class Ticker _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(float seconds, callback_function_t callback) { _callback_function = std::move(callback); @@ -80,6 +99,17 @@ class Ticker _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) { @@ -94,6 +124,13 @@ class Ticker _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; From e069d83f4d228e49b39ae7f42958d6398dc011c6 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 09:50:38 +0200 Subject: [PATCH 06/49] Return Ticker to libraries only for modularity. --- CMakeLists.txt | 1 - cores/esp32/Ticker.cpp | 86 ------------------ cores/esp32/Ticker.h | 151 -------------------------------- libraries/Ticker/keywords.txt | 7 ++ libraries/Ticker/src/Ticker.cpp | 19 +++- libraries/Ticker/src/Ticker.h | 39 +++++++-- 6 files changed, 56 insertions(+), 247 deletions(-) delete mode 100644 cores/esp32/Ticker.cpp delete mode 100644 cores/esp32/Ticker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bb286c227a..32979ad4acf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,6 @@ set(CORE_SRCS cores/esp32/stdlib_noniso.c cores/esp32/Stream.cpp cores/esp32/StreamString.cpp - cores/esp32/Ticker.cpp cores/esp32/wiring_pulse.c cores/esp32/wiring_shift.c cores/esp32/WMath.cpp diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp deleted file mode 100644 index 6a9193e6ee8..00000000000 --- a/cores/esp32/Ticker.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - 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. - - 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 "Ticker.h" - -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); -} - -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_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; - _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/cores/esp32/Ticker.h b/cores/esp32/Ticker.h deleted file mode 100644 index f7db7b75e3e..00000000000 --- a/cores/esp32/Ticker.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - 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. - - 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 TICKER_H -#define TICKER_H - -extern "C" { -#include "esp_timer.h" -} -#include -#include - -class Ticker -{ -public: - Ticker(); - ~Ticker(); - - typedef void (*callback_with_arg_t)(void*); - typedef std::function callback_function_t; - - void attach(float seconds, callback_function_t callback) - { - _callback_function = std::move(callback); - _attach_s(seconds, true, _static_callback, this); - } - - 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(float seconds, callback_function_t callback) - { - _callback_function = std::move(callback); - _attach_s(seconds, false, _static_callback, this); - } - - 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: - 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 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 a92c474ec42..6a9193e6ee8 100644 --- a/libraries/Ticker/src/Ticker.cpp +++ b/libraries/Ticker/src/Ticker.cpp @@ -34,7 +34,17 @@ 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); +} + 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_us(uint32_t micros, bool repeat, callback_with_arg_t callback, void* arg) { esp_timer_create_args_t _timerConfig; _timerConfig.arg = reinterpret_cast(arg); @@ -47,10 +57,10 @@ void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t } esp_timer_create(&_timerConfig, &_timer); if (repeat) { - esp_timer_start_periodic(_timer, milliseconds * 1000); + esp_timer_start_periodic(_timer, micros); } else { - esp_timer_start_once(_timer, milliseconds * 1000); + esp_timer_start_once(_timer, micros); } } @@ -59,6 +69,7 @@ void Ticker::detach() { esp_timer_stop(_timer); esp_timer_delete(_timer); _timer = nullptr; + _callback_function = nullptr; } } @@ -70,6 +81,6 @@ bool Ticker::active() const void Ticker::_static_callback(void* arg) { Ticker* _this = reinterpret_cast(arg); - if (!_this) return; - if (_this->_callback_function) _this->_callback_function(); + if (_this && _this->_callback_function) + _this->_callback_function(); } diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index cc816f96815..88a79962c3a 100644 --- a/libraries/Ticker/src/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -42,7 +42,7 @@ class Ticker void attach(float seconds, callback_function_t callback) { _callback_function = std::move(callback); - _attach_ms(seconds * 1000, true, _static_callback, this); + _attach_s(seconds, true, _static_callback, this); } void attach_ms(uint32_t milliseconds, callback_function_t callback) @@ -51,6 +51,12 @@ class Ticker _attach_ms(milliseconds, true, _static_callback, this); } + 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) { @@ -58,7 +64,7 @@ class Ticker // C-cast serves two purposes: // static_cast for smaller integer types, // reinterpret_cast + const_cast for pointer types - _attach_ms(seconds * 1000, true, reinterpret_cast(callback), (void*)arg); + _attach_s(seconds, true, reinterpret_cast(callback), (void*)arg); } template @@ -68,10 +74,17 @@ class Ticker _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(float seconds, callback_function_t callback) { _callback_function = std::move(callback); - _attach_ms(seconds * 1000, false, _static_callback, this); + _attach_s(seconds, false, _static_callback, this); } void once_ms(uint32_t milliseconds, callback_function_t callback) @@ -80,11 +93,17 @@ class Ticker _attach_ms(milliseconds, false, _static_callback, this); } + 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_ms(seconds * 1000, false, reinterpret_cast(callback), (void*)arg); + _attach_s(seconds, false, reinterpret_cast(callback), (void*)arg); } template @@ -94,6 +113,13 @@ class Ticker _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; @@ -103,8 +129,11 @@ class Ticker callback_function_t _callback_function = nullptr; -protected: 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); }; From cc9aa7df9c7ebfea449e7ffb0ed6e2e84f3eb247 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 16:06:36 +0200 Subject: [PATCH 07/49] Unify Ticker examples. --- .../Ticker/examples/Arguments/Arguments.ino | 27 -------- .../examples/TickerBasic/TickerBasic.ino | 45 +++++++++++++ .../TickerFunctional/TickerFunctional.ino | 64 +++++++++++++++++++ .../TickerParameter/TickerParameter.ino | 35 ++++++++++ 4 files changed, 144 insertions(+), 27 deletions(-) delete mode 100644 libraries/Ticker/examples/Arguments/Arguments.ino create mode 100644 libraries/Ticker/examples/TickerBasic/TickerBasic.ino create mode 100644 libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino create mode 100644 libraries/Ticker/examples/TickerParameter/TickerParameter.ino 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/TickerBasic/TickerBasic.ino b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino new file mode 100644 index 00000000000..2fabd983159 --- /dev/null +++ b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino @@ -0,0 +1,45 @@ +/* + 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 + +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/TickerFunctional/TickerFunctional.ino b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino new file mode 100644 index 00000000000..387e6d6bcb0 --- /dev/null +++ b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino @@ -0,0 +1,64 @@ +#include +#include + +#define LED1 2 +#define LED2 4 +#define LED3 12 +#define LED4 14 +#define LED5 15 + + +class ExampleClass { + public: + ExampleClass(int pin, int duration) : _pin(pin), _duration(duration) { + pinMode(_pin, OUTPUT); + _myTicker.attach_ms(_duration, std::bind(&ExampleClass::classBlink, this)); + } + ~ExampleClass() {}; + + int _pin, _duration; + Ticker _myTicker; + + void classBlink() { + digitalWrite(_pin, !digitalRead(_pin)); + } +}; + +void staticBlink() { + digitalWrite(LED2, !digitalRead(LED2)); +} + +void scheduledBlink() { + digitalWrite(LED3, !digitalRead(LED2)); +} + +void parameterBlink(int p) { + digitalWrite(p, !digitalRead(p)); +} + +Ticker staticTicker; +Ticker scheduledTicker; +Ticker parameterTicker; +Ticker lambdaTicker; + +ExampleClass example(LED1, 100); + + +void setup() { + pinMode(LED2, OUTPUT); + staticTicker.attach_ms(100, staticBlink); + + pinMode(LED3, OUTPUT); + scheduledTicker.attach_ms_scheduled(100, scheduledBlink); + + pinMode(LED4, OUTPUT); + parameterTicker.attach_ms(100, std::bind(parameterBlink, LED4)); + + pinMode(LED5, OUTPUT); + lambdaTicker.attach_ms(100, []() { + digitalWrite(LED5, !digitalRead(LED5)); + }); +} + +void loop() { +} diff --git a/libraries/Ticker/examples/TickerParameter/TickerParameter.ino b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino new file mode 100644 index 00000000000..67c6256b5b7 --- /dev/null +++ b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino @@ -0,0 +1,35 @@ +/* + 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 + +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() { +} From b6b5afd815e9c12a95576ac884e457602ebf5150 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 16:26:11 +0200 Subject: [PATCH 08/49] Default for LED_BUILTIN --- libraries/Ticker/examples/TickerBasic/TickerBasic.ino | 4 ++++ libraries/Ticker/examples/TickerParameter/TickerParameter.ino | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/libraries/Ticker/examples/TickerBasic/TickerBasic.ino b/libraries/Ticker/examples/TickerBasic/TickerBasic.ino index 2fabd983159..a387b554b71 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/TickerParameter/TickerParameter.ino b/libraries/Ticker/examples/TickerParameter/TickerParameter.ino index 67c6256b5b7..99c69c62567 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 tickerSetLow; From 2f2b66c570c734833ee2ac57911ae554e6b53ab2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 16:36:25 +0200 Subject: [PATCH 09/49] In Ticker, the *scheduled functions become available in another development branch. --- .../TickerFunctional/TickerFunctional.ino | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino diff --git a/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino b/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino deleted file mode 100644 index 387e6d6bcb0..00000000000 --- a/libraries/Ticker/examples/TickerFunctional/TickerFunctional.ino +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include - -#define LED1 2 -#define LED2 4 -#define LED3 12 -#define LED4 14 -#define LED5 15 - - -class ExampleClass { - public: - ExampleClass(int pin, int duration) : _pin(pin), _duration(duration) { - pinMode(_pin, OUTPUT); - _myTicker.attach_ms(_duration, std::bind(&ExampleClass::classBlink, this)); - } - ~ExampleClass() {}; - - int _pin, _duration; - Ticker _myTicker; - - void classBlink() { - digitalWrite(_pin, !digitalRead(_pin)); - } -}; - -void staticBlink() { - digitalWrite(LED2, !digitalRead(LED2)); -} - -void scheduledBlink() { - digitalWrite(LED3, !digitalRead(LED2)); -} - -void parameterBlink(int p) { - digitalWrite(p, !digitalRead(p)); -} - -Ticker staticTicker; -Ticker scheduledTicker; -Ticker parameterTicker; -Ticker lambdaTicker; - -ExampleClass example(LED1, 100); - - -void setup() { - pinMode(LED2, OUTPUT); - staticTicker.attach_ms(100, staticBlink); - - pinMode(LED3, OUTPUT); - scheduledTicker.attach_ms_scheduled(100, scheduledBlink); - - pinMode(LED4, OUTPUT); - parameterTicker.attach_ms(100, std::bind(parameterBlink, LED4)); - - pinMode(LED5, OUTPUT); - lambdaTicker.attach_ms(100, []() { - digitalWrite(LED5, !digitalRead(LED5)); - }); -} - -void loop() { -} From 28f71f33b021cfaaba47294d62a1fe5566648d9a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 22:58:51 +0200 Subject: [PATCH 10/49] Astyle from ESP8266 --- libraries/Ticker/examples/Blinker/Blinker.ino | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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() { - + } From af944b4854c1b7e66985ee4c6c2e3d9366ef7744 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 12:43:07 +0200 Subject: [PATCH 11/49] Add hooks for sketches/libraries to perform work on each yield() and each completed loop. --- cores/esp32/Arduino.h | 3 +++ cores/esp32/esp32-hal-misc.c | 47 ++++++++++++++++++++---------------- cores/esp32/main.cpp | 4 +++ 3 files changed, 33 insertions(+), 21 deletions(-) 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/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 From e7d54115f3e7142a96579743d47230006ebecc0b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 5 May 2019 04:00:59 +0200 Subject: [PATCH 12/49] Add Schedule.(h|cpp) from ESP8266 to run off loopTask. (cherry picked from commit d39be670e98e1a99e6675bd3958d9f4a03c74db8) --- CMakeLists.txt | 1 + cores/esp32/Arduino.h | 1 + cores/esp32/Schedule.cpp | 78 ++++++++++++++++++++++++++++++++++++++++ cores/esp32/Schedule.h | 27 ++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 cores/esp32/Schedule.cpp create mode 100644 cores/esp32/Schedule.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 32979ad4acf..08725e1303d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set(CORE_SRCS cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c + cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp cores/esp32/wiring_pulse.c diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h index bc7ab85b779..a736ac5e15f 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -156,6 +156,7 @@ void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val); #include "Udp.h" #include "HardwareSerial.h" #include "Esp.h" +#include "Schedule.h" using std::abs; using std::isinf; diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp new file mode 100644 index 00000000000..27b9731954d --- /dev/null +++ b/cores/esp32/Schedule.cpp @@ -0,0 +1,78 @@ +#include "Schedule.h" + +struct scheduled_fn_t +{ + scheduled_fn_t* mNext; + std::function mFunc; +}; + +static scheduled_fn_t* sFirst = 0; +static scheduled_fn_t* sLast = 0; + +static scheduled_fn_t* sFirstUnused = 0; +static scheduled_fn_t* sLastUnused = 0; + +static int sCount = 0; + +static scheduled_fn_t* get_fn() { + scheduled_fn_t* result = NULL; + // try to get an item from unused items list + if (sFirstUnused) { + result = sFirstUnused; + sFirstUnused = result->mNext; + if (sFirstUnused == NULL) { + sLastUnused = NULL; + } + } + // if no unused items, and count not too high, allocate a new one + else if (sCount != SCHEDULED_FN_MAX_COUNT) { + result = new scheduled_fn_t; + result->mNext = NULL; + ++sCount; + } + return result; +} + +static void recycle_fn(scheduled_fn_t* fn) +{ + if (!sLastUnused) { + sFirstUnused = fn; + } + else { + sLastUnused->mNext = fn; + } + fn->mNext = NULL; + sLastUnused = fn; +} + +bool schedule_function(std::function fn) +{ + scheduled_fn_t* item = get_fn(); + if (!item) { + return false; + } + item->mFunc = fn; + item->mNext = NULL; + if (!sFirst) { + sFirst = item; + } + else { + sLast->mNext = item; + } + sLast = item; + return true; +} + +void run_scheduled_functions() +{ + scheduled_fn_t* rFirst = sFirst; + sFirst = NULL; + sLast = NULL; + while (rFirst) { + scheduled_fn_t* item = rFirst; + rFirst = item->mNext; + item->mFunc(); + item->mFunc = std::function(); + recycle_fn(item); + } +} diff --git a/cores/esp32/Schedule.h b/cores/esp32/Schedule.h new file mode 100644 index 00000000000..3399972945d --- /dev/null +++ b/cores/esp32/Schedule.h @@ -0,0 +1,27 @@ +#ifndef ESP_SCHEDULE_H +#define ESP_SCHEDULE_H + +#include + +#define SCHEDULED_FN_MAX_COUNT 32 +#define SCHEDULED_FN_INITIAL_COUNT 4 + +// Warning +// This API is not considered stable. +// Function signatures will change. +// You have been warned. + +// Run given function next time `loop` function returns, +// or `run_scheduled_functions` is called. +// Use std::bind to pass arguments to a function, or call a class member function. +// Note: there is no mechanism for cancelling scheduled functions. +// Keep that in mind when binding functions to objects which may have short lifetime. +// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. +bool schedule_function(std::function fn); + +// Run all scheduled functions. +// Use this function if your are not using `loop`, or `loop` does not return +// on a regular basis. +void run_scheduled_functions(); + +#endif //ESP_SCHEDULE_H From 37cebc1eee3eba24338df5608aba42f771e5699c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 28 May 2019 21:39:46 +0200 Subject: [PATCH 13/49] Updated with latest sources and ESP8266/ESP32 portability. --- cores/esp32/PolledTimeout.h | 289 ++++++++++++++++++++++++++++++ cores/esp32/Schedule.cpp | 128 +++++++------ cores/esp32/Schedule.h | 67 +++++-- cores/esp32/circular_queue.h | 336 +++++++++++++++++++++++++++++++++++ 4 files changed, 743 insertions(+), 77 deletions(-) create mode 100644 cores/esp32/PolledTimeout.h create mode 100644 cores/esp32/circular_queue.h diff --git a/cores/esp32/PolledTimeout.h b/cores/esp32/PolledTimeout.h new file mode 100644 index 00000000000..fe9c9ca4bcd --- /dev/null +++ b/cores/esp32/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/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index 27b9731954d..4aeef57d760 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -1,78 +1,88 @@ + #include "Schedule.h" +#include "PolledTimeout.h" +#ifdef ESP8266 +#include "interrupts.h" +#else +#include +#endif +#include "circular_queue.h" + +typedef std::function mFuncT; struct scheduled_fn_t { - scheduled_fn_t* mNext; - std::function mFunc; -}; + mFuncT mFunc; + esp8266::polledTimeout::periodicFastUs callNow; + schedule_e policy; -static scheduled_fn_t* sFirst = 0; -static scheduled_fn_t* sLast = 0; + scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { } +}; -static scheduled_fn_t* sFirstUnused = 0; -static scheduled_fn_t* sLastUnused = 0; +namespace { + static circular_queue_mp schedule_queue(SCHEDULED_FN_MAX_COUNT); +#ifndef ESP8266 + std::mutex schedulerMutex; +#endif +}; -static int sCount = 0; +bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t repeat_us, schedule_e policy) +{ + scheduled_fn_t item; + item.mFunc = std::move(fn); + if (repeat_us) item.callNow.reset(repeat_us); + return schedule_queue.push(std::move(item)); + item.policy = policy; +} -static scheduled_fn_t* get_fn() { - scheduled_fn_t* result = NULL; - // try to get an item from unused items list - if (sFirstUnused) { - result = sFirstUnused; - sFirstUnused = result->mNext; - if (sFirstUnused == NULL) { - sLastUnused = NULL; - } - } - // if no unused items, and count not too high, allocate a new one - else if (sCount != SCHEDULED_FN_MAX_COUNT) { - result = new scheduled_fn_t; - result->mNext = NULL; - ++sCount; - } - return result; +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); } -static void recycle_fn(scheduled_fn_t* fn) +bool IRAM_ATTR schedule_function(std::function&& fn, schedule_e policy) { - if (!sLastUnused) { - sFirstUnused = fn; - } - else { - sLastUnused->mNext = fn; - } - fn->mNext = NULL; - sLastUnused = fn; + return schedule_function_us([fn = std::move(fn)]() { fn(); return false; }, 0, policy); } -bool schedule_function(std::function fn) +bool IRAM_ATTR schedule_function(const std::function& fn, schedule_e policy) { - scheduled_fn_t* item = get_fn(); - if (!item) { - return false; - } - item->mFunc = fn; - item->mNext = NULL; - if (!sFirst) { - sFirst = item; - } - else { - sLast->mNext = item; - } - sLast = item; - return true; + return schedule_function(std::function(fn), policy); } -void run_scheduled_functions() +void run_scheduled_functions(schedule_e policy) { - scheduled_fn_t* rFirst = sFirst; - sFirst = NULL; - sLast = NULL; - while (rFirst) { - scheduled_fn_t* item = rFirst; - rFirst = item->mNext; - item->mFunc(); - item->mFunc = std::function(); - recycle_fn(item); + // 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; + + // 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](scheduled_fn_t& func) + { + return + (func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && policy != SCHEDULE_FUNCTION_FROM_LOOP) + || !func.callNow + || func.mFunc(); + }); + fence = false; } diff --git a/cores/esp32/Schedule.h b/cores/esp32/Schedule.h index 3399972945d..7b959a84963 100644 --- a/cores/esp32/Schedule.h +++ b/cores/esp32/Schedule.h @@ -3,25 +3,56 @@ #include +// 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. + #define SCHEDULED_FN_MAX_COUNT 32 -#define SCHEDULED_FN_INITIAL_COUNT 4 - -// Warning -// This API is not considered stable. -// Function signatures will change. -// You have been warned. - -// Run given function next time `loop` function returns, -// or `run_scheduled_functions` is called. -// Use std::bind to pass arguments to a function, or call a class member function. -// Note: there is no mechanism for cancelling scheduled functions. -// Keep that in mind when binding functions to objects which may have short lifetime. -// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. -bool schedule_function(std::function fn); - -// Run all scheduled functions. + +enum schedule_e +{ + SCHEDULE_FUNCTION_FROM_LOOP, + SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS +}; + +// * 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); + +// Run all scheduled functions. // Use this function if your are not using `loop`, or `loop` does not return // on a regular basis. -void run_scheduled_functions(); +void run_scheduled_functions(schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); -#endif //ESP_SCHEDULE_H +#endif //ESP_SCHEDULE_H \ No newline at end of file diff --git a/cores/esp32/circular_queue.h b/cores/esp32/circular_queue.h new file mode 100644 index 00000000000..b183d5990fd --- /dev/null +++ b/cores/esp32/circular_queue.h @@ -0,0 +1,336 @@ +/* +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 + +template< typename T > class circular_queue +{ +public: + circular_queue() : m_bufSize(1) + { + m_inPos.store(0); + m_outPos.store(0); + } + 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; + + size_t capacity() const + { + return m_bufSize - 1; + } + + 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; + } + + void flush() + { + m_outPos.store(m_inPos.load()); + } + + size_t available() const + { + int avail = static_cast(m_inPos.load() - m_outPos.load()); + if (avail < 0) avail += m_bufSize; + return avail; + } + + 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; + } + + 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]; + } + + 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; + } + + bool IRAM_ATTR push(const T& val) + { + return push(T(val)); + } + + 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; + } + + 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; + } + + 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; + } + + 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; +}; + +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; + + 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)); + } + + bool IRAM_ATTR push(const T& val) + { +#ifdef ESP8266 + InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(val); + } + + 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); + } + + 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; + } + + 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 From bfe87399a223654255ac107b16367ace75dd5bdb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 28 May 2019 22:17:53 +0200 Subject: [PATCH 14/49] Misordering resulting from merge fixed. --- cores/esp32/Schedule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index 4aeef57d760..c0f786c5c53 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -31,8 +31,8 @@ bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t rep scheduled_fn_t item; item.mFunc = std::move(fn); if (repeat_us) item.callNow.reset(repeat_us); - return schedule_queue.push(std::move(item)); 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) From b5b6dc966ebca4165494ed5d37306b30c6ae606c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 29 May 2019 06:57:39 +0200 Subject: [PATCH 15/49] Yield in scheduling loop to prevent wdt elapsing. --- cores/esp32/Schedule.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index c0f786c5c53..4baf66e9909 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -1,8 +1,8 @@ - #include "Schedule.h" #include "PolledTimeout.h" #ifdef ESP8266 #include "interrupts.h" +#include "coredecls.h" #else #include #endif @@ -29,9 +29,9 @@ namespace { bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t repeat_us, schedule_e policy) { scheduled_fn_t item; + item.policy = policy; item.mFunc = std::move(fn); if (repeat_us) item.callNow.reset(repeat_us); - item.policy = policy; return schedule_queue.push(std::move(item)); } @@ -70,15 +70,24 @@ void run_scheduled_functions(schedule_e policy) return; } fence = true; - - // 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](scheduled_fn_t& func) + + 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) { +#ifdef ESP8266 + cont_yield(g_pcont); +#else + yield(); +#endif + } return (func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && policy != SCHEDULE_FUNCTION_FROM_LOOP) || !func.callNow From 65d94678c8805e5d0f689ba85c8697d3be1e849c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 00:45:29 +0200 Subject: [PATCH 16/49] Ticker and Schedule updated from ESP8266 Arduino squash! Ticker and Schedule updated from ESP8266 Arduino --- CMakeLists.txt | 1 + cores/esp32/Schedule.cpp | 133 ++++++++++++++++++++++------------ cores/esp32/Ticker.cpp | 75 +++++++++++++++++++ cores/esp32/Ticker.h | 132 +++++++++++++++++++++++++++++++++ libraries/Ticker/src/Ticker.h | 6 ++ 5 files changed, 299 insertions(+), 48 deletions(-) create mode 100644 cores/esp32/Ticker.cpp create mode 100644 cores/esp32/Ticker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 08725e1303d..8300a6cfaed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ set(CORE_SRCS cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp + cores/esp32/Ticker.cpp cores/esp32/wiring_pulse.c cores/esp32/wiring_shift.c cores/esp32/WMath.cpp diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index 4baf66e9909..7ce77348083 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -1,5 +1,6 @@ #include "Schedule.h" #include "PolledTimeout.h" +#include "Ticker.h" #ifdef ESP8266 #include "interrupts.h" #include "coredecls.h" @@ -10,88 +11,124 @@ typedef std::function mFuncT; +constexpr uint32_t TICKER_MIN_US = 5000; + struct scheduled_fn_t { - mFuncT mFunc; - esp8266::polledTimeout::periodicFastUs callNow; - schedule_e policy; - - scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { } + mFuncT mFunc = nullptr; + esp8266::polledTimeout::periodicFastUs callNow; + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP; + scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { } }; namespace { - static circular_queue_mp schedule_queue(SCHEDULED_FN_MAX_COUNT); + static circular_queue_mp schedule_queue(SCHEDULED_FN_MAX_COUNT); #ifndef ESP8266 - std::mutex schedulerMutex; + std::mutex schedulerMutex; #endif + + void ticker_scheduled(Ticker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) + { + auto repeat_ms = (repeat_us + 500) / 1000; + ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() + { + 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); + } + }); + } }; bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t repeat_us, schedule_e policy) { - scheduled_fn_t item; - item.policy = policy; - item.mFunc = std::move(fn); - if (repeat_us) item.callNow.reset(repeat_us); - return schedule_queue.push(std::move(item)); + if (repeat_us >= TICKER_MIN_US) + { + auto ticker = new Ticker; + if (!ticker) return false; + 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); + 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); + 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); + 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). + // 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; - { + static bool fence = false; + { #ifdef ESP8266 - InterruptLock lockAllInterruptsInThisScope; + InterruptLock lockAllInterruptsInThisScope; #else - std::lock_guard lock(schedulerMutex); + std::lock_guard lock(schedulerMutex); #endif - if (fence) { - // prevent recursive calls from yield() - return; - } - fence = true; - } + if (fence) { + // prevent recursive calls from yield() + return; + } + fence = true; + } - esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms + 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) { + // 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) { #ifdef ESP8266 - cont_yield(g_pcont); + cont_yield(g_pcont); #else - yield(); + yield(); #endif - } - return - (func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && policy != SCHEDULE_FUNCTION_FROM_LOOP) - || !func.callNow - || func.mFunc(); - }); - fence = false; + } + return + (func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && policy != SCHEDULE_FUNCTION_FROM_LOOP) + || !func.callNow + || func.mFunc(); + }); + fence = false; } diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp new file mode 100644 index 00000000000..a92c474ec42 --- /dev/null +++ b/cores/esp32/Ticker.cpp @@ -0,0 +1,75 @@ +/* + 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. + + 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 "Ticker.h" + +Ticker::Ticker() + : _timer(nullptr) +{ +} + +Ticker::~Ticker() +{ + detach(); +} + +void Ticker::_attach_ms(uint32_t milliseconds, 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, milliseconds * 1000); + } + else { + esp_timer_start_once(_timer, milliseconds * 1000); + } +} + +void Ticker::detach() { + if (_timer) { + esp_timer_stop(_timer); + esp_timer_delete(_timer); + _timer = nullptr; + } +} + +bool Ticker::active() const +{ + return _timer; +} + +void Ticker::_static_callback(void* arg) +{ + Ticker* _this = reinterpret_cast(arg); + if (!_this) return; + if (_this->_callback_function) _this->_callback_function(); +} diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h new file mode 100644 index 00000000000..ce9146797a1 --- /dev/null +++ b/cores/esp32/Ticker.h @@ -0,0 +1,132 @@ +/* + 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. + + 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 TICKER_H +#define TICKER_H + +extern "C" { +#include "esp_timer.h" +} +#include +#include + +class Ticker +{ +public: + 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_ms(seconds * 1000, 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); + } + + 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_ms(seconds * 1000, true, callback, 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, callback, 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_ms(seconds * 1000, 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); + } + + 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_ms(seconds * 1000, false, callback, 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, callback, arg); + } + + void detach(); + bool active() const; + +protected: + 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; + +protected: + esp_timer_handle_t _timer; +}; + + +#endif//TICKER_H diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index 88a79962c3a..b91da36ac16 100644 --- a/libraries/Ticker/src/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -29,6 +29,7 @@ extern "C" { #include "esp_timer.h" } #include +#include class Ticker { @@ -55,6 +56,11 @@ class Ticker { _callback_function = std::move(callback); _attach_us(micros, true, _static_callback, this); + + void attach_ms(uint32_t milliseconds, callback_function_t callback) + { + _callback_function = std::move(callback); + _attach_ms(milliseconds, true, _static_callback, this); } template From 42d535003c86e8860bd6a1229bbca1404a6a5de7 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 11:29:12 +0200 Subject: [PATCH 17/49] Port Schedule to ESP32. Same yield behavior as on ESP8266 - scheduler has policies for loop and yield initiated runs. --- cores/esp32/Schedule.cpp | 24 +++++++++++++----------- cores/esp32/Schedule.h | 27 +++++++++++++++++++++------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index 7ce77348083..edf0cbdadc6 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -31,17 +31,17 @@ namespace { { auto repeat_ms = (repeat_us + 500) / 1000; ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() - { - 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); - } - }); + 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); + } + }); } }; @@ -119,8 +119,10 @@ void run_scheduled_functions(schedule_e policy) schedule_queue.for_each_requeue([policy, &yieldNow](scheduled_fn_t& func) { if (yieldNow) { -#ifdef ESP8266 +#if defined(ESP8266) cont_yield(g_pcont); +#elif defined(ESP32) + vPortYield(); #else yield(); #endif diff --git a/cores/esp32/Schedule.h b/cores/esp32/Schedule.h index 7b959a84963..eeb1dac06fa 100644 --- a/cores/esp32/Schedule.h +++ b/cores/esp32/Schedule.h @@ -1,8 +1,6 @@ #ifndef ESP_SCHEDULE_H #define ESP_SCHEDULE_H -#include - // This API is stabilizing // Function signatures may change, internal queue will remain FIFO. // @@ -24,13 +22,21 @@ // * 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 -enum schedule_e +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, @@ -50,9 +56,18 @@ 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. -void run_scheduled_functions(schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP); -#endif //ESP_SCHEDULE_H \ No newline at end of file +#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 From bc62a5aefbb3dfad8f35fd5d0ca712339a6e00d5 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 3 Jun 2019 13:44:29 +0200 Subject: [PATCH 18/49] circular_queue.h with API comments. --- cores/esp32/circular_queue.h | 625 ++++++++++++++++++++--------------- 1 file changed, 364 insertions(+), 261 deletions(-) diff --git a/cores/esp32/circular_queue.h b/cores/esp32/circular_queue.h index b183d5990fd..83c01b968b3 100644 --- a/cores/esp32/circular_queue.h +++ b/cores/esp32/circular_queue.h @@ -37,299 +37,402 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #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: - circular_queue() : m_bufSize(1) - { - m_inPos.store(0); - m_outPos.store(0); - } - 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; - - size_t capacity() const - { - return m_bufSize - 1; - } - - 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; - } - - void flush() - { - m_outPos.store(m_inPos.load()); - } - - size_t available() const - { - int avail = static_cast(m_inPos.load() - m_outPos.load()); - if (avail < 0) avail += m_bufSize; - return avail; - } - - 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; - } - - 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]; - } - - 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; - } - - bool IRAM_ATTR push(const T& val) - { - return push(T(val)); - } - - 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; - } - - 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; - } - - 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; - } - - 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); - } - } + /*! + @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; + 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; - - bool capacity(const size_t cap) - { + 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; + InterruptLock lock; #else - std::lock_guard lock(m_pushMtx); + std::lock_guard lock(m_pushMtx); #endif - return circular_queue::capacity(cap); - } + return circular_queue::capacity(cap); + } - bool IRAM_ATTR push(T&& val) - { + bool IRAM_ATTR push(T&& val) + { #ifdef ESP8266 - InterruptLock lock; + InterruptLock lock; #else - std::lock_guard lock(m_pushMtx); + std::lock_guard lock(m_pushMtx); #endif - return circular_queue::push(std::move(val)); - } - - bool IRAM_ATTR push(const T& val) - { + 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; + InterruptLock lock; #else - std::lock_guard lock(m_pushMtx); + std::lock_guard lock(m_pushMtx); #endif - return circular_queue::push(val); - } - - size_t push_n(const T* buffer, size_t size) - { + 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; + InterruptLock lock; #else - std::lock_guard lock(m_pushMtx); + std::lock_guard lock(m_pushMtx); #endif - return circular_queue::push_n(buffer, size); - } - - T& pop_requeue() - { + 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; + InterruptLock lock; #else - std::lock_guard lock(m_pushMtx); + 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; - } - - 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)) - { + 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; + InterruptLock lock; #else - std::lock_guard lock(m_pushMtx); + 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; - } + 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; + std::mutex m_pushMtx; #endif }; From 35018f4027f9aac644884495c68915cbe7d3676c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 3 Jun 2019 15:44:15 +0200 Subject: [PATCH 19/49] Provide own memory management for Ticker instances to conserve heap. Insightful source code remark for future refactoring --- cores/esp32/Schedule.cpp | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index edf0cbdadc6..d80674a7fd7 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -21,13 +21,42 @@ struct scheduled_fn_t 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(Ticker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) + void ticker_scheduled(SchedulerTicker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) { auto repeat_ms = (repeat_us + 500) / 1000; ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() @@ -49,8 +78,12 @@ bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t rep { if (repeat_us >= TICKER_MIN_US) { - auto ticker = new Ticker; - if (!ticker) return false; + // 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); From 274159b5d7c52c2190bf9e630ef1c18cb8d31bfa Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 00:14:46 +0200 Subject: [PATCH 20/49] =?UTF-8?q?Use=20=C2=B5s=20resolution=20in=20Ticker?= =?UTF-8?q?=20from=20Schedule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cores/esp32/Schedule.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index d80674a7fd7..c85a983de10 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -11,8 +11,6 @@ typedef std::function mFuncT; -constexpr uint32_t TICKER_MIN_US = 5000; - struct scheduled_fn_t { mFuncT mFunc = nullptr; @@ -58,8 +56,7 @@ namespace { void ticker_scheduled(SchedulerTicker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) { - auto repeat_ms = (repeat_us + 500) / 1000; - ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() + ticker->once_us(repeat_us, [ticker, fn, repeat_us, policy]() { if (!schedule_function([ticker, fn, repeat_us, policy]() { @@ -72,6 +69,12 @@ namespace { } }); } + +#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) From ce691dd0f56d3f7118f79ced62408e221f9e30e0 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 00:40:39 +0200 Subject: [PATCH 21/49] Adapt to ESP32/ESP8266 native ticker resolution. --- cores/esp32/Schedule.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index c85a983de10..9358bb1ed7c 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -56,7 +56,12 @@ namespace { 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]() { From 7b5489e5015cca4e27b8102bffe4faa7b46ddf80 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 09:50:38 +0200 Subject: [PATCH 22/49] Return Ticker to libraries only for modularity. squash! Return Ticker to libraries only for modularity. --- CMakeLists.txt | 1 - cores/esp32/Ticker.cpp | 75 ------------------- cores/esp32/Ticker.h | 132 ---------------------------------- libraries/Ticker/src/Ticker.h | 37 ++++++++-- 4 files changed, 31 insertions(+), 214 deletions(-) delete mode 100644 cores/esp32/Ticker.cpp delete mode 100644 cores/esp32/Ticker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8300a6cfaed..08725e1303d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,6 @@ set(CORE_SRCS cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp - cores/esp32/Ticker.cpp cores/esp32/wiring_pulse.c cores/esp32/wiring_shift.c cores/esp32/WMath.cpp diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp deleted file mode 100644 index a92c474ec42..00000000000 --- a/cores/esp32/Ticker.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - 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. - - 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 "Ticker.h" - -Ticker::Ticker() - : _timer(nullptr) -{ -} - -Ticker::~Ticker() -{ - detach(); -} - -void Ticker::_attach_ms(uint32_t milliseconds, 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, milliseconds * 1000); - } - else { - esp_timer_start_once(_timer, milliseconds * 1000); - } -} - -void Ticker::detach() { - if (_timer) { - esp_timer_stop(_timer); - esp_timer_delete(_timer); - _timer = nullptr; - } -} - -bool Ticker::active() const -{ - return _timer; -} - -void Ticker::_static_callback(void* arg) -{ - Ticker* _this = reinterpret_cast(arg); - if (!_this) return; - if (_this->_callback_function) _this->_callback_function(); -} diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h deleted file mode 100644 index ce9146797a1..00000000000 --- a/cores/esp32/Ticker.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - 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. - - 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 TICKER_H -#define TICKER_H - -extern "C" { -#include "esp_timer.h" -} -#include -#include - -class Ticker -{ -public: - 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_ms(seconds * 1000, 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); - } - - 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_ms(seconds * 1000, true, callback, 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, callback, 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_ms(seconds * 1000, 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); - } - - 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_ms(seconds * 1000, false, callback, 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, callback, arg); - } - - void detach(); - bool active() const; - -protected: - 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; - -protected: - esp_timer_handle_t _timer; -}; - - -#endif//TICKER_H diff --git a/libraries/Ticker/src/Ticker.h b/libraries/Ticker/src/Ticker.h index b91da36ac16..771bb6478b3 100644 --- a/libraries/Ticker/src/Ticker.h +++ b/libraries/Ticker/src/Ticker.h @@ -40,27 +40,37 @@ class 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(uint32_t micros, callback_function_t callback) + void attach_us_scheduled(uint32_t micros, callback_function_t callback) { - _callback_function = std::move(callback); - _attach_us(micros, true, _static_callback, this); + attach_us(micros, [callback]() { schedule_function(callback); }); + } - void attach_ms(uint32_t milliseconds, callback_function_t callback) + void attach_us(uint32_t micros, callback_function_t callback) { _callback_function = std::move(callback); - _attach_ms(milliseconds, true, _static_callback, this); + _attach_us(micros, true, _static_callback, this); } template @@ -86,19 +96,34 @@ class Ticker 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); From 13554677079300e970f29f6f13fcca1bbfe538b9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 12:09:47 +0200 Subject: [PATCH 23/49] Make entry points into sequential cont scheduler weak functions Move Schedule to libraries --- CMakeLists.txt | 3 ++- cores/esp32/Arduino.h | 1 - libraries/Schedule/library.properties | 9 +++++++++ .../Schedule/src/PolledTimeout}/PolledTimeout.h | 0 .../esp32 => libraries/Schedule/src}/Schedule.cpp | 14 ++++++++++++-- {cores/esp32 => libraries/Schedule/src}/Schedule.h | 0 .../Schedule/src/circular_queue}/circular_queue.h | 0 7 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 libraries/Schedule/library.properties rename {cores/esp32 => libraries/Schedule/src/PolledTimeout}/PolledTimeout.h (100%) rename {cores/esp32 => libraries/Schedule/src}/Schedule.cpp (94%) rename {cores/esp32 => libraries/Schedule/src}/Schedule.h (100%) rename {cores/esp32 => libraries/Schedule/src/circular_queue}/circular_queue.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08725e1303d..8ee4c01f3ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ set(CORE_SRCS cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c - cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp cores/esp32/wiring_pulse.c @@ -52,6 +51,7 @@ set(LIBRARY_SRCS 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 @@ -191,6 +191,7 @@ set(COMPONENT_ADD_INCLUDEDIRS libraries/HTTPUpdate/src libraries/NetBIOS/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 a736ac5e15f..bc7ab85b779 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -156,7 +156,6 @@ void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val); #include "Udp.h" #include "HardwareSerial.h" #include "Esp.h" -#include "Schedule.h" using std::abs; using std::isinf; 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/cores/esp32/PolledTimeout.h b/libraries/Schedule/src/PolledTimeout/PolledTimeout.h similarity index 100% rename from cores/esp32/PolledTimeout.h rename to libraries/Schedule/src/PolledTimeout/PolledTimeout.h diff --git a/cores/esp32/Schedule.cpp b/libraries/Schedule/src/Schedule.cpp similarity index 94% rename from cores/esp32/Schedule.cpp rename to libraries/Schedule/src/Schedule.cpp index 9358bb1ed7c..183d33b4adc 100644 --- a/cores/esp32/Schedule.cpp +++ b/libraries/Schedule/src/Schedule.cpp @@ -1,5 +1,5 @@ #include "Schedule.h" -#include "PolledTimeout.h" +#include "PolledTimeout/PolledTimeout.h" #include "Ticker.h" #ifdef ESP8266 #include "interrupts.h" @@ -7,7 +7,17 @@ #else #include #endif -#include "circular_queue.h" +#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; diff --git a/cores/esp32/Schedule.h b/libraries/Schedule/src/Schedule.h similarity index 100% rename from cores/esp32/Schedule.h rename to libraries/Schedule/src/Schedule.h diff --git a/cores/esp32/circular_queue.h b/libraries/Schedule/src/circular_queue/circular_queue.h similarity index 100% rename from cores/esp32/circular_queue.h rename to libraries/Schedule/src/circular_queue/circular_queue.h From c7fc06cd253569d83d1a22813acb6168bec478b4 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 11:03:22 +0200 Subject: [PATCH 24/49] Move PolledTimeout to own libraries for source code compatibility with ESP8266 --- CMakeLists.txt | 1 + libraries/PolledTimeout/library.properties | 9 +++++++++ .../PolledTimeout => PolledTimeout/src}/PolledTimeout.h | 0 libraries/Schedule/src/Schedule.cpp | 8 ++++---- 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 libraries/PolledTimeout/library.properties rename libraries/{Schedule/src/PolledTimeout => PolledTimeout/src}/PolledTimeout.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ee4c01f3ad..e078c351cb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,7 @@ set(COMPONENT_ADD_INCLUDEDIRS libraries/HTTPClient/src libraries/HTTPUpdate/src libraries/NetBIOS/src + libraries/PolledTimeout/src libraries/Preferences/src libraries/Schedule/src libraries/SD_MMC/src 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/Schedule/src/PolledTimeout/PolledTimeout.h b/libraries/PolledTimeout/src/PolledTimeout.h similarity index 100% rename from libraries/Schedule/src/PolledTimeout/PolledTimeout.h rename to libraries/PolledTimeout/src/PolledTimeout.h diff --git a/libraries/Schedule/src/Schedule.cpp b/libraries/Schedule/src/Schedule.cpp index 183d33b4adc..8a36849ef5a 100644 --- a/libraries/Schedule/src/Schedule.cpp +++ b/libraries/Schedule/src/Schedule.cpp @@ -1,9 +1,9 @@ #include "Schedule.h" -#include "PolledTimeout/PolledTimeout.h" -#include "Ticker.h" +#include +#include #ifdef ESP8266 -#include "interrupts.h" -#include "coredecls.h" +#include +#include #else #include #endif From fda9f1ec645b192f209343f576c2957ea1a4236f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 5 May 2019 04:00:59 +0200 Subject: [PATCH 25/49] Add Schedule.(h|cpp) from ESP8266 to run off loopTask. (cherry picked from commit d39be670e98e1a99e6675bd3958d9f4a03c74db8) --- CMakeLists.txt | 1 + cores/esp32/Arduino.h | 1 + cores/esp32/Schedule.cpp | 78 ++++++++++++++++++++++++++++++++++++++++ cores/esp32/Schedule.h | 27 ++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 cores/esp32/Schedule.cpp create mode 100644 cores/esp32/Schedule.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e078c351cb2..b88aad1cc9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set(CORE_SRCS cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c + cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp cores/esp32/wiring_pulse.c diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h index bc7ab85b779..a736ac5e15f 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -156,6 +156,7 @@ void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val); #include "Udp.h" #include "HardwareSerial.h" #include "Esp.h" +#include "Schedule.h" using std::abs; using std::isinf; diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp new file mode 100644 index 00000000000..27b9731954d --- /dev/null +++ b/cores/esp32/Schedule.cpp @@ -0,0 +1,78 @@ +#include "Schedule.h" + +struct scheduled_fn_t +{ + scheduled_fn_t* mNext; + std::function mFunc; +}; + +static scheduled_fn_t* sFirst = 0; +static scheduled_fn_t* sLast = 0; + +static scheduled_fn_t* sFirstUnused = 0; +static scheduled_fn_t* sLastUnused = 0; + +static int sCount = 0; + +static scheduled_fn_t* get_fn() { + scheduled_fn_t* result = NULL; + // try to get an item from unused items list + if (sFirstUnused) { + result = sFirstUnused; + sFirstUnused = result->mNext; + if (sFirstUnused == NULL) { + sLastUnused = NULL; + } + } + // if no unused items, and count not too high, allocate a new one + else if (sCount != SCHEDULED_FN_MAX_COUNT) { + result = new scheduled_fn_t; + result->mNext = NULL; + ++sCount; + } + return result; +} + +static void recycle_fn(scheduled_fn_t* fn) +{ + if (!sLastUnused) { + sFirstUnused = fn; + } + else { + sLastUnused->mNext = fn; + } + fn->mNext = NULL; + sLastUnused = fn; +} + +bool schedule_function(std::function fn) +{ + scheduled_fn_t* item = get_fn(); + if (!item) { + return false; + } + item->mFunc = fn; + item->mNext = NULL; + if (!sFirst) { + sFirst = item; + } + else { + sLast->mNext = item; + } + sLast = item; + return true; +} + +void run_scheduled_functions() +{ + scheduled_fn_t* rFirst = sFirst; + sFirst = NULL; + sLast = NULL; + while (rFirst) { + scheduled_fn_t* item = rFirst; + rFirst = item->mNext; + item->mFunc(); + item->mFunc = std::function(); + recycle_fn(item); + } +} diff --git a/cores/esp32/Schedule.h b/cores/esp32/Schedule.h new file mode 100644 index 00000000000..3399972945d --- /dev/null +++ b/cores/esp32/Schedule.h @@ -0,0 +1,27 @@ +#ifndef ESP_SCHEDULE_H +#define ESP_SCHEDULE_H + +#include + +#define SCHEDULED_FN_MAX_COUNT 32 +#define SCHEDULED_FN_INITIAL_COUNT 4 + +// Warning +// This API is not considered stable. +// Function signatures will change. +// You have been warned. + +// Run given function next time `loop` function returns, +// or `run_scheduled_functions` is called. +// Use std::bind to pass arguments to a function, or call a class member function. +// Note: there is no mechanism for cancelling scheduled functions. +// Keep that in mind when binding functions to objects which may have short lifetime. +// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. +bool schedule_function(std::function fn); + +// Run all scheduled functions. +// Use this function if your are not using `loop`, or `loop` does not return +// on a regular basis. +void run_scheduled_functions(); + +#endif //ESP_SCHEDULE_H From 562fbcb37ba4d01f0f149776df789e1a14bf0de3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 11:29:12 +0200 Subject: [PATCH 26/49] Port Schedule to ESP32. Same yield behavior as on ESP8266 - scheduler has policies for loop and yield initiated runs. --- cores/esp32/Schedule.cpp | 176 ++++++++++++++++++++++++++------------- cores/esp32/Schedule.h | 80 ++++++++++++++---- 2 files changed, 180 insertions(+), 76 deletions(-) diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp index 27b9731954d..edf0cbdadc6 100644 --- a/cores/esp32/Schedule.cpp +++ b/cores/esp32/Schedule.cpp @@ -1,78 +1,136 @@ #include "Schedule.h" +#include "PolledTimeout.h" +#include "Ticker.h" +#ifdef ESP8266 +#include "interrupts.h" +#include "coredecls.h" +#else +#include +#endif +#include "circular_queue.h" + +typedef std::function mFuncT; + +constexpr uint32_t TICKER_MIN_US = 5000; struct scheduled_fn_t { - scheduled_fn_t* mNext; - std::function mFunc; + mFuncT mFunc = nullptr; + esp8266::polledTimeout::periodicFastUs callNow; + schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP; + scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { } }; -static scheduled_fn_t* sFirst = 0; -static scheduled_fn_t* sLast = 0; +namespace { + static circular_queue_mp schedule_queue(SCHEDULED_FN_MAX_COUNT); +#ifndef ESP8266 + std::mutex schedulerMutex; +#endif -static scheduled_fn_t* sFirstUnused = 0; -static scheduled_fn_t* sLastUnused = 0; + void ticker_scheduled(Ticker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) + { + auto repeat_ms = (repeat_us + 500) / 1000; + ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() + { + 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); + } + }); + } +}; -static int sCount = 0; +bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t repeat_us, schedule_e policy) +{ + if (repeat_us >= TICKER_MIN_US) + { + auto ticker = new Ticker; + if (!ticker) return false; + 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)); + } +} -static scheduled_fn_t* get_fn() { - scheduled_fn_t* result = NULL; - // try to get an item from unused items list - if (sFirstUnused) { - result = sFirstUnused; - sFirstUnused = result->mNext; - if (sFirstUnused == NULL) { - sLastUnused = NULL; - } - } - // if no unused items, and count not too high, allocate a new one - else if (sCount != SCHEDULED_FN_MAX_COUNT) { - result = new scheduled_fn_t; - result->mNext = NULL; - ++sCount; - } - return result; +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); } -static void recycle_fn(scheduled_fn_t* fn) +bool IRAM_ATTR schedule_function(std::function&& fn, schedule_e policy) { - if (!sLastUnused) { - sFirstUnused = fn; - } - else { - sLastUnused->mNext = fn; - } - fn->mNext = NULL; - sLastUnused = fn; + return schedule_function_us([fn = std::move(fn)]() { fn(); return false; }, 0, policy); } -bool schedule_function(std::function fn) +bool IRAM_ATTR schedule_function(const std::function& fn, schedule_e policy) { - scheduled_fn_t* item = get_fn(); - if (!item) { - return false; - } - item->mFunc = fn; - item->mNext = NULL; - if (!sFirst) { - sFirst = item; - } - else { - sLast->mNext = item; - } - sLast = item; - return true; + return schedule_function(std::function(fn), policy); } -void run_scheduled_functions() +void run_scheduled_functions(schedule_e policy) { - scheduled_fn_t* rFirst = sFirst; - sFirst = NULL; - sLast = NULL; - while (rFirst) { - scheduled_fn_t* item = rFirst; - rFirst = item->mNext; - item->mFunc(); - item->mFunc = std::function(); - recycle_fn(item); - } + // 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/cores/esp32/Schedule.h b/cores/esp32/Schedule.h index 3399972945d..eeb1dac06fa 100644 --- a/cores/esp32/Schedule.h +++ b/cores/esp32/Schedule.h @@ -1,27 +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 -#define SCHEDULED_FN_INITIAL_COUNT 4 - -// Warning -// This API is not considered stable. -// Function signatures will change. -// You have been warned. - -// Run given function next time `loop` function returns, -// or `run_scheduled_functions` is called. -// Use std::bind to pass arguments to a function, or call a class member function. -// Note: there is no mechanism for cancelling scheduled functions. -// Keep that in mind when binding functions to objects which may have short lifetime. -// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. -bool schedule_function(std::function fn); - -// Run all scheduled functions. + +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. -void run_scheduled_functions(); + +#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 From 487a7b046344563f5d9a037a32253f07b00b5898 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 12:09:47 +0200 Subject: [PATCH 27/49] Make entry points into sequential cont scheduler weak functions Move Schedule to libraries --- CMakeLists.txt | 1 - cores/esp32/Arduino.h | 1 - cores/esp32/Schedule.cpp | 136 ----------------------------------- cores/esp32/Schedule.h | 73 ------------------- cores/esp32/esp32-hal-misc.c | 2 +- 5 files changed, 1 insertion(+), 212 deletions(-) delete mode 100644 cores/esp32/Schedule.cpp delete mode 100644 cores/esp32/Schedule.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b88aad1cc9d..e078c351cb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ set(CORE_SRCS cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c - cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp cores/esp32/wiring_pulse.c diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h index a736ac5e15f..bc7ab85b779 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -156,7 +156,6 @@ void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val); #include "Udp.h" #include "HardwareSerial.h" #include "Esp.h" -#include "Schedule.h" using std::abs; using std::isinf; diff --git a/cores/esp32/Schedule.cpp b/cores/esp32/Schedule.cpp deleted file mode 100644 index edf0cbdadc6..00000000000 --- a/cores/esp32/Schedule.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "Schedule.h" -#include "PolledTimeout.h" -#include "Ticker.h" -#ifdef ESP8266 -#include "interrupts.h" -#include "coredecls.h" -#else -#include -#endif -#include "circular_queue.h" - -typedef std::function mFuncT; - -constexpr uint32_t TICKER_MIN_US = 5000; - -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) { } -}; - -namespace { - static circular_queue_mp schedule_queue(SCHEDULED_FN_MAX_COUNT); -#ifndef ESP8266 - std::mutex schedulerMutex; -#endif - - void ticker_scheduled(Ticker* ticker, const std::function& fn, uint32_t repeat_us, schedule_e policy) - { - auto repeat_ms = (repeat_us + 500) / 1000; - ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]() - { - 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); - } - }); - } -}; - -bool IRAM_ATTR schedule_function_us(std::function&& fn, uint32_t repeat_us, schedule_e policy) -{ - if (repeat_us >= TICKER_MIN_US) - { - auto ticker = new Ticker; - if (!ticker) return false; - 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/cores/esp32/Schedule.h b/cores/esp32/Schedule.h deleted file mode 100644 index eeb1dac06fa..00000000000 --- a/cores/esp32/Schedule.h +++ /dev/null @@ -1,73 +0,0 @@ -#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/cores/esp32/esp32-hal-misc.c b/cores/esp32/esp32-hal-misc.c index e636ba93eab..7f96ed0f825 100644 --- a/cores/esp32/esp32-hal-misc.c +++ b/cores/esp32/esp32-hal-misc.c @@ -160,7 +160,7 @@ void yield() void delay(uint32_t ms) { - vTaskDelay(ms / portTICK_PERIOD_MS); + vTaskDelay(ms / portTICK_PERIOD_MS); yield_completed(); } From f307262e7899e0f4dddb07c319ceb521f680e3f3 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 6 Jun 2019 12:43:07 +0200 Subject: [PATCH 28/49] Add hooks for sketches/libraries to perform work on each yield() and each completed loop. --- cores/esp32/esp32-hal-misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp32/esp32-hal-misc.c b/cores/esp32/esp32-hal-misc.c index 7f96ed0f825..e636ba93eab 100644 --- a/cores/esp32/esp32-hal-misc.c +++ b/cores/esp32/esp32-hal-misc.c @@ -160,7 +160,7 @@ void yield() void delay(uint32_t ms) { - vTaskDelay(ms / portTICK_PERIOD_MS); + vTaskDelay(ms / portTICK_PERIOD_MS); yield_completed(); } From adcc3801efee154defe4653040e8de523584219f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 5 May 2019 04:00:59 +0200 Subject: [PATCH 29/49] Add Schedule.(h|cpp) from ESP8266 to run off loopTask. (cherry picked from commit d39be670e98e1a99e6675bd3958d9f4a03c74db8) squash! Add Schedule.(h|cpp) from ESP8266 to run off loopTask. squash! Add Schedule.(h|cpp) from ESP8266 to run off loopTask. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e078c351cb2..b88aad1cc9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set(CORE_SRCS cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c + cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp cores/esp32/wiring_pulse.c From 5972c52ccf0ec3e3967d45bee9fa125f48142483 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 00:45:29 +0200 Subject: [PATCH 30/49] Ticker and Schedule updated from ESP8266 Arduino squash! Ticker and Schedule updated from ESP8266 Arduino --- CMakeLists.txt | 2 +- cores/esp32/Ticker.cpp | 75 +++++++++++++++++++++++ cores/esp32/Ticker.h | 132 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 cores/esp32/Ticker.cpp create mode 100644 cores/esp32/Ticker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b88aad1cc9d..ff4fdb35d07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,9 @@ set(CORE_SRCS cores/esp32/MD5Builder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c - cores/esp32/Schedule.cpp cores/esp32/Stream.cpp cores/esp32/StreamString.cpp + cores/esp32/Ticker.cpp cores/esp32/wiring_pulse.c cores/esp32/wiring_shift.c cores/esp32/WMath.cpp diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp new file mode 100644 index 00000000000..a92c474ec42 --- /dev/null +++ b/cores/esp32/Ticker.cpp @@ -0,0 +1,75 @@ +/* + 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. + + 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 "Ticker.h" + +Ticker::Ticker() + : _timer(nullptr) +{ +} + +Ticker::~Ticker() +{ + detach(); +} + +void Ticker::_attach_ms(uint32_t milliseconds, 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, milliseconds * 1000); + } + else { + esp_timer_start_once(_timer, milliseconds * 1000); + } +} + +void Ticker::detach() { + if (_timer) { + esp_timer_stop(_timer); + esp_timer_delete(_timer); + _timer = nullptr; + } +} + +bool Ticker::active() const +{ + return _timer; +} + +void Ticker::_static_callback(void* arg) +{ + Ticker* _this = reinterpret_cast(arg); + if (!_this) return; + if (_this->_callback_function) _this->_callback_function(); +} diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h new file mode 100644 index 00000000000..ce9146797a1 --- /dev/null +++ b/cores/esp32/Ticker.h @@ -0,0 +1,132 @@ +/* + 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. + + 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 TICKER_H +#define TICKER_H + +extern "C" { +#include "esp_timer.h" +} +#include +#include + +class Ticker +{ +public: + 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_ms(seconds * 1000, 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); + } + + 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_ms(seconds * 1000, true, callback, 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, callback, 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_ms(seconds * 1000, 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); + } + + 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_ms(seconds * 1000, false, callback, 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, callback, arg); + } + + void detach(); + bool active() const; + +protected: + 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; + +protected: + esp_timer_handle_t _timer; +}; + + +#endif//TICKER_H From b9689050354641df0cfa4682b82f512c316db59e Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 May 2019 09:38:07 +0200 Subject: [PATCH 31/49] Remove special support for FunctionalInterrupt from core interrupt handling: - VIP treatment in general attach/detach/handling looks untidy: extern symbols without matching header include but copy&paste special under-the-hood c++ delete support, where everyone else has to do their own resource tracking as usual. - FunctionalInterrupt is not used by anything inside core ESP32 Arduino for 8 months now, it can be a library, which reduces precious core size for everyone else. This reverts commit ea61563c690ea4c41cc8cfa808799f84bcfa0540. --- CMakeLists.txt | 1 - cores/esp32/esp32-hal-gpio.c | 23 ++----------------- .../FunctionalInterrupt.cpp | 2 +- .../FunctionalInterrupt.h | 0 .../FunctionalInterrupt.ino | 8 +++---- 5 files changed, 7 insertions(+), 27 deletions(-) rename {cores/esp32 => libraries/ESP32/examples/GPIO/FunctionalInterrupt}/FunctionalInterrupt.cpp (97%) rename {cores/esp32 => libraries/ESP32/examples/GPIO/FunctionalInterrupt}/FunctionalInterrupt.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff4fdb35d07..ec4f4f92be3 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 diff --git a/cores/esp32/esp32-hal-gpio.c b/cores/esp32/esp32-hal-gpio.c index d22af774c81..1870eb1676f 100644 --- a/cores/esp32/esp32-hal-gpio.c +++ b/cores/esp32/esp32-hal-gpio.c @@ -74,7 +74,6 @@ typedef void (*voidFuncPtrArg)(void*); typedef struct { voidFuncPtr fn; void* arg; - bool functional; } InterruptHandle_t; static InterruptHandle_t __pinInterruptHandlers[GPIO_PIN_COUNT] = {0,}; @@ -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,25 +260,15 @@ 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; diff --git a/cores/esp32/FunctionalInterrupt.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp similarity index 97% rename from cores/esp32/FunctionalInterrupt.cpp rename to libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp index d2e6dfd4236..97bf239eb51 100644 --- a/cores/esp32/FunctionalInterrupt.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp @@ -6,7 +6,7 @@ */ #include "FunctionalInterrupt.h" -#include "Arduino.h" +#include typedef void (*voidFuncPtr)(void); typedef void (*voidFuncPtrArg)(void*); diff --git a/cores/esp32/FunctionalInterrupt.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.h similarity index 100% rename from cores/esp32/FunctionalInterrupt.h rename to libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.h diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index 0e9f97414bb..c076745af76 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -1,5 +1,5 @@ #include -#include +#include "FunctionalInterrupt.h" #define BUTTON1 16 #define BUTTON2 17 @@ -29,8 +29,8 @@ public: private: const uint8_t PIN; - volatile uint32_t numberKeyPresses; - volatile bool pressed; + volatile uint32_t numberKeyPresses; + volatile bool pressed; }; Button button1(BUTTON1); @@ -38,7 +38,7 @@ Button button2(BUTTON2); void setup() { - Serial.begin(115200); + Serial.begin(115200); } void loop() { From f1416fd0333d05f51dcd1343f23294887b852765 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 May 2019 13:10:02 +0200 Subject: [PATCH 32/49] Add supporting function for interrupt arg memory management, linke with functional interrupts. --- cores/esp32/esp32-hal-gpio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cores/esp32/esp32-hal-gpio.c b/cores/esp32/esp32-hal-gpio.c index 1870eb1676f..a7708ef63e7 100644 --- a/cores/esp32/esp32-hal-gpio.c +++ b/cores/esp32/esp32-hal-gpio.c @@ -275,6 +275,10 @@ extern void __detachInterrupt(uint8_t pin) esp_intr_enable(gpio_intr_handle); } +extern InterruptHandle_t* __getInterruptHandler(uint8_t pin) { + return (pin < GPIO_PIN_COUNT) ? &__pinInterruptHandlers[pin] : NULL; +} + 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"))); From 549f2ab41a5ee6917f33074d926e7f52abfa959c Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 May 2019 13:12:22 +0200 Subject: [PATCH 33/49] Bring back FunctionalInterrupt, and ScheduledFunctions from ESP8266, as example code. It should be decided whether to reinstate these in the core, or make them an optional library. --- .../FunctionalInterrupt.cpp | 44 ------- .../FunctionalInterrupt/FunctionalInterrupt.h | 20 --- .../FunctionalInterrupt.ino | 18 ++- .../FunctionalInterrupts.cpp | 121 ++++++++++++++++++ .../FunctionalInterrupts.h | 37 ++++++ .../GPIO/FunctionalInterrupt/Schedule.cpp | 78 +++++++++++ .../GPIO/FunctionalInterrupt/Schedule.h | 27 ++++ .../ScheduledFunctions.cpp | 117 +++++++++++++++++ .../FunctionalInterrupt/ScheduledFunctions.h | 51 ++++++++ 9 files changed, 445 insertions(+), 68 deletions(-) delete mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp delete mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.h create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp deleted file mode 100644 index 97bf239eb51..00000000000 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * FunctionalInterrupt.cpp - * - * Created on: 8 jul. 2018 - * Author: Herman - */ - -#include "FunctionalInterrupt.h" -#include - -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/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.h deleted file mode 100644 index b5e3181f986..00000000000 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/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/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index c076745af76..5f631069914 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -1,21 +1,31 @@ #include -#include "FunctionalInterrupt.h" +#include "FunctionalInterrupts.h" +#if defined(ESP8266) || defined(ARDUINO_D1_MINI32) +#define BUTTON1 D3 +#define BUTTON2 D4 +#else #define BUTTON1 16 #define BUTTON2 17 +#endif class Button { public: - Button(uint8_t reqPin) : PIN(reqPin){ + Button(uint8_t reqPin) : PIN(reqPin) { pinMode(PIN, INPUT_PULLUP); attachInterrupt(PIN, std::bind(&Button::isr,this), FALLING); }; ~Button() { - detachInterrupt(PIN); + detachFunctionalInterrupt(PIN); } - void IRAM_ATTR isr() { +#if defined(ESP8266) + void ICACHE_RAM_ATTR isr() +#elif defined(ESP32) + void IRAM_ATTR isr() +#endif + { numberKeyPresses += 1; pressed = true; } diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp new file mode 100644 index 00000000000..3426e146770 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp @@ -0,0 +1,121 @@ +#include "FunctionalInterrupts.h" +#include "Schedule.h" +#include "Arduino.h" + +#if defined(ESP8266) + +// Duplicate typedefs from core_esp8266_wiring_digital.cpp +// Keep in sync +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrArg)(void*); + +typedef struct { + uint8_t mode; + voidFuncPtr fn; + void* arg; +} interrupt_handler_t; + +// Helper functions for Functional interrupt routines +extern "C" interrupt_handler_t* __getInterruptHandler(uint8_t pin); + +#elif defined(ESP32) + +// Duplicate typedefs from esp32-hal-gpio.c +// Keep in sync +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrArg)(void*); +typedef struct { + voidFuncPtr fn; + void* arg; +} InterruptHandle_t; + +// Helper functions for Functional interrupt routines +extern "C" InterruptHandle_t* __getInterruptHandler(uint8_t pin); + +#endif + +void ICACHE_RAM_ATTR interruptFunctional(void* arg) +{ + ArgStructure* localArg = static_cast(arg); + if (localArg->interruptInfo) + { + localArg->interruptInfo->value = digitalRead(localArg->interruptInfo->pin); + localArg->interruptInfo->micro = micros(); + } + if (localArg->functionInfo->reqScheduledFunction) + { + schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo)))); + } + if (localArg->functionInfo->reqFunction) + { + localArg->functionInfo->reqFunction(); + } +} + + void cleanupFunctional(void* arg) + { + ArgStructure* localArg = static_cast(arg); + delete localArg; + } + +void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) +{ + // use the local interrupt routine which takes the ArgStructure as argument + +#if defined(ESP8266) + interrupt_handler_t* handler = __getInterruptHandler(pin); +#elif defined(ESP32) + InterruptHandle_t* handler = __getInterruptHandler(pin); +#endif + if (handler->arg) + { + cleanupFunctional(handler->arg); + } + + FunctionInfo* fi = new FunctionInfo; + fi->reqFunction = intRoutine; + + ArgStructure* as = new ArgStructure; + as->functionInfo = fi; + + ::attachInterruptArg (pin, static_cast(interruptFunctional), as, mode); +} + +void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode) +{ +#if defined(ESP8266) + interrupt_handler_t* handler = __getInterruptHandler(pin); +#elif defined(ESP32) + InterruptHandle_t* handler = __getInterruptHandler(pin); +#endif + if (handler->arg) + { + cleanupFunctional(handler->arg); + } + + InterruptInfo* ii = new InterruptInfo(pin); + + FunctionInfo* fi = new FunctionInfo; + fi->reqScheduledFunction = scheduledIntRoutine; + + ArgStructure* as = new ArgStructure; + as->interruptInfo = ii; + as->functionInfo = fi; + + ::attachInterruptArg (pin, static_cast(interruptFunctional), as, mode); +} + +void detachFunctionalInterrupt(uint8_t pin) +{ +#if defined(ESP8266) + interrupt_handler_t* handler = __getInterruptHandler(pin); +#elif defined(ESP32) + InterruptHandle_t* handler = __getInterruptHandler(pin); +#endif + if (handler->arg) + { + cleanupFunctional(handler->arg); + } + ::detachInterrupt (pin); +} + diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h new file mode 100644 index 00000000000..ef75a9525f1 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h @@ -0,0 +1,37 @@ +#ifndef FUNCTIONALINTERRUPTS_H +#define FUNCTIONALINTERRUPTS_H + +#include +#include +#include +#include "ScheduledFunctions.h" + +// Structures for communication + +struct InterruptInfo { + InterruptInfo(uint8_t _pin) : pin(_pin) {} + const uint8_t pin; + uint8_t value = 0; + uint32_t micro = 0; +}; + +struct FunctionInfo { + std::function reqFunction = nullptr; + std::function reqScheduledFunction = nullptr; +}; + +struct ArgStructure { + ~ArgStructure() + { + delete functionInfo; + delete interruptInfo; + } + InterruptInfo* interruptInfo = nullptr; + FunctionInfo* functionInfo = nullptr; +}; + +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 //INTERRUPTS_H diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp new file mode 100644 index 00000000000..27b9731954d --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp @@ -0,0 +1,78 @@ +#include "Schedule.h" + +struct scheduled_fn_t +{ + scheduled_fn_t* mNext; + std::function mFunc; +}; + +static scheduled_fn_t* sFirst = 0; +static scheduled_fn_t* sLast = 0; + +static scheduled_fn_t* sFirstUnused = 0; +static scheduled_fn_t* sLastUnused = 0; + +static int sCount = 0; + +static scheduled_fn_t* get_fn() { + scheduled_fn_t* result = NULL; + // try to get an item from unused items list + if (sFirstUnused) { + result = sFirstUnused; + sFirstUnused = result->mNext; + if (sFirstUnused == NULL) { + sLastUnused = NULL; + } + } + // if no unused items, and count not too high, allocate a new one + else if (sCount != SCHEDULED_FN_MAX_COUNT) { + result = new scheduled_fn_t; + result->mNext = NULL; + ++sCount; + } + return result; +} + +static void recycle_fn(scheduled_fn_t* fn) +{ + if (!sLastUnused) { + sFirstUnused = fn; + } + else { + sLastUnused->mNext = fn; + } + fn->mNext = NULL; + sLastUnused = fn; +} + +bool schedule_function(std::function fn) +{ + scheduled_fn_t* item = get_fn(); + if (!item) { + return false; + } + item->mFunc = fn; + item->mNext = NULL; + if (!sFirst) { + sFirst = item; + } + else { + sLast->mNext = item; + } + sLast = item; + return true; +} + +void run_scheduled_functions() +{ + scheduled_fn_t* rFirst = sFirst; + sFirst = NULL; + sLast = NULL; + while (rFirst) { + scheduled_fn_t* item = rFirst; + rFirst = item->mNext; + item->mFunc(); + item->mFunc = std::function(); + recycle_fn(item); + } +} diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h new file mode 100644 index 00000000000..3399972945d --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h @@ -0,0 +1,27 @@ +#ifndef ESP_SCHEDULE_H +#define ESP_SCHEDULE_H + +#include + +#define SCHEDULED_FN_MAX_COUNT 32 +#define SCHEDULED_FN_INITIAL_COUNT 4 + +// Warning +// This API is not considered stable. +// Function signatures will change. +// You have been warned. + +// Run given function next time `loop` function returns, +// or `run_scheduled_functions` is called. +// Use std::bind to pass arguments to a function, or call a class member function. +// Note: there is no mechanism for cancelling scheduled functions. +// Keep that in mind when binding functions to objects which may have short lifetime. +// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. +bool schedule_function(std::function fn); + +// Run all scheduled functions. +// Use this function if your are not using `loop`, or `loop` does not return +// on a regular basis. +void run_scheduled_functions(); + +#endif //ESP_SCHEDULE_H diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp new file mode 100644 index 00000000000..25bc58db61c --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp @@ -0,0 +1,117 @@ +/* + * ScheduledFunctions.cpp + * + * Created on: 27 apr. 2018 + * Author: Herman + */ +#include "ScheduledFunctions.h" + +std::list ScheduledFunctions::scheduledFunctions; + +ScheduledFunctions::ScheduledFunctions() +:ScheduledFunctions(UINT_MAX) +{ +} + +ScheduledFunctions::ScheduledFunctions(unsigned int reqMax) +{ + maxElements = reqMax; +} + +ScheduledFunctions::~ScheduledFunctions() { +} + +ScheduledRegistration ScheduledFunctions::insertElement(ScheduledElement se, bool front) +{ + if (countElements >= maxElements) + { + return nullptr; + } + else + { + countElements++; + if (front) + { + scheduledFunctions.push_front(se); + return scheduledFunctions.begin()->registration; + } + else + { + scheduledFunctions.push_back(se); + return scheduledFunctions.rbegin()->registration; + } + } +} + +std::list::iterator ScheduledFunctions::eraseElement(std::list::iterator it) +{ + countElements--; + return scheduledFunctions.erase(it); +} + +bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf, bool continuous, bool front) +{ + return (insertElement({this,continuous,nullptr,sf}, front) == nullptr); +} + +bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf) +{ + return scheduleFunction(sf, false, false); +} + +ScheduledRegistration ScheduledFunctions::scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front) +{ + return insertElement({this,continuous,std::make_shared(1),sf},front); +} + +void ScheduledFunctions::runScheduledFunctions() +{ + auto lastElement = scheduledFunctions.end(); // do not execute elements added during runScheduledFunctions + auto it = scheduledFunctions.begin(); + while (it != lastElement) + { + bool erase = false; + if (it->registration == nullptr) + { + it->function(); + } + else + { + if (it->registration.use_count() > 1) + { + it->function(); + } + else + { + erase = true; + } + } + if ((!it->continuous) || (erase)) + { + it = it->_this->eraseElement(it); + } + else + { + it++; + } + } +} + +void ScheduledFunctions::removeFunction(ScheduledRegistration sr) +{ + auto it = scheduledFunctions.begin(); + bool removed = false; + while ((!removed) && (it != scheduledFunctions.end())) + { + if (it->registration == sr) + { + it = eraseElement(it); + removed = true; + } + else + { + it++; + } + } +} + diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h new file mode 100644 index 00000000000..01296353641 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h @@ -0,0 +1,51 @@ +/* + * ScheduledFunctions.h + * + * Created on: 27 apr. 2018 + * Author: Herman + */ +#include "Arduino.h" +#include "Schedule.h" + +#include +#include +#include +#include + +#ifndef SCHEDULEDFUNCTIONS_H_ +#define SCHEDULEDFUNCTIONS_H_ + +typedef std::function ScheduledFunction; +typedef std::shared_ptr ScheduledRegistration; + +class ScheduledFunctions { + +public: + ScheduledFunctions(); + ScheduledFunctions(unsigned int reqMax); + virtual ~ScheduledFunctions(); + + struct ScheduledElement + { + ScheduledFunctions* _this; + bool continuous; + ScheduledRegistration registration; + ScheduledFunction function; + }; + + ScheduledRegistration insertElement(ScheduledElement se, bool front); + std::list::iterator eraseElement(std::list::iterator); + bool scheduleFunction(ScheduledFunction sf, bool continuous, bool front); + bool scheduleFunction(ScheduledFunction sf); + ScheduledRegistration scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front); + static void runScheduledFunctions(); + void removeFunction(ScheduledRegistration sr); + + + static std::list scheduledFunctions; + unsigned int maxElements; + unsigned int countElements = 0; + +}; + +#endif /* SCHEDULEDFUNCTIONS_H_ */ From d192d3c263b08fd7cde23c11c09cac10ace774cb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 May 2019 14:03:04 +0200 Subject: [PATCH 34/49] Ridiculous port number mistake. --- .../GPIO/FunctionalInterrupt/FunctionalInterrupt.ino | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index 5f631069914..491d1962ff7 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -1,12 +1,15 @@ #include #include "FunctionalInterrupts.h" -#if defined(ESP8266) || defined(ARDUINO_D1_MINI32) -#define BUTTON1 D3 -#define BUTTON2 D4 -#else +#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 From ad3a24af2745ef13cf9896aea8a758d50d16c268 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 May 2019 17:42:18 +0200 Subject: [PATCH 35/49] Apply astyle to example INO. --- .../FunctionalInterrupt.ino | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index 491d1962ff7..b386b1d83f6 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -12,38 +12,37 @@ #define BUTTON2 0 #endif -class Button -{ -public: - Button(uint8_t reqPin) : PIN(reqPin) { - pinMode(PIN, INPUT_PULLUP); - attachInterrupt(PIN, std::bind(&Button::isr,this), FALLING); - }; - ~Button() { - detachFunctionalInterrupt(PIN); - } +class Button { + public: + Button(uint8_t reqPin) : PIN(reqPin) { + pinMode(PIN, INPUT_PULLUP); + attachInterrupt(PIN, std::bind(&Button::isr, this), FALLING); + }; + ~Button() { + detachFunctionalInterrupt(PIN); + } #if defined(ESP8266) - void ICACHE_RAM_ATTR isr() + void ICACHE_RAM_ATTR isr() #elif defined(ESP32) - void IRAM_ATTR isr() + void IRAM_ATTR isr() #endif - { - 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; + { + 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); @@ -51,10 +50,10 @@ Button button2(BUTTON2); void setup() { - Serial.begin(115200); + Serial.begin(115200); } void loop() { - button1.checkPressed(); - button2.checkPressed(); + button1.checkPressed(); + button2.checkPressed(); } From e530b689d82970f2d9cfd1d6794a34b51882b7af Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 2 May 2019 21:43:27 +0200 Subject: [PATCH 36/49] Remove ScheduledFunctions.(h|cpp), per comment https://github.com/esp8266/Arduino/pull/6038#issuecomment-488758509 --- .../FunctionalInterrupts.h | 1 - .../ScheduledFunctions.cpp | 117 ------------------ .../FunctionalInterrupt/ScheduledFunctions.h | 51 -------- 3 files changed, 169 deletions(-) delete mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp delete mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h index ef75a9525f1..9c5d554e273 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h @@ -4,7 +4,6 @@ #include #include #include -#include "ScheduledFunctions.h" // Structures for communication diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp deleted file mode 100644 index 25bc58db61c..00000000000 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * ScheduledFunctions.cpp - * - * Created on: 27 apr. 2018 - * Author: Herman - */ -#include "ScheduledFunctions.h" - -std::list ScheduledFunctions::scheduledFunctions; - -ScheduledFunctions::ScheduledFunctions() -:ScheduledFunctions(UINT_MAX) -{ -} - -ScheduledFunctions::ScheduledFunctions(unsigned int reqMax) -{ - maxElements = reqMax; -} - -ScheduledFunctions::~ScheduledFunctions() { -} - -ScheduledRegistration ScheduledFunctions::insertElement(ScheduledElement se, bool front) -{ - if (countElements >= maxElements) - { - return nullptr; - } - else - { - countElements++; - if (front) - { - scheduledFunctions.push_front(se); - return scheduledFunctions.begin()->registration; - } - else - { - scheduledFunctions.push_back(se); - return scheduledFunctions.rbegin()->registration; - } - } -} - -std::list::iterator ScheduledFunctions::eraseElement(std::list::iterator it) -{ - countElements--; - return scheduledFunctions.erase(it); -} - -bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf, bool continuous, bool front) -{ - return (insertElement({this,continuous,nullptr,sf}, front) == nullptr); -} - -bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf) -{ - return scheduleFunction(sf, false, false); -} - -ScheduledRegistration ScheduledFunctions::scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front) -{ - return insertElement({this,continuous,std::make_shared(1),sf},front); -} - -void ScheduledFunctions::runScheduledFunctions() -{ - auto lastElement = scheduledFunctions.end(); // do not execute elements added during runScheduledFunctions - auto it = scheduledFunctions.begin(); - while (it != lastElement) - { - bool erase = false; - if (it->registration == nullptr) - { - it->function(); - } - else - { - if (it->registration.use_count() > 1) - { - it->function(); - } - else - { - erase = true; - } - } - if ((!it->continuous) || (erase)) - { - it = it->_this->eraseElement(it); - } - else - { - it++; - } - } -} - -void ScheduledFunctions::removeFunction(ScheduledRegistration sr) -{ - auto it = scheduledFunctions.begin(); - bool removed = false; - while ((!removed) && (it != scheduledFunctions.end())) - { - if (it->registration == sr) - { - it = eraseElement(it); - removed = true; - } - else - { - it++; - } - } -} - diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h deleted file mode 100644 index 01296353641..00000000000 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ScheduledFunctions.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * ScheduledFunctions.h - * - * Created on: 27 apr. 2018 - * Author: Herman - */ -#include "Arduino.h" -#include "Schedule.h" - -#include -#include -#include -#include - -#ifndef SCHEDULEDFUNCTIONS_H_ -#define SCHEDULEDFUNCTIONS_H_ - -typedef std::function ScheduledFunction; -typedef std::shared_ptr ScheduledRegistration; - -class ScheduledFunctions { - -public: - ScheduledFunctions(); - ScheduledFunctions(unsigned int reqMax); - virtual ~ScheduledFunctions(); - - struct ScheduledElement - { - ScheduledFunctions* _this; - bool continuous; - ScheduledRegistration registration; - ScheduledFunction function; - }; - - ScheduledRegistration insertElement(ScheduledElement se, bool front); - std::list::iterator eraseElement(std::list::iterator); - bool scheduleFunction(ScheduledFunction sf, bool continuous, bool front); - bool scheduleFunction(ScheduledFunction sf); - ScheduledRegistration scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front); - static void runScheduledFunctions(); - void removeFunction(ScheduledRegistration sr); - - - static std::list scheduledFunctions; - unsigned int maxElements; - unsigned int countElements = 0; - -}; - -#endif /* SCHEDULEDFUNCTIONS_H_ */ From 2768906b67cd5a963c97d7b0a7ad7491d53e7a5a Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 May 2019 12:10:17 +0200 Subject: [PATCH 37/49] Invented void* detachInterruptArg() that returns the argument given in attachInterruptArg(). This is sufficient for resource management - the function has static duration anyway. --- cores/esp32/esp32-hal-gpio.c | 13 +-- cores/esp32/esp32-hal-gpio.h | 3 +- .../FunctionalInterrupts.cpp | 82 +++++-------------- 3 files changed, 28 insertions(+), 70 deletions(-) diff --git a/cores/esp32/esp32-hal-gpio.c b/cores/esp32/esp32-hal-gpio.c index a7708ef63e7..82dbf818638 100644 --- a/cores/esp32/esp32-hal-gpio.c +++ b/cores/esp32/esp32-hal-gpio.c @@ -75,7 +75,7 @@ typedef struct { voidFuncPtr fn; void* arg; } InterruptHandle_t; -static InterruptHandle_t __pinInterruptHandlers[GPIO_PIN_COUNT] = {0,}; +static InterruptHandle_t __pinInterruptHandlers[GPIO_PIN_COUNT] = { {0,0}, }; #include "driver/rtc_io.h" @@ -275,15 +275,16 @@ extern void __detachInterrupt(uint8_t pin) esp_intr_enable(gpio_intr_handle); } -extern InterruptHandle_t* __getInterruptHandler(uint8_t pin) { - return (pin < GPIO_PIN_COUNT) ? &__pinInterruptHandlers[pin] : NULL; +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/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp index 3426e146770..b1725f84c5f 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp @@ -2,38 +2,6 @@ #include "Schedule.h" #include "Arduino.h" -#if defined(ESP8266) - -// Duplicate typedefs from core_esp8266_wiring_digital.cpp -// Keep in sync -typedef void (*voidFuncPtr)(void); -typedef void (*voidFuncPtrArg)(void*); - -typedef struct { - uint8_t mode; - voidFuncPtr fn; - void* arg; -} interrupt_handler_t; - -// Helper functions for Functional interrupt routines -extern "C" interrupt_handler_t* __getInterruptHandler(uint8_t pin); - -#elif defined(ESP32) - -// Duplicate typedefs from esp32-hal-gpio.c -// Keep in sync -typedef void (*voidFuncPtr)(void); -typedef void (*voidFuncPtrArg)(void*); -typedef struct { - voidFuncPtr fn; - void* arg; -} InterruptHandle_t; - -// Helper functions for Functional interrupt routines -extern "C" InterruptHandle_t* __getInterruptHandler(uint8_t pin); - -#endif - void ICACHE_RAM_ATTR interruptFunctional(void* arg) { ArgStructure* localArg = static_cast(arg); @@ -44,7 +12,9 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg) } if (localArg->functionInfo->reqScheduledFunction) { - schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo)))); + schedule_function( + [reqScheduledFunction = localArg->functionInfo->reqScheduledFunction, + interruptInfo = *localArg->interruptInfo]() { reqScheduledFunction(interruptInfo); }); } if (localArg->functionInfo->reqFunction) { @@ -52,24 +22,20 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg) } } - void cleanupFunctional(void* arg) - { - ArgStructure* localArg = static_cast(arg); - delete localArg; - } +void cleanupFunctional(void* arg) +{ + ArgStructure* localArg = static_cast(arg); + delete localArg; +} void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) { // use the local interrupt routine which takes the ArgStructure as argument -#if defined(ESP8266) - interrupt_handler_t* handler = __getInterruptHandler(pin); -#elif defined(ESP32) - InterruptHandle_t* handler = __getInterruptHandler(pin); -#endif - if (handler->arg) + void* localArg = detachInterruptArg(pin); + if (localArg) { - cleanupFunctional(handler->arg); + cleanupFunctional(localArg); } FunctionInfo* fi = new FunctionInfo; @@ -78,19 +44,15 @@ void attachInterrupt(uint8_t pin, std::function intRoutine, int mode ArgStructure* as = new ArgStructure; as->functionInfo = fi; - ::attachInterruptArg (pin, static_cast(interruptFunctional), as, mode); + attachInterruptArg (pin, interruptFunctional, as, mode); } void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode) { -#if defined(ESP8266) - interrupt_handler_t* handler = __getInterruptHandler(pin); -#elif defined(ESP32) - InterruptHandle_t* handler = __getInterruptHandler(pin); -#endif - if (handler->arg) + void* localArg = detachInterruptArg(pin); + if (localArg) { - cleanupFunctional(handler->arg); + cleanupFunctional(localArg); } InterruptInfo* ii = new InterruptInfo(pin); @@ -102,20 +64,14 @@ void attachScheduledInterrupt(uint8_t pin, std::function sc as->interruptInfo = ii; as->functionInfo = fi; - ::attachInterruptArg (pin, static_cast(interruptFunctional), as, mode); + attachInterruptArg(pin, interruptFunctional, as, mode); } void detachFunctionalInterrupt(uint8_t pin) { -#if defined(ESP8266) - interrupt_handler_t* handler = __getInterruptHandler(pin); -#elif defined(ESP32) - InterruptHandle_t* handler = __getInterruptHandler(pin); -#endif - if (handler->arg) + void* localArg = detachInterruptArg(pin); + if (localArg) { - cleanupFunctional(handler->arg); + cleanupFunctional(localArg); } - ::detachInterrupt (pin); } - From 4960474066fc6dc7b7d1cfb5561121d44c6b2a16 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 May 2019 14:39:30 +0200 Subject: [PATCH 38/49] Modified example to showcase how general FunctionalInterrupt uses violate the ISR in ICACHE_RAM rule (ESP8266 core catches this). --- .../FunctionalInterrupt.ino | 38 +++++++++++++++---- .../FunctionalInterrupts.cpp | 17 ++------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index b386b1d83f6..aae06082ebf 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -1,4 +1,5 @@ #include +#include #include "FunctionalInterrupts.h" #if defined(ESP32) @@ -16,10 +17,15 @@ class Button { public: Button(uint8_t reqPin) : PIN(reqPin) { pinMode(PIN, INPUT_PULLUP); - attachInterrupt(PIN, std::bind(&Button::isr, this), FALLING); + // 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: + //attachInterrupt(PIN, [this]() { isr(); }, FALLING); // works on ESP32; works on ESP8266 + //attachScheduledInterrupt(PIN, [this](InterruptInfo ii) { Serial.print("Pin "); Serial.println(ii.pin); isr(); }, FALLING); // works on ESP32; works on ESP8266 }; ~Button() { - detachFunctionalInterrupt(PIN); + detachInterrupt(PIN); } #if defined(ESP8266) @@ -32,6 +38,15 @@ class Button { 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); @@ -41,19 +56,26 @@ class Button { private: const uint8_t PIN; - volatile uint32_t numberKeyPresses; - volatile bool pressed; + volatile uint32_t numberKeyPresses = 0; + volatile bool pressed = false; }; -Button button1(BUTTON1); -Button button2(BUTTON2); +Button* button1; +Button* button2; void setup() { Serial.begin(115200); + schedule_function([]() { Serial.println("Scheduled function"); }); + Serial.println("FunctionalInterrupt test/example"); + + button1 = new Button(BUTTON1); + button2 = new Button(BUTTON2); + + Serial.println("setup() complete"); } void loop() { - button1.checkPressed(); - button2.checkPressed(); + button1->checkPressed(); + button2->checkPressed(); } diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp index b1725f84c5f..1867e7bd924 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp @@ -1,5 +1,5 @@ #include "FunctionalInterrupts.h" -#include "Schedule.h" +#include #include "Arduino.h" void ICACHE_RAM_ATTR interruptFunctional(void* arg) @@ -33,10 +33,7 @@ void attachInterrupt(uint8_t pin, std::function intRoutine, int mode // use the local interrupt routine which takes the ArgStructure as argument void* localArg = detachInterruptArg(pin); - if (localArg) - { - cleanupFunctional(localArg); - } + if (localArg) cleanupFunctional(localArg); FunctionInfo* fi = new FunctionInfo; fi->reqFunction = intRoutine; @@ -50,10 +47,7 @@ void attachInterrupt(uint8_t pin, std::function intRoutine, int mode void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode) { void* localArg = detachInterruptArg(pin); - if (localArg) - { - cleanupFunctional(localArg); - } + if (localArg) cleanupFunctional(localArg); InterruptInfo* ii = new InterruptInfo(pin); @@ -70,8 +64,5 @@ void attachScheduledInterrupt(uint8_t pin, std::function sc void detachFunctionalInterrupt(uint8_t pin) { void* localArg = detachInterruptArg(pin); - if (localArg) - { - cleanupFunctional(localArg); - } + if (localArg) cleanupFunctional(localArg); } From a55364c594935bd37d90055e04ec28613b974298 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 May 2019 15:26:50 +0200 Subject: [PATCH 39/49] Direct call or scheduled is either-or. --- .../FunctionalInterrupts.cpp | 4 +-- .../FunctionalInterrupts.h | 26 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp index 1867e7bd924..6d990440be1 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp @@ -1,6 +1,6 @@ #include "FunctionalInterrupts.h" #include -#include "Arduino.h" +#include void ICACHE_RAM_ATTR interruptFunctional(void* arg) { @@ -16,7 +16,7 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg) [reqScheduledFunction = localArg->functionInfo->reqScheduledFunction, interruptInfo = *localArg->interruptInfo]() { reqScheduledFunction(interruptInfo); }); } - if (localArg->functionInfo->reqFunction) + else if (localArg->functionInfo->reqFunction) { localArg->functionInfo->reqFunction(); } diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h index 9c5d554e273..b1867f73ad2 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h @@ -1,32 +1,30 @@ #ifndef FUNCTIONALINTERRUPTS_H #define FUNCTIONALINTERRUPTS_H -#include -#include #include // Structures for communication struct InterruptInfo { - InterruptInfo(uint8_t _pin) : pin(_pin) {} - const uint8_t pin; - uint8_t value = 0; - uint32_t micro = 0; + InterruptInfo(uint8_t _pin) : pin(_pin) {} + const uint8_t pin; + uint8_t value = 0; + uint32_t micro = 0; }; struct FunctionInfo { std::function reqFunction = nullptr; - std::function reqScheduledFunction = nullptr; + std::function reqScheduledFunction = nullptr; }; struct ArgStructure { - ~ArgStructure() - { - delete functionInfo; - delete interruptInfo; - } - InterruptInfo* interruptInfo = nullptr; - FunctionInfo* functionInfo = nullptr; + ~ArgStructure() + { + delete functionInfo; + delete interruptInfo; + } + InterruptInfo* interruptInfo = nullptr; + FunctionInfo* functionInfo = nullptr; }; void attachInterrupt(uint8_t pin, std::function intRoutine, int mode); From 16cd6ff12e612cb0e560036c2bf5dd2a9e52ef5b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 May 2019 15:32:44 +0200 Subject: [PATCH 40/49] While Schedule.cpp is not part of ESP32 core... --- .../examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino | 4 +++- .../GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index aae06082ebf..46144cf1d49 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -1,5 +1,5 @@ #include -#include +#include "Schedule.h" #include "FunctionalInterrupts.h" #if defined(ESP32) @@ -78,4 +78,6 @@ void setup() { void loop() { button1->checkPressed(); button2->checkPressed(); + + run_scheduled_functions(); } diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp index 6d990440be1..6b644861a8c 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp @@ -1,5 +1,5 @@ #include "FunctionalInterrupts.h" -#include +#include "Schedule.h" #include void ICACHE_RAM_ATTR interruptFunctional(void* arg) From cc0183d9fd02fa2c2feab634821cbb4eeade359b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 May 2019 15:43:30 +0200 Subject: [PATCH 41/49] Always apply astyle to examples. --- .../FunctionalInterrupt.ino | 10 ++- .../FunctionalInterrupts.cpp | 86 +++++++++++-------- .../FunctionalInterrupts.h | 9 +- .../GPIO/FunctionalInterrupt/Schedule.cpp | 36 +++++--- .../GPIO/FunctionalInterrupt/Schedule.h | 8 +- 5 files changed, 89 insertions(+), 60 deletions(-) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino index 46144cf1d49..bc9c07ba3d8 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino @@ -18,7 +18,9 @@ class Button { 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, [](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: //attachInterrupt(PIN, [this]() { isr(); }, FALLING); // works on ESP32; works on ESP8266 @@ -44,7 +46,7 @@ class Button { static void IRAM_ATTR isr_static(Button* const self) #endif { - self->isr(); + self->isr(); } void checkPressed() { @@ -66,7 +68,9 @@ Button* button2; void setup() { Serial.begin(115200); - schedule_function([]() { Serial.println("Scheduled function"); }); + schedule_function([]() { + Serial.println("Scheduled function"); + }); Serial.println("FunctionalInterrupt test/example"); button1 = new Button(BUTTON1); diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp index 6b644861a8c..a61b24e5e67 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp @@ -4,65 +4,77 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg) { - ArgStructure* localArg = static_cast(arg); - if (localArg->interruptInfo) - { - localArg->interruptInfo->value = digitalRead(localArg->interruptInfo->pin); - localArg->interruptInfo->micro = micros(); - } - if (localArg->functionInfo->reqScheduledFunction) - { - schedule_function( - [reqScheduledFunction = localArg->functionInfo->reqScheduledFunction, - interruptInfo = *localArg->interruptInfo]() { reqScheduledFunction(interruptInfo); }); - } - else if (localArg->functionInfo->reqFunction) - { - localArg->functionInfo->reqFunction(); - } + ArgStructure* localArg = static_cast(arg); + if (localArg->interruptInfo) + { + localArg->interruptInfo->value = digitalRead(localArg->interruptInfo->pin); + localArg->interruptInfo->micro = micros(); + } + if (localArg->functionInfo->reqScheduledFunction) + { + schedule_function( + [reqScheduledFunction = localArg->functionInfo->reqScheduledFunction, + interruptInfo = *localArg->interruptInfo]() + { + reqScheduledFunction(interruptInfo); + }); + } + else if (localArg->functionInfo->reqFunction) + { + localArg->functionInfo->reqFunction(); + } } void cleanupFunctional(void* arg) { - ArgStructure* localArg = static_cast(arg); - delete localArg; + ArgStructure* localArg = static_cast(arg); + delete localArg; } void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) { - // use the local interrupt routine which takes the ArgStructure as argument + // use the local interrupt routine which takes the ArgStructure as argument - void* localArg = detachInterruptArg(pin); - if (localArg) cleanupFunctional(localArg); + void* localArg = detachInterruptArg(pin); + if (localArg) + { + cleanupFunctional(localArg); + } - FunctionInfo* fi = new FunctionInfo; - fi->reqFunction = intRoutine; + FunctionInfo* fi = new FunctionInfo; + fi->reqFunction = intRoutine; - ArgStructure* as = new ArgStructure; - as->functionInfo = fi; + ArgStructure* as = new ArgStructure; + as->functionInfo = fi; - attachInterruptArg (pin, interruptFunctional, as, mode); + attachInterruptArg(pin, interruptFunctional, as, mode); } void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode) { - void* localArg = detachInterruptArg(pin); - if (localArg) cleanupFunctional(localArg); + void* localArg = detachInterruptArg(pin); + if (localArg) + { + cleanupFunctional(localArg); + } - InterruptInfo* ii = new InterruptInfo(pin); + InterruptInfo* ii = new InterruptInfo(pin); - FunctionInfo* fi = new FunctionInfo; - fi->reqScheduledFunction = scheduledIntRoutine; + FunctionInfo* fi = new FunctionInfo; + fi->reqScheduledFunction = scheduledIntRoutine; - ArgStructure* as = new ArgStructure; - as->interruptInfo = ii; - as->functionInfo = fi; + ArgStructure* as = new ArgStructure; + as->interruptInfo = ii; + as->functionInfo = fi; - attachInterruptArg(pin, interruptFunctional, as, mode); + attachInterruptArg(pin, interruptFunctional, as, mode); } void detachFunctionalInterrupt(uint8_t pin) { - void* localArg = detachInterruptArg(pin); - if (localArg) cleanupFunctional(localArg); + void* localArg = detachInterruptArg(pin); + if (localArg) + { + cleanupFunctional(localArg); + } } diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h index b1867f73ad2..a0444f4260c 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h @@ -5,19 +5,22 @@ // Structures for communication -struct InterruptInfo { +struct InterruptInfo +{ InterruptInfo(uint8_t _pin) : pin(_pin) {} const uint8_t pin; uint8_t value = 0; uint32_t micro = 0; }; -struct FunctionInfo { +struct FunctionInfo +{ std::function reqFunction = nullptr; std::function reqScheduledFunction = nullptr; }; -struct ArgStructure { +struct ArgStructure +{ ~ArgStructure() { delete functionInfo; diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp index 27b9731954d..c8eb5d706c0 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp @@ -14,18 +14,22 @@ static scheduled_fn_t* sLastUnused = 0; static int sCount = 0; -static scheduled_fn_t* get_fn() { +static scheduled_fn_t* get_fn() +{ scheduled_fn_t* result = NULL; // try to get an item from unused items list - if (sFirstUnused) { + if (sFirstUnused) + { result = sFirstUnused; sFirstUnused = result->mNext; - if (sFirstUnused == NULL) { + if (sFirstUnused == NULL) + { sLastUnused = NULL; } } // if no unused items, and count not too high, allocate a new one - else if (sCount != SCHEDULED_FN_MAX_COUNT) { + else if (sCount != SCHEDULED_FN_MAX_COUNT) + { result = new scheduled_fn_t; result->mNext = NULL; ++sCount; @@ -35,10 +39,12 @@ static scheduled_fn_t* get_fn() { static void recycle_fn(scheduled_fn_t* fn) { - if (!sLastUnused) { + if (!sLastUnused) + { sFirstUnused = fn; } - else { + else + { sLastUnused->mNext = fn; } fn->mNext = NULL; @@ -48,15 +54,18 @@ static void recycle_fn(scheduled_fn_t* fn) bool schedule_function(std::function fn) { scheduled_fn_t* item = get_fn(); - if (!item) { + if (!item) + { return false; } item->mFunc = fn; item->mNext = NULL; - if (!sFirst) { + if (!sFirst) + { sFirst = item; } - else { + else + { sLast->mNext = item; } sLast = item; @@ -65,10 +74,11 @@ bool schedule_function(std::function fn) void run_scheduled_functions() { - scheduled_fn_t* rFirst = sFirst; - sFirst = NULL; - sLast = NULL; - while (rFirst) { + scheduled_fn_t* rFirst = sFirst; + sFirst = NULL; + sLast = NULL; + while (rFirst) + { scheduled_fn_t* item = rFirst; rFirst = item->mNext; item->mFunc(); diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h index 3399972945d..0868f27c8c7 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h @@ -6,12 +6,12 @@ #define SCHEDULED_FN_MAX_COUNT 32 #define SCHEDULED_FN_INITIAL_COUNT 4 -// Warning -// This API is not considered stable. +// Warning +// This API is not considered stable. // Function signatures will change. // You have been warned. -// Run given function next time `loop` function returns, +// Run given function next time `loop` function returns, // or `run_scheduled_functions` is called. // Use std::bind to pass arguments to a function, or call a class member function. // Note: there is no mechanism for cancelling scheduled functions. @@ -19,7 +19,7 @@ // Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. bool schedule_function(std::function fn); -// Run all scheduled functions. +// Run all scheduled functions. // Use this function if your are not using `loop`, or `loop` does not return // on a regular basis. void run_scheduled_functions(); From 896ec6b55837053a4f382e84e75d29d9b53cd77e Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 11 May 2019 17:45:30 +0200 Subject: [PATCH 42/49] Fix renaming for better PR readability. --- .../FunctionalInterrupt.ino => Functional/Functional.ino} | 2 +- .../FunctionalInterrupt.cpp} | 2 +- .../FunctionalInterrupt.h} | 6 +++--- .../GPIO/{FunctionalInterrupt => Functional}/Schedule.cpp | 0 .../GPIO/{FunctionalInterrupt => Functional}/Schedule.h | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename libraries/ESP32/examples/GPIO/{FunctionalInterrupt/FunctionalInterrupt.ino => Functional/Functional.ino} (98%) rename libraries/ESP32/examples/GPIO/{FunctionalInterrupt/FunctionalInterrupts.cpp => Functional/FunctionalInterrupt.cpp} (98%) rename libraries/ESP32/examples/GPIO/{FunctionalInterrupt/FunctionalInterrupts.h => Functional/FunctionalInterrupt.h} (89%) rename libraries/ESP32/examples/GPIO/{FunctionalInterrupt => Functional}/Schedule.cpp (100%) rename libraries/ESP32/examples/GPIO/{FunctionalInterrupt => Functional}/Schedule.h (100%) diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino b/libraries/ESP32/examples/GPIO/Functional/Functional.ino similarity index 98% rename from libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino rename to libraries/ESP32/examples/GPIO/Functional/Functional.ino index bc9c07ba3d8..873846afd47 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupt.ino +++ b/libraries/ESP32/examples/GPIO/Functional/Functional.ino @@ -1,6 +1,6 @@ #include #include "Schedule.h" -#include "FunctionalInterrupts.h" +#include "FunctionalInterrupt.h" #if defined(ESP32) #define BUTTON1 16 diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp b/libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.cpp similarity index 98% rename from libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp rename to libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.cpp index a61b24e5e67..1bda21fc8d8 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.cpp +++ b/libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.cpp @@ -1,4 +1,4 @@ -#include "FunctionalInterrupts.h" +#include "FunctionalInterrupt.h" #include "Schedule.h" #include diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h b/libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.h similarity index 89% rename from libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h rename to libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.h index a0444f4260c..15678485fd7 100644 --- a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/FunctionalInterrupts.h +++ b/libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.h @@ -1,5 +1,5 @@ -#ifndef FUNCTIONALINTERRUPTS_H -#define FUNCTIONALINTERRUPTS_H +#ifndef FUNCTIONALINTERRUPT_H +#define FUNCTIONALINTERRUPT_H #include @@ -34,4 +34,4 @@ 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 //INTERRUPTS_H +#endif //FUNCTIONALINTERRUPT_H diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp b/libraries/ESP32/examples/GPIO/Functional/Schedule.cpp similarity index 100% rename from libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.cpp rename to libraries/ESP32/examples/GPIO/Functional/Schedule.cpp diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h b/libraries/ESP32/examples/GPIO/Functional/Schedule.h similarity index 100% rename from libraries/ESP32/examples/GPIO/FunctionalInterrupt/Schedule.h rename to libraries/ESP32/examples/GPIO/Functional/Schedule.h From c9d8753fec46da726c1d6ed1ceffcbf43c361402 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 15:44:41 +0200 Subject: [PATCH 43/49] Properly turn FunctionalInterrupt into integral library --- CMakeLists.txt | 2 ++ .../examples}/Functional/Functional.ino | 14 +++++--------- libraries/FunctionalInterrupt/keywords.txt | 14 ++++++++++++++ libraries/FunctionalInterrupt/library.properties | 9 +++++++++ .../src}/FunctionalInterrupt.cpp | 0 .../src}/FunctionalInterrupt.h | 0 6 files changed, 30 insertions(+), 9 deletions(-) rename libraries/{ESP32/examples/GPIO => FunctionalInterrupt/examples}/Functional/Functional.ino (81%) create mode 100644 libraries/FunctionalInterrupt/keywords.txt create mode 100644 libraries/FunctionalInterrupt/library.properties rename libraries/{ESP32/examples/GPIO/Functional => FunctionalInterrupt/src}/FunctionalInterrupt.cpp (100%) rename libraries/{ESP32/examples/GPIO/Functional => FunctionalInterrupt/src}/FunctionalInterrupt.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec4f4f92be3..4378e759464 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ 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 @@ -187,6 +188,7 @@ 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 diff --git a/libraries/ESP32/examples/GPIO/Functional/Functional.ino b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino similarity index 81% rename from libraries/ESP32/examples/GPIO/Functional/Functional.ino rename to libraries/FunctionalInterrupt/examples/Functional/Functional.ino index 873846afd47..69fe24f1a48 100644 --- a/libraries/ESP32/examples/GPIO/Functional/Functional.ino +++ b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino @@ -1,6 +1,5 @@ #include -#include "Schedule.h" -#include "FunctionalInterrupt.h" +#include #if defined(ESP32) #define BUTTON1 16 @@ -18,12 +17,12 @@ class Button { 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, [](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: - //attachInterrupt(PIN, [this]() { isr(); }, FALLING); // works on ESP32; works on ESP8266 + attachInterrupt(PIN, [this]() { isr(); }, FALLING); // works on ESP32; works on ESP8266 //attachScheduledInterrupt(PIN, [this](InterruptInfo ii) { Serial.print("Pin "); Serial.println(ii.pin); isr(); }, FALLING); // works on ESP32; works on ESP8266 }; ~Button() { @@ -68,9 +67,6 @@ Button* button2; void setup() { Serial.begin(115200); - schedule_function([]() { - Serial.println("Scheduled function"); - }); Serial.println("FunctionalInterrupt test/example"); button1 = new Button(BUTTON1); diff --git a/libraries/FunctionalInterrupt/keywords.txt b/libraries/FunctionalInterrupt/keywords.txt new file mode 100644 index 00000000000..0c81c01ace9 --- /dev/null +++ b/libraries/FunctionalInterrupt/keywords.txt @@ -0,0 +1,14 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### + +FunctionalInterrupt KEYWORD1 +InterruptInfo KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +attachInterrupt KEYWORD2 +attachScheduledInterrupt KEYWORD2 +detachFunctionalInterrupt KEYWORD2 diff --git a/libraries/FunctionalInterrupt/library.properties b/libraries/FunctionalInterrupt/library.properties new file mode 100644 index 00000000000..cce101fe044 --- /dev/null +++ b/libraries/FunctionalInterrupt/library.properties @@ -0,0 +1,9 @@ +name=FunctionalInterrupt +version=1.0 +author= +maintainer= +sentence= +paragraph= +category= +url= +architectures=esp32 diff --git a/libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.cpp b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp similarity index 100% rename from libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.cpp rename to libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp diff --git a/libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.h b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h similarity index 100% rename from libraries/ESP32/examples/GPIO/Functional/FunctionalInterrupt.h rename to libraries/FunctionalInterrupt/src/FunctionalInterrupt.h From 260aaafcec174a9ce7768606271e1e1965e0c281 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 16:15:14 +0200 Subject: [PATCH 44/49] remove leftover files --- .../examples/GPIO/Functional/Schedule.cpp | 88 ------------------- .../ESP32/examples/GPIO/Functional/Schedule.h | 27 ------ 2 files changed, 115 deletions(-) delete mode 100644 libraries/ESP32/examples/GPIO/Functional/Schedule.cpp delete mode 100644 libraries/ESP32/examples/GPIO/Functional/Schedule.h diff --git a/libraries/ESP32/examples/GPIO/Functional/Schedule.cpp b/libraries/ESP32/examples/GPIO/Functional/Schedule.cpp deleted file mode 100644 index c8eb5d706c0..00000000000 --- a/libraries/ESP32/examples/GPIO/Functional/Schedule.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "Schedule.h" - -struct scheduled_fn_t -{ - scheduled_fn_t* mNext; - std::function mFunc; -}; - -static scheduled_fn_t* sFirst = 0; -static scheduled_fn_t* sLast = 0; - -static scheduled_fn_t* sFirstUnused = 0; -static scheduled_fn_t* sLastUnused = 0; - -static int sCount = 0; - -static scheduled_fn_t* get_fn() -{ - scheduled_fn_t* result = NULL; - // try to get an item from unused items list - if (sFirstUnused) - { - result = sFirstUnused; - sFirstUnused = result->mNext; - if (sFirstUnused == NULL) - { - sLastUnused = NULL; - } - } - // if no unused items, and count not too high, allocate a new one - else if (sCount != SCHEDULED_FN_MAX_COUNT) - { - result = new scheduled_fn_t; - result->mNext = NULL; - ++sCount; - } - return result; -} - -static void recycle_fn(scheduled_fn_t* fn) -{ - if (!sLastUnused) - { - sFirstUnused = fn; - } - else - { - sLastUnused->mNext = fn; - } - fn->mNext = NULL; - sLastUnused = fn; -} - -bool schedule_function(std::function fn) -{ - scheduled_fn_t* item = get_fn(); - if (!item) - { - return false; - } - item->mFunc = fn; - item->mNext = NULL; - if (!sFirst) - { - sFirst = item; - } - else - { - sLast->mNext = item; - } - sLast = item; - return true; -} - -void run_scheduled_functions() -{ - scheduled_fn_t* rFirst = sFirst; - sFirst = NULL; - sLast = NULL; - while (rFirst) - { - scheduled_fn_t* item = rFirst; - rFirst = item->mNext; - item->mFunc(); - item->mFunc = std::function(); - recycle_fn(item); - } -} diff --git a/libraries/ESP32/examples/GPIO/Functional/Schedule.h b/libraries/ESP32/examples/GPIO/Functional/Schedule.h deleted file mode 100644 index 0868f27c8c7..00000000000 --- a/libraries/ESP32/examples/GPIO/Functional/Schedule.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ESP_SCHEDULE_H -#define ESP_SCHEDULE_H - -#include - -#define SCHEDULED_FN_MAX_COUNT 32 -#define SCHEDULED_FN_INITIAL_COUNT 4 - -// Warning -// This API is not considered stable. -// Function signatures will change. -// You have been warned. - -// Run given function next time `loop` function returns, -// or `run_scheduled_functions` is called. -// Use std::bind to pass arguments to a function, or call a class member function. -// Note: there is no mechanism for cancelling scheduled functions. -// Keep that in mind when binding functions to objects which may have short lifetime. -// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. -bool schedule_function(std::function fn); - -// Run all scheduled functions. -// Use this function if your are not using `loop`, or `loop` does not return -// on a regular basis. -void run_scheduled_functions(); - -#endif //ESP_SCHEDULE_H From f770056bc09a84beb01970434f1fda7fcae1a519 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 17:46:33 +0200 Subject: [PATCH 45/49] Update to latest ESP8266/ESP32 compatible version --- .../examples/Functional/Functional.ino | 7 +--- .../src/FunctionalInterrupt.cpp | 42 ++++--------------- .../src/FunctionalInterrupt.h | 10 +---- 3 files changed, 10 insertions(+), 49 deletions(-) diff --git a/libraries/FunctionalInterrupt/examples/Functional/Functional.ino b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino index 69fe24f1a48..0cb4a0906d4 100644 --- a/libraries/FunctionalInterrupt/examples/Functional/Functional.ino +++ b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino @@ -1,5 +1,5 @@ -#include #include +#include #if defined(ESP32) #define BUTTON1 16 @@ -22,8 +22,7 @@ class Button { //}, 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: - attachInterrupt(PIN, [this]() { isr(); }, FALLING); // works on ESP32; works on ESP8266 - //attachScheduledInterrupt(PIN, [this](InterruptInfo ii) { Serial.print("Pin "); Serial.println(ii.pin); isr(); }, FALLING); // works on ESP32; works on ESP8266 + attachScheduledInterrupt(PIN, [this](InterruptInfo ii) { Serial.print("Pin "); Serial.println(ii.pin); isr(); }, FALLING); // works on ESP32; works on ESP8266 }; ~Button() { detachInterrupt(PIN); @@ -78,6 +77,4 @@ void setup() { void loop() { button1->checkPressed(); button2->checkPressed(); - - run_scheduled_functions(); } diff --git a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp index 1bda21fc8d8..ffcab52e44c 100644 --- a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp +++ b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp @@ -1,5 +1,5 @@ #include "FunctionalInterrupt.h" -#include "Schedule.h" +#include #include void ICACHE_RAM_ATTR interruptFunctional(void* arg) @@ -10,19 +10,15 @@ void ICACHE_RAM_ATTR interruptFunctional(void* arg) localArg->interruptInfo->value = digitalRead(localArg->interruptInfo->pin); localArg->interruptInfo->micro = micros(); } - if (localArg->functionInfo->reqScheduledFunction) + if (localArg->scheduledFunction) { schedule_function( - [reqScheduledFunction = localArg->functionInfo->reqScheduledFunction, - interruptInfo = *localArg->interruptInfo]() + [scheduledFunction = localArg->scheduledFunction, + interruptInfo = *localArg->interruptInfo]() { - reqScheduledFunction(interruptInfo); + scheduledFunction(interruptInfo); }); } - else if (localArg->functionInfo->reqFunction) - { - localArg->functionInfo->reqFunction(); - } } void cleanupFunctional(void* arg) @@ -31,25 +27,6 @@ void cleanupFunctional(void* arg) delete localArg; } -void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) -{ - // use the local interrupt routine which takes the ArgStructure as argument - - void* localArg = detachInterruptArg(pin); - if (localArg) - { - cleanupFunctional(localArg); - } - - FunctionInfo* fi = new FunctionInfo; - fi->reqFunction = intRoutine; - - ArgStructure* as = new ArgStructure; - as->functionInfo = fi; - - attachInterruptArg(pin, interruptFunctional, as, mode); -} - void attachScheduledInterrupt(uint8_t pin, std::function scheduledIntRoutine, int mode) { void* localArg = detachInterruptArg(pin); @@ -58,14 +35,9 @@ void attachScheduledInterrupt(uint8_t pin, std::function sc cleanupFunctional(localArg); } - InterruptInfo* ii = new InterruptInfo(pin); - - FunctionInfo* fi = new FunctionInfo; - fi->reqScheduledFunction = scheduledIntRoutine; - ArgStructure* as = new ArgStructure; - as->interruptInfo = ii; - as->functionInfo = fi; + as->interruptInfo = new InterruptInfo(pin); + as->scheduledFunction = scheduledIntRoutine; attachInterruptArg(pin, interruptFunctional, as, mode); } diff --git a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h index 15678485fd7..f8f5a8153bd 100644 --- a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h +++ b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h @@ -13,24 +13,16 @@ struct InterruptInfo uint32_t micro = 0; }; -struct FunctionInfo -{ - std::function reqFunction = nullptr; - std::function reqScheduledFunction = nullptr; -}; - struct ArgStructure { ~ArgStructure() { - delete functionInfo; delete interruptInfo; } InterruptInfo* interruptInfo = nullptr; - FunctionInfo* functionInfo = nullptr; + std::function scheduledFunction = nullptr; }; -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); From 41265dd07ec5f54384e215a95fb3193532604eae Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jun 2019 17:56:54 +0200 Subject: [PATCH 46/49] Proper detach for scheduled ISR example --- .../examples/Functional/Functional.ino | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/libraries/FunctionalInterrupt/examples/Functional/Functional.ino b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino index 0cb4a0906d4..cc6bc9d6131 100644 --- a/libraries/FunctionalInterrupt/examples/Functional/Functional.ino +++ b/libraries/FunctionalInterrupt/examples/Functional/Functional.ino @@ -13,51 +13,54 @@ #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() { - detachInterrupt(PIN); - } +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() + void ICACHE_RAM_ATTR isr() #elif defined(ESP32) - void IRAM_ATTR isr() + void IRAM_ATTR isr() #endif - { - numberKeyPresses += 1; - pressed = true; - } + { + numberKeyPresses += 1; + pressed = true; + } #if defined(ESP8266) - static void ICACHE_RAM_ATTR isr_static(Button* const self) + static void ICACHE_RAM_ATTR isr_static(Button* const self) #elif defined(ESP32) - static void IRAM_ATTR isr_static(Button* const self) + static void IRAM_ATTR isr_static(Button* const self) #endif - { - self->isr(); - } + { + self->isr(); + } - void checkPressed() { - if (pressed) { - Serial.printf("Button on pin %u has been pressed %u times\n", PIN, numberKeyPresses); - pressed = false; - } - } + 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; +private: + const uint8_t PIN; + volatile uint32_t numberKeyPresses = 0; + volatile bool pressed = false; }; Button* button1; @@ -65,16 +68,16 @@ Button* button2; void setup() { - Serial.begin(115200); - Serial.println("FunctionalInterrupt test/example"); + Serial.begin(115200); + Serial.println("FunctionalInterrupt test/example"); - button1 = new Button(BUTTON1); - button2 = new Button(BUTTON2); + button1 = new Button(BUTTON1); + button2 = new Button(BUTTON2); - Serial.println("setup() complete"); + Serial.println("setup() complete"); } void loop() { - button1->checkPressed(); - button2->checkPressed(); + button1->checkPressed(); + button2->checkPressed(); } From 386b6e6032ae10cfcf56b52456d37fd10304b0d1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 09:50:38 +0200 Subject: [PATCH 47/49] Return Ticker to libraries only for modularity. squash! Return Ticker to libraries only for modularity. --- CMakeLists.txt | 1 - cores/esp32/Ticker.cpp | 75 ----------------------- cores/esp32/Ticker.h | 132 ----------------------------------------- 3 files changed, 208 deletions(-) delete mode 100644 cores/esp32/Ticker.cpp delete mode 100644 cores/esp32/Ticker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4378e759464..bf53fe91080 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,6 @@ set(CORE_SRCS cores/esp32/stdlib_noniso.c cores/esp32/Stream.cpp cores/esp32/StreamString.cpp - cores/esp32/Ticker.cpp cores/esp32/wiring_pulse.c cores/esp32/wiring_shift.c cores/esp32/WMath.cpp diff --git a/cores/esp32/Ticker.cpp b/cores/esp32/Ticker.cpp deleted file mode 100644 index a92c474ec42..00000000000 --- a/cores/esp32/Ticker.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - 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. - - 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 "Ticker.h" - -Ticker::Ticker() - : _timer(nullptr) -{ -} - -Ticker::~Ticker() -{ - detach(); -} - -void Ticker::_attach_ms(uint32_t milliseconds, 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, milliseconds * 1000); - } - else { - esp_timer_start_once(_timer, milliseconds * 1000); - } -} - -void Ticker::detach() { - if (_timer) { - esp_timer_stop(_timer); - esp_timer_delete(_timer); - _timer = nullptr; - } -} - -bool Ticker::active() const -{ - return _timer; -} - -void Ticker::_static_callback(void* arg) -{ - Ticker* _this = reinterpret_cast(arg); - if (!_this) return; - if (_this->_callback_function) _this->_callback_function(); -} diff --git a/cores/esp32/Ticker.h b/cores/esp32/Ticker.h deleted file mode 100644 index ce9146797a1..00000000000 --- a/cores/esp32/Ticker.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - 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. - - 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 TICKER_H -#define TICKER_H - -extern "C" { -#include "esp_timer.h" -} -#include -#include - -class Ticker -{ -public: - 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_ms(seconds * 1000, 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); - } - - 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_ms(seconds * 1000, true, callback, 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, callback, 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_ms(seconds * 1000, 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); - } - - 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_ms(seconds * 1000, false, callback, 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, callback, arg); - } - - void detach(); - bool active() const; - -protected: - 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; - -protected: - esp_timer_handle_t _timer; -}; - - -#endif//TICKER_H From ef061bce07b63d1b3a9a344d6cc5613fdb9d1d14 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 5 Jun 2019 18:21:42 +0200 Subject: [PATCH 48/49] Fill in empty fields --- libraries/FunctionalInterrupt/keywords.txt | 3 +-- libraries/FunctionalInterrupt/library.properties | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libraries/FunctionalInterrupt/keywords.txt b/libraries/FunctionalInterrupt/keywords.txt index 0c81c01ace9..52423e5f954 100644 --- a/libraries/FunctionalInterrupt/keywords.txt +++ b/libraries/FunctionalInterrupt/keywords.txt @@ -2,13 +2,12 @@ # Datatypes (KEYWORD1) ####################################### -FunctionalInterrupt KEYWORD1 InterruptInfo KEYWORD1 +ArgStructure KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### -attachInterrupt KEYWORD2 attachScheduledInterrupt KEYWORD2 detachFunctionalInterrupt KEYWORD2 diff --git a/libraries/FunctionalInterrupt/library.properties b/libraries/FunctionalInterrupt/library.properties index cce101fe044..09987009e80 100644 --- a/libraries/FunctionalInterrupt/library.properties +++ b/libraries/FunctionalInterrupt/library.properties @@ -1,9 +1,9 @@ name=FunctionalInterrupt version=1.0 -author= -maintainer= -sentence= +author=hreintke +maintainer=hreintke +sentence=C++ functional and scheduled interrupt handling paragraph= -category= +category=Other url= architectures=esp32 From 5c46e6da91311d0c00c17ce6d2a148406f822ba1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 22 Jun 2019 19:52:05 +0200 Subject: [PATCH 49/49] Bring back FunctionalInterrupt's attachInterrupt() --- .../src/FunctionalInterrupt.cpp | 61 +++++++++++++------ .../src/FunctionalInterrupt.h | 11 +--- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp index ffcab52e44c..fd3fddfb0d3 100644 --- a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp +++ b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.cpp @@ -2,29 +2,42 @@ #include #include -void ICACHE_RAM_ATTR interruptFunctional(void* arg) -{ - ArgStructure* localArg = static_cast(arg); - if (localArg->interruptInfo) +namespace { + + struct ArgStructure { - localArg->interruptInfo->value = digitalRead(localArg->interruptInfo->pin); - localArg->interruptInfo->micro = micros(); + std::function function = nullptr; + }; + + void ICACHE_RAM_ATTR interruptFunctional(void* arg) + { + ArgStructure* localArg = static_cast(arg); + localArg->function(); } - if (localArg->scheduledFunction) + + void cleanupFunctional(void* arg) { - schedule_function( - [scheduledFunction = localArg->scheduledFunction, - interruptInfo = *localArg->interruptInfo]() - { - scheduledFunction(interruptInfo); - }); + ArgStructure* localArg = static_cast(arg); + delete localArg; } + } -void cleanupFunctional(void* arg) +void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) { - ArgStructure* localArg = static_cast(arg); - delete localArg; + 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) @@ -35,11 +48,19 @@ void attachScheduledInterrupt(uint8_t pin, std::function sc cleanupFunctional(localArg); } - ArgStructure* as = new ArgStructure; - as->interruptInfo = new InterruptInfo(pin); - as->scheduledFunction = scheduledIntRoutine; + 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, as, mode); + attachInterruptArg(pin, interruptFunctional, arg, mode); + } } void detachFunctionalInterrupt(uint8_t pin) diff --git a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h index f8f5a8153bd..b51ef93f53d 100644 --- a/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h +++ b/libraries/FunctionalInterrupt/src/FunctionalInterrupt.h @@ -13,16 +13,7 @@ struct InterruptInfo uint32_t micro = 0; }; -struct ArgStructure -{ - ~ArgStructure() - { - delete interruptInfo; - } - InterruptInfo* interruptInfo = nullptr; - std::function scheduledFunction = nullptr; -}; - +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);