From 543d19d0328b75244f99b7893214091e7a173ebe Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 15 Oct 2021 08:26:35 +0200 Subject: [PATCH 01/61] Replace Sink::read() with Sink::operator T() to equalise interfaces between Shared and Sink/Source. --- .../Threading/Demo_Source_Sink_Counter/Consumer.inot | 2 +- .../Threading/Demo_Source_Sink_LED/Sink_Thread.inot | 3 +-- src/threading/Sink.hpp | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot b/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot index d892c67..b5c1fd6 100644 --- a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot +++ b/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot @@ -7,5 +7,5 @@ void setup() void loop() { - Serial.println(counter.read()); + Serial.println(counter); } diff --git a/examples/Threading/Demo_Source_Sink_LED/Sink_Thread.inot b/examples/Threading/Demo_Source_Sink_LED/Sink_Thread.inot index 51f9031..14a9723 100644 --- a/examples/Threading/Demo_Source_Sink_LED/Sink_Thread.inot +++ b/examples/Threading/Demo_Source_Sink_LED/Sink_Thread.inot @@ -16,6 +16,5 @@ void loop() * this call will surely block until something comes from the connected SOURCE. In this case * the pace is dictated by the SOURCE that sends data every 100 ms. */ - bool led_on = led.read(); - digitalWrite(LED_BUILTIN, led_on); + digitalWrite(LED_BUILTIN, led); } diff --git a/src/threading/Sink.hpp b/src/threading/Sink.hpp index 40179a2..0d9c291 100644 --- a/src/threading/Sink.hpp +++ b/src/threading/Sink.hpp @@ -36,7 +36,7 @@ class SinkBase virtual ~SinkBase() { } - virtual T read() = 0; + virtual operator T() = 0; virtual void inject(T const & value) = 0; }; @@ -48,7 +48,7 @@ class SinkNonBlocking : public SinkBase SinkNonBlocking() { } virtual ~SinkNonBlocking() { } - virtual T read() override; + virtual operator T() override; virtual void inject(T const & value) override; @@ -67,7 +67,7 @@ class SinkBlocking : public SinkBase SinkBlocking(); virtual ~SinkBlocking() { } - virtual T read() override; + virtual operator T() override; virtual void inject(T const & value) override; @@ -86,7 +86,7 @@ class SinkBlocking : public SinkBase **************************************************************************************/ template -T SinkNonBlocking::read() +SinkNonBlocking::operator T() { _mutex.lock(); return _data; @@ -113,7 +113,7 @@ SinkBlocking::SinkBlocking() { } template -T SinkBlocking::read() +SinkBlocking::operator T() { _mutex.lock(); while (!_is_data_available) From e0b3f9fbe7efc0e07c3dd5bdf1c4750c35d4f365 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 15 Oct 2021 08:29:44 +0200 Subject: [PATCH 02/61] Replace Source::write() with Source::operator =() to equalise interfaces between Shared and Sink/Source. --- examples/Threading/Demo_Source_Sink_Counter/Producer.inot | 2 +- examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot | 4 ++-- src/threading/Source.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/Threading/Demo_Source_Sink_Counter/Producer.inot b/examples/Threading/Demo_Source_Sink_Counter/Producer.inot index fe0a250..a038d37 100644 --- a/examples/Threading/Demo_Source_Sink_Counter/Producer.inot +++ b/examples/Threading/Demo_Source_Sink_Counter/Producer.inot @@ -8,6 +8,6 @@ void setup() void loop() { static int i = 0; - counter.write(i); + counter = i; i++; } diff --git a/examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot b/examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot index 1bf7fca..4585f85 100644 --- a/examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot +++ b/examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot @@ -8,8 +8,8 @@ void setup() void loop() { - led.write(true); + led = true; delay(100); - led.write(false); + led = false; delay(100); } diff --git a/src/threading/Source.hpp b/src/threading/Source.hpp index 0a3893c..d75ac8c 100644 --- a/src/threading/Source.hpp +++ b/src/threading/Source.hpp @@ -43,7 +43,7 @@ class Source public: void connectTo(SinkBase & sink); - void write(T const & value); + void operator = (T const & other); private: std::list *> _sink_list; @@ -60,7 +60,7 @@ void Source::connectTo(SinkBase & sink) } template -void Source::write(T const & value) +void Source::operator = (T const & value) { std::for_each(std::begin(_sink_list), std::end (_sink_list), From 127682afc6f7eee9fe8f24329d0b5b6d6010e394 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 15 Oct 2021 09:13:35 +0200 Subject: [PATCH 03/61] Providing templated circular buffer with size configurable at run-time (heavily borrowed from api::RingBufferN --- src/threading/CircularBuffer.hpp | 118 +++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/threading/CircularBuffer.hpp diff --git a/src/threading/CircularBuffer.hpp b/src/threading/CircularBuffer.hpp new file mode 100644 index 0000000..b003f77 --- /dev/null +++ b/src/threading/CircularBuffer.hpp @@ -0,0 +1,118 @@ +/* + * This file is part of the Arduino_ThreadsafeIO library. + * Copyright (c) 2021 Arduino SA. + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ARDUINO_THREADS_RINGBUFFER_HPP_ +#define ARDUINO_THREADS_RINGBUFFER_HPP_ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +/************************************************************************************** + * CLASS DECLARATION + **************************************************************************************/ + +template +class CircularBuffer +{ +public: + + CircularBuffer(size_t const size); + + void store(T const data); + T read(); + bool isFull() const; + bool isEmpty() const; + + +private: + + mbed::SharedPtr _data; + size_t const _size; + size_t _head, _tail, _num_elems; + + size_t next(size_t const idx); +}; + +/************************************************************************************** + * CTOR/DTOR + **************************************************************************************/ + +template +CircularBuffer::CircularBuffer(size_t const size) +: _data{new T[size]} +, _size{size} +, _head{0} +, _tail{0} +, _num_elems{0} +{ +} + +/************************************************************************************** + * PUBLIC MEMBER FUNCTIONS + **************************************************************************************/ + +template +void CircularBuffer::store(T const data) +{ + if (!isFull()) + { + _data.get()[_head] = data; + _head = next(_head); + _num_elems++; + } +} + +template +T CircularBuffer::read() +{ + if (isEmpty()) + return T{0}; + + T const value = _data.get()[_tail]; + _tail = next(_tail); + _num_elems--; + + return value; +} + +template +bool CircularBuffer::isFull() const +{ + return (_num_elems == _size); +} + +template +bool CircularBuffer::isEmpty() const +{ + return (_num_elems == 0); +} + +/************************************************************************************** + * PRIVATE MEMBER FUNCTIONS + **************************************************************************************/ + +template +size_t CircularBuffer::next(size_t const idx) +{ + return ((idx + 1) % _size); +} + +#endif /* ARDUINO_THREADS_RINGBUFFER_HPP_ */ From 5c3b1ae16d4f1a5210117f4d3eed6da0e8a0abd2 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 15 Oct 2021 09:14:30 +0200 Subject: [PATCH 04/61] Replace single value with run-time configurable circular buffer. --- src/threading/Sink.hpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/threading/Sink.hpp b/src/threading/Sink.hpp index 0d9c291..539ecb2 100644 --- a/src/threading/Sink.hpp +++ b/src/threading/Sink.hpp @@ -25,6 +25,8 @@ #include +#include "CircularBuffer.hpp" + /************************************************************************************** * CLASS DECLARATION **************************************************************************************/ @@ -64,7 +66,7 @@ class SinkBlocking : public SinkBase { public: - SinkBlocking(); + SinkBlocking(size_t const size); virtual ~SinkBlocking() { } virtual operator T() override; @@ -73,8 +75,7 @@ class SinkBlocking : public SinkBase private: - T _data; - bool _is_data_available; + CircularBuffer _data; rtos::Mutex _mutex; rtos::ConditionVariable _cond_data_available; rtos::ConditionVariable _cond_slot_available; @@ -106,8 +107,8 @@ void SinkNonBlocking::inject(T const & value) **************************************************************************************/ template -SinkBlocking::SinkBlocking() -: _is_data_available{false} +SinkBlocking::SinkBlocking(size_t const size) +: _data(size) , _cond_data_available(_mutex) , _cond_slot_available(_mutex) { } @@ -116,10 +117,9 @@ template SinkBlocking::operator T() { _mutex.lock(); - while (!_is_data_available) + while (_data.isEmpty()) _cond_data_available.wait(); - T const d = _data; - _is_data_available = false; + T const d = _data.read(); _cond_slot_available.notify_all(); _mutex.unlock(); return d; @@ -129,10 +129,9 @@ template void SinkBlocking::inject(T const & value) { _mutex.lock(); - while (_is_data_available) + while (_data.isFull()) _cond_slot_available.wait(); - _data = value; - _is_data_available = true; + _data.store(value); _cond_data_available.notify_all(); _mutex.unlock(); } From 48a7ff738d11bfefa7748227f4d7e365106f30c8 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 15 Oct 2021 09:15:01 +0200 Subject: [PATCH 05/61] Adjust example to a Sink size of 10. --- examples/Threading/Demo_Source_Sink_Counter/Consumer.inot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot b/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot index b5c1fd6..b03acdb 100644 --- a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot +++ b/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot @@ -1,4 +1,4 @@ -SINK(counter, int); +SINK(counter, int, 10); void setup() { From 6c16c41e134bb37b1f08e2e2f181039064cce5b4 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 18 Oct 2021 07:03:42 +0200 Subject: [PATCH 06/61] Employ black-macro-magic to achieve 'macro overloading' Same macro name, different macro parameter. --- src/Arduino_Threads.h | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index 685b09c..fe05cb5 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -44,19 +44,37 @@ public: \ Source name; \ private: -#define SINK(name, type) \ +/* We need to call the SinkBlocking(size_t const size) + * non-default constructor using size as parameter. + + * This is achieved via + * SinkBlocking name{size}; + * instead of + * SinkBlocking name(size); + * otherwise the compiler will read it as a declaration + * of a method called "name" and we get a syntax error. + * + * This is called "C++11 uniform init" (using "{}" instead + * of "()" without "="... yikes!) + * https://chromium.googlesource.com/chromium/src/+/master/styleguide/c++/c++-dos-and-donts.md + */ + +#define SINK_2_ARG(name, type) \ +public: \ + SinkBlocking name{1}; \ +private: + +#define SINK_3_ARG(name, type, size) \ public: \ - SinkBlocking name; \ + SinkBlocking name{size}; \ private: -// we need to call the Sink(int size) non-default constructor using size as parameter. -// This is done by writing -// Sink name{size}; -// instead of: -// Sink name(size); -// otherwise the compiler will read it as a declaration of a method called "name" and we -// get a syntax error. -// This is called "C++11 uniform init" (using "{}" instead of "()" without "="... yikes!) -// https://chromium.googlesource.com/chromium/src/+/master/styleguide/c++/c++-dos-and-donts.md + +/* Black C macro magic enabling "macro overloading" + * with same name macro, but multiple arguments. + * https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments + */ +#define GET_SINK_MACRO(_1,_2,_3,NAME,...) NAME +#define SINK(...) GET_SINK_MACRO(__VA_ARGS__, SINK_3_ARG, SINK_2_ARG)(__VA_ARGS__) #define SHARED(name, type) \ Shared name; From 9af57d47bf0e6771cd991f5b1d5c97fe9783e161 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 18 Oct 2021 09:16:32 +0200 Subject: [PATCH 07/61] Remove superfluous macro 'INCF' --- src/Arduino_Threads.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index fe05cb5..1aef446 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -86,9 +86,6 @@ public: \ #define CONCAT2(x,y) x##y #define CONCAT(x,y) CONCAT2(x,y) -#define INCF(F) INCF_(F) -#define INCF_(F) #F - #define _macroToString(sequence) #sequence class Arduino_Threads From e96894cfe795974f48d4342a571de8bd015e16dc Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 18 Oct 2021 09:18:45 +0200 Subject: [PATCH 08/61] Rename CONCAT/CONCAT2 to ARDUINO_THREADS_CONCAT/_ in order to prevent name-clashes with similar named macros. --- src/Arduino_Threads.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index 1aef446..83d7f71 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -79,13 +79,13 @@ public: \ #define SHARED(name, type) \ Shared name; +#define ARDUINO_THREADS_CONCAT_(x,y) x##y +#define ARDUINO_THREADS_CONCAT(x,y) ARDUINO_THREADS_CONCAT_(x,y) + /************************************************************************************** * CLASS DECLARATION **************************************************************************************/ -#define CONCAT2(x,y) x##y -#define CONCAT(x,y) CONCAT2(x,y) - #define _macroToString(sequence) #sequence class Arduino_Threads @@ -121,13 +121,13 @@ class Arduino_Threads void threadFunc(); }; -#define THD_ENTER(tabname) class CONCAT(tabname, Class) : public Arduino_Threads { \ +#define THD_ENTER(tabname) class ARDUINO_THREADS_CONCAT(tabname, Class) : public Arduino_Threads { \ public: \ - CONCAT(tabname, Class)() { _tabname = _macroToString(tabname); } \ + ARDUINO_THREADS_CONCAT(tabname, Class)() { _tabname = _macroToString(tabname); } \ private: \ #define THD_DONE(tabname) \ }; \ -CONCAT(tabname,Class) tabname; +ARDUINO_THREADS_CONCAT(tabname,Class) tabname; #endif /* ARDUINO_THREADS_H_ */ From b233ee4f7214e4629b2bf38fb1b5a5b812ae2f15 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 18 Oct 2021 09:38:50 +0200 Subject: [PATCH 09/61] Rename _macroToString to ARDUINO_THREADS_TO_STRING in order to avoid name collissions with different libraries. --- src/Arduino_Threads.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index 83d7f71..c2c2fca 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -82,12 +82,12 @@ public: \ #define ARDUINO_THREADS_CONCAT_(x,y) x##y #define ARDUINO_THREADS_CONCAT(x,y) ARDUINO_THREADS_CONCAT_(x,y) +#define ARDUINO_THREADS_TO_STRING(sequence) #sequence + /************************************************************************************** * CLASS DECLARATION **************************************************************************************/ -#define _macroToString(sequence) #sequence - class Arduino_Threads { public: @@ -123,7 +123,7 @@ class Arduino_Threads #define THD_ENTER(tabname) class ARDUINO_THREADS_CONCAT(tabname, Class) : public Arduino_Threads { \ public: \ - ARDUINO_THREADS_CONCAT(tabname, Class)() { _tabname = _macroToString(tabname); } \ + ARDUINO_THREADS_CONCAT(tabname, Class)() { _tabname = ARDUINO_THREADS_TO_STRING(tabname); } \ private: \ #define THD_DONE(tabname) \ From 1be8f611aa5997ca0843a295da2a0c356311c8ea Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 18 Oct 2021 14:34:27 +0200 Subject: [PATCH 10/61] Bugfix: Yield after every execution of 'loop' if a loop delay of 0 is specified. (#38) Otherwise we experience ressource starvation with the first started thread hogging the CPU all the time and the other threads will be never served. --- src/Arduino_Threads.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Arduino_Threads.cpp b/src/Arduino_Threads.cpp index 444ded5..89a6181 100644 --- a/src/Arduino_Threads.cpp +++ b/src/Arduino_Threads.cpp @@ -118,8 +118,17 @@ void Arduino_Threads::threadFunc() } } - /* Sleep for the time we've been asked to insert between loops. - */ - rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(_loop_delay_ms)); + if (_loop_delay_ms) { + /* Sleep for the time we've been asked to insert between loops. + */ + rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(_loop_delay_ms)); + } + else + { + /* In any case yield here so that other threads can also be + * executed following the round-robin scheduling paradigm. + */ + rtos::ThisThread::yield(); + } } } From 1541cbc829a599873ba7092994bbacd467ee43f3 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 19 Oct 2021 06:23:13 +0200 Subject: [PATCH 11/61] The user can't be bothered with the cumbersome #ifdef logic, rather directly instead the SerialDispatcher within the library and make it available via extern declaration. --- src/io/serial/SerialDispatcher.cpp | 16 ++++++++++++++++ src/io/serial/SerialDispatcher.h | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/src/io/serial/SerialDispatcher.cpp b/src/io/serial/SerialDispatcher.cpp index 7fb6dea..cc7a41b 100644 --- a/src/io/serial/SerialDispatcher.cpp +++ b/src/io/serial/SerialDispatcher.cpp @@ -332,3 +332,19 @@ void SerialDispatcher::handleSerialReader() }); } } + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +/************************************************************************************** + * GLOBAL VARIABLE DECLARATION + **************************************************************************************/ + +#ifdef ARDUINO_PORTENTA_H7_M4 + SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */ +#else + SerialDispatcher Serial(SerialUSB); +#endif diff --git a/src/io/serial/SerialDispatcher.h b/src/io/serial/SerialDispatcher.h index ca17793..0072f87 100644 --- a/src/io/serial/SerialDispatcher.h +++ b/src/io/serial/SerialDispatcher.h @@ -102,4 +102,11 @@ class SerialDispatcher : public arduino::HardwareSerial void handleSerialReader(); }; +/************************************************************************************** + * EXTERN DECLARATION + **************************************************************************************/ + +#undef Serial +extern SerialDispatcher Serial; + #endif /* SERIAL_DISPATCHER_H_ */ From 07d98380c92c0a3b8addf814e2a0ec5bcdabcc0f Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 19 Oct 2021 06:24:36 +0200 Subject: [PATCH 12/61] Use Arduino_Threads for Threadsafe_Serial_Write. --- .../SharedVariables.h | 0 .../Threadsafe_Serial_Writer/Thread_1.inot | 14 ++++ .../Threadsafe_Serial_Writer/Thread_2.inot | 14 ++++ .../Threadsafe_Serial_Writer/Thread_3.inot | 14 ++++ .../Threadsafe_Serial_Writer.ino | 75 +++++-------------- 5 files changed, 60 insertions(+), 57 deletions(-) create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Writer/SharedVariables.h create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_1.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_2.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_3.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/SharedVariables.h b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_1.inot b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_1.inot new file mode 100644 index 0000000..71039e4 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_1.inot @@ -0,0 +1,14 @@ +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #1: Lorem ipsum ..."); + Serial.println(); + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_2.inot b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_2.inot new file mode 100644 index 0000000..d02c9cd --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_2.inot @@ -0,0 +1,14 @@ +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #2: Lorem ipsum ..."); + Serial.println(); + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_3.inot b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_3.inot new file mode 100644 index 0000000..4591cf0 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_3.inot @@ -0,0 +1,14 @@ +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #3: Lorem ipsum ..."); + Serial.println(); + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino index f5b47c2..050f438 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino @@ -4,71 +4,32 @@ #include -/************************************************************************************** - * CONSTANTS - **************************************************************************************/ - -static size_t constexpr NUM_THREADS = 5; - -/************************************************************************************** - * FUNCTION DECLARATION - **************************************************************************************/ - -void serial_thread_func(); - -/************************************************************************************** - * GLOBAL VARIABLES - **************************************************************************************/ - -static char thread_name[NUM_THREADS][32]; -#undef Serial -#ifdef ARDUINO_PORTENTA_H7_M4 - SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */ -#else - SerialDispatcher Serial(SerialUSB); -#endif - /************************************************************************************** * SETUP/LOOP **************************************************************************************/ void setup() { - /* Fire up some threads all accessing the LSM6DSOX */ - for(size_t i = 0; i < NUM_THREADS; i++) - { - snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); - rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); - t->start(serial_thread_func); - } -} - -void loop() -{ + Serial.begin(9600); + while (!Serial) { } + Thread_1.start(); + Thread_2.start(); + Thread_3.start(); } -/************************************************************************************** - * FUNCTION DEFINITION - **************************************************************************************/ - -void serial_thread_func() +void loop() { - Serial.begin(9600); - - for(;;) - { - /* Sleep between 5 and 500 ms */ - rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); - /* Print thread id and chip id value to serial. */ - char msg[64] = {0}; - snprintf(msg, sizeof(msg), "[%05lu] %s: Lorem ipsum ...", millis(), rtos::ThisThread::get_name()); - /* Every Serial.print/println() encapsulated between - * block/unblock statements will only be printed after - * a block statement has occurred. - */ - Serial.block(); - Serial.println(msg); - Serial.unblock(); - } + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #0: Lorem ipsum ..."); + Serial.println(); + Serial.unblock(); + + /* If we don't hand back control then the main thread + * will hog the CPU and all other thread's won't get + * time to be executed. + */ + rtos::ThisThread::yield(); } From 01c27b091c38f46d16013a308a3493b84d2692c3 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 19 Oct 2021 06:39:40 +0200 Subject: [PATCH 13/61] Use Arduino_Threads for Threadsafe_Serial_Reader. --- .../SharedVariables.h | 0 .../Threadsafe_Serial_Reader/Thread_1.inot | 28 ++++++ .../Threadsafe_Serial_Reader/Thread_2.inot | 28 ++++++ .../Threadsafe_Serial_Reader/Thread_3.inot | 28 ++++++ .../Threadsafe_Serial_Reader.ino | 86 ++++++------------- 5 files changed, 112 insertions(+), 58 deletions(-) create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Reader/SharedVariables.h create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_1.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_2.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_3.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/SharedVariables.h b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_1.inot b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_1.inot new file mode 100644 index 0000000..04e9bf5 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_1.inot @@ -0,0 +1,28 @@ +void setup() +{ + Serial.begin(9600); + + Serial.block(); + Serial.println("Thread #1 started."); + Serial.unblock(); +} + +void loop() +{ + /* Read data from the serial interface into a String. */ + String serial_msg; + while (Serial.available()) + serial_msg += (char)Serial.read(); + + /* Print thread id and chip id value to serial. */ + if (serial_msg.length()) + { + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #1: "); + Serial.print(serial_msg); + Serial.println(); + Serial.unblock(); + } +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_2.inot b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_2.inot new file mode 100644 index 0000000..e5940c2 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_2.inot @@ -0,0 +1,28 @@ +void setup() +{ + Serial.begin(9600); + + Serial.block(); + Serial.println("Thread #2 started."); + Serial.unblock(); +} + +void loop() +{ + /* Read data from the serial interface into a String. */ + String serial_msg; + while (Serial.available()) + serial_msg += (char)Serial.read(); + + /* Print thread id and chip id value to serial. */ + if (serial_msg.length()) + { + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #2: "); + Serial.print(serial_msg); + Serial.println(); + Serial.unblock(); + } +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_3.inot b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_3.inot new file mode 100644 index 0000000..3a21fa2 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_3.inot @@ -0,0 +1,28 @@ +void setup() +{ + Serial.begin(9600); + + Serial.block(); + Serial.println("Thread #3 started."); + Serial.unblock(); +} + +void loop() +{ + /* Read data from the serial interface into a String. */ + String serial_msg; + while (Serial.available()) + serial_msg += (char)Serial.read(); + + /* Print thread id and chip id value to serial. */ + if (serial_msg.length()) + { + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #3: "); + Serial.print(serial_msg); + Serial.println(); + Serial.unblock(); + } +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino index 5fbf546..2ddc81d 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino @@ -4,76 +4,46 @@ #include -/************************************************************************************** - * CONSTANTS - **************************************************************************************/ - -static size_t constexpr NUM_THREADS = 5; - -/************************************************************************************** - * FUNCTION DECLARATION - **************************************************************************************/ - -void serial_thread_func(); - -/************************************************************************************** - * GLOBAL VARIABLES - **************************************************************************************/ - -static char thread_name[NUM_THREADS][32]; -#undef Serial -#ifdef ARDUINO_PORTENTA_H7_M4 - SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */ -#else - SerialDispatcher Serial(SerialUSB); -#endif - /************************************************************************************** * SETUP/LOOP **************************************************************************************/ void setup() { - /* Fire up some threads all accessing the LSM6DSOX */ - for(size_t i = 0; i < NUM_THREADS; i++) - { - snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); - rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); - t->start(serial_thread_func); - } -} + Serial.begin(9600); + while (!Serial) { } -void loop() -{ + Thread_1.start(); + Thread_2.start(); + Thread_3.start(); + Serial.block(); + Serial.println("Thread #0 started."); + Serial.unblock(); } -/************************************************************************************** - * FUNCTION DEFINITION - **************************************************************************************/ - -void serial_thread_func() +void loop() { - Serial.begin(9600); + /* Read data from the serial interface into a String. */ + String serial_msg; + while (Serial.available()) + serial_msg += (char)Serial.read(); - for(;;) + /* Print thread id and chip id value to serial. */ + if (serial_msg.length()) { - /* Sleep between 5 and 500 ms */ - rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); - - /* Read data from the serial interface into a String. */ - String serial_msg; - while (Serial.available()) - serial_msg += (char)Serial.read(); - - /* Print thread id and chip id value to serial. */ - if (serial_msg.length()) - { - char msg[64] = {0}; - snprintf(msg, sizeof(msg), "[%05lu] %s: %s ...", millis(), rtos::ThisThread::get_name(), serial_msg.c_str()); - Serial.block(); - Serial.println(msg); - Serial.unblock(); - } + Serial.block(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] Thread #0: "); + Serial.print(serial_msg); + Serial.println(); + Serial.unblock(); } + + /* If we don't hand back control then the main thread + * will hog the CPU and all other thread's won't get + * time to be executed. + */ + rtos::ThisThread::yield(); } From 70eaaf2ed55ab98f003ff35413e4d2eb1387f9ca Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 19 Oct 2021 08:36:12 +0200 Subject: [PATCH 14/61] Use Arduino_Threads for Threadsafe_Serial_GlobalPrefixSuffix. --- .../SharedVariables.h | 0 .../Thread_1.inot | 11 ++++ .../Thread_2.inot | 11 ++++ .../Thread_3.inot | 11 ++++ .../Threadsafe_Serial_GlobalPrefixSuffix.ino | 60 +++++-------------- 5 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/SharedVariables.h create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_1.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_2.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_3.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/SharedVariables.h b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_1.inot b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_1.inot new file mode 100644 index 0000000..3de8ac9 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_1.inot @@ -0,0 +1,11 @@ +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Serial.block(); + Serial.println("Thread #1: Lorem ipsum ..."); + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_2.inot b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_2.inot new file mode 100644 index 0000000..12731f0 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_2.inot @@ -0,0 +1,11 @@ +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Serial.block(); + Serial.println("Thread #2: Lorem ipsum ..."); + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_3.inot b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_3.inot new file mode 100644 index 0000000..eccc6e8 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_3.inot @@ -0,0 +1,11 @@ +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + Serial.block(); + Serial.println("Thread #3: Lorem ipsum ..."); + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino index 94ac3bd..12e302a 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino @@ -3,32 +3,12 @@ **************************************************************************************/ #include - -/************************************************************************************** - * CONSTANTS - **************************************************************************************/ - -static size_t constexpr NUM_THREADS = 5; - /************************************************************************************** * FUNCTION DECLARATION **************************************************************************************/ String serial_log_message_prefix(String const & /* msg */); String serial_log_message_suffix(String const & prefix, String const & msg); -void serial_thread_func(); - -/************************************************************************************** - * GLOBAL VARIABLES - **************************************************************************************/ - -static char thread_name[NUM_THREADS][32]; -#undef Serial -#ifdef ARDUINO_PORTENTA_H7_M4 - SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */ -#else - SerialDispatcher Serial(SerialUSB); -#endif /************************************************************************************** * SETUP/LOOP @@ -36,21 +16,28 @@ static char thread_name[NUM_THREADS][32]; void setup() { + Serial.begin(9600); + while (!Serial) { } + Serial.global_prefix(serial_log_message_prefix); Serial.global_suffix(serial_log_message_suffix); - /* Fire up some threads all accessing the LSM6DSOX */ - for(size_t i = 0; i < NUM_THREADS; i++) - { - snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); - rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); - t->start(serial_thread_func); - } + Thread_1.start(); + Thread_2.start(); + Thread_3.start(); } void loop() { - + Serial.block(); + Serial.println("Thread #0: Lorem ipsum ..."); + Serial.unblock(); + + /* If we don't hand back control then the main thread + * will hog the CPU and all other thread's won't get + * time to be executed. + */ + rtos::ThisThread::yield(); } /************************************************************************************** @@ -68,20 +55,3 @@ String serial_log_message_suffix(String const & prefix, String const & msg) { return String("\r\n"); } - -void serial_thread_func() -{ - Serial.begin(9600); - - for(;;) - { - /* Sleep between 5 and 500 ms */ - rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); - /* Print a unbroken log message including thread name and timestamp as a prefix. */ - Serial.block(); - Serial.print(rtos::ThisThread::get_name()); - Serial.print(": "); - Serial.print("Lorem ipsum ..."); - Serial.unblock(); - } -} From b6ce5377b9c7377cb94b57e79a3b7e3983ae8023 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 19 Oct 2021 09:03:21 +0200 Subject: [PATCH 15/61] Use Arduino_Threads for Threadsafe_Serial_ProtocolWrapping. --- .../GPS_Thread.inot | 64 +++++++++++ .../SharedVariables.h | 0 .../Threadsafe_Serial_ProtocolWrapping.ino | 100 ++---------------- 3 files changed, 72 insertions(+), 92 deletions(-) create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/GPS_Thread.inot create mode 100644 examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/SharedVariables.h diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/GPS_Thread.inot b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/GPS_Thread.inot new file mode 100644 index 0000000..85a99a5 --- /dev/null +++ b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/GPS_Thread.inot @@ -0,0 +1,64 @@ +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +static String nmea_message_prefix(String const & /* msg */) +{ + return String("$"); +} + +static String nmea_message_suffix(String const & prefix, String const & msg) +{ + /* NMEA checksum is calculated over the complete message + * starting with '$' and ending with the end of the message. + */ + byte checksum = 0; + std::for_each(msg.c_str(), + msg.c_str() + msg.length(), + [&checksum](char const c) + { + checksum ^= static_cast(c); + }); + /* Assemble the footer of the NMEA message. */ + char footer[16] = {0}; + snprintf(footer, sizeof(footer), "*%02X\r\n", checksum); + return String(footer); +} + +/************************************************************************************** + * SETUP/LOOP + **************************************************************************************/ + +void setup() +{ + Serial.begin(9600); + + Serial.prefix(nmea_message_prefix); + Serial.suffix(nmea_message_suffix); +} + +void loop() +{ + /* Sleep between 5 and 500 ms */ + rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); + + /* Print a fake NMEA GPRMC message: + * $GPRMC,062101.714,A,5001.869,N,01912.114,E,955535.7,116.2,290520,000.0,W*45\r\n + */ + Serial.block(); + + Serial.print("GPRMC,"); + Serial.print(millis()); + Serial.print(",A,"); + Serial.print("5001.869,"); + Serial.print("N,"); + Serial.print("01912.114,"); + Serial.print("E,"); + Serial.print("955535.7,"); + Serial.print("116.2,"); + Serial.print("290520,"); + Serial.print("000.0,"); + Serial.print("W"); + + Serial.unblock(); +} diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/SharedVariables.h b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino index af200e8..090ba04 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino @@ -4,107 +4,23 @@ #include -/************************************************************************************** - * CONSTANTS - **************************************************************************************/ - -static size_t constexpr NUM_THREADS = 5; - -/************************************************************************************** - * FUNCTION DECLARATION - **************************************************************************************/ - -void serial_thread_func(); - -/************************************************************************************** - * GLOBAL VARIABLES - **************************************************************************************/ - -static char thread_name[NUM_THREADS][32]; -#undef Serial -#ifdef ARDUINO_PORTENTA_H7_M4 - SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */ -#else - SerialDispatcher Serial(SerialUSB); -#endif - /************************************************************************************** * SETUP/LOOP **************************************************************************************/ void setup() { - /* Fire up some threads all accessing the LSM6DSOX */ - for(size_t i = 0; i < NUM_THREADS; i++) - { - snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); - rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); - t->start(serial_thread_func); - } -} - -void loop() -{ - -} - -/************************************************************************************** - * FUNCTION DEFINITION - **************************************************************************************/ + Serial.begin(9600); + while (!Serial) { } -String nmea_message_prefix(String const & /* msg */) -{ - return String("$"); + GPS_Thread.start(); } -String nmea_message_suffix(String const & prefix, String const & msg) +void loop() { - /* NMEA checksum is calculated over the complete message - * starting with '$' and ending with the end of the message. + /* If we don't hand back control then the main thread + * will hog the CPU and all other thread's won't get + * time to be executed. */ - byte checksum = 0; - std::for_each(msg.c_str(), - msg.c_str() + msg.length(), - [&checksum](char const c) - { - checksum ^= static_cast(c); - }); - /* Assemble the footer of the NMEA message. */ - char footer[16] = {0}; - snprintf(footer, sizeof(footer), "*%02X\r\n", checksum); - return String(footer); -} - -void serial_thread_func() -{ - Serial.begin(9600); - - Serial.prefix(nmea_message_prefix); - Serial.suffix(nmea_message_suffix); - - for(;;) - { - /* Sleep between 5 and 500 ms */ - rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); - - /* Print a fake NMEA GPRMC message: - * $GPRMC,062101.714,A,5001.869,N,01912.114,E,955535.7,116.2,290520,000.0,W*45\r\n - */ - Serial.block(); - - Serial.print("GPRMC,"); - Serial.print(millis()); - Serial.print(",A,"); - Serial.print("5001.869,"); - Serial.print("N,"); - Serial.print("01912.114,"); - Serial.print("E,"); - Serial.print("955535.7,"); - Serial.print("116.2,"); - Serial.print("290520,"); - Serial.print("000.0,"); - Serial.print("W"); - - Serial.unblock(); - } + rtos::ThisThread::yield(); } From 350efc41b50e9b614926db169a5d4d8dbe03af5c Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 20 Oct 2021 06:49:08 +0200 Subject: [PATCH 16/61] Bugfix: 'Serial.begin()' now needs to be called within every thread that wants to use it. (#41) --- examples/Threading/Demo_Shared_Counter/Consumer.inot | 9 ++++++--- .../Demo_Shared_Counter/Demo_Shared_Counter.ino | 3 --- examples/Threading/Demo_Shared_Counter/Producer.inot | 6 ++++-- .../Threading/Demo_Source_Sink_Counter/Consumer.inot | 3 ++- .../Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino | 6 ++++-- examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino | 3 +++ .../Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino | 6 +++--- .../Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino | 6 +++--- .../Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino | 6 +++--- 9 files changed, 28 insertions(+), 20 deletions(-) diff --git a/examples/Threading/Demo_Shared_Counter/Consumer.inot b/examples/Threading/Demo_Shared_Counter/Consumer.inot index 9d2d190..5030a61 100644 --- a/examples/Threading/Demo_Shared_Counter/Consumer.inot +++ b/examples/Threading/Demo_Shared_Counter/Consumer.inot @@ -1,7 +1,10 @@ -void setup() { - +void setup() +{ + Serial.begin(9600); + while(!Serial) { } } -void loop() { +void loop() +{ Serial.println(counter); } diff --git a/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino b/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino index aa71781..51377e8 100644 --- a/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino +++ b/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino @@ -1,8 +1,5 @@ void setup() { - Serial.begin(115200); - while (!Serial) { } - Producer.start(); Consumer.start(); } diff --git a/examples/Threading/Demo_Shared_Counter/Producer.inot b/examples/Threading/Demo_Shared_Counter/Producer.inot index 8932873..59a76a9 100644 --- a/examples/Threading/Demo_Shared_Counter/Producer.inot +++ b/examples/Threading/Demo_Shared_Counter/Producer.inot @@ -1,8 +1,10 @@ -void setup() { +void setup() +{ } -void loop() { +void loop() +{ static int i = 0; counter = i; i++; diff --git a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot b/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot index b03acdb..57b217a 100644 --- a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot +++ b/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot @@ -2,7 +2,8 @@ SINK(counter, int, 10); void setup() { - + Serial.begin(9600); + while(!Serial) { } } void loop() diff --git a/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino b/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino index d3007b3..b5381af 100644 --- a/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino +++ b/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino @@ -4,12 +4,14 @@ * together using the "connectTo" method. */ -void setup() { +void setup() +{ Source_Thread.led.connectTo(Sink_Thread.led); Sink_Thread.start(); Source_Thread.start(); } -void loop() { +void loop() +{ rtos::ThisThread::yield(); } diff --git a/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino b/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino index 30fd6e3..38dfda2 100644 --- a/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino +++ b/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino @@ -75,6 +75,9 @@ byte bmp388_read_reg(byte const reg_addr) void bmp388_thread_func() { + Serial.begin(9600); + while(!Serial) { } + for(;;) { /* Sleep between 5 and 500 ms */ diff --git a/examples/Threadsafe_IO/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino b/examples/Threadsafe_IO/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino index e1614d7..5ebf5f5 100644 --- a/examples/Threadsafe_IO/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino +++ b/examples/Threadsafe_IO/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino @@ -35,9 +35,6 @@ static char thread_name[NUM_THREADS][32]; void setup() { - Serial.begin(9600); - while (!Serial) { } - pinMode(BMP388_CS_PIN, OUTPUT); digitalWrite(BMP388_CS_PIN, HIGH); @@ -70,6 +67,9 @@ byte bmp388_read_reg(byte const reg_addr) void bmp388_thread_func() { + Serial.begin(9600); + while(!Serial) { } + for(;;) { /* Sleep between 5 and 500 ms */ diff --git a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino b/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino index 6372534..c15cf5d 100644 --- a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino +++ b/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino @@ -34,9 +34,6 @@ static char thread_name[NUM_THREADS][32]; void setup() { - Serial.begin(9600); - while (!Serial) { } - /* Fire up some threads all accessing the LSM6DSOX */ for(size_t i = 0; i < NUM_THREADS; i++) { @@ -76,6 +73,9 @@ byte lsm6dsox_read_reg(byte const reg_addr) void lsm6dsox_thread_func() { + Serial.begin(9600); + while(!Serial) { } + for(;;) { /* Sleep between 5 and 500 ms */ diff --git a/examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino b/examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino index 5fe3718..062841d 100644 --- a/examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino +++ b/examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino @@ -34,9 +34,6 @@ static char thread_name[NUM_THREADS][32]; void setup() { - Serial.begin(9600); - while (!Serial) { } - /* Fire up some threads all accessing the LSM6DSOX */ for(size_t i = 0; i < NUM_THREADS; i++) { @@ -64,6 +61,9 @@ byte lsm6dsox_read_reg(byte reg_addr) void lsm6dsox_thread_func() { + Serial.begin(9600); + while(!Serial) { } + for(;;) { /* Sleep between 5 and 500 ms */ From 2951862adc81db722aca9ade8a411013cf165237 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 20 Oct 2021 07:07:19 +0200 Subject: [PATCH 17/61] Group IO transfer code within utility function 'transfer_and_wait'. (#42) This allows to write more compact code while still containing the same semantics --- .../Threadsafe_SPI/Threadsafe_SPI.ino | 6 +--- .../Threadsafe_Wire/Threadsafe_Wire.ino | 12 +++---- src/Arduino_Threads.h | 1 + src/io/util/util.cpp | 34 ++++++++++++++++++ src/io/util/util.h | 35 +++++++++++++++++++ 5 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 src/io/util/util.cpp create mode 100644 src/io/util/util.h diff --git a/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino b/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino index 38dfda2..b24a2ee 100644 --- a/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino +++ b/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino @@ -64,11 +64,7 @@ byte bmp388_read_reg(byte const reg_addr) byte read_write_buf[] = {static_cast(0x80 | reg_addr), 0, 0}; IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0); - IoResponse rsp = bmp388.transfer(req); - - /* Do other stuff */ - - rsp->wait(); + IoResponse rsp = transfer_and_wait(bmp388, req); return read_write_buf[2]; } diff --git a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino b/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino index c15cf5d..4f6c72c 100644 --- a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino +++ b/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino @@ -45,7 +45,11 @@ void setup() void loop() { - + /* If we don't hand back control then the main thread + * will hog the CPU and all other thread's won't get + * time to be executed. + */ + rtos::ThisThread::yield(); } /************************************************************************************** @@ -61,11 +65,7 @@ byte lsm6dsox_read_reg(byte const reg_addr) byte read_buf = 0; IoRequest req(write_buf, read_buf); - IoResponse rsp = lsm6dsox.transfer(req); - - /* Optionally do other stuff */ - - rsp->wait(); + IoResponse rsp = transfer_and_wait(lsm6dsox, req); return read_buf; } diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index c2c2fca..11902bf 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -31,6 +31,7 @@ #include "threading/Shared.hpp" #include "io/BusDevice.h" +#include "io/util/util.h" #include "io/spi/SpiBusDevice.h" #include "io/wire/WireBusDevice.h" #include "io/serial/SerialDispatcher.h" diff --git a/src/io/util/util.cpp b/src/io/util/util.cpp new file mode 100644 index 0000000..3810be6 --- /dev/null +++ b/src/io/util/util.cpp @@ -0,0 +1,34 @@ +/* + * This file is part of the Arduino_ThreadsafeIO library. + * Copyright (c) 2021 Arduino SA. + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include "util.h" + +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +IoResponse transfer_and_wait(BusDevice & dev, IoRequest & req) +{ + IoResponse rsp = dev.transfer(req); + rsp->wait(); + return rsp; +} diff --git a/src/io/util/util.h b/src/io/util/util.h new file mode 100644 index 0000000..4dc7193 --- /dev/null +++ b/src/io/util/util.h @@ -0,0 +1,35 @@ +/* + * This file is part of the Arduino_ThreadsafeIO library. + * Copyright (c) 2021 Arduino SA. + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ARDUINO_THREADS_UTIL_H_ +#define ARDUINO_THREADS_UTIL_H_ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include "../BusDevice.h" +#include "../IoTransaction.h" + +/************************************************************************************** + * FUNCTION DECLARATION + **************************************************************************************/ + +IoResponse transfer_and_wait(BusDevice & dev, IoRequest & req); + +#endif /* ARDUINO_THREADS_UTIL_H_ */ \ No newline at end of file From 543e96ab7032b4df80ddd649e30b951393621272 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 5 Nov 2021 07:17:48 +0100 Subject: [PATCH 18/61] Revert "Bugfix: Yield after every execution of 'loop' if a loop delay of 0 is specified. (#38)" (#45) This reverts commit 1be8f611aa5997ca0843a295da2a0c356311c8ea. --- src/Arduino_Threads.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Arduino_Threads.cpp b/src/Arduino_Threads.cpp index 89a6181..444ded5 100644 --- a/src/Arduino_Threads.cpp +++ b/src/Arduino_Threads.cpp @@ -118,17 +118,8 @@ void Arduino_Threads::threadFunc() } } - if (_loop_delay_ms) { - /* Sleep for the time we've been asked to insert between loops. - */ - rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(_loop_delay_ms)); - } - else - { - /* In any case yield here so that other threads can also be - * executed following the round-robin scheduling paradigm. - */ - rtos::ThisThread::yield(); - } + /* Sleep for the time we've been asked to insert between loops. + */ + rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(_loop_delay_ms)); } } From 46d08b25ccb49217fd09d18ff0af332b59f1664f Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 5 Nov 2021 07:44:42 +0100 Subject: [PATCH 19/61] Remove superfluous rtos::ThisThread::yield(). See #45 for more information. --- .../Threadsafe_Serial_GlobalPrefixSuffix.ino | 6 ------ .../Threadsafe_Serial_ProtocolWrapping.ino | 6 +----- .../Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino | 6 ------ .../Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino | 6 ------ 4 files changed, 1 insertion(+), 23 deletions(-) diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino index 12e302a..e514c04 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino @@ -32,12 +32,6 @@ void loop() Serial.block(); Serial.println("Thread #0: Lorem ipsum ..."); Serial.unblock(); - - /* If we don't hand back control then the main thread - * will hog the CPU and all other thread's won't get - * time to be executed. - */ - rtos::ThisThread::yield(); } /************************************************************************************** diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino index 090ba04..a9632df 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino @@ -18,9 +18,5 @@ void setup() void loop() { - /* If we don't hand back control then the main thread - * will hog the CPU and all other thread's won't get - * time to be executed. - */ - rtos::ThisThread::yield(); + } diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino index 2ddc81d..3e8aa86 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino @@ -40,10 +40,4 @@ void loop() Serial.println(); Serial.unblock(); } - - /* If we don't hand back control then the main thread - * will hog the CPU and all other thread's won't get - * time to be executed. - */ - rtos::ThisThread::yield(); } diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino index 050f438..3759ab2 100644 --- a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino +++ b/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino @@ -26,10 +26,4 @@ void loop() Serial.print("] Thread #0: Lorem ipsum ..."); Serial.println(); Serial.unblock(); - - /* If we don't hand back control then the main thread - * will hog the CPU and all other thread's won't get - * time to be executed. - */ - rtos::ThisThread::yield(); } From 29c42ee61ef78909c80812fb0337ed083709f7e5 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 5 Nov 2021 07:46:07 +0100 Subject: [PATCH 20/61] Replace single condition variable with event flags - one dedicated flag per thread. --- src/io/serial/SerialDispatcher.cpp | 59 +++++++++++++++--------------- src/io/serial/SerialDispatcher.h | 18 +++++++-- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/io/serial/SerialDispatcher.cpp b/src/io/serial/SerialDispatcher.cpp index cc7a41b..caa0592 100644 --- a/src/io/serial/SerialDispatcher.cpp +++ b/src/io/serial/SerialDispatcher.cpp @@ -29,7 +29,6 @@ SerialDispatcher::SerialDispatcher(arduino::HardwareSerial & serial) : _is_initialized{false} , _mutex{} -, _cond{_mutex} , _serial{serial} , _thread(osPriorityRealtime, 4096, nullptr, "SerialDispatcher") , _has_tread_started{false} @@ -68,11 +67,8 @@ void SerialDispatcher::begin(unsigned long baudrate, uint16_t config) /* Since the thread is not in the list yet we are * going to create a new entry to the list. */ - ThreadCustomerData data; - data.thread_id = current_thread_id; - data.block_tx_buffer = false; - data.prefix_func = nullptr; - data.suffix_func = nullptr; + uint32_t const thread_event_flag = (1<<(_thread_customer_list.size())); + ThreadCustomerData data{current_thread_id, thread_event_flag}; _thread_customer_list.push_back(data); } } @@ -87,7 +83,10 @@ void SerialDispatcher::end() osThreadId_t const current_thread_id = rtos::ThisThread::get_id(); std::remove_if(std::begin(_thread_customer_list), std::end (_thread_customer_list), - [current_thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == current_thread_id); }); + [current_thread_id](ThreadCustomerData const d) -> bool + { + return (d.thread_id == current_thread_id); + }); /* If no thread consumers are left also end * the serial device altogether. @@ -104,7 +103,7 @@ int SerialDispatcher::available() { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return 0; + assert(iter != std::end(_thread_customer_list)); prepareSerialReader(iter); handleSerialReader(); @@ -116,7 +115,7 @@ int SerialDispatcher::peek() { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return 0; + assert(iter != std::end(_thread_customer_list)); prepareSerialReader(iter); handleSerialReader(); @@ -128,7 +127,7 @@ int SerialDispatcher::read() { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return 0; + assert(iter != std::end(_thread_customer_list)); prepareSerialReader(iter); handleSerialReader(); @@ -150,14 +149,8 @@ size_t SerialDispatcher::write(uint8_t const b) size_t SerialDispatcher::write(const uint8_t * data, size_t len) { mbed::ScopedLock lock(_mutex); - auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - - /* If this thread hasn't registered yet - * with the SerialDispatcher via 'begin'. - */ - if (iter == std::end(_thread_customer_list)) - return 0; + assert(iter != std::end(_thread_customer_list)); size_t bytes_written = 0; for (; (bytes_written < len) && iter->tx_buffer.availableForStore(); bytes_written++) @@ -166,7 +159,7 @@ size_t SerialDispatcher::write(const uint8_t * data, size_t len) /* Inform the worker thread that new data has * been written to a Serial transmit buffer. */ - _cond.notify_one(); + _data_available_for_transmit.set(iter->thread_event_flag); return bytes_written; } @@ -175,7 +168,8 @@ void SerialDispatcher::block() { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return; + assert(iter != std::end(_thread_customer_list)); + iter->block_tx_buffer = true; } @@ -183,16 +177,19 @@ void SerialDispatcher::unblock() { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return; + assert(iter != std::end(_thread_customer_list)); + iter->block_tx_buffer = false; - _cond.notify_one(); + + _data_available_for_transmit.set(iter->thread_event_flag); } void SerialDispatcher::prefix(PrefixInjectorCallbackFunc func) { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return; + assert(iter != std::end(_thread_customer_list)); + iter->prefix_func = func; } @@ -200,7 +197,8 @@ void SerialDispatcher::suffix(SuffixInjectorCallbackFunc func) { mbed::ScopedLock lock(_mutex); auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); - if (iter == std::end(_thread_customer_list)) return; + assert(iter != std::end(_thread_customer_list)); + iter->suffix_func = func; } @@ -226,12 +224,10 @@ void SerialDispatcher::threadFunc() while(!_terminate_thread) { - /* Prevent race conditions by multi-threaded - * access to shared data. - */ - mbed::ScopedLock lock(_mutex); - /* Wait for new data to be available */ - _cond.wait(); + /* Wait for data to be available in a transmit buffer. */ + static uint32_t constexpr ALL_EVENT_FLAGS = 0x7fffffff; + _data_available_for_transmit.wait_any(ALL_EVENT_FLAGS, osWaitForever, /* clear */ true); + /* Iterate over all list entries. */ std::for_each(std::begin(_thread_customer_list), std::end (_thread_customer_list), @@ -303,7 +299,10 @@ std::list::iterator SerialDispatcher::find { return std::find_if(std::begin(_thread_customer_list), std::end (_thread_customer_list), - [thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == thread_id); }); + [thread_id](ThreadCustomerData const d) -> bool + { + return (d.thread_id == thread_id); + }); } void SerialDispatcher::prepareSerialReader(std::list::iterator & iter) diff --git a/src/io/serial/SerialDispatcher.h b/src/io/serial/SerialDispatcher.h index 0072f87..d883198 100644 --- a/src/io/serial/SerialDispatcher.h +++ b/src/io/serial/SerialDispatcher.h @@ -71,7 +71,7 @@ class SerialDispatcher : public arduino::HardwareSerial bool _is_initialized; rtos::Mutex _mutex; - rtos::ConditionVariable _cond; + rtos::EventFlags _data_available_for_transmit; arduino::HardwareSerial & _serial; rtos::Thread _thread; @@ -84,15 +84,27 @@ class SerialDispatcher : public arduino::HardwareSerial static int constexpr THREADSAFE_SERIAL_TRANSMIT_RINGBUFFER_SIZE = 128; typedef arduino::RingBufferN SerialTransmitRingbuffer; - typedef struct + class ThreadCustomerData { + public: + ThreadCustomerData(osThreadId_t const t, uint32_t const t_event_flag) + : thread_id{t} + , thread_event_flag{t_event_flag} + , tx_buffer{} + , block_tx_buffer{false} + , rx_buffer{} + , prefix_func{nullptr} + , suffix_func{nullptr} + { } + osThreadId_t thread_id; + uint32_t thread_event_flag; SerialTransmitRingbuffer tx_buffer; bool block_tx_buffer; mbed::SharedPtr rx_buffer; /* Only when a thread has expressed interested to read from serial a receive ringbuffer is allocated. */ PrefixInjectorCallbackFunc prefix_func; SuffixInjectorCallbackFunc suffix_func; - } ThreadCustomerData; + }; std::list _thread_customer_list; From a1cc5602a4f1240fde1945f0b668e9ee569bb3f2 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 5 Nov 2021 10:24:31 +0100 Subject: [PATCH 21/61] Remove superflous rtos::ThisThread::yield() from main thread 'loop()' (#48) See #45. --- .../Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino | 2 +- .../Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino | 5 +++-- .../Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino | 2 +- examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino | 6 +----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino b/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino index 51377e8..2577997 100644 --- a/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino +++ b/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino @@ -6,5 +6,5 @@ void setup() void loop() { - rtos::ThisThread::yield(); + } diff --git a/examples/Threading/Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino b/examples/Threading/Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino index 6ce05a6..e2c1363 100644 --- a/examples/Threading/Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino +++ b/examples/Threading/Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino @@ -11,6 +11,7 @@ void setup() Consumer.start(); } -void loop() { - rtos::ThisThread::yield(); +void loop() +{ + } diff --git a/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino b/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino index b5381af..48ddafc 100644 --- a/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino +++ b/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino @@ -13,5 +13,5 @@ void setup() void loop() { - rtos::ThisThread::yield(); + } diff --git a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino b/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino index 4f6c72c..11a7372 100644 --- a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino +++ b/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino @@ -45,11 +45,7 @@ void setup() void loop() { - /* If we don't hand back control then the main thread - * will hog the CPU and all other thread's won't get - * time to be executed. - */ - rtos::ThisThread::yield(); + } /************************************************************************************** From 9ce7a4d08a68c87659961262d60f4e97ad13f95b Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 5 Nov 2021 10:46:27 +0100 Subject: [PATCH 22/61] Adding macro 'SINK_NON_BLOCKING' to instantiate non-blocking-sink. (#49) Blocking sinks are instantiated via macro 'SINK' --- src/Arduino_Threads.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index 11902bf..1750f95 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -77,6 +77,11 @@ public: \ #define GET_SINK_MACRO(_1,_2,_3,NAME,...) NAME #define SINK(...) GET_SINK_MACRO(__VA_ARGS__, SINK_3_ARG, SINK_2_ARG)(__VA_ARGS__) +#define SINK_NON_BLOCKING(name, type) \ +public: \ + SinkNonBlocking name{}; \ +private: + #define SHARED(name, type) \ Shared name; From c69498f112c7e8081b2ba57177cf804d609eebeb Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 8 Nov 2021 07:25:58 +0100 Subject: [PATCH 23/61] Drop 'Demo_' prefix from all 'Threading' examples since it does not contain relevant information. --- .../{Demo_Shared_Counter => Shared_Counter}/Consumer.inot | 0 .../{Demo_Shared_Counter => Shared_Counter}/Producer.inot | 0 .../{Demo_Shared_Counter => Shared_Counter}/SharedVariables.h | 0 .../Demo_Shared_Counter.ino => Shared_Counter/Shared_Counter.ino} | 0 .../Consumer.inot | 0 .../Producer.inot | 0 .../SharedVariables.h | 0 .../Source_Sink_Counter.ino} | 0 .../{Demo_Source_Sink_LED => Source_Sink_LED}/SharedVariables.h | 0 .../{Demo_Source_Sink_LED => Source_Sink_LED}/Sink_Thread.inot | 0 .../Source_Sink_LED.ino} | 0 .../{Demo_Source_Sink_LED => Source_Sink_LED}/Source_Thread.inot | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename examples/Threading/{Demo_Shared_Counter => Shared_Counter}/Consumer.inot (100%) rename examples/Threading/{Demo_Shared_Counter => Shared_Counter}/Producer.inot (100%) rename examples/Threading/{Demo_Shared_Counter => Shared_Counter}/SharedVariables.h (100%) rename examples/Threading/{Demo_Shared_Counter/Demo_Shared_Counter.ino => Shared_Counter/Shared_Counter.ino} (100%) rename examples/Threading/{Demo_Source_Sink_Counter => Source_Sink_Counter}/Consumer.inot (100%) rename examples/Threading/{Demo_Source_Sink_Counter => Source_Sink_Counter}/Producer.inot (100%) rename examples/Threading/{Demo_Source_Sink_Counter => Source_Sink_Counter}/SharedVariables.h (100%) rename examples/Threading/{Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino => Source_Sink_Counter/Source_Sink_Counter.ino} (100%) rename examples/Threading/{Demo_Source_Sink_LED => Source_Sink_LED}/SharedVariables.h (100%) rename examples/Threading/{Demo_Source_Sink_LED => Source_Sink_LED}/Sink_Thread.inot (100%) rename examples/Threading/{Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino => Source_Sink_LED/Source_Sink_LED.ino} (100%) rename examples/Threading/{Demo_Source_Sink_LED => Source_Sink_LED}/Source_Thread.inot (100%) diff --git a/examples/Threading/Demo_Shared_Counter/Consumer.inot b/examples/Threading/Shared_Counter/Consumer.inot similarity index 100% rename from examples/Threading/Demo_Shared_Counter/Consumer.inot rename to examples/Threading/Shared_Counter/Consumer.inot diff --git a/examples/Threading/Demo_Shared_Counter/Producer.inot b/examples/Threading/Shared_Counter/Producer.inot similarity index 100% rename from examples/Threading/Demo_Shared_Counter/Producer.inot rename to examples/Threading/Shared_Counter/Producer.inot diff --git a/examples/Threading/Demo_Shared_Counter/SharedVariables.h b/examples/Threading/Shared_Counter/SharedVariables.h similarity index 100% rename from examples/Threading/Demo_Shared_Counter/SharedVariables.h rename to examples/Threading/Shared_Counter/SharedVariables.h diff --git a/examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino b/examples/Threading/Shared_Counter/Shared_Counter.ino similarity index 100% rename from examples/Threading/Demo_Shared_Counter/Demo_Shared_Counter.ino rename to examples/Threading/Shared_Counter/Shared_Counter.ino diff --git a/examples/Threading/Demo_Source_Sink_Counter/Consumer.inot b/examples/Threading/Source_Sink_Counter/Consumer.inot similarity index 100% rename from examples/Threading/Demo_Source_Sink_Counter/Consumer.inot rename to examples/Threading/Source_Sink_Counter/Consumer.inot diff --git a/examples/Threading/Demo_Source_Sink_Counter/Producer.inot b/examples/Threading/Source_Sink_Counter/Producer.inot similarity index 100% rename from examples/Threading/Demo_Source_Sink_Counter/Producer.inot rename to examples/Threading/Source_Sink_Counter/Producer.inot diff --git a/examples/Threading/Demo_Source_Sink_Counter/SharedVariables.h b/examples/Threading/Source_Sink_Counter/SharedVariables.h similarity index 100% rename from examples/Threading/Demo_Source_Sink_Counter/SharedVariables.h rename to examples/Threading/Source_Sink_Counter/SharedVariables.h diff --git a/examples/Threading/Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino b/examples/Threading/Source_Sink_Counter/Source_Sink_Counter.ino similarity index 100% rename from examples/Threading/Demo_Source_Sink_Counter/Demo_Source_Sink_Counter.ino rename to examples/Threading/Source_Sink_Counter/Source_Sink_Counter.ino diff --git a/examples/Threading/Demo_Source_Sink_LED/SharedVariables.h b/examples/Threading/Source_Sink_LED/SharedVariables.h similarity index 100% rename from examples/Threading/Demo_Source_Sink_LED/SharedVariables.h rename to examples/Threading/Source_Sink_LED/SharedVariables.h diff --git a/examples/Threading/Demo_Source_Sink_LED/Sink_Thread.inot b/examples/Threading/Source_Sink_LED/Sink_Thread.inot similarity index 100% rename from examples/Threading/Demo_Source_Sink_LED/Sink_Thread.inot rename to examples/Threading/Source_Sink_LED/Sink_Thread.inot diff --git a/examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino b/examples/Threading/Source_Sink_LED/Source_Sink_LED.ino similarity index 100% rename from examples/Threading/Demo_Source_Sink_LED/Demo_Source_Sink_LED.ino rename to examples/Threading/Source_Sink_LED/Source_Sink_LED.ino diff --git a/examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot b/examples/Threading/Source_Sink_LED/Source_Thread.inot similarity index 100% rename from examples/Threading/Demo_Source_Sink_LED/Source_Thread.inot rename to examples/Threading/Source_Sink_LED/Source_Thread.inot From 868c8b1602a10d37b6d82bcd246ec19db15d349c Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 8 Nov 2021 07:29:18 +0100 Subject: [PATCH 24/61] Drop 'Threadsafe_' prefix from all 'Threadsafe_IO' examples since it does not contain relevant information. --- .../{Threadsafe_SPI/Threadsafe_SPI.ino => SPI/SPI.ino} | 0 .../Threadsafe_SPI_BusIO.ino => SPI_BusIO/SPI_BusIO.ino} | 0 .../Serial_GlobalPrefixSuffix.ino} | 0 .../SharedVariables.h | 0 .../Thread_1.inot | 0 .../Thread_2.inot | 0 .../Thread_3.inot | 0 .../GPS_Thread.inot | 0 .../Serial_ProtocolWrapping.ino} | 0 .../SharedVariables.h | 0 .../Serial_Reader.ino} | 0 .../{Threadsafe_Serial_Reader => Serial_Reader}/SharedVariables.h | 0 .../{Threadsafe_Serial_Reader => Serial_Reader}/Thread_1.inot | 0 .../{Threadsafe_Serial_Reader => Serial_Reader}/Thread_2.inot | 0 .../{Threadsafe_Serial_Reader => Serial_Reader}/Thread_3.inot | 0 .../Serial_Writer.ino} | 0 .../{Threadsafe_Serial_Writer => Serial_Writer}/SharedVariables.h | 0 .../{Threadsafe_Serial_Writer => Serial_Writer}/Thread_1.inot | 0 .../{Threadsafe_Serial_Writer => Serial_Writer}/Thread_2.inot | 0 .../{Threadsafe_Serial_Writer => Serial_Writer}/Thread_3.inot | 0 .../{Threadsafe_Wire/Threadsafe_Wire.ino => Wire/Wire.ino} | 0 .../Threadsafe_Wire_BusIO.ino => Wire_BusIO/Wire_BusIO.ino} | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename examples/Threadsafe_IO/{Threadsafe_SPI/Threadsafe_SPI.ino => SPI/SPI.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino => SPI_BusIO/SPI_BusIO.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino => Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_GlobalPrefixSuffix => Serial_GlobalPrefixSuffix}/SharedVariables.h (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_GlobalPrefixSuffix => Serial_GlobalPrefixSuffix}/Thread_1.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_GlobalPrefixSuffix => Serial_GlobalPrefixSuffix}/Thread_2.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_GlobalPrefixSuffix => Serial_GlobalPrefixSuffix}/Thread_3.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_ProtocolWrapping => Serial_ProtocolWrapping}/GPS_Thread.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino => Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_ProtocolWrapping => Serial_ProtocolWrapping}/SharedVariables.h (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino => Serial_Reader/Serial_Reader.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Reader => Serial_Reader}/SharedVariables.h (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Reader => Serial_Reader}/Thread_1.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Reader => Serial_Reader}/Thread_2.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Reader => Serial_Reader}/Thread_3.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino => Serial_Writer/Serial_Writer.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Writer => Serial_Writer}/SharedVariables.h (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Writer => Serial_Writer}/Thread_1.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Writer => Serial_Writer}/Thread_2.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Serial_Writer => Serial_Writer}/Thread_3.inot (100%) rename examples/Threadsafe_IO/{Threadsafe_Wire/Threadsafe_Wire.ino => Wire/Wire.ino} (100%) rename examples/Threadsafe_IO/{Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino => Wire_BusIO/Wire_BusIO.ino} (100%) diff --git a/examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino b/examples/Threadsafe_IO/SPI/SPI.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino rename to examples/Threadsafe_IO/SPI/SPI.ino diff --git a/examples/Threadsafe_IO/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino b/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino rename to examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino rename to examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/SharedVariables.h b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/SharedVariables.h similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/SharedVariables.h rename to examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/SharedVariables.h diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_1.inot b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_1.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_1.inot rename to examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_1.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_2.inot b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_2.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_2.inot rename to examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_2.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_3.inot b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_3.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_GlobalPrefixSuffix/Thread_3.inot rename to examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_3.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/GPS_Thread.inot b/examples/Threadsafe_IO/Serial_ProtocolWrapping/GPS_Thread.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/GPS_Thread.inot rename to examples/Threadsafe_IO/Serial_ProtocolWrapping/GPS_Thread.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino b/examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino rename to examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/SharedVariables.h b/examples/Threadsafe_IO/Serial_ProtocolWrapping/SharedVariables.h similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_ProtocolWrapping/SharedVariables.h rename to examples/Threadsafe_IO/Serial_ProtocolWrapping/SharedVariables.h diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino b/examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino rename to examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/SharedVariables.h b/examples/Threadsafe_IO/Serial_Reader/SharedVariables.h similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Reader/SharedVariables.h rename to examples/Threadsafe_IO/Serial_Reader/SharedVariables.h diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_1.inot b/examples/Threadsafe_IO/Serial_Reader/Thread_1.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_1.inot rename to examples/Threadsafe_IO/Serial_Reader/Thread_1.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_2.inot b/examples/Threadsafe_IO/Serial_Reader/Thread_2.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_2.inot rename to examples/Threadsafe_IO/Serial_Reader/Thread_2.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_3.inot b/examples/Threadsafe_IO/Serial_Reader/Thread_3.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Reader/Thread_3.inot rename to examples/Threadsafe_IO/Serial_Reader/Thread_3.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino b/examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino rename to examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/SharedVariables.h b/examples/Threadsafe_IO/Serial_Writer/SharedVariables.h similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Writer/SharedVariables.h rename to examples/Threadsafe_IO/Serial_Writer/SharedVariables.h diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_1.inot b/examples/Threadsafe_IO/Serial_Writer/Thread_1.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_1.inot rename to examples/Threadsafe_IO/Serial_Writer/Thread_1.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_2.inot b/examples/Threadsafe_IO/Serial_Writer/Thread_2.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_2.inot rename to examples/Threadsafe_IO/Serial_Writer/Thread_2.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_3.inot b/examples/Threadsafe_IO/Serial_Writer/Thread_3.inot similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Serial_Writer/Thread_3.inot rename to examples/Threadsafe_IO/Serial_Writer/Thread_3.inot diff --git a/examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino b/examples/Threadsafe_IO/Wire/Wire.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino rename to examples/Threadsafe_IO/Wire/Wire.ino diff --git a/examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino b/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino similarity index 100% rename from examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino rename to examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino From d5dca2807076e70b62f1004d409d3b0402b7a92c Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 8 Nov 2021 07:30:15 +0100 Subject: [PATCH 25/61] Rename examples subfolder from Threading to Threading_Basics to make more clear what the examples in the folder are about. --- .../{Threading => Threading_Basics}/Shared_Counter/Consumer.inot | 0 .../{Threading => Threading_Basics}/Shared_Counter/Producer.inot | 0 .../Shared_Counter/SharedVariables.h | 0 .../Shared_Counter/Shared_Counter.ino | 0 .../Source_Sink_Counter/Consumer.inot | 0 .../Source_Sink_Counter/Producer.inot | 0 .../Source_Sink_Counter/SharedVariables.h | 0 .../Source_Sink_Counter/Source_Sink_Counter.ino | 0 .../Source_Sink_LED/SharedVariables.h | 0 .../Source_Sink_LED/Sink_Thread.inot | 0 .../Source_Sink_LED/Source_Sink_LED.ino | 0 .../Source_Sink_LED/Source_Thread.inot | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename examples/{Threading => Threading_Basics}/Shared_Counter/Consumer.inot (100%) rename examples/{Threading => Threading_Basics}/Shared_Counter/Producer.inot (100%) rename examples/{Threading => Threading_Basics}/Shared_Counter/SharedVariables.h (100%) rename examples/{Threading => Threading_Basics}/Shared_Counter/Shared_Counter.ino (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_Counter/Consumer.inot (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_Counter/Producer.inot (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_Counter/SharedVariables.h (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_Counter/Source_Sink_Counter.ino (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_LED/SharedVariables.h (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_LED/Sink_Thread.inot (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_LED/Source_Sink_LED.ino (100%) rename examples/{Threading => Threading_Basics}/Source_Sink_LED/Source_Thread.inot (100%) diff --git a/examples/Threading/Shared_Counter/Consumer.inot b/examples/Threading_Basics/Shared_Counter/Consumer.inot similarity index 100% rename from examples/Threading/Shared_Counter/Consumer.inot rename to examples/Threading_Basics/Shared_Counter/Consumer.inot diff --git a/examples/Threading/Shared_Counter/Producer.inot b/examples/Threading_Basics/Shared_Counter/Producer.inot similarity index 100% rename from examples/Threading/Shared_Counter/Producer.inot rename to examples/Threading_Basics/Shared_Counter/Producer.inot diff --git a/examples/Threading/Shared_Counter/SharedVariables.h b/examples/Threading_Basics/Shared_Counter/SharedVariables.h similarity index 100% rename from examples/Threading/Shared_Counter/SharedVariables.h rename to examples/Threading_Basics/Shared_Counter/SharedVariables.h diff --git a/examples/Threading/Shared_Counter/Shared_Counter.ino b/examples/Threading_Basics/Shared_Counter/Shared_Counter.ino similarity index 100% rename from examples/Threading/Shared_Counter/Shared_Counter.ino rename to examples/Threading_Basics/Shared_Counter/Shared_Counter.ino diff --git a/examples/Threading/Source_Sink_Counter/Consumer.inot b/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot similarity index 100% rename from examples/Threading/Source_Sink_Counter/Consumer.inot rename to examples/Threading_Basics/Source_Sink_Counter/Consumer.inot diff --git a/examples/Threading/Source_Sink_Counter/Producer.inot b/examples/Threading_Basics/Source_Sink_Counter/Producer.inot similarity index 100% rename from examples/Threading/Source_Sink_Counter/Producer.inot rename to examples/Threading_Basics/Source_Sink_Counter/Producer.inot diff --git a/examples/Threading/Source_Sink_Counter/SharedVariables.h b/examples/Threading_Basics/Source_Sink_Counter/SharedVariables.h similarity index 100% rename from examples/Threading/Source_Sink_Counter/SharedVariables.h rename to examples/Threading_Basics/Source_Sink_Counter/SharedVariables.h diff --git a/examples/Threading/Source_Sink_Counter/Source_Sink_Counter.ino b/examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino similarity index 100% rename from examples/Threading/Source_Sink_Counter/Source_Sink_Counter.ino rename to examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino diff --git a/examples/Threading/Source_Sink_LED/SharedVariables.h b/examples/Threading_Basics/Source_Sink_LED/SharedVariables.h similarity index 100% rename from examples/Threading/Source_Sink_LED/SharedVariables.h rename to examples/Threading_Basics/Source_Sink_LED/SharedVariables.h diff --git a/examples/Threading/Source_Sink_LED/Sink_Thread.inot b/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot similarity index 100% rename from examples/Threading/Source_Sink_LED/Sink_Thread.inot rename to examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot diff --git a/examples/Threading/Source_Sink_LED/Source_Sink_LED.ino b/examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino similarity index 100% rename from examples/Threading/Source_Sink_LED/Source_Sink_LED.ino rename to examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino diff --git a/examples/Threading/Source_Sink_LED/Source_Thread.inot b/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot similarity index 100% rename from examples/Threading/Source_Sink_LED/Source_Thread.inot rename to examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot From f0e2391a0a9df1cb921b545751a0eb2f42ba0c3e Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 5 Nov 2021 10:02:36 +0100 Subject: [PATCH 26/61] Adding examples breaking the current build. --- examples/Breaks_1/Breaks_1.ino | 11 +++++++ examples/Breaks_1/SharedVariables.h | 0 examples/Breaks_1/Thread.inot | 23 ++++++++++++++ examples/Breaks_2/Breaks_2.ino | 11 +++++++ examples/Breaks_2/SharedVariables.h | 0 examples/Breaks_2/Thread.inot | 21 +++++++++++++ examples/Breaks_3/Breaks_3.ino | 11 +++++++ examples/Breaks_3/SharedVariables.h | 0 examples/Breaks_3/Thread.inot | 15 +++++++++ examples/Breaks_4/Breaks_4.ino | 11 +++++++ examples/Breaks_4/SharedVariables.h | 0 examples/Breaks_4/Thread.inot | 47 +++++++++++++++++++++++++++++ 12 files changed, 150 insertions(+) create mode 100644 examples/Breaks_1/Breaks_1.ino create mode 100644 examples/Breaks_1/SharedVariables.h create mode 100644 examples/Breaks_1/Thread.inot create mode 100644 examples/Breaks_2/Breaks_2.ino create mode 100644 examples/Breaks_2/SharedVariables.h create mode 100644 examples/Breaks_2/Thread.inot create mode 100644 examples/Breaks_3/Breaks_3.ino create mode 100644 examples/Breaks_3/SharedVariables.h create mode 100644 examples/Breaks_3/Thread.inot create mode 100644 examples/Breaks_4/Breaks_4.ino create mode 100644 examples/Breaks_4/SharedVariables.h create mode 100644 examples/Breaks_4/Thread.inot diff --git a/examples/Breaks_1/Breaks_1.ino b/examples/Breaks_1/Breaks_1.ino new file mode 100644 index 0000000..fbb7922 --- /dev/null +++ b/examples/Breaks_1/Breaks_1.ino @@ -0,0 +1,11 @@ +#include + +void setup() +{ + Thread.start(); +} + +void loop() +{ + +} \ No newline at end of file diff --git a/examples/Breaks_1/SharedVariables.h b/examples/Breaks_1/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Breaks_1/Thread.inot b/examples/Breaks_1/Thread.inot new file mode 100644 index 0000000..1f88db4 --- /dev/null +++ b/examples/Breaks_1/Thread.inot @@ -0,0 +1,23 @@ +/* This fails to compile because it doesn't look like a + * declaration and a definitio but rather like an attempt + * to overload myFunc with the same signature (again) + * resulting in a compile error. + */ + +int myFunc(int const a, int const b); + +void setup() +{ + +} + +void loop() +{ + static int c = 0; + c += myFunc(0,c); +} + +int myFunc(int const a, int const b) +{ + return (a + b); +} \ No newline at end of file diff --git a/examples/Breaks_2/Breaks_2.ino b/examples/Breaks_2/Breaks_2.ino new file mode 100644 index 0000000..fbb7922 --- /dev/null +++ b/examples/Breaks_2/Breaks_2.ino @@ -0,0 +1,11 @@ +#include + +void setup() +{ + Thread.start(); +} + +void loop() +{ + +} \ No newline at end of file diff --git a/examples/Breaks_2/SharedVariables.h b/examples/Breaks_2/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Breaks_2/Thread.inot b/examples/Breaks_2/Thread.inot new file mode 100644 index 0000000..0eff097 --- /dev/null +++ b/examples/Breaks_2/Thread.inot @@ -0,0 +1,21 @@ +/* This fails to compile because myEventHandler has the + * function signature of 'void Thread::myEventHandler(void)' + * and is a member function of 'class Thread' while + * attachInterrupt expects a function with the signature + * 'void myEventHandler(void)' + */ + +void myEventHandler() +{ + /* Do something. */ +} + +void setup() +{ + attachInterrupt(digitalPinToInterrupt(2), myEventHandler, CHANGE); +} + +void loop() +{ + +} diff --git a/examples/Breaks_3/Breaks_3.ino b/examples/Breaks_3/Breaks_3.ino new file mode 100644 index 0000000..fbb7922 --- /dev/null +++ b/examples/Breaks_3/Breaks_3.ino @@ -0,0 +1,11 @@ +#include + +void setup() +{ + Thread.start(); +} + +void loop() +{ + +} \ No newline at end of file diff --git a/examples/Breaks_3/SharedVariables.h b/examples/Breaks_3/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Breaks_3/Thread.inot b/examples/Breaks_3/Thread.inot new file mode 100644 index 0000000..6b2b2b4 --- /dev/null +++ b/examples/Breaks_3/Thread.inot @@ -0,0 +1,15 @@ +/* This fails to compile because in-class-initialisation of + * a static member variable is forbidden. + */ + +static int my_global_variable = 0; + +void setup() +{ + +} + +void loop() +{ + +} diff --git a/examples/Breaks_4/Breaks_4.ino b/examples/Breaks_4/Breaks_4.ino new file mode 100644 index 0000000..fbb7922 --- /dev/null +++ b/examples/Breaks_4/Breaks_4.ino @@ -0,0 +1,11 @@ +#include + +void setup() +{ + Thread.start(); +} + +void loop() +{ + +} \ No newline at end of file diff --git a/examples/Breaks_4/SharedVariables.h b/examples/Breaks_4/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Breaks_4/Thread.inot b/examples/Breaks_4/Thread.inot new file mode 100644 index 0000000..91875b5 --- /dev/null +++ b/examples/Breaks_4/Thread.inot @@ -0,0 +1,47 @@ +/* Breaks for all kind of stuff ... + */ + +/************************************************************************************** + * CONSTANTS + **************************************************************************************/ + +static byte constexpr LSM6DSOX_ADDRESS = 0x6A; +static byte constexpr LSM6DSOX_WHO_AM_I_REG = 0x0F; + +/************************************************************************************** + * GLOBAL VARIABLES + **************************************************************************************/ + +BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); + +/************************************************************************************** + * FUNCTIONS + **************************************************************************************/ + +byte lsm6dsox_read_reg(byte const reg_addr) +{ + byte read_buf = 0; + lsm6dsox.wire().write_then_read(®_addr, 1, &read_buf, 1); + return read_buf; +} + +/************************************************************************************** + * SETUP/LOOP + **************************************************************************************/ + +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + /* Sleep between 5 and 500 ms */ + rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); + /* Try to read some data from the LSM6DSOX. */ + byte const who_am_i = lsm6dsox_read_reg(LSM6DSOX_WHO_AM_I_REG); + /* Print thread id and chip id value to serial. */ + char msg[64] = {0}; + snprintf(msg, sizeof(msg), "%s: LSM6DSOX[WHO_AM_I] = 0x%X", rtos::ThisThread::get_name(), who_am_i); + Serial.println(msg); +} From c9cbe077203c081637ea2e84ce8bf954313fd4f9 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 10 Nov 2021 08:46:14 +0100 Subject: [PATCH 27/61] Fixing compilation break, unrelated to thread-specific code. --- examples/Breaks_4/Thread.inot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Breaks_4/Thread.inot b/examples/Breaks_4/Thread.inot index 91875b5..4ba81e9 100644 --- a/examples/Breaks_4/Thread.inot +++ b/examples/Breaks_4/Thread.inot @@ -18,7 +18,7 @@ BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); * FUNCTIONS **************************************************************************************/ -byte lsm6dsox_read_reg(byte const reg_addr) +byte lsm6dsox_read_reg(byte reg_addr) { byte read_buf = 0; lsm6dsox.wire().write_then_read(®_addr, 1, &read_buf, 1); From 9c399edf555e14fcd4e515dec037c2a8c5ee072f Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 11 Nov 2021 10:08:38 +0100 Subject: [PATCH 28/61] Adding currently still breaking example to source code. --- examples/Breaks_5/Breaks_5.ino | 11 +++++++++++ examples/Breaks_5/SharedVariables.h | 0 examples/Breaks_5/Thread.inot | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 examples/Breaks_5/Breaks_5.ino create mode 100644 examples/Breaks_5/SharedVariables.h create mode 100644 examples/Breaks_5/Thread.inot diff --git a/examples/Breaks_5/Breaks_5.ino b/examples/Breaks_5/Breaks_5.ino new file mode 100644 index 0000000..fbb7922 --- /dev/null +++ b/examples/Breaks_5/Breaks_5.ino @@ -0,0 +1,11 @@ +#include + +void setup() +{ + Thread.start(); +} + +void loop() +{ + +} \ No newline at end of file diff --git a/examples/Breaks_5/SharedVariables.h b/examples/Breaks_5/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Breaks_5/Thread.inot b/examples/Breaks_5/Thread.inot new file mode 100644 index 0000000..c430b03 --- /dev/null +++ b/examples/Breaks_5/Thread.inot @@ -0,0 +1,19 @@ +/* This fails to compile because myEventHandler is declared + * after setup/loop and currently there's not automatic prototype + * generation as done for the ino file. + */ + +void setup() +{ + attachInterrupt(digitalPinToInterrupt(2), myEventHandler, CHANGE); +} + +void loop() +{ + +} + +void myEventHandler() +{ + /* Do something. */ +} From ac183cdff1966a9e81b8a0576954a51fb4d8a95f Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 10 Nov 2021 09:39:42 +0100 Subject: [PATCH 29/61] Fixing spelling error. --- examples/Breaks_1/Thread.inot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Breaks_1/Thread.inot b/examples/Breaks_1/Thread.inot index 1f88db4..0c86dce 100644 --- a/examples/Breaks_1/Thread.inot +++ b/examples/Breaks_1/Thread.inot @@ -1,5 +1,5 @@ /* This fails to compile because it doesn't look like a - * declaration and a definitio but rather like an attempt + * declaration and a definition but rather like an attempt * to overload myFunc with the same signature (again) * resulting in a compile error. */ From 7d503b2ef936c1b9501c5d07abe4d722752c605e Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 24 Nov 2021 06:08:09 +0100 Subject: [PATCH 30/61] Adding example which breaks for both class-body/namespaced compilation, but shows private vs namespace issue. --- examples/Breaks_6/Breaks_6.ino | 11 +++++++++++ examples/Breaks_6/SharedVariables.h | 0 examples/Breaks_6/Thread.inot | 11 +++++++++++ 3 files changed, 22 insertions(+) create mode 100644 examples/Breaks_6/Breaks_6.ino create mode 100644 examples/Breaks_6/SharedVariables.h create mode 100644 examples/Breaks_6/Thread.inot diff --git a/examples/Breaks_6/Breaks_6.ino b/examples/Breaks_6/Breaks_6.ino new file mode 100644 index 0000000..e56c606 --- /dev/null +++ b/examples/Breaks_6/Breaks_6.ino @@ -0,0 +1,11 @@ +#include + +void setup() +{ + Thread.start(); +} + +void loop() +{ + int const my_var = var; +} diff --git a/examples/Breaks_6/SharedVariables.h b/examples/Breaks_6/SharedVariables.h new file mode 100644 index 0000000..e69de29 diff --git a/examples/Breaks_6/Thread.inot b/examples/Breaks_6/Thread.inot new file mode 100644 index 0000000..b31007b --- /dev/null +++ b/examples/Breaks_6/Thread.inot @@ -0,0 +1,11 @@ +void setup() +{ + +} + +void loop() +{ + +} + +int var = 0; From 33fb1ce20977e5b37240437611176935c087b6a5 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 6 Dec 2021 15:18:18 +0100 Subject: [PATCH 31/61] Cleaning up/adding missing comments within examples. (#58) This is done in order to put clearer into context what actually is happening. --- examples/Breaks_6/Breaks_6.ino | 4 ++++ .../Threading_Basics/Shared_Counter/Consumer.inot | 6 ++++++ .../Threading_Basics/Shared_Counter/Producer.inot | 4 ++++ .../Shared_Counter/SharedVariables.h | 1 + .../Shared_Counter/Shared_Counter.ino | 5 +++++ .../Source_Sink_Counter/Consumer.inot | 1 + .../Source_Sink_Counter/Producer.inot | 1 + .../Source_Sink_Counter/Source_Sink_Counter.ino | 7 +++---- .../Source_Sink_LED/Sink_Thread.inot | 12 ++++-------- .../Source_Sink_LED/Source_Sink_LED.ino | 7 +++---- .../Source_Sink_LED/Source_Thread.inot | 2 +- examples/Threadsafe_IO/SPI/SPI.ino | 6 ++++++ examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino | 9 +++++++++ .../Serial_GlobalPrefixSuffix.ino | 7 +++++++ .../Serial_ProtocolWrapping.ino | 7 +++++++ .../Threadsafe_IO/Serial_Reader/Serial_Reader.ino | 6 ++++++ .../Threadsafe_IO/Serial_Writer/Serial_Writer.ino | 5 +++++ examples/Threadsafe_IO/Wire/Wire.ino | 6 ++++++ examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino | 11 ++++++++++- 19 files changed, 89 insertions(+), 18 deletions(-) diff --git a/examples/Breaks_6/Breaks_6.ino b/examples/Breaks_6/Breaks_6.ino index e56c606..86b3bfa 100644 --- a/examples/Breaks_6/Breaks_6.ino +++ b/examples/Breaks_6/Breaks_6.ino @@ -1,3 +1,7 @@ +/* This example is in fact expected to break, since we try + * to access a thread-private variable. + */ + #include void setup() diff --git a/examples/Threading_Basics/Shared_Counter/Consumer.inot b/examples/Threading_Basics/Shared_Counter/Consumer.inot index 5030a61..c03efd9 100644 --- a/examples/Threading_Basics/Shared_Counter/Consumer.inot +++ b/examples/Threading_Basics/Shared_Counter/Consumer.inot @@ -6,5 +6,11 @@ void setup() void loop() { + /* If a value is available for reading within the internal + * queue then the value is removed from the queue and made + * available to the calling function. Should no data be + * available, then this thread is suspended until new data + * is available for reading. + */ Serial.println(counter); } diff --git a/examples/Threading_Basics/Shared_Counter/Producer.inot b/examples/Threading_Basics/Shared_Counter/Producer.inot index 59a76a9..7475653 100644 --- a/examples/Threading_Basics/Shared_Counter/Producer.inot +++ b/examples/Threading_Basics/Shared_Counter/Producer.inot @@ -6,6 +6,10 @@ void setup() void loop() { static int i = 0; + /* Every 100 ms a new value is inserted into the shared variable + * 'counter'. Internally this is stored within a queue in a FIFO + * (First-In/First-Out) manner. + */ counter = i; i++; delay(100); diff --git a/examples/Threading_Basics/Shared_Counter/SharedVariables.h b/examples/Threading_Basics/Shared_Counter/SharedVariables.h index 5891a84..7cc8c0c 100644 --- a/examples/Threading_Basics/Shared_Counter/SharedVariables.h +++ b/examples/Threading_Basics/Shared_Counter/SharedVariables.h @@ -1 +1,2 @@ +/* Define a shared variable named 'counter' of type 'int'. */ SHARED(counter, int); diff --git a/examples/Threading_Basics/Shared_Counter/Shared_Counter.ino b/examples/Threading_Basics/Shared_Counter/Shared_Counter.ino index 2577997..87f7007 100644 --- a/examples/Threading_Basics/Shared_Counter/Shared_Counter.ino +++ b/examples/Threading_Basics/Shared_Counter/Shared_Counter.ino @@ -1,3 +1,8 @@ +/* This example demonstrates data exchange between + * threads using a shared counter variable defined + * within 'SharedVariables.h'. + */ + void setup() { Producer.start(); diff --git a/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot b/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot index 57b217a..5ae30b5 100644 --- a/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot +++ b/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot @@ -1,3 +1,4 @@ +/* Define a data sink named 'counter' of type 'int' with a internal queue size of 10. */ SINK(counter, int, 10); void setup() diff --git a/examples/Threading_Basics/Source_Sink_Counter/Producer.inot b/examples/Threading_Basics/Source_Sink_Counter/Producer.inot index a038d37..3938a1c 100644 --- a/examples/Threading_Basics/Source_Sink_Counter/Producer.inot +++ b/examples/Threading_Basics/Source_Sink_Counter/Producer.inot @@ -1,3 +1,4 @@ +/* Define a data source named 'counter' of type 'int'. */ SOURCE(counter, int); void setup() diff --git a/examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino b/examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino index e2c1363..f237bb2 100644 --- a/examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino +++ b/examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino @@ -1,7 +1,6 @@ -/* - * This examples demonstrates the SOURCE/SINK abstraction. - * Each thread may have any number of SOURCES and SINKS that can be connected - * together using the "connectTo" method. +/* This examples demonstrates the SOURCE/SINK abstraction. Each thread + * may have any number of SOURCES and SINKS that can be connected + * together using the 'connectTo' method. */ void setup() diff --git a/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot b/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot index 14a9723..369de6b 100644 --- a/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot +++ b/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot @@ -1,8 +1,4 @@ - -/* - * An 'bool' SINK with a size of '0'. This kind of SINK has no buffer so the reading thread - * will block until the writing thread has written something, or vice versa. - */ +/* Define a data sink named 'led' of type 'bool' with a internal queue size of 1. */ SINK(led, bool); void setup() @@ -12,9 +8,9 @@ void setup() void loop() { - /* Read a 'bool' from the SINK and discard it. Since there is basically no delay in the loop - * this call will surely block until something comes from the connected SOURCE. In this case - * the pace is dictated by the SOURCE that sends data every 100 ms. + /* Read a 'bool' value from the SINK and discard it. Since there is no delay in the loop + * this call will block until new data is inserted from the connected SOURCE. This means + * that the pace is dictated by the SOURCE that sends data every 100 ms. */ digitalWrite(LED_BUILTIN, led); } diff --git a/examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino b/examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino index 48ddafc..c0e94be 100644 --- a/examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino +++ b/examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino @@ -1,7 +1,6 @@ -/* - * This examples demonstrates the SOURCE/SINK abstraction. - * Each thread may have any number of SOURCES and SINKS that can be connected - * together using the "connectTo" method. +/* This examples demonstrates the SOURCE/SINK abstraction. Each thread + * may have any number of SOURCES and SINKS that can be connected + * together using the 'connectTo' method. */ void setup() diff --git a/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot b/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot index 4585f85..78be7f4 100644 --- a/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot +++ b/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot @@ -1,4 +1,4 @@ -/* The output SOURCE, it sends 'bool' values. */ +/* Define a data source named 'led' of type 'bool'. */ SOURCE(led, bool); void setup() diff --git a/examples/Threadsafe_IO/SPI/SPI.ino b/examples/Threadsafe_IO/SPI/SPI.ino index b24a2ee..333299a 100644 --- a/examples/Threadsafe_IO/SPI/SPI.ino +++ b/examples/Threadsafe_IO/SPI/SPI.ino @@ -1,3 +1,9 @@ +/* This example demonstrates how multiple threads can communicate + * with a single SPI client device using the BusDevice abstraction + * for SPI. In a similar way multiple threads can interface + * with different client devices on the same SPI bus. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino b/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino index 5ebf5f5..863d3e3 100644 --- a/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino +++ b/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino @@ -1,3 +1,12 @@ +/* This example demonstrates how multiple threads can communicate + * with a single SPI client device using the BusDevice abstraction + * for SPI. In a similar way multiple threads can interface + * with different client devices on the same SPI bus. + * + * This example uses Adafruit_BusIO style read(), write(), + * write_then_read() APIs. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino index e514c04..1121b1f 100644 --- a/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino +++ b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino @@ -1,3 +1,10 @@ +/* This example demonstrates how every Serial message can be prefixed + * as well as suffixed by a user-configurable message. In this example + * this functionality is used for appending the current timestamp and + * prepending a line feed. Other uses might be to prepend the thread + * from which a given serial message is originating. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino b/examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino index a9632df..7faffb0 100644 --- a/examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino +++ b/examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino @@ -1,3 +1,10 @@ +/* This example demonstrates how every Serial message can be prefixed + * as well as suffixed by a user-configurable message. In this example + * this functionality is used for prepending the right header for a + * pseudo NMEA-encoded (think GPS) message as well as for calculating + * and appending the checksum at the end. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino b/examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino index 3e8aa86..16dad9e 100644 --- a/examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino +++ b/examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino @@ -1,3 +1,9 @@ +/* This example demonstrates how multiple threads can subscribe to + * reading from the same physical Serial interface. Incoming data + * is copied into per-thread receive buffers so that no thread + * can "steal" data from another thread by reading it first. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino b/examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino index 3759ab2..d019492 100644 --- a/examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino +++ b/examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino @@ -1,3 +1,8 @@ +/* This example demonstrates how multiple threads can write to + * the same physical Serial interface without interfering with + * one another. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/Wire/Wire.ino b/examples/Threadsafe_IO/Wire/Wire.ino index 11a7372..543d5e9 100644 --- a/examples/Threadsafe_IO/Wire/Wire.ino +++ b/examples/Threadsafe_IO/Wire/Wire.ino @@ -1,3 +1,9 @@ +/* This example demonstrates how multiple threads can communicate + * with a single Wire client device using the BusDevice abstraction + * for Wire. In a similar way multiple threads can interface + * with different client devices on the same Wire bus. + */ + /************************************************************************************** * INCLUDE **************************************************************************************/ diff --git a/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino b/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino index 062841d..d241628 100644 --- a/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino +++ b/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino @@ -1,4 +1,13 @@ - /************************************************************************************** +/* This example demonstrates how multiple threads can communicate + * with a single Wire client device using the BusDevice abstraction + * for Wire. In a similar way multiple threads can interface + * with different client devices on the same Wire bus. + * + * This example uses Adafruit_BusIO style read(), write(), + * write_then_read() APIs. + */ + +/************************************************************************************** * INCLUDE **************************************************************************************/ From 1b162b57c7692f42a93c0eb87856043cedf8bd56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 05:26:07 +0000 Subject: [PATCH 32/61] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-arduino.yml | 2 +- .github/workflows/compile-examples.yml | 6 +++--- .github/workflows/spell-check.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml index 0d03f48..291351b 100644 --- a/.github/workflows/check-arduino.yml +++ b/.github/workflows/check-arduino.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Arduino Lint uses: arduino/arduino-lint-action@v1 diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 45efc0f..9f06e53 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -60,12 +60,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 # it's necessary to checkout the platform before installing it so that the ArduinoCore-API dependency can be added - name: Checkout ArduinoCore-mbed # this step only needed when the Arduino mbed-Enabled Boards platform sourced from the repository is being used - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: arduino/ArduinoCore-mbed # the arduino/actions/libraries/compile-examples action will install the platform from this path @@ -73,7 +73,7 @@ jobs: - name: Checkout ArduinoCore-API # this step only needed when the Arduino mbed-Enabled Boards platform sourced from the repository is being used - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: arduino/ArduinoCore-API path: ${{ env.ARDUINOCORE_API_STAGING_PATH }} diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 4601442..76aadd7 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Spell check uses: codespell-project/actions-codespell@master From d4a9c8ddf48f0a7cca244fee073556398eaf6e46 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 28 Mar 2022 07:31:33 +0200 Subject: [PATCH 33/61] Add GitHub Actions workflow to synchronize with shared repository labels. (#62) --- .github/workflows/sync-labels.yml | 138 ++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 .github/workflows/sync-labels.yml diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..4ea5755 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,138 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md +name: Sync Labels + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + pull_request: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + schedule: + # Run daily at 8 AM UTC to sync with changes to shared label configurations. + - cron: "0 8 * * *" + workflow_dispatch: + repository_dispatch: + +env: + CONFIGURATIONS_FOLDER: .github/label-configuration-files + CONFIGURATIONS_ARTIFACT: label-configuration-files + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Download JSON schema for labels configuration file + id: download-schema + uses: carlosperate/download-file-action@v1 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json + location: ${{ runner.temp }}/label-configuration-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate local labels configuration + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" + + download: + needs: check + runs-on: ubuntu-latest + + strategy: + matrix: + filename: + # Filenames of the shared configurations to apply to the repository in addition to the local configuration. + # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels + - universal.yml + + steps: + - name: Download + uses: carlosperate/download-file-action@v1 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} + + - name: Pass configuration files to next job via workflow artifact + uses: actions/upload-artifact@v2 + with: + path: | + *.yaml + *.yml + if-no-files-found: error + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + sync: + needs: download + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" + + - name: Determine whether to dry run + id: dry-run + if: > + github.event_name == 'pull_request' || + ( + ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' + ) && + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + ) + run: | + # Use of this flag in the github-label-sync command will cause it to only check the validity of the + # configuration. + echo "::set-output name=flag::--dry-run" + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Download configuration files artifact + uses: actions/download-artifact@v2 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + path: ${{ env.CONFIGURATIONS_FOLDER }} + + - name: Remove unneeded artifact + uses: geekyeggo/delete-artifact@v1 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + - name: Merge label configuration files + run: | + # Merge all configuration files + shopt -s extglob + cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" + + - name: Install github-label-sync + run: sudo npm install --global github-label-sync + + - name: Sync labels + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # See: https://github.com/Financial-Times/github-label-sync + github-label-sync \ + --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ + ${{ steps.dry-run.outputs.flag }} \ + ${{ github.repository }} From 60378f360ec1653562b6522b6c90b613cb96d641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 05:19:23 +0000 Subject: [PATCH 34/61] Bump actions/download-artifact from 2 to 3 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 3. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/compile-examples.yml | 2 +- .github/workflows/sync-labels.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 9f06e53..2a964d1 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -114,7 +114,7 @@ jobs: - name: Download sketches reports artifact id: download-artifact continue-on-error: true # If compilation failed for all boards then there are no artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.SKETCHES_REPORTS_ARTIFACT_NAME }} path: ${{ env.SKETCHES_REPORTS_PATH }} diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 4ea5755..e84e803 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -108,7 +108,7 @@ jobs: uses: actions/checkout@v3 - name: Download configuration files artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.CONFIGURATIONS_ARTIFACT }} path: ${{ env.CONFIGURATIONS_FOLDER }} From b8467d63160724c64457751fe680be1b6c9d1d3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 05:19:27 +0000 Subject: [PATCH 35/61] Bump actions/upload-artifact from 2 to 3 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/compile-examples.yml | 2 +- .github/workflows/sync-labels.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 9f06e53..9b06d82 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -99,7 +99,7 @@ jobs: verbose: 'true' - name: Save memory usage change report as artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: ${{ env.SKETCHES_REPORTS_ARTIFACT_NAME }} if-no-files-found: error diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 4ea5755..1d969d5 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -70,7 +70,7 @@ jobs: file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} - name: Pass configuration files to next job via workflow artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: | *.yaml From 4b8865c10cdbf0344f07772243309b3a1b852bd0 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 8 Jun 2022 12:08:36 +0200 Subject: [PATCH 36/61] Adding documentation. (#57) * Adding documentation for threading basics. * Adding documentation for Shared/Sink/Source semantics. * Adding documentation for threadsafe Serial. * Adding documentation for threadsafe Wire. * Adding download link for multi-threading supporting arduino-cli. * Adding documentation for threadsafe SPI. * Adding download links for all platforms. * Fix: a arduino sketch can contain multiple ino-files, not just a single one. (But you can only define setup/loop once. See https://github.com/bcmi-labs/Arduino_Threads/pull/57#discussion_r772261027. * Adding information on limitation of inot-file names. * Documentation suggestions (#59) * Add questions and suggestions to main readme * Add questions and suggestions to the main docs readme * Add numbers to toc * Add introduction about single-thread limitations * Add reference to example * Add Caveats section * Rephrase benefits paragraph * Add note about single threaded example * Add question about multi core * Perform minor rewording * Add recommendation for naming * Add questions, comments and rephrasing to data exchange * Add note about hierarchy graphic * Add rephrasings and questions to Thread-safe serial * Add rewording and questions to threadsafe wire * Add questions, comments and rephrasing to threadsafe SPI * Clarify on comment * Explain threading and blocking * Remove questions * Update docs/01-threading-basics.md Co-authored-by: Alexander Entinger * Update docs/01-threading-basics.md Co-authored-by: Alexander Entinger * Update docs/01-threading-basics.md Co-authored-by: Alexander Entinger * Update README.md Co-authored-by: Alexander Entinger * Update docs/01-threading-basics.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger * Update docs/05-threadsafe-spi.md Co-authored-by: Alexander Entinger * Update docs/04-threadsafe-wire.md Co-authored-by: Alexander Entinger * Update docs/05-threadsafe-spi.md Co-authored-by: Alexander Entinger * Update docs/05-threadsafe-spi.md Co-authored-by: Alexander Entinger * Update docs/05-threadsafe-spi.md Co-authored-by: Alexander Entinger * Update docs/README.md Co-authored-by: Alexander Entinger * Update docs/05-threadsafe-spi.md Co-authored-by: Alexander Entinger * Update docs/01-threading-basics.md * Update docs/02-data-exchange.md Co-authored-by: Alexander Entinger Co-authored-by: Sebastian Romero --- README.md | 57 ++++++++------- docs/01-threading-basics.md | 119 +++++++++++++++++++++++++++++++ docs/02-data-exchange.md | 77 ++++++++++++++++++++ docs/03-threadsafe-serial.md | 134 +++++++++++++++++++++++++++++++++++ docs/04-threadsafe-wire.md | 84 ++++++++++++++++++++++ docs/05-threadsafe-spi.md | 80 +++++++++++++++++++++ docs/README.md | 33 +++++++++ 7 files changed, 558 insertions(+), 26 deletions(-) create mode 100644 docs/01-threading-basics.md create mode 100644 docs/02-data-exchange.md create mode 100644 docs/03-threadsafe-serial.md create mode 100644 docs/04-threadsafe-wire.md create mode 100644 docs/05-threadsafe-spi.md create mode 100644 docs/README.md diff --git a/README.md b/README.md index c1f173b..df282d1 100644 --- a/README.md +++ b/README.md @@ -7,60 +7,65 @@ [![Check Arduino status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml) [![Spell Check status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/spell-check.yml) -This library makes it easy to use the multi-threading capability of [Arduino](https://www.arduino.cc/) boards that use an [Mbed OS](https://os.mbed.com/docs/mbed-os/latest/introduction/index.html)-based core library. Additionally this library provides thread-safe access to `Wire`, `SPI` and `Serial` which is relevant when creating multi-threaded sketches in order to avoid common pitfalls such as race-conditions and invalid state. +This library makes it easy to use the multi-threading capability of [Arduino](https://www.arduino.cc/) boards that use an [Mbed OS](https://os.mbed.com/docs/mbed-os/latest/introduction/index.html)-based core library. Additionally this library provides thread-safe access to `Wire`, `SPI` and `Serial` which is relevant when creating multi-threaded sketches in order to avoid common pitfalls such as race-conditions and invalid state. ​ -## :zap: Features +## :star: Features ### :thread: Multi-threaded sketch execution -Instead of one big state-machine-of-doom you can split your application into multiple independent threads, each with it's own `setup()` and `loop()` function. Instead of implementing your application in a single `.ino` file, each independent thread is implemented in a dedicated `.inot` representing a clear separation of concerns on a file level. +Instead of one big state-machine-of-doom you can split your application into multiple independent threads, each with it's own `setup()` and `loop()` function. Instead of implementing your application in a single `.ino` file, each independent thread is implemented in a dedicated `.inot` file (t suffix stands for **t**hread) representing a clear separation of concerns on a file level. ### :calling: Easy communication between multiple threads -Easy inter-thread-communication is facilitated via a `Shared` abstraction providing thread-safe sink/source semantics allowing to safely exchange data of any type between threads. +Easy inter-thread-communication is facilitated via a `Shared` abstraction object providing thread-safe sink/source semantics allowing to safely exchange data of any type between threads. -### :electric_plug: Threadsafe, asynchronous and convenient Input/Output API -#### Threadsafe -A key problem of multi-tasking is the **prevention of erroneous state when multiple threads share a single resource**. The following example borrowed from a typical application demonstrates the problems resulting from multiple threads sharing a single resource: +### :shield: Thread-safe I/O +A key problem of multi-tasking is the **prevention of erroneous state when multiple threads share a single resource**. The following example borrowed from a typical application demonstrates the problems resulting from multiple threads accessing a single resource: -Imagine a embedded system where multiple `Wire` client devices are physically connected to a single `Wire` server. Each `Wire` client device is managed by a separate software thread. Each thread polls its `Wire` client device periodically. Access to the I2C bus is managed via the `Wire` library and typically follows this pattern: +Imagine an embedded system where multiple `Wire` client devices are physically connected to a single `Wire` server. Each `Wire` client device is managed by a separate software thread. Each thread polls its `Wire` client device periodically. Access to the I2C bus is managed via the `Wire` library and typically follows this pattern: ```C++ /* Wire Write Access */ -Wire.beginTransmission(addr); -Wire.write(val); +Wire.beginTransmission(address); +Wire.write(value); +// Interrupting the current thread e.g. at this point can lead to an erroneous state +// if another thread performs Wire I/O before the transmission ends. Wire.endTransmission(); /* Wire Read Access */ -Wire.beginTransmission(addr); -Wire.write(val); -Wire.endTransmission(); -Wire.requestFrom(addr, bytes) +Wire.requestFrom(address, bytes) while(Wire.available()) { - int val = Wire.read(); + int value = Wire.read(); } ``` -Since we are using [ARM Mbed OS](https://os.mbed.com/mbed-os/) which is a [preemptive](https://en.wikipedia.org/wiki/Preemption_(computing)) [RTOS](https://en.wikipedia.org/wiki/Real-time_operating_system) for achieving multi-tasking capability and under the assumption that all threads share the same priority (which leads to a [round-robin](https://en.wikipedia.org/wiki/Round-robin_scheduling) scheduling) it can easily happen that one thread is half-way through its Wire IO access when the scheduler interrupts it and schedules the next thread which in turn starts/continues/ends its own Wire IO access. +Since we are using [ARM Mbed OS](https://os.mbed.com/mbed-os/) which is a [preemptive](https://en.wikipedia.org/wiki/Preemption_(computing)) [RTOS](https://en.wikipedia.org/wiki/Real-time_operating_system) for achieving multi-tasking capability and under the assumption that all threads share the same priority (which leads to a [round-robin](https://en.wikipedia.org/wiki/Round-robin_scheduling) scheduling) it can easily happen that one thread is half-way through its Wire I/O access when the scheduler interrupts its execution and schedules the next thread which in turn starts, continues or ends its own Wire I/O access. + +As a result this interruption by the scheduler will break Wire I/O access for both devices and leave the Wire I/O controller in an undefined state :fire:. + +`Arduino_Threads` solves this problem by encapsulating the complete I/O access (e.g. reading from a `Wire` client device) within a single function call which generates an I/O request to be asynchronously executed by a high-priority I/O thread. The high-priority I/O thread is the **only** instance which directly communicates with physical hardware. + +### :runner: Asynchronous +The mechanisms implemented in this library allow any thread to dispatch an I/O request asynchronously and either continue its operation or [yield](https://en.wikipedia.org/wiki/Yield_(multithreading)) control to the next scheduled thread. All I/O requests are stored in a queue and are executed within a high-priority I/O thread after a [context-switch](https://en.wikipedia.org/wiki/Context_switch). An example of this can be seen [here](examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino)). + +### :relieved: Convenient API +Although you are free to directly manipulate I/O requests and responses (e.g. [Threadsafe_Wire](examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino)) there are convenient `read`/`write`/`write_then_read` abstractions inspired by the [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) library (e.g. [Threadsafe_Wire_BusIO](examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino)). + + -As a result this interruption by the scheduler will break Wire IO access for both devices and leave the Wire IO controller in an undefined state. :fire:. +## :zap: Caveats -`Arduino_Threads` solves this problem by encapsulating a complete IO access (e.g. reading from a `Wire` client device) within a single function call which generates an IO request to be asynchronously executed by a high-priority IO thread. The high-priority IO thread is the **only** instance which actually directly communicates with physical hardware. -#### Asynchronous -The mechanisms implemented in this library allow any thread to dispatch an IO request asynchronously and either continue operation or [yield](https://en.wikipedia.org/wiki/Yield_(multithreading))-ing control to the next scheduled thread. All IO requests are stored in a queue and are executed within a high-priority IO thread after a context-switch. An example of this can be seen [here](examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino)). -#### Convenient API -Although you are free to directly manipulate IO requests and responses (e.g. [Threadsafe_Wire](examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino)) there do exist convenient `read`/`write`/`write_then_read` abstractions inspired by the [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) library (e.g. [Threadsafe_Wire_BusIO](examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino)). ## :mag_right: Resources * [How to install a library](https://www.arduino.cc/en/guide/libraries) -* [Help Center](https://support.arduino.cc/) -* [Forum](https://forum.arduino.cc) +* [Help Center](https://support.arduino.cc/) - Get help from Arduino's official support team +* [Forum](https://forum.arduino.cc) - Get support from the community ## :bug: Bugs & Issues -If you want to report an issue with this library, you can submit it to the [issue tracker](issues) of this repository. Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. Make sure you're using an original Arduino board. +If you found an issue in this library, you can submit it to the [issue tracker](issues) of this repository. Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. To prevent hardware related incompatibilities make sure to use an [original Arduino board](https://support.arduino.cc/hc/en-us/articles/360020652100-How-to-spot-a-counterfeit-Arduino). -## :technologist: Development +## :technologist: Contribute There are many ways to contribute: diff --git a/docs/01-threading-basics.md b/docs/01-threading-basics.md new file mode 100644 index 0000000..3de1b15 --- /dev/null +++ b/docs/01-threading-basics.md @@ -0,0 +1,119 @@ + + +Threading Basics +================ +## Introduction +Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed one after another. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. + +In the historic single-threaded execution of Arduino sketches the complete program logic is contained within the `*.ino` file. It contains both a `setup()` function, which is executed only once at program start, and a `loop()` function, which is executed indefinitely. A single-threaded Arduino project can theoretically contain multiple `*.ino` files but you can define `setup()` or `loop()` only once. +In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. + +The advantage of this approach is that a complex and lengthy `loop()` function (potentially consisting of nested [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine)) found in typical Arduino sketches can be broken up into several, parallelly executed `loop()` functions with a much smaller scope. This not only increases program readability and maintainability but as a result leads to a reduction of software errors (bugs). + +#### Example (Single-Threaded): +This sketch demonstrates how one would implement a program which requires the execution of three different actions on three different periodic intervals. In this example we blink three different LEDs at three different intervals. + +**Blink_Three_LEDs.ino**: + +```C++ +void setup() +{ + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); +} + +int const DELAY_RED_msec = 900; +int const DELAY_GREEN_msec = 500; +int const DELAY_BLUE_msec = 750; + +void loop() +{ + static unsigned long prev_red = millis(); + static unsigned long prev_green = millis(); + static unsigned long prev_blue = millis(); + + unsigned long const now = millis(); + + if ((now - prev_red) > DELAY_RED_msec) { + prev_red = now; + digitalWrite(LED_RED, !digitalRead(LED_RED)); + } + + if ((now - prev_green) > DELAY_GREEN_msec) { + prev_green = now; + digitalWrite(LED_GREEN, !digitalRead(LED_GREEN)); + } + + if ((now - prev_blue) > DELAY_BLUE_msec) { + prev_blue = now; + digitalWrite(LED_BLUE, !digitalRead(LED_BLUE)); + } +} +``` +You can imagine that with increasing complexity of a sketch it gets quite difficult to keep track of the different states used for the different sub-tasks (here: blinking each of the LEDs). + +#### Example (Multi-Threaded): + +The same functionality can be provided via multi-threaded execution in a much cleaner way. + +**Blink_Three_LEDs.ino** + +```C++ +void setup() { + // Start the task defined in the corresponding .inot file + LedRed.start(); + LedGreen.start(); + LedBlue.start(); +} + +void loop() { +} +``` +**LedRed.inot** +```C++ +void setup() { + pinMode(LED_RED, OUTPUT); +} + +int const DELAY_RED_msec = 900; + +void loop() { + digitalWrite(LED_RED, !digitalRead(LED_RED)); + delay(DELAY_RED_msec); +} +``` +**LedGreen.inot** +```C++ +void setup() { + pinMode(LED_GREEN, OUTPUT); +} + +int const DELAY_GREEN_msec = 500; + +void loop() { + digitalWrite(LED_GREEN, !digitalRead(LED_GREEN)); + delay(DELAY_GREEN_msec); +} +``` +**LedBlue.inot** +```C++ +void setup() { + pinMode(LED_BLUE, OUTPUT); +} + +int const DELAY_BLUE_msec = 750; + +void loop() { + digitalWrite(LED_BLUE, !digitalRead(LED_BLUE)); + delay(DELAY_BLUE_msec); +} +``` +As you can see from the example the name of the `*.inot`-file is used to generate a class and instantiate an object with the **same name** as the `*.inot`-file. Hence the `*.inot`-file can be only named in concordance with the rules to declare a variable in C++. `*.inot`-file names: +* must begin with a letter of the alphabet or an underscore (_). +* can contain letters and numbers after the first initial letter. +* are case sensitive. +* cannot contain spaces or special characters. +* cannot be a C++ keyword (i.e. `register`, `volatile`, `while`, etc.). + +To be consistent with the Arduino programming style we recommend using [camel case](https://en.wikipedia.org/wiki/Camel_case) for the file names. \ No newline at end of file diff --git a/docs/02-data-exchange.md b/docs/02-data-exchange.md new file mode 100644 index 0000000..e30aeaf --- /dev/null +++ b/docs/02-data-exchange.md @@ -0,0 +1,77 @@ + + +Data Exchange between Threads +============================= +## Introduction +When an Arduino sketch formerly consisting of just a single `loop()` is split into multiple `loop()` functions contained in multiple `*.inot` files it becomes necessary to define a thread-safe mechanism to exchange data between those threads. For example, one `*.inot` file could calculate a math formula and send the result back to the main `*.ino` file to act upon it. Meanwhile the main `*.ino` file can take care of other tasks. + + `Arduino_Threads` supports two different mechanisms for data exchange between threads: `Shared` variables and `Sink`/`Source` semantics. Both have their pros and cons as described below. + +## `Shared` +A `Shared` variable is a global variable accessible to all threads. It can be declared within a header file named `SharedVariables.h` which is automatically included at the top of each `*.inot`-file : +```C++ +/* SharedVariables.h */ +SHARED(counter, int); /* A globally available, threadsafe, shared variable of type 'int'. */ +``` +Writing to and reading from the shared variable may not always happen concurrently. I.e. a thread reading sensor data may update the shared variable faster than a slower reader thread would extract those values. Therefore the shared variable is modeled as a queue which can store (buffer) a certain number of entries. That way the slower reader thread can access all the values in the same order as they have been written. +New values can be inserted naturally by using the assignment operator `=` as if it was just any ordinary variable type, i.e. `int`, `char`, ... + +```C++ +/* Thread_1.inot */ +counter = 10; /* Store a value into the shared variable in a threadsafe manner. */ +``` +If the internal queue is full the oldest element is discarded and the latest element is inserted into the queue. + +Retrieving stored data works also very naturally like it would for any POD data type: +```C++ +/* Thread_2.inot */ +Serial.println(counter); /* Retrieves a value from the shared variable in a threadsafe manner. */ +``` + +Should the internal queue be empty when trying to read the latest available value then the thread reading the shared variable will be suspended and the next available thread will be scheduled. Once a new value is stored inside the shared variable the suspended thread resumes operation and consumes the value which has been stored in the internal queue. +Since shared variables are globally accessible from every thread, each thread can read from or write to the shared variable. The user is responsible for using the shared variable in a responsible and sensible way, i.e. reading a shared variable from different threads is generally a bad idea, as on every read an item is removed from the queue within the shared variable and other threads can't access the read value anymore . + +## `Sink`/`Source` +The idea behind the `Sink`/`Source` semantics is to model data exchange between one data producer (`Source`) and one or multiple data consumers (`Sink`). A data producer or `Source` can be declared in any `*.ino` or `*.inot`-file using the `SOURCE` macro: +```C++ +/* DataProducerThread.inot */ +SOURCE(counter, int); /* Declaration of a data source of type `int`. */ +``` +In a similar way, a data consumer can be declared in any `*.ino` or `*.inot`-file using the `SINK` macro. In difference to `Shared` where the size of the internal queue is globally set for all shared variables you can define your desired internal buffer size separately for each `Sink`. +```C++ +/* DataConsumerThread_1.inot */ +SINK(counter, int); /* Declaration of a data sink of type `int` with a internal queue size of '1'. */ +``` +```C++ +/* DataConsumerThread_2.inot */ +SINK(counter, int, 10); /* Declaration of a data sink of type `int` with a internal queue size of '10'. */ +``` +In order to actually facilitate the flow of data from a source to a sink the sinks must be connected to the desired data source. This is done within the main `ino`-file: +```C++ +/* MySinkSourceDemo.ino */ +DataProducerThread.counter.connectTo(DataConsumerThread_1.counter); +DataProducerThread.counter.connectTo(DataConsumerThread_2.counter); +``` +Whenever a new value is assigned to a data source, i.e. +```C++ +/* DataProducerThread.inot */ +counter = 10; +``` +data is automatically copied and stored within the internal queues of all connected data sinks, from where it can be retrieved, i.e. +```C++ +/* DataConsumerThread_1.inot */ +Serial.println(counter); +``` +```C++ +/* DataConsumerThread_2.inot */ +Serial.println(counter); +``` +If a thread tries to read from an empty `Sink` the thread is suspended and the next ready thread is scheduled. When a new value is written to a `Source` and consequently copied to a `Sink` the suspended thread is resumed and continuous execution (i.e. read the data and act upon it). + +Since the data added to the source is copied multiple threads can read data from a single source without data being lost. This is an advantage compared to a simple shared variable. Furthermore you cannot accidentally write to a `Sink` or read from a `Source`. Attempting to do so results in a compilation error. + +## Comparison +| | :+1: | :-1: | +|:---:|:---:|:---:| +| `Shared` | :+1: Needs to be declared only once (in `SharedVariables.h`). | :-1: Basically a global variable, with all the disadvantages those entail.
:-1: Size of internal queue fixed for ALL shared variables.
:-1: No protection against misuse (i.e. reading from multiple threads).
| +| `Sink`/`Source` | :+1: Define internal queue size separately for each `Sink`.
:+1: Supports multiple data consumers for a single data producer.
:+1: Read/Write protection: Can't read from `Source`, can't write to `Sink`.
:+1: Mandatory connecting (plumbing) within main `*.ino`-file makes data flows easily visible.
| :-1: Needs manual connection (plumbing) to connect `Sink`'s to `Source`'s. | diff --git a/docs/03-threadsafe-serial.md b/docs/03-threadsafe-serial.md new file mode 100644 index 0000000..a60811c --- /dev/null +++ b/docs/03-threadsafe-serial.md @@ -0,0 +1,134 @@ + + +Thread-safe `Serial` +=================== +## Introduction +While both `SPI` and `Wire` are communication protocols which explicitly exist to facilitate communication between one server device and multiple client devices there are no such considerations for the `Serial` interface. `Serial` communication usually exists in a one-to-one mapping between two communication partners of equal power (both can initiate communication on their own right, there is no server/client relationship). + +One example would be an Arduino sketch sending AT commands to a modem over a Serial connection and interpreting the result of those commands. Another example would be a GPS unit sending NMEA encoded location data to the Arduino for parsing. In both cases the only sensible software representation for such functionality (AT command module or NMEA message parser) is a single thread. Also in both cases it is undesirable for other threads to inject other kind of data into the serial communication as this would only confuse i.e. the AT controlled modem which reads that data. + +A good example for multiple threads writing to `Serial` would be logging where mixing messages from different sources doesn't cause any harm. A possible example for multiple threads reading from `Serial` would be to i.e. split an NMEA parser across multiple threads, i.e. one thread only parses RMC-messages, another parses GGA-messages, etc. In any case the thread-safe `Serial` supports both single-writer/single-reader and multiple-write/multiple-reader scenarios. + +## Initialize Serial with `begin()` +In order to initialize the serial interface for any given thread `Serial.begin(baudrate)` needs to be called in each thread's `setup()` which desires to have **writing** access to the serial interface. Since it does not make sense to support multiple different baudrates (i.e. Thread_A writing with 9600 baud and Thread_B writing with 115200 baud - if you really need this, spread the attached serial devices to different serial ports), the first thread to call `Serial.begin()` locks in the baud rate configuration for all other threads. A sensible approach is to call `Serial.begin()` within the main `*.ino`-file and only then start the other threads, i.e. +```C++ +/* MyThreadsafeSerialDemo.ino */ +void setup() +{ + Serial.begin(9600); + while (!Serial) { } + /* ... */ + Thread_1.start(); + Thread_2.start(); + /* ... */ +} +``` +```C++ +/* Thread_1.inot */ +void setup() { + Serial.begin(9600); // Baud rate will be neglected as it's set in the main file +} +``` +```C++ +/* Thread_2.inot */ +void setup() { + Serial.begin(9600); // Baud rate will be neglected as it's set in the main file +} +``` + +## Write to Serial using `print()`/`println()` +([`examples/Threadsafe_IO/Serial_Writer`](../examples/Threadsafe_IO/Serial_Writer)) + +Since the thread-safe `Serial` is derived from [`arduino::HardwareSerial`](https://github.com/arduino/ArduinoCore-API/blob/master/api/HardwareSerial.h) it supports the complete `Serial` API. This means that you can simply write to the `Serial` interface as you would do with a single threaded application. +```C++ +Serial.print("This is a test message #"); +Serial.println(counter); +``` + +### Prevent message break-up using `block()`/`unblock()` +([`examples/Threadsafe_IO/Serial_Writer`](../examples/Threadsafe_IO/Serial_Writer)) + +Due to the pre-emptive nature of the underlying mbed-os threading mechanism a multi-line sequence of `Serial.print/println()` could be interrupted at any point in time. When multiple threads are writing to the same Serial interface, this can lead to jumbled-up messages. + +```C++ +/* Thread_1.inot */ +void loop() { + Serial.print("This "); + Serial.print("is "); + Serial.print("a "); + Serial.print("multi-line "); + Serial.print("log "); + /* Interruption by scheduler and context switch. */ + Serial.print("message "); + Serial.print("from "); + Serial.print("thread #1."); + Serial.println(); +} +``` +```C++ +/* Thread_2.inot */ +void loop() { + Serial.print("This "); + Serial.print("is "); + Serial.print("a "); + Serial.print("multi-line "); + Serial.print("log "); + Serial.print("message "); + Serial.print("from "); + Serial.print("thread #2."); + Serial.println(); +} +``` +The resulting serial output of `Thread_1` being interrupted at the marked spot and `Thread_2` being scheduled can be seen below: +```bash +This is a multi-line log This is a multi-line log message from thread #2. +message from thread #1. + +``` +In order to prevent such break-ups a `block()`/`unblock()` API is introduced which ensures that the messages are printed in the intended order, i.e. +```C++ +/* Thread_1.inot */ +void loop() { + Serial.block(); + Serial.print("This "); + Serial.print("is "); + /* ... */ + Serial.print("thread #1."); + Serial.println(); + Serial.unblock(); +} +``` +```C++ +/* Thread_2.inot */ +void loop() { + Serial.block(); + Serial.print("This "); + Serial.print("is "); + /* ... */ + Serial.print("thread #2."); + Serial.println(); + Serial.unblock(); +} +``` +Now the thread messages are printed in the order one would expect: +```bash +This is a multi-line log message from thread #2. +This is a multi-line log message from thread #1. +``` + +## Read from `Serial` +([`examples/Threadsafe_IO/Serial_Reader`](../examples/Threadsafe_IO/Serial_Reader)) + +Reading from the `Serial` interface can be accomplished using the `read()`, `peek()` and `available()` APIs. +```C++ +/* Thread_1.inot */ +void loop() +{ + /* Read data from the serial interface into a String. */ + String serial_msg; + while (Serial.available()) + serial_msg += (char)Serial.read(); + /* ... */ +} +``` +Whenever a thread first calls any of those three APIs a thread-specific receive ring buffer is created. From that moment on any incoming serial communication is copied into that buffer. Maintaining a copy of the serial data in a dedicated buffer per thread prevents "data stealing" from other threads in a multiple reader scenario, where the first thread to call `read()` would in fact receive the data and all other threads would miss out on it. diff --git a/docs/04-threadsafe-wire.md b/docs/04-threadsafe-wire.md new file mode 100644 index 0000000..4c16836 --- /dev/null +++ b/docs/04-threadsafe-wire.md @@ -0,0 +1,84 @@ + + +Thread-safe `Wire` +================= +## Introduction +A common problem of multi-tasking is the prevention of erroneous state when multiple threads share a single resource. The following example borrowed from a typical application demonstrates these problems: + +Imagine an embedded system where multiple `Wire` client devices are physically connected to a single `Wire` server. Each `Wire` client device is managed by a dedicated software thread. Each thread polls its `Wire` client device periodically. Access to the `Wire` bus is managed via the `Wire` library and typically follows the pattern described below: +```C++ +/* Wire Write Access */ +Wire.beginTransmission(address); +Wire.write(value); +Wire.endTransmission(); + +/* Wire Read Access */ +Wire.requestFrom(address, bytes) +while(Wire.available()) { + int value = Wire.read(); +} +``` +Since we are using the [preemptive](https://os.mbed.com/docs/mbed-os/v6.11/program-setup/concepts.html#threads) [RTOS](https://en.wikipedia.org/wiki/Real-time_operating_system) [ARM Mbed OS](https://os.mbed.com/mbed-os/) with a tick time of 10 ms for achieving multi-tasking capability and under the assumption that all threads share the same priority (which leads to a [round-robin scheduling](https://en.wikipedia.org/wiki/Round-robin_scheduling)) it can easily happen that one thread is half-way through its `Wire` access when the scheduler interrupts it and schedules the next thread which in turn starts/continues/ends its own `Wire` access. + +As a result this interruption by the scheduler will break `Wire` access for both devices and leave the `Wire` controller in an undefined state. + +In Arduino Parallela we introduced the concept of `BusDevice`s which are meant to unify the way sketches access peripherals through heterogeneous busses such as `Wire`, `SPI` and `Serial`. A `BusDevice` is declared simply by specifying the type of interface and its parameters: +```C++ +BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); +/* or */ +BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, true /* restart */); +/* or */ +BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, false /* restart */, true, /* stop */); +``` + +### Asynchronous thread-safe `Wire` access with `transfer`/`wait` +Once a `BusDevice` is declared it can be used to transfer data to and from the peripheral by means of the `transfer()` API. As opposed to the traditional Arduino bus APIs, `transfer()` is asynchronous and thus won't block execution unless the `wait()` function is called. +Note that we are in a parallel programming environment which means that calls to `transfer()` on the same bus from different sketches will be arbitrated. + +```C++ +byte lsm6dsox_read_reg(byte const reg_addr) +{ + byte write_buffer = reg_addr; + byte read_buffer = 0; + + IoRequest request(write_buffer, read_buffer); + IoResponse response = lsm6dsox.transfer(request); + + /* Wait for the completion of the IO Request. + Allows other threads to run */ + response->wait(); + + return read_buffer; +} +``` + +### Synchronous thread-safe `Wire` access with `transfer_and_wait` +([`examples/Threadsafe_IO/Wire`](../examples/Threadsafe_IO/Wire)) + +As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transfer_and_wait`. +```C++ +byte lsm6dsox_read_reg(byte const reg_addr) +{ + byte write_buffer = reg_addr; + byte read_buffer = 0; + + IoRequest request(write_buffer, read_buffer); + IoResponse response = transfer_and_wait(lsm6dsox, request); /* Transmit IO request for execution and wait for completion of request. */ + + return read_buffer; +} +``` + +### `Adafruit_BusIO` style **synchronous** thread-safe `Wire` access +([`examples/Threadsafe_IO/Wire_BusIO`](../examples/Threadsafe_IO/Wire_BusIO)) + +For further simplification [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) style APIs are provided: + +```C++ +byte lsm6dsox_read_reg(byte reg_addr) +{ + byte read_buffer = 0; + lsm6dsox.wire().write_then_read(®_addr, 1, &read_buffer, 1); + return read_buffer; +} +``` diff --git a/docs/05-threadsafe-spi.md b/docs/05-threadsafe-spi.md new file mode 100644 index 0000000..c7189b6 --- /dev/null +++ b/docs/05-threadsafe-spi.md @@ -0,0 +1,80 @@ + + +Thread-safe `SPI` +=============== +## Introduction +`SPI` communication shares the same problems that [have been outlined](04-threadsafe-wire.md) for using `Wire` in a multithreaded environment. This is due to the fact that every `SPI` transaction consists of multiple function calls, i.e. + +```C++ +digitalWrite(cs, LOW); +SPI.beginTransaction(SPI_SETTINGS); +rx = SPI.transfer(tx); +SPI.endTransaction(); +digitalWrite(cs, HIGH); +``` +Due to the preemptive nature of the underlying RTOS the execution of the `SPI` transaction can be interrupted at any point in time. This would leave both the `SPI` driver code and the client device in a undefined state. Similar to what has been implemented for `Wire`, Arduino Parallela solves this by introducing a `BusDevice` which allows for single-function calls. The Parallela API allows for thread-safe, atomic SPI transactions. A `BusDevice` is declared simply by specifying the type of the interface and its parameters: +```C++ +int const DEVICE_CS_PIN = 4; +void device_cs_select() { /* ... */ } +void device_cs_deselect() { /* ... */ } +/* ... */ +BusDevice bmp388(SPI, DEVICE_CS_PIN, spi_settings); +/* or */ +BusDevice bmp388(SPI, DEVICE_CS_PIN, spi_clock, spi_bit_order, spi_bit_mode); +/* or */ +BusDevice bmp388(SPI, device_cs_select, device_cs_deselect, spi_settings); +``` + +### Asynchronous thread-safe `SPI` access with `transfer`/`wait` +Once a `BusDevice` is declared it can be used to transfer data to and from the peripheral by means of the `transfer()` API. As opposed to the traditional Arduino bus APIs, `transfer()` is asynchronous and thus won't block execution unless the `wait()` function is called. +Note that we are in a parallel programming environment which means that calls to `transfer()` on the same bus from different sketches will be arbitrated. + +```C++ +byte bmp388_read_reg(byte const reg_addr) +{ + /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ + byte read_write_buffer[] = {0x80 | reg_addr, 0, 0}; + + IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0); + IoResponse response = bmp388.transfer(request); + /* Do other stuff */ + response->wait(); /* Wait for the completion of the IO Request. */ + auto value = read_write_buffer[2]; + return value; +} +``` + +### Synchronous thread-safe `SPI` access with `transfer_and_wait` +([`examples/Threadsafe_IO/SPI`](../examples/Threadsafe_IO/SPI)) + +As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transfer_and_wait`. +```C++ +byte bmp388_read_reg(byte const reg_addr) +{ + /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ + byte read_write_buffer[] = {0x80 | reg_addr, 0, 0}; + + IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0); + IoResponse response = transfer_and_wait(bmp388, request); + + auto value = read_write_buffer[2]; + return value; +} +``` + +### `Adafruit_BusIO` style **synchronous** thread-safe `SPI` access +([`examples/Threadsafe_IO/SPI_BusIO`](../examples/Threadsafe_IO/SPI_BusIO)) + +For a further simplification [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) style APIs are provided: + +```C++ +byte bmp388_read_reg(byte const reg_addr) +{ + /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ + byte write_buffer[2] = {0x80 | reg_addr, 0}; + byte read_buffer = 0; + + bmp388.spi().write_then_read(write_buffer, sizeof(write_buffer), &read_buffer, sizeof(read_buffer)); + return read_buffer; +} +``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7511394 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,33 @@ + + +`Arduino_Threads/docs` +====================== +The Arduino threading APIs brings multi-threading to the world of Arduino. If you're new to the concept of threads we suggest you have first a look at the [Threading Basics](01-threading-basics.md) document. + +The following Arduino architectures and boards are currently supported: +* `mbed_portenta`: [Portenta H7](https://store.arduino.cc/products/portenta-h7) +* `mbed_nano`: [Nano 33 BLE](https://store.arduino.cc/arduino-nano-33-ble), [Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect) +* `mbed_edge`: [Edge Control](https://store.arduino.cc/products/arduino-edge-control) +* `mbed_nicla`: [Nicla Sense ME](https://store.arduino.cc/products/nicla-sense-me), [Nicla Vision](http://store.arduino.cc/products/nicla-vision) + + +Threading with the above mentioned Arduino cores can be achieved by leveraging the [Arduino_Threads](https://github.com/bcmi-labs/Arduino_Threads) library in combination with the [Arduino Command Line Interface](https://github.com/facchinm/arduino-cli/commits/arduino_threads_rebased) (arduino-cli). + +Download a preliminary, pre-built `arduino-cli` binary for your operating system that supports threading: +* [MacOS_64Bit](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_macOS_64bit.tar.gz) +* [Linux_32Bit](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_32bit.tar.gz) +* [Linux_64Bit](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_64bit.tar.gz) +* [Linux_ARMv6](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_ARMv6.tar.gz) +* [Linux_ARMv7](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_ARMv7.tar.gz) +* [Windows_32Bit](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Windows_32bit.zip) +* [Windows_64Bit](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Windows_64bit.zip) + +### Table of Contents + +In the following documents you will find more information about the topics covered by this library. + +1. [Threading Basics](01-threading-basics.md) +2. [Data exchange between threads](02-data-exchange.md) +3. [Threadsafe `Serial`](03-threadsafe-serial.md) +4. [Threadsafe `Wire`](04-threadsafe-wire.md) +5. [Threadsafe `SPI`](05-threadsafe-spi.md) From 12caec4dd5ba371af4a7efb47d82816d5a7080f5 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 9 Jun 2022 06:27:59 +0200 Subject: [PATCH 37/61] Shared: providing set/get method as alternative for operator overloading. --- src/threading/Shared.hpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/threading/Shared.hpp b/src/threading/Shared.hpp index 8b35cc9..6ed1b13 100644 --- a/src/threading/Shared.hpp +++ b/src/threading/Shared.hpp @@ -40,6 +40,9 @@ class Shared { public: + T get(); + void set(T const & val); + operator T(); void operator = (T const & other); inline T peek() const { return _val; } @@ -57,7 +60,7 @@ class Shared **************************************************************************************/ template -Shared::operator T() +T Shared::get() { T * val_ptr = _mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever); if (val_ptr) @@ -70,7 +73,7 @@ Shared::operator T() } template -void Shared::operator = (T const & other) +void Shared::set(T const & val) { /* If the mailbox is full we are discarding the * oldest element and then push the new one into @@ -92,4 +95,16 @@ void Shared::operator = (T const & other) } } +template +Shared::operator T() +{ + return get(); +} + +template +void Shared::operator = (T const & other) +{ + set(other); +} + #endif /* ARDUINO_THREADS_SHARED_HPP_ */ From fe399afc41398c1f5773df37ed98898e61884fe8 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 9 Jun 2022 06:35:51 +0200 Subject: [PATCH 38/61] Source: Providing set() method as alternate for operator overloading. --- src/threading/Source.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/threading/Source.hpp b/src/threading/Source.hpp index d75ac8c..ebe531a 100644 --- a/src/threading/Source.hpp +++ b/src/threading/Source.hpp @@ -43,7 +43,8 @@ class Source public: void connectTo(SinkBase & sink); - void operator = (T const & other); + void set(T const & val); + void operator = (T const & val); private: std::list *> _sink_list; @@ -60,14 +61,20 @@ void Source::connectTo(SinkBase & sink) } template -void Source::operator = (T const & value) +void Source::set(T const & val) { std::for_each(std::begin(_sink_list), std::end (_sink_list), - [value](SinkBase * sink) + [val](SinkBase * sink) { - sink->inject(value); + sink->inject(val); }); } +template +void Source::operator = (T const & val) +{ + set(val); +} + #endif /* ARDUINO_THREADS_SOURCE_HPP_ */ From 0b90807a799d0ac26b8653e128a645f0fb55acfa Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 9 Jun 2022 06:40:00 +0200 Subject: [PATCH 39/61] Sink: Providing get() method as alternate for operator overloading. --- src/threading/Sink.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/threading/Sink.hpp b/src/threading/Sink.hpp index 539ecb2..23a6db9 100644 --- a/src/threading/Sink.hpp +++ b/src/threading/Sink.hpp @@ -38,8 +38,10 @@ class SinkBase virtual ~SinkBase() { } - virtual operator T() = 0; + virtual T get() = 0; virtual void inject(T const & value) = 0; + + inline operator T() { return get(); } }; template @@ -50,7 +52,7 @@ class SinkNonBlocking : public SinkBase SinkNonBlocking() { } virtual ~SinkNonBlocking() { } - virtual operator T() override; + virtual T get() override; virtual void inject(T const & value) override; @@ -69,7 +71,7 @@ class SinkBlocking : public SinkBase SinkBlocking(size_t const size); virtual ~SinkBlocking() { } - virtual operator T() override; + virtual T get() override; virtual void inject(T const & value) override; @@ -87,7 +89,7 @@ class SinkBlocking : public SinkBase **************************************************************************************/ template -SinkNonBlocking::operator T() +T SinkNonBlocking::get() { _mutex.lock(); return _data; @@ -114,7 +116,7 @@ SinkBlocking::SinkBlocking(size_t const size) { } template -SinkBlocking::operator T() +T SinkBlocking::get() { _mutex.lock(); while (_data.isEmpty()) From 45db5daccb5075d75d258bc2a0b8f597295a1f35 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 9 Jun 2022 06:54:57 +0200 Subject: [PATCH 40/61] Fixing compilation error. --- src/threading/Shared.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/threading/Shared.hpp b/src/threading/Shared.hpp index 6ed1b13..1708340 100644 --- a/src/threading/Shared.hpp +++ b/src/threading/Shared.hpp @@ -44,7 +44,7 @@ class Shared void set(T const & val); operator T(); - void operator = (T const & other); + void operator = (T const & val); inline T peek() const { return _val; } @@ -85,12 +85,12 @@ void Shared::set(T const & val) _mailbox.free(val_ptr); } - _val = other; + _val = val; T * val_ptr = _mailbox.try_alloc(); if (val_ptr) { - *val_ptr = other; + *val_ptr = val; _mailbox.put(val_ptr); } } @@ -102,9 +102,9 @@ Shared::operator T() } template -void Shared::operator = (T const & other) +void Shared::operator = (T const & val) { - set(other); + set(val); } #endif /* ARDUINO_THREADS_SHARED_HPP_ */ From 34071c7e8806b5c63dd54ce4b5e2f87bf31d56de Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Fri, 10 Jun 2022 07:21:50 +0200 Subject: [PATCH 41/61] Clearly mark library as BETA and provide information what we are looking for from the community (#68) * Fix: Removing extraneous brace. * Pointing towards documentation. * Adding note on the libraries BETA-ness and suggested input. * Adding top-level BETA note. * Add in-page link to directly lead to the caveats associated with the beta stage. * Also mention the necessity of specific tooling. * Update README.md Co-authored-by: Sebastian Romero Co-authored-by: Sebastian Romero --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index df282d1..0862c29 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ `Arduino_Threads` ================= +*Note: This library is currently in [beta](#zap-caveats).* [![Compile Examples status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/compile-examples.yml) [![Check Arduino status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml) @@ -9,6 +10,8 @@ This library makes it easy to use the multi-threading capability of [Arduino](https://www.arduino.cc/) boards that use an [Mbed OS](https://os.mbed.com/docs/mbed-os/latest/introduction/index.html)-based core library. Additionally this library provides thread-safe access to `Wire`, `SPI` and `Serial` which is relevant when creating multi-threaded sketches in order to avoid common pitfalls such as race-conditions and invalid state. ​ +Preeliminary **documentation** and download links for **required tooling** are available within the [`/docs`](docs/README.md) subfolder. + ## :star: Features ### :thread: Multi-threaded sketch execution Instead of one big state-machine-of-doom you can split your application into multiple independent threads, each with it's own `setup()` and `loop()` function. Instead of implementing your application in a single `.ino` file, each independent thread is implemented in a dedicated `.inot` file (t suffix stands for **t**hread) representing a clear separation of concerns on a file level. @@ -43,17 +46,14 @@ As a result this interruption by the scheduler will break Wire I/O access for bo `Arduino_Threads` solves this problem by encapsulating the complete I/O access (e.g. reading from a `Wire` client device) within a single function call which generates an I/O request to be asynchronously executed by a high-priority I/O thread. The high-priority I/O thread is the **only** instance which directly communicates with physical hardware. ### :runner: Asynchronous -The mechanisms implemented in this library allow any thread to dispatch an I/O request asynchronously and either continue its operation or [yield](https://en.wikipedia.org/wiki/Yield_(multithreading)) control to the next scheduled thread. All I/O requests are stored in a queue and are executed within a high-priority I/O thread after a [context-switch](https://en.wikipedia.org/wiki/Context_switch). An example of this can be seen [here](examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino)). +The mechanisms implemented in this library allow any thread to dispatch an I/O request asynchronously and either continue its operation or [yield](https://en.wikipedia.org/wiki/Yield_(multithreading)) control to the next scheduled thread. All I/O requests are stored in a queue and are executed within a high-priority I/O thread after a [context-switch](https://en.wikipedia.org/wiki/Context_switch). An example of this can be seen [here](examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino). ### :relieved: Convenient API Although you are free to directly manipulate I/O requests and responses (e.g. [Threadsafe_Wire](examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino)) there are convenient `read`/`write`/`write_then_read` abstractions inspired by the [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) library (e.g. [Threadsafe_Wire_BusIO](examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino)). - - ## :zap: Caveats - - +This library is currently in **BETA** phase. This means that neither the API nor the usage patterns are set in stone and are likely to change. We are publishing this library in the full knowledge that we can't foresee every possible use-case and edge-case. Therefore we would like to treat this library, while it's in beta phase, as an experiment and ask for your input for shaping this library. Please help us by providing feedback in the [issues section](https://github.com/bcmi-labs/Arduino_Threads/issues) or participating in our [discussions](https://github.com/arduino/ArduinoCore-API/discussions). ## :mag_right: Resources From 43a4d7e4e276c11ef659bf0b446e25f1785dccab Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 20 Jun 2022 10:14:54 +0200 Subject: [PATCH 42/61] Rename set/get to push/pop to better reflect on the fact that its not a simple variable but a queue. --- src/threading/Shared.hpp | 12 ++++++------ src/threading/Sink.hpp | 12 ++++++------ src/threading/Source.hpp | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/threading/Shared.hpp b/src/threading/Shared.hpp index 1708340..9b7835c 100644 --- a/src/threading/Shared.hpp +++ b/src/threading/Shared.hpp @@ -40,8 +40,8 @@ class Shared { public: - T get(); - void set(T const & val); + T pop(); + void push(T const & val); operator T(); void operator = (T const & val); @@ -60,7 +60,7 @@ class Shared **************************************************************************************/ template -T Shared::get() +T Shared::pop() { T * val_ptr = _mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever); if (val_ptr) @@ -73,7 +73,7 @@ T Shared::get() } template -void Shared::set(T const & val) +void Shared::push(T const & val) { /* If the mailbox is full we are discarding the * oldest element and then push the new one into @@ -98,13 +98,13 @@ void Shared::set(T const & val) template Shared::operator T() { - return get(); + return pop(); } template void Shared::operator = (T const & val) { - set(val); + push(val); } #endif /* ARDUINO_THREADS_SHARED_HPP_ */ diff --git a/src/threading/Sink.hpp b/src/threading/Sink.hpp index 23a6db9..919accc 100644 --- a/src/threading/Sink.hpp +++ b/src/threading/Sink.hpp @@ -38,10 +38,10 @@ class SinkBase virtual ~SinkBase() { } - virtual T get() = 0; + virtual T pop() = 0; virtual void inject(T const & value) = 0; - inline operator T() { return get(); } + inline operator T() { return pop(); } }; template @@ -52,7 +52,7 @@ class SinkNonBlocking : public SinkBase SinkNonBlocking() { } virtual ~SinkNonBlocking() { } - virtual T get() override; + virtual T pop() override; virtual void inject(T const & value) override; @@ -71,7 +71,7 @@ class SinkBlocking : public SinkBase SinkBlocking(size_t const size); virtual ~SinkBlocking() { } - virtual T get() override; + virtual T pop() override; virtual void inject(T const & value) override; @@ -89,7 +89,7 @@ class SinkBlocking : public SinkBase **************************************************************************************/ template -T SinkNonBlocking::get() +T SinkNonBlocking::pop() { _mutex.lock(); return _data; @@ -116,7 +116,7 @@ SinkBlocking::SinkBlocking(size_t const size) { } template -T SinkBlocking::get() +T SinkBlocking::pop() { _mutex.lock(); while (_data.isEmpty()) diff --git a/src/threading/Source.hpp b/src/threading/Source.hpp index ebe531a..d4506f8 100644 --- a/src/threading/Source.hpp +++ b/src/threading/Source.hpp @@ -43,7 +43,7 @@ class Source public: void connectTo(SinkBase & sink); - void set(T const & val); + void push(T const & val); void operator = (T const & val); private: @@ -61,7 +61,7 @@ void Source::connectTo(SinkBase & sink) } template -void Source::set(T const & val) +void Source::push(T const & val) { std::for_each(std::begin(_sink_list), std::end (_sink_list), @@ -74,7 +74,7 @@ void Source::set(T const & val) template void Source::operator = (T const & val) { - set(val); + push(val); } #endif /* ARDUINO_THREADS_SOURCE_HPP_ */ From 515ee2419219f3fd9f89bdd3235896b31a3f339e Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 20 Jun 2022 10:21:35 +0200 Subject: [PATCH 43/61] Mark functions utilizing operator overloading as deprected. --- src/threading/Shared.hpp | 5 ++--- src/threading/Sink.hpp | 5 ++++- src/threading/Source.hpp | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/threading/Shared.hpp b/src/threading/Shared.hpp index 9b7835c..45f70ae 100644 --- a/src/threading/Shared.hpp +++ b/src/threading/Shared.hpp @@ -42,11 +42,10 @@ class Shared T pop(); void push(T const & val); - - operator T(); - void operator = (T const & val); inline T peek() const { return _val; } + operator T() [[deprecated("Use 'pop()' instead.")]]; + void operator = (T const & val) [[deprecated("Use 'push()' instead.")]]; private: diff --git a/src/threading/Sink.hpp b/src/threading/Sink.hpp index 919accc..eb159b3 100644 --- a/src/threading/Sink.hpp +++ b/src/threading/Sink.hpp @@ -41,7 +41,10 @@ class SinkBase virtual T pop() = 0; virtual void inject(T const & value) = 0; - inline operator T() { return pop(); } + inline operator T() [[deprecated("Use 'pop()' instead.")]] + { + return pop(); + } }; template diff --git a/src/threading/Source.hpp b/src/threading/Source.hpp index d4506f8..0f64af5 100644 --- a/src/threading/Source.hpp +++ b/src/threading/Source.hpp @@ -44,7 +44,8 @@ class Source void connectTo(SinkBase & sink); void push(T const & val); - void operator = (T const & val); + + void operator = (T const & val) [[deprecated("Use 'push()' instead.")]]; private: std::list *> _sink_list; From fc4930c26cc18637791beb23240d4fb9d07b65e1 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 20 Jun 2022 10:26:25 +0200 Subject: [PATCH 44/61] Update examples to no longer use operator overloading (use push/pop instead). --- examples/Threading_Basics/Shared_Counter/Consumer.inot | 2 +- examples/Threading_Basics/Shared_Counter/Producer.inot | 2 +- examples/Threading_Basics/Source_Sink_Counter/Consumer.inot | 2 +- examples/Threading_Basics/Source_Sink_Counter/Producer.inot | 2 +- examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot | 2 +- examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/Threading_Basics/Shared_Counter/Consumer.inot b/examples/Threading_Basics/Shared_Counter/Consumer.inot index c03efd9..c6a89fd 100644 --- a/examples/Threading_Basics/Shared_Counter/Consumer.inot +++ b/examples/Threading_Basics/Shared_Counter/Consumer.inot @@ -12,5 +12,5 @@ void loop() * available, then this thread is suspended until new data * is available for reading. */ - Serial.println(counter); + Serial.println(counter.pop()); } diff --git a/examples/Threading_Basics/Shared_Counter/Producer.inot b/examples/Threading_Basics/Shared_Counter/Producer.inot index 7475653..59578d4 100644 --- a/examples/Threading_Basics/Shared_Counter/Producer.inot +++ b/examples/Threading_Basics/Shared_Counter/Producer.inot @@ -10,7 +10,7 @@ void loop() * 'counter'. Internally this is stored within a queue in a FIFO * (First-In/First-Out) manner. */ - counter = i; + counter.push(i); i++; delay(100); } diff --git a/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot b/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot index 5ae30b5..21aafec 100644 --- a/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot +++ b/examples/Threading_Basics/Source_Sink_Counter/Consumer.inot @@ -9,5 +9,5 @@ void setup() void loop() { - Serial.println(counter); + Serial.println(counter.pop()); } diff --git a/examples/Threading_Basics/Source_Sink_Counter/Producer.inot b/examples/Threading_Basics/Source_Sink_Counter/Producer.inot index 3938a1c..c359c40 100644 --- a/examples/Threading_Basics/Source_Sink_Counter/Producer.inot +++ b/examples/Threading_Basics/Source_Sink_Counter/Producer.inot @@ -9,6 +9,6 @@ void setup() void loop() { static int i = 0; - counter = i; + counter.push(i); i++; } diff --git a/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot b/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot index 369de6b..e4dd975 100644 --- a/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot +++ b/examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot @@ -12,5 +12,5 @@ void loop() * this call will block until new data is inserted from the connected SOURCE. This means * that the pace is dictated by the SOURCE that sends data every 100 ms. */ - digitalWrite(LED_BUILTIN, led); + digitalWrite(LED_BUILTIN, led.pop()); } diff --git a/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot b/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot index 78be7f4..dc8f864 100644 --- a/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot +++ b/examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot @@ -8,8 +8,8 @@ void setup() void loop() { - led = true; + led.push(true); delay(100); - led = false; + led.push(false); delay(100); } From 948fcd3d1c4324dfd70e59d04a7d8d3175a10438 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 20 Jun 2022 13:01:48 +0200 Subject: [PATCH 45/61] Update documentation re pop/push. --- docs/02-data-exchange.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/02-data-exchange.md b/docs/02-data-exchange.md index e30aeaf..5471f8e 100644 --- a/docs/02-data-exchange.md +++ b/docs/02-data-exchange.md @@ -18,14 +18,14 @@ New values can be inserted naturally by using the assignment operator `=` as if ```C++ /* Thread_1.inot */ -counter = 10; /* Store a value into the shared variable in a threadsafe manner. */ +counter.push(10); /* Store a value into the shared variable in a threadsafe manner. */ ``` If the internal queue is full the oldest element is discarded and the latest element is inserted into the queue. Retrieving stored data works also very naturally like it would for any POD data type: ```C++ /* Thread_2.inot */ -Serial.println(counter); /* Retrieves a value from the shared variable in a threadsafe manner. */ +Serial.println(counter.pop()); /* Retrieves a value from the shared variable in a threadsafe manner. */ ``` Should the internal queue be empty when trying to read the latest available value then the thread reading the shared variable will be suspended and the next available thread will be scheduled. Once a new value is stored inside the shared variable the suspended thread resumes operation and consumes the value which has been stored in the internal queue. @@ -55,16 +55,16 @@ DataProducerThread.counter.connectTo(DataConsumerThread_2.counter); Whenever a new value is assigned to a data source, i.e. ```C++ /* DataProducerThread.inot */ -counter = 10; +counter.push(10); ``` data is automatically copied and stored within the internal queues of all connected data sinks, from where it can be retrieved, i.e. ```C++ /* DataConsumerThread_1.inot */ -Serial.println(counter); +Serial.println(counter.pop()); ``` ```C++ /* DataConsumerThread_2.inot */ -Serial.println(counter); +Serial.println(counter.pop()); ``` If a thread tries to read from an empty `Sink` the thread is suspended and the next ready thread is scheduled. When a new value is written to a `Source` and consequently copied to a `Sink` the suspended thread is resumed and continuous execution (i.e. read the data and act upon it). From 669f2729283b011911cbac2bea6b0bbf189f6dc2 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 22 Jun 2022 06:55:24 +0200 Subject: [PATCH 46/61] Fix: Use only camel case for class member functions. (#69) This fixes #63. --- README.md | 2 +- docs/04-threadsafe-wire.md | 8 ++++---- docs/05-threadsafe-spi.md | 8 ++++---- examples/Breaks_4/Thread.inot | 2 +- examples/Threadsafe_IO/SPI/SPI.ino | 2 +- examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino | 4 ++-- .../Serial_GlobalPrefixSuffix.ino | 4 ++-- examples/Threadsafe_IO/Wire/Wire.ino | 2 +- examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino | 4 ++-- keywords.txt | 7 ++++--- src/io/serial/SerialDispatcher.cpp | 4 ++-- src/io/serial/SerialDispatcher.h | 4 ++-- src/io/spi/SpiBusDevice.cpp | 6 +++--- src/io/spi/SpiBusDevice.h | 2 +- src/io/spi/SpiBusDeviceConfig.h | 6 +++--- src/io/spi/SpiDispatcher.cpp | 2 +- src/io/util/util.cpp | 2 +- src/io/util/util.h | 2 +- src/io/wire/WireBusDevice.cpp | 8 ++++---- src/io/wire/WireBusDevice.h | 2 +- src/io/wire/WireBusDeviceConfig.h | 2 +- src/io/wire/WireDispatcher.cpp | 4 ++-- 22 files changed, 44 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0862c29..08801ec 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ As a result this interruption by the scheduler will break Wire I/O access for bo The mechanisms implemented in this library allow any thread to dispatch an I/O request asynchronously and either continue its operation or [yield](https://en.wikipedia.org/wiki/Yield_(multithreading)) control to the next scheduled thread. All I/O requests are stored in a queue and are executed within a high-priority I/O thread after a [context-switch](https://en.wikipedia.org/wiki/Context_switch). An example of this can be seen [here](examples/Threadsafe_IO/Threadsafe_SPI/Threadsafe_SPI.ino). ### :relieved: Convenient API -Although you are free to directly manipulate I/O requests and responses (e.g. [Threadsafe_Wire](examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino)) there are convenient `read`/`write`/`write_then_read` abstractions inspired by the [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) library (e.g. [Threadsafe_Wire_BusIO](examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino)). +Although you are free to directly manipulate I/O requests and responses (e.g. [Threadsafe_Wire](examples/Threadsafe_IO/Threadsafe_Wire/Threadsafe_Wire.ino)) there are convenient `read`/`write`/`writeThenRead` abstractions inspired by the [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) library (e.g. [Threadsafe_Wire_BusIO](examples/Threadsafe_IO/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino)). ## :zap: Caveats diff --git a/docs/04-threadsafe-wire.md b/docs/04-threadsafe-wire.md index 4c16836..851baac 100644 --- a/docs/04-threadsafe-wire.md +++ b/docs/04-threadsafe-wire.md @@ -52,10 +52,10 @@ byte lsm6dsox_read_reg(byte const reg_addr) } ``` -### Synchronous thread-safe `Wire` access with `transfer_and_wait` +### Synchronous thread-safe `Wire` access with `transferAndWait` ([`examples/Threadsafe_IO/Wire`](../examples/Threadsafe_IO/Wire)) -As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transfer_and_wait`. +As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transferAndWait`. ```C++ byte lsm6dsox_read_reg(byte const reg_addr) { @@ -63,7 +63,7 @@ byte lsm6dsox_read_reg(byte const reg_addr) byte read_buffer = 0; IoRequest request(write_buffer, read_buffer); - IoResponse response = transfer_and_wait(lsm6dsox, request); /* Transmit IO request for execution and wait for completion of request. */ + IoResponse response = transferAndWait(lsm6dsox, request); /* Transmit IO request for execution and wait for completion of request. */ return read_buffer; } @@ -78,7 +78,7 @@ For further simplification [Adafruit_BusIO](https://github.com/adafruit/Adafruit byte lsm6dsox_read_reg(byte reg_addr) { byte read_buffer = 0; - lsm6dsox.wire().write_then_read(®_addr, 1, &read_buffer, 1); + lsm6dsox.wire().writeThenRead(®_addr, 1, &read_buffer, 1); return read_buffer; } ``` diff --git a/docs/05-threadsafe-spi.md b/docs/05-threadsafe-spi.md index c7189b6..079cc04 100644 --- a/docs/05-threadsafe-spi.md +++ b/docs/05-threadsafe-spi.md @@ -44,10 +44,10 @@ byte bmp388_read_reg(byte const reg_addr) } ``` -### Synchronous thread-safe `SPI` access with `transfer_and_wait` +### Synchronous thread-safe `SPI` access with `transferAndWait` ([`examples/Threadsafe_IO/SPI`](../examples/Threadsafe_IO/SPI)) -As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transfer_and_wait`. +As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transferAndWait`. ```C++ byte bmp388_read_reg(byte const reg_addr) { @@ -55,7 +55,7 @@ byte bmp388_read_reg(byte const reg_addr) byte read_write_buffer[] = {0x80 | reg_addr, 0, 0}; IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0); - IoResponse response = transfer_and_wait(bmp388, request); + IoResponse response = transferAndWait(bmp388, request); auto value = read_write_buffer[2]; return value; @@ -74,7 +74,7 @@ byte bmp388_read_reg(byte const reg_addr) byte write_buffer[2] = {0x80 | reg_addr, 0}; byte read_buffer = 0; - bmp388.spi().write_then_read(write_buffer, sizeof(write_buffer), &read_buffer, sizeof(read_buffer)); + bmp388.spi().writeThenRead(write_buffer, sizeof(write_buffer), &read_buffer, sizeof(read_buffer)); return read_buffer; } ``` diff --git a/examples/Breaks_4/Thread.inot b/examples/Breaks_4/Thread.inot index 4ba81e9..2588a25 100644 --- a/examples/Breaks_4/Thread.inot +++ b/examples/Breaks_4/Thread.inot @@ -21,7 +21,7 @@ BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); byte lsm6dsox_read_reg(byte reg_addr) { byte read_buf = 0; - lsm6dsox.wire().write_then_read(®_addr, 1, &read_buf, 1); + lsm6dsox.wire().writeThenRead(®_addr, 1, &read_buf, 1); return read_buf; } diff --git a/examples/Threadsafe_IO/SPI/SPI.ino b/examples/Threadsafe_IO/SPI/SPI.ino index 333299a..b4b6ae1 100644 --- a/examples/Threadsafe_IO/SPI/SPI.ino +++ b/examples/Threadsafe_IO/SPI/SPI.ino @@ -70,7 +70,7 @@ byte bmp388_read_reg(byte const reg_addr) byte read_write_buf[] = {static_cast(0x80 | reg_addr), 0, 0}; IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0); - IoResponse rsp = transfer_and_wait(bmp388, req); + IoResponse rsp = transferAndWait(bmp388, req); return read_write_buf[2]; } diff --git a/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino b/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino index 863d3e3..d5b2648 100644 --- a/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino +++ b/examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino @@ -4,7 +4,7 @@ * with different client devices on the same SPI bus. * * This example uses Adafruit_BusIO style read(), write(), - * write_then_read() APIs. + * writeThenRead() APIs. */ /************************************************************************************** @@ -70,7 +70,7 @@ byte bmp388_read_reg(byte const reg_addr) byte write_buf[2] = {static_cast(0x80 | reg_addr), 0}; byte read_buf = 0; - bmp388.spi().write_then_read(write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf)); + bmp388.spi().writeThenRead(write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf)); return read_buf; } diff --git a/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino index 1121b1f..9c3328a 100644 --- a/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino +++ b/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino @@ -26,8 +26,8 @@ void setup() Serial.begin(9600); while (!Serial) { } - Serial.global_prefix(serial_log_message_prefix); - Serial.global_suffix(serial_log_message_suffix); + Serial.globalPrefix(serial_log_message_prefix); + Serial.globalSuffix(serial_log_message_suffix); Thread_1.start(); Thread_2.start(); diff --git a/examples/Threadsafe_IO/Wire/Wire.ino b/examples/Threadsafe_IO/Wire/Wire.ino index 543d5e9..476b3c5 100644 --- a/examples/Threadsafe_IO/Wire/Wire.ino +++ b/examples/Threadsafe_IO/Wire/Wire.ino @@ -67,7 +67,7 @@ byte lsm6dsox_read_reg(byte const reg_addr) byte read_buf = 0; IoRequest req(write_buf, read_buf); - IoResponse rsp = transfer_and_wait(lsm6dsox, req); + IoResponse rsp = transferAndWait(lsm6dsox, req); return read_buf; } diff --git a/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino b/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino index d241628..ec669f5 100644 --- a/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino +++ b/examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino @@ -4,7 +4,7 @@ * with different client devices on the same Wire bus. * * This example uses Adafruit_BusIO style read(), write(), - * write_then_read() APIs. + * writeThenRead() APIs. */ /************************************************************************************** @@ -64,7 +64,7 @@ void loop() byte lsm6dsox_read_reg(byte reg_addr) { byte read_buf = 0; - lsm6dsox.wire().write_then_read(®_addr, 1, &read_buf, 1); + lsm6dsox.wire().writeThenRead(®_addr, 1, &read_buf, 1); return read_buf; } diff --git a/keywords.txt b/keywords.txt index 6cb05c1..e8fdcb6 100644 --- a/keywords.txt +++ b/keywords.txt @@ -34,13 +34,14 @@ block KEYWORD2 unblock KEYWORD2 prefix KEYWORD2 suffix KEYWORD2 -global_prefix KEYWORD2 -global_suffix KEYWORD2 +globalPrefix KEYWORD2 +globalSuffix KEYWORD2 spi KEYWORD2 wire KEYWORD2 read KEYWORD2 write KEYWORD2 -write_then_read KEYWORD2 +writeThenRead KEYWORD2 +transferAndWait KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/src/io/serial/SerialDispatcher.cpp b/src/io/serial/SerialDispatcher.cpp index caa0592..67dfc00 100644 --- a/src/io/serial/SerialDispatcher.cpp +++ b/src/io/serial/SerialDispatcher.cpp @@ -202,13 +202,13 @@ void SerialDispatcher::suffix(SuffixInjectorCallbackFunc func) iter->suffix_func = func; } -void SerialDispatcher::global_prefix(PrefixInjectorCallbackFunc func) +void SerialDispatcher::globalPrefix(PrefixInjectorCallbackFunc func) { mbed::ScopedLock lock(_mutex); _global_prefix_callback = func; } -void SerialDispatcher::global_suffix(SuffixInjectorCallbackFunc func) +void SerialDispatcher::globalSuffix(SuffixInjectorCallbackFunc func) { mbed::ScopedLock lock(_mutex); _global_suffix_callback = func; diff --git a/src/io/serial/SerialDispatcher.h b/src/io/serial/SerialDispatcher.h index d883198..2cac17b 100644 --- a/src/io/serial/SerialDispatcher.h +++ b/src/io/serial/SerialDispatcher.h @@ -63,8 +63,8 @@ class SerialDispatcher : public arduino::HardwareSerial typedef std::function SuffixInjectorCallbackFunc; void prefix(PrefixInjectorCallbackFunc func); void suffix(SuffixInjectorCallbackFunc func); - void global_prefix(PrefixInjectorCallbackFunc func); - void global_suffix(SuffixInjectorCallbackFunc func); + void globalPrefix(PrefixInjectorCallbackFunc func); + void globalSuffix(SuffixInjectorCallbackFunc func); private: diff --git a/src/io/spi/SpiBusDevice.cpp b/src/io/spi/SpiBusDevice.cpp index 42dd09b..3e5b788 100644 --- a/src/io/spi/SpiBusDevice.cpp +++ b/src/io/spi/SpiBusDevice.cpp @@ -45,7 +45,7 @@ IoResponse SpiBusDevice::transfer(IoRequest & req) bool SpiBusDevice::read(uint8_t * buffer, size_t len, uint8_t sendvalue) { - SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.select_func(), _config.deselect_func(), sendvalue); + SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.selectFunc(), _config.deselectFunc(), sendvalue); IoRequest req(nullptr, 0, buffer, len); IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &config); rsp->wait(); @@ -60,9 +60,9 @@ bool SpiBusDevice::write(uint8_t * buffer, size_t len) return true; } -bool SpiBusDevice::write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue) +bool SpiBusDevice::writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue) { - SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.select_func(), _config.deselect_func(), sendvalue); + SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.selectFunc(), _config.deselectFunc(), sendvalue); IoRequest req(write_buffer, write_len, read_buffer, read_len); IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &config); rsp->wait(); diff --git a/src/io/spi/SpiBusDevice.h b/src/io/spi/SpiBusDevice.h index f16347b..cdc5f83 100644 --- a/src/io/spi/SpiBusDevice.h +++ b/src/io/spi/SpiBusDevice.h @@ -44,7 +44,7 @@ class SpiBusDevice : public BusDeviceBase bool read(uint8_t * buffer, size_t len, uint8_t sendvalue = 0xFF); bool write(uint8_t * buffer, size_t len); - bool write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue = 0xFF); + bool writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue = 0xFF); private: diff --git a/src/io/spi/SpiBusDeviceConfig.h b/src/io/spi/SpiBusDeviceConfig.h index 196fc43..692acb7 100644 --- a/src/io/spi/SpiBusDeviceConfig.h +++ b/src/io/spi/SpiBusDeviceConfig.h @@ -64,10 +64,10 @@ class SpiBusDeviceConfig SPISettings settings () const { return _spi_settings; } void select () const { if (_spi_select) _spi_select(); } void deselect () const { if (_spi_deselect) _spi_deselect(); } - byte fill_symbol() const { return _fill_symbol; } + byte fillSymbol () const { return _fill_symbol; } - SpiSelectFunc select_func () const { return _spi_select; } - SpiDeselectFunc deselect_func() const { return _spi_deselect; } + SpiSelectFunc selectFunc () const { return _spi_select; } + SpiDeselectFunc deselectFunc() const { return _spi_deselect; } private: diff --git a/src/io/spi/SpiDispatcher.cpp b/src/io/spi/SpiDispatcher.cpp index a5ab024..a619834 100644 --- a/src/io/spi/SpiDispatcher.cpp +++ b/src/io/spi/SpiDispatcher.cpp @@ -158,7 +158,7 @@ void SpiDispatcher::processSpiIoRequest(SpiIoTransaction * spi_io_transaction) size_t bytes_received = 0; for(; bytes_received < io_request->bytes_to_read; bytes_received++) { - uint8_t const tx_byte = config->fill_symbol(); + uint8_t const tx_byte = config->fillSymbol(); uint8_t const rx_byte = config->spi().transfer(tx_byte); io_request->read_buf[bytes_received] = rx_byte; diff --git a/src/io/util/util.cpp b/src/io/util/util.cpp index 3810be6..a67c6a2 100644 --- a/src/io/util/util.cpp +++ b/src/io/util/util.cpp @@ -26,7 +26,7 @@ * FUNCTION DEFINITION **************************************************************************************/ -IoResponse transfer_and_wait(BusDevice & dev, IoRequest & req) +IoResponse transferAndWait(BusDevice & dev, IoRequest & req) { IoResponse rsp = dev.transfer(req); rsp->wait(); diff --git a/src/io/util/util.h b/src/io/util/util.h index 4dc7193..d129985 100644 --- a/src/io/util/util.h +++ b/src/io/util/util.h @@ -30,6 +30,6 @@ * FUNCTION DECLARATION **************************************************************************************/ -IoResponse transfer_and_wait(BusDevice & dev, IoRequest & req); +IoResponse transferAndWait(BusDevice & dev, IoRequest & req); #endif /* ARDUINO_THREADS_UTIL_H_ */ \ No newline at end of file diff --git a/src/io/wire/WireBusDevice.cpp b/src/io/wire/WireBusDevice.cpp index 19477fb..763c331 100644 --- a/src/io/wire/WireBusDevice.cpp +++ b/src/io/wire/WireBusDevice.cpp @@ -45,7 +45,7 @@ IoResponse WireBusDevice::transfer(IoRequest & req) bool WireBusDevice::read(uint8_t * buffer, size_t len, bool stop) { - WireBusDeviceConfig config(_config.wire(), _config.slave_addr(), _config.restart(), stop); + WireBusDeviceConfig config(_config.wire(), _config.slaveAddr(), _config.restart(), stop); IoRequest req(nullptr, 0, buffer, len); IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config); rsp->wait(); @@ -55,20 +55,20 @@ bool WireBusDevice::read(uint8_t * buffer, size_t len, bool stop) bool WireBusDevice::write(uint8_t * buffer, size_t len, bool stop) { bool const restart = !stop; - WireBusDeviceConfig config(_config.wire(), _config.slave_addr(), restart, _config.stop()); + WireBusDeviceConfig config(_config.wire(), _config.slaveAddr(), restart, _config.stop()); IoRequest req(buffer, len, nullptr, 0); IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config); rsp->wait(); return true; } -bool WireBusDevice::write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop) +bool WireBusDevice::writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop) { /* Copy the Wire parameters from the device and modify only those * which can be modified via the parameters of this function. */ bool const restart = !stop; - WireBusDeviceConfig config(_config.wire(), _config.slave_addr(), restart, _config.stop()); + WireBusDeviceConfig config(_config.wire(), _config.slaveAddr(), restart, _config.stop()); /* Fire off the IO request and await its response. */ IoRequest req(write_buffer, write_len, read_buffer, read_len); IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config); diff --git a/src/io/wire/WireBusDevice.h b/src/io/wire/WireBusDevice.h index 6c9f8bf..08340f5 100644 --- a/src/io/wire/WireBusDevice.h +++ b/src/io/wire/WireBusDevice.h @@ -44,7 +44,7 @@ class WireBusDevice : public BusDeviceBase bool read(uint8_t * buffer, size_t len, bool stop = true); bool write(uint8_t * buffer, size_t len, bool stop = true); - bool write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop = false); + bool writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop = false); private: diff --git a/src/io/wire/WireBusDeviceConfig.h b/src/io/wire/WireBusDeviceConfig.h index c58e5b9..33d1dd4 100644 --- a/src/io/wire/WireBusDeviceConfig.h +++ b/src/io/wire/WireBusDeviceConfig.h @@ -44,7 +44,7 @@ class WireBusDeviceConfig inline arduino::HardwareI2C & wire() { return _wire; } - inline byte slave_addr() const { return _slave_addr; } + inline byte slaveAddr() const { return _slave_addr; } inline bool restart() const { return _restart; } inline bool stop() const { return _stop; } diff --git a/src/io/wire/WireDispatcher.cpp b/src/io/wire/WireDispatcher.cpp index 9a8a664..d13c55e 100644 --- a/src/io/wire/WireDispatcher.cpp +++ b/src/io/wire/WireDispatcher.cpp @@ -138,7 +138,7 @@ void WireDispatcher::processWireIoRequest(WireIoTransaction * wire_io_transactio if (io_request->bytes_to_write > 0) { - config->wire().beginTransmission(config->slave_addr()); + config->wire().beginTransmission(config->slaveAddr()); size_t bytes_written = 0; for (; bytes_written < io_request->bytes_to_write; bytes_written++) @@ -155,7 +155,7 @@ void WireDispatcher::processWireIoRequest(WireIoTransaction * wire_io_transactio if (io_request->bytes_to_read > 0) { - config->wire().requestFrom(config->slave_addr(), io_request->bytes_to_read, config->stop()); + config->wire().requestFrom(config->slaveAddr(), io_request->bytes_to_read, config->stop()); while(config->wire().available() != static_cast(io_request->bytes_to_read)) { From 3ee47b044376eb0b1103ec9ede3e0aefba597177 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 22 Jun 2022 07:01:11 +0200 Subject: [PATCH 47/61] An upgraded 'Shared' macro allows a definition of the internal queue size on a per-object-basis. This fixes #61. --- src/Arduino_Threads.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index 1750f95..1e8b85d 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -85,6 +85,9 @@ public: \ #define SHARED(name, type) \ Shared name; +#define SHARED(name, type, size) \ + Shared name; + #define ARDUINO_THREADS_CONCAT_(x,y) x##y #define ARDUINO_THREADS_CONCAT(x,y) ARDUINO_THREADS_CONCAT_(x,y) From 81604f5f0663938c8eb3a7ad4e1c1994778c99f0 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 22 Jun 2022 07:02:43 +0200 Subject: [PATCH 48/61] Extending documentation to mention the possibility of a size-configurable Shared abstraction. --- docs/02-data-exchange.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/02-data-exchange.md b/docs/02-data-exchange.md index e30aeaf..1695068 100644 --- a/docs/02-data-exchange.md +++ b/docs/02-data-exchange.md @@ -12,6 +12,8 @@ A `Shared` variable is a global variable accessible to all threads. It can be de ```C++ /* SharedVariables.h */ SHARED(counter, int); /* A globally available, threadsafe, shared variable of type 'int'. */ +/* ... or ... */ +SHARED(counter, int, 8); /* Same as before, but now the internal queue size is defined as 8. */ ``` Writing to and reading from the shared variable may not always happen concurrently. I.e. a thread reading sensor data may update the shared variable faster than a slower reader thread would extract those values. Therefore the shared variable is modeled as a queue which can store (buffer) a certain number of entries. That way the slower reader thread can access all the values in the same order as they have been written. New values can be inserted naturally by using the assignment operator `=` as if it was just any ordinary variable type, i.e. `int`, `char`, ... From c6db209a9bc0290ed1892008f76fd770726b3e75 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 28 Jun 2022 16:49:46 +0200 Subject: [PATCH 49/61] Shared/Sink/Source: remove operator overloaded API (#71) No more assignment operator, type operator. This leads to less confusion as discussed in #67. --- src/threading/Shared.hpp | 15 --------------- src/threading/Sink.hpp | 5 ----- src/threading/Source.hpp | 8 -------- 3 files changed, 28 deletions(-) diff --git a/src/threading/Shared.hpp b/src/threading/Shared.hpp index 45f70ae..158d303 100644 --- a/src/threading/Shared.hpp +++ b/src/threading/Shared.hpp @@ -44,9 +44,6 @@ class Shared void push(T const & val); inline T peek() const { return _val; } - operator T() [[deprecated("Use 'pop()' instead.")]]; - void operator = (T const & val) [[deprecated("Use 'push()' instead.")]]; - private: T _val; @@ -94,16 +91,4 @@ void Shared::push(T const & val) } } -template -Shared::operator T() -{ - return pop(); -} - -template -void Shared::operator = (T const & val) -{ - push(val); -} - #endif /* ARDUINO_THREADS_SHARED_HPP_ */ diff --git a/src/threading/Sink.hpp b/src/threading/Sink.hpp index eb159b3..3358793 100644 --- a/src/threading/Sink.hpp +++ b/src/threading/Sink.hpp @@ -40,11 +40,6 @@ class SinkBase virtual T pop() = 0; virtual void inject(T const & value) = 0; - - inline operator T() [[deprecated("Use 'pop()' instead.")]] - { - return pop(); - } }; template diff --git a/src/threading/Source.hpp b/src/threading/Source.hpp index 0f64af5..9e7e11f 100644 --- a/src/threading/Source.hpp +++ b/src/threading/Source.hpp @@ -45,8 +45,6 @@ class Source void connectTo(SinkBase & sink); void push(T const & val); - void operator = (T const & val) [[deprecated("Use 'push()' instead.")]]; - private: std::list *> _sink_list; }; @@ -72,10 +70,4 @@ void Source::push(T const & val) }); } -template -void Source::operator = (T const & val) -{ - push(val); -} - #endif /* ARDUINO_THREADS_SOURCE_HPP_ */ From d97ec7cc7ab13eae4df7e88ec45fa0a488258934 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 30 Jun 2022 15:03:14 +0200 Subject: [PATCH 50/61] Add introductory sentence about multitasking --- docs/01-threading-basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/01-threading-basics.md b/docs/01-threading-basics.md index 3de1b15..1bcfb8c 100644 --- a/docs/01-threading-basics.md +++ b/docs/01-threading-basics.md @@ -3,7 +3,7 @@ Threading Basics ================ ## Introduction -Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed one after another. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. +Threading is a concept that is used on many operating systems to run tasks in parallel. An Arduino example of two such tasks could be to read the position of a potentiometer knob while controlling a servo motor to follow that position. Running such tasks in parallel is also called multitasking. Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed one after another. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. In the historic single-threaded execution of Arduino sketches the complete program logic is contained within the `*.ino` file. It contains both a `setup()` function, which is executed only once at program start, and a `loop()` function, which is executed indefinitely. A single-threaded Arduino project can theoretically contain multiple `*.ino` files but you can define `setup()` or `loop()` only once. In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. @@ -116,4 +116,4 @@ As you can see from the example the name of the `*.inot`-file is used to generat * cannot contain spaces or special characters. * cannot be a C++ keyword (i.e. `register`, `volatile`, `while`, etc.). -To be consistent with the Arduino programming style we recommend using [camel case](https://en.wikipedia.org/wiki/Camel_case) for the file names. \ No newline at end of file +To be consistent with the Arduino programming style we recommend using [camel case](https://en.wikipedia.org/wiki/Camel_case) for the file names. From 495847071e9d3a63f8b66b5363e75ed0a1f8e999 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 30 Jun 2022 16:23:04 +0200 Subject: [PATCH 51/61] Add introductory diagram --- docs/01-threading-basics.md | 6 +- docs/assets/Arduino-Threads-Tasks-Example.svg | 864 ++++++++++++++++++ 2 files changed, 869 insertions(+), 1 deletion(-) create mode 100644 docs/assets/Arduino-Threads-Tasks-Example.svg diff --git a/docs/01-threading-basics.md b/docs/01-threading-basics.md index 1bcfb8c..5eea3b6 100644 --- a/docs/01-threading-basics.md +++ b/docs/01-threading-basics.md @@ -3,7 +3,11 @@ Threading Basics ================ ## Introduction -Threading is a concept that is used on many operating systems to run tasks in parallel. An Arduino example of two such tasks could be to read the position of a potentiometer knob while controlling a servo motor to follow that position. Running such tasks in parallel is also called multitasking. Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed one after another. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. +Threading is a concept that is used on many operating systems to run tasks in parallel. An Arduino example of two such tasks could be to read the position of a potentiometer knob while controlling a servo motor to follow that position. Running such tasks in parallel is also called multitasking. + +![Example of two tasks that ideally run in parallel.](assets/Arduino-Threads-Tasks-Example.svg) + +Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed one after another. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. In the historic single-threaded execution of Arduino sketches the complete program logic is contained within the `*.ino` file. It contains both a `setup()` function, which is executed only once at program start, and a `loop()` function, which is executed indefinitely. A single-threaded Arduino project can theoretically contain multiple `*.ino` files but you can define `setup()` or `loop()` only once. In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. diff --git a/docs/assets/Arduino-Threads-Tasks-Example.svg b/docs/assets/Arduino-Threads-Tasks-Example.svg new file mode 100644 index 0000000..2908a24 --- /dev/null +++ b/docs/assets/Arduino-Threads-Tasks-Example.svg @@ -0,0 +1,864 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c64ddd6fcc6006e860bfaf86a7c31e3902413b66 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 30 Jun 2022 16:26:34 +0200 Subject: [PATCH 52/61] Improve wording --- docs/01-threading-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/01-threading-basics.md b/docs/01-threading-basics.md index 5eea3b6..8aacf1d 100644 --- a/docs/01-threading-basics.md +++ b/docs/01-threading-basics.md @@ -7,7 +7,7 @@ Threading is a concept that is used on many operating systems to run tasks in pa ![Example of two tasks that ideally run in parallel.](assets/Arduino-Threads-Tasks-Example.svg) -Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed one after another. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. +Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed sequentially one by one. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. In the historic single-threaded execution of Arduino sketches the complete program logic is contained within the `*.ino` file. It contains both a `setup()` function, which is executed only once at program start, and a `loop()` function, which is executed indefinitely. A single-threaded Arduino project can theoretically contain multiple `*.ino` files but you can define `setup()` or `loop()` only once. In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. From 8269e7cb56bb685e663402480fee792180efd4a2 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 30 Jun 2022 16:28:15 +0200 Subject: [PATCH 53/61] Move graphic further down in the text --- docs/01-threading-basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/01-threading-basics.md b/docs/01-threading-basics.md index 8aacf1d..411343a 100644 --- a/docs/01-threading-basics.md +++ b/docs/01-threading-basics.md @@ -5,9 +5,9 @@ Threading Basics ## Introduction Threading is a concept that is used on many operating systems to run tasks in parallel. An Arduino example of two such tasks could be to read the position of a potentiometer knob while controlling a servo motor to follow that position. Running such tasks in parallel is also called multitasking. -![Example of two tasks that ideally run in parallel.](assets/Arduino-Threads-Tasks-Example.svg) +Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed sequentially one by one. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a potentiometer as mentioned above. While the servo motor is moving to its target position no further reading of the potentiometer can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. -Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed sequentially one by one. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a distance sensor. While the servo motor is moving to its target position no further reading of the distance sensor can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. +![Example of two tasks that ideally run in parallel.](assets/Arduino-Threads-Tasks-Example.svg) In the historic single-threaded execution of Arduino sketches the complete program logic is contained within the `*.ino` file. It contains both a `setup()` function, which is executed only once at program start, and a `loop()` function, which is executed indefinitely. A single-threaded Arduino project can theoretically contain multiple `*.ino` files but you can define `setup()` or `loop()` only once. In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. From 218795f43433bcac94347ef1074877c231b45e61 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 30 Jun 2022 17:53:19 +0200 Subject: [PATCH 54/61] Add new graphics --- docs/assets/Arduino-Threads-Parallel.svg | 127 ++++++++++++++++++ docs/assets/Arduino-Threads-Sequential.svg | 92 +++++++++++++ docs/assets/Arduino-Threads-Tasks-Example.svg | 9 +- 3 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 docs/assets/Arduino-Threads-Parallel.svg create mode 100644 docs/assets/Arduino-Threads-Sequential.svg diff --git a/docs/assets/Arduino-Threads-Parallel.svg b/docs/assets/Arduino-Threads-Parallel.svg new file mode 100644 index 0000000..fc4c5e3 --- /dev/null +++ b/docs/assets/Arduino-Threads-Parallel.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/Arduino-Threads-Sequential.svg b/docs/assets/Arduino-Threads-Sequential.svg new file mode 100644 index 0000000..3d6666a --- /dev/null +++ b/docs/assets/Arduino-Threads-Sequential.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/Arduino-Threads-Tasks-Example.svg b/docs/assets/Arduino-Threads-Tasks-Example.svg index 2908a24..1753b7a 100644 --- a/docs/assets/Arduino-Threads-Tasks-Example.svg +++ b/docs/assets/Arduino-Threads-Tasks-Example.svg @@ -842,10 +842,11 @@ - - - - + + + + + From eb3343a1d51c47b10cb812e2c797fde09c847ec1 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 30 Jun 2022 17:53:45 +0200 Subject: [PATCH 55/61] Insert graphics --- docs/01-threading-basics.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/01-threading-basics.md b/docs/01-threading-basics.md index 411343a..ca5833c 100644 --- a/docs/01-threading-basics.md +++ b/docs/01-threading-basics.md @@ -17,6 +17,8 @@ The advantage of this approach is that a complex and lengthy `loop()` function ( #### Example (Single-Threaded): This sketch demonstrates how one would implement a program which requires the execution of three different actions on three different periodic intervals. In this example we blink three different LEDs at three different intervals. +![Diagram showing the sequential execution of the tasks](assets/Arduino-Threads-Sequential.svg) + **Blink_Three_LEDs.ino**: ```C++ @@ -59,7 +61,9 @@ You can imagine that with increasing complexity of a sketch it gets quite diffic #### Example (Multi-Threaded): -The same functionality can be provided via multi-threaded execution in a much cleaner way. +The same functionality can be provided via multi-threaded execution in a much cleaner way by splitting up the tasks into separate files / threads. + +![Diagram showing the parallel execution of the tasks](assets/Arduino-Threads-Parallel.svg) **Blink_Three_LEDs.ino** From 35e9e33f0bf87c27c35b7222ecc2acd66d87b790 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 6 Jul 2022 13:10:35 +0200 Subject: [PATCH 56/61] Improved graphic (#73) * Add improved comments in graphic * Add labels to components * Clarify loop function --- docs/assets/Arduino-Threads-Tasks-Example.svg | 1673 +++++++++-------- 1 file changed, 837 insertions(+), 836 deletions(-) diff --git a/docs/assets/Arduino-Threads-Tasks-Example.svg b/docs/assets/Arduino-Threads-Tasks-Example.svg index 1753b7a..879877d 100644 --- a/docs/assets/Arduino-Threads-Tasks-Example.svg +++ b/docs/assets/Arduino-Threads-Tasks-Example.svg @@ -1,865 +1,866 @@ - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + From 5c3f1053459ea71fbeb64665f4af8ac6aaf6efd5 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 7 Jul 2022 15:50:12 +0200 Subject: [PATCH 57/61] Fix: Arduino Language related discussions will take place in the language repository. (#74) https://github.com/bcmi-labs/language --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08801ec..ca585ae 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Although you are free to directly manipulate I/O requests and responses (e.g. [T ## :zap: Caveats -This library is currently in **BETA** phase. This means that neither the API nor the usage patterns are set in stone and are likely to change. We are publishing this library in the full knowledge that we can't foresee every possible use-case and edge-case. Therefore we would like to treat this library, while it's in beta phase, as an experiment and ask for your input for shaping this library. Please help us by providing feedback in the [issues section](https://github.com/bcmi-labs/Arduino_Threads/issues) or participating in our [discussions](https://github.com/arduino/ArduinoCore-API/discussions). +This library is currently in **BETA** phase. This means that neither the API nor the usage patterns are set in stone and are likely to change. We are publishing this library in the full knowledge that we can't foresee every possible use-case and edge-case. Therefore we would like to treat this library, while it's in beta phase, as an experiment and ask for your input for shaping this library. Please help us by providing feedback in the [issues section](https://github.com/bcmi-labs/Arduino_Threads/issues) or participating in our [discussions](https://github.com/arduino/language/discussions). ## :mag_right: Resources From ba020c0c95474238a873828ff29955c2c3618283 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 12 Jul 2022 17:50:40 +0200 Subject: [PATCH 58/61] Change descriptions to use push function --- docs/02-data-exchange.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-data-exchange.md b/docs/02-data-exchange.md index 0b21082..b9030b4 100644 --- a/docs/02-data-exchange.md +++ b/docs/02-data-exchange.md @@ -16,7 +16,7 @@ SHARED(counter, int); /* A globally available, threadsafe, shared variable of ty SHARED(counter, int, 8); /* Same as before, but now the internal queue size is defined as 8. */ ``` Writing to and reading from the shared variable may not always happen concurrently. I.e. a thread reading sensor data may update the shared variable faster than a slower reader thread would extract those values. Therefore the shared variable is modeled as a queue which can store (buffer) a certain number of entries. That way the slower reader thread can access all the values in the same order as they have been written. -New values can be inserted naturally by using the assignment operator `=` as if it was just any ordinary variable type, i.e. `int`, `char`, ... +New values can be inserted by using the `push` function that you may know from other data structures. ```C++ /* Thread_1.inot */ From 49eae7e5534d46eec7b6a6b93249264a9b3ec40f Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 12 Jul 2022 17:54:53 +0200 Subject: [PATCH 59/61] Change description for retrieving data --- docs/02-data-exchange.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-data-exchange.md b/docs/02-data-exchange.md index b9030b4..65e8c63 100644 --- a/docs/02-data-exchange.md +++ b/docs/02-data-exchange.md @@ -24,7 +24,7 @@ counter.push(10); /* Store a value into the shared variable in a threadsafe mann ``` If the internal queue is full the oldest element is discarded and the latest element is inserted into the queue. -Retrieving stored data works also very naturally like it would for any POD data type: +Stored data can be retrieved by using the `pop` function: ```C++ /* Thread_2.inot */ Serial.println(counter.pop()); /* Retrieves a value from the shared variable in a threadsafe manner. */ From e8139dc9e923026e8e7c18dee2128bfe7ac949d7 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Wed, 13 Jul 2022 11:43:34 +0200 Subject: [PATCH 60/61] Fix: Automagically select the right SHARED macro depending on the list of arguments. (#76) --- src/Arduino_Threads.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Arduino_Threads.h b/src/Arduino_Threads.h index 1e8b85d..6a233ed 100644 --- a/src/Arduino_Threads.h +++ b/src/Arduino_Threads.h @@ -82,12 +82,17 @@ public: \ SinkNonBlocking name{}; \ private: -#define SHARED(name, type) \ + +#define SHARED_2_ARG(name, type) \ Shared name; -#define SHARED(name, type, size) \ +#define SHARED_3_ARG(name, type, size) \ Shared name; +#define GET_SHARED_MACRO(_1,_2,_3,NAME,...) NAME +#define SHARED(...) GET_SHARED_MACRO(__VA_ARGS__, SHARED_3_ARG, SHARED_2_ARG)(__VA_ARGS__) + + #define ARDUINO_THREADS_CONCAT_(x,y) x##y #define ARDUINO_THREADS_CONCAT(x,y) ARDUINO_THREADS_CONCAT_(x,y) From 63735dd7c4b2c16459d52ac7fbe326ab8a5a9cf8 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Mon, 18 Jul 2022 09:44:56 +0200 Subject: [PATCH 61/61] Release v0.2.0. --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index ae91cb0..f2079e1 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino_Threads -version=0.1.0 +version=0.2.0 author=Arduino maintainer=Arduino sentence=Easy multi-threading for your Mbed OS-based Arduino.