diff --git a/README.md b/README.md
index c1f173b..df282d1 100644
--- a/README.md
+++ b/README.md
@@ -7,60 +7,65 @@
[](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml)
[](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
index f100c7b..3de1b15 100644
--- a/docs/01-threading-basics.md
+++ b/docs/01-threading-basics.md
@@ -3,13 +3,18 @@
Threading Basics
================
## Introduction
-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. In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. While a Arduino project can contain multiple `*.ino` file you can define `setup()` or `loop()` only once. On the contrary a Arduino project can contain multiple `*.inot` files. Each `*.inot` file represents a separate thread and contains it's own `setup()` and `loop()` functions.
+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.
-The advantage of this approach is that a complicated and a long `loop()` function (consisting of nested [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine)) found in typical Arduino sketches can be broken up in several, parallel executed `loop()` functions with a much smaller scope. This increases program readability and maintainability, directly reducing to bugs (software errors).
+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.
+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()
{
@@ -46,18 +51,23 @@ void loop()
}
}
```
+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 simpler way.
+
+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**
@@ -99,9 +109,11 @@ void loop() {
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 a 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++, which are: `*.inot`-file names
-* must begin with a letter of the alphabet or an underscore(_).
-* can also contain letters and numbers after the first initial letter.
+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.
-* no spaces or special characters are allowed.
+* 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
index 571bdbf..75c08f4 100644
--- a/docs/02-data-exchange.md
+++ b/docs/02-data-exchange.md
@@ -3,15 +3,19 @@
Data Exchange between Threads
=============================
## Introduction
-When a Arduino sketch formerly consisting of a single `loop()` is split into multiple `loop()` functions contained in multiple `*.inot` files it becomes necessary to device a thead-safe mechanism to exchange data between those threads. `Arduino_Threads` supports two different semantics for data exchange between threads, `Shared` variables and `Sink`/`Source` semantic.
+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 is declared within `SharedVariables.h` which is automatically included on the top of each `*.inot`-file. Shared variables are declared using the `SHARED` macro in the following way:
+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'. */
```
-A shared variable can contain up to `SHARED_QUEUE_SIZE` entries and semantically represents a FIFO ([First-In/First-Out](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))) queue. New values can be inserted into the queue by naturally using the assignment operator `=` as if it was just any ordinary [plain-old-data](https://en.wikipedia.org/wiki/Passive_data_structure) (POD) type, i.e. `int`, `char`, ...
+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 modelled 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. */
@@ -23,12 +27,12 @@ Retrieving stored data works also very naturally like it would for any POD data
/* 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 schedulded. 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 a item is removed from the queue within the shared variable and other threads can't access the read value anymore.
+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 zero, one or multiple data consumers (`Sink`). A data producer or `Source` can be declared in any `*.ino` or `*.inot`-file using the `SOURCE` macro:
+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`. */
@@ -64,10 +68,10 @@ 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 data is actually 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 can not write to `Sink` or read from a `Source` - attempting to do so results in a compilation error.
+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 only to declared once (`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. |
+| `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
index 1bb4abc..a60811c 100644
--- a/docs/03-threadsafe-serial.md
+++ b/docs/03-threadsafe-serial.md
@@ -1,14 +1,16 @@
-Threadsafe `Serial`
+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 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. A good example for multiple threads writing to serial would be logging. A plausible example for multiple threads reading from `Serial` would be to i.e. split the NMEA parser across multiple threads, i.e. one thread only parses RMC-messages, another parses GGA-messages, etc. In any case the threadsafe `Serial` has to both support single-writer/single-reader and multiple-write/multiple-reader scenarios.
+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 any 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 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.
+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()
@@ -24,20 +26,20 @@ void setup()
```C++
/* Thread_1.inot */
void setup() {
- Serial.begin(9600);
+ 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);
+ Serial.begin(9600); // Baud rate will be neglected as it's set in the main file
}
```
-## Write to Serial with `print()`/`println()`
+## Write to Serial using `print()`/`println()`
([`examples/Threadsafe_IO/Serial_Writer`](../examples/Threadsafe_IO/Serial_Writer))
-Since the threadsafe `Serial` is derived from [`arduino::HardwareSerial`](https://github.com/arduino/ArduinoCore-API/blob/master/api/HardwareSerial.h) it does support the full range of the usual `Serial` API. This means that you can simply write to the `Serial` interface you've been using with the single threaded application.
+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);
@@ -46,7 +48,8 @@ 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 a multi-line `Serial.print/println()` could be interrupted at any point in time. When multiple threads are writing to the Serial interface, this can lead to jumbled-up messages.
+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() {
@@ -128,4 +131,4 @@ void loop()
/* ... */
}
```
-Whenever a thread first call any of those three APIs a thread-dedicated receive ringbuffer is created and any incoming serial communication from that point on is copied in the threads dedicated receive ringbuffer. Having a dedicated receive ringbuffer 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.
+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
index ead6c3d..4c16836 100644
--- a/docs/04-threadsafe-wire.md
+++ b/docs/04-threadsafe-wire.md
@@ -1,27 +1,24 @@
-Threadsafe `Wire`
+Thread-safe `Wire`
=================
## Introduction
-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:
+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 a 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:
+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(addr);
-Wire.write(val);
+Wire.beginTransmission(address);
+Wire.write(value);
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 a [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.
+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.
@@ -34,50 +31,54 @@ BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, true /* restart */);
BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, false /* restart */, true, /* stop */);
```
-### `transfer`/`wait` **asynchronous** threadsafe `Wire` access
-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 traditional Arduino bus APIs `transfer()` is asynchronous and thus won't block execution unless the `wait()` function is called.
-Note that since we are in a parallel programming environment this means that calls to `transfer()` on the same bus from different sketches will be arbitrated and that the `wait()` API will suspend the sketch until the transfer is complete, thus allowing other processes to execute or going to low power state.
+### 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_buf = reg_addr;
- byte read_buf = 0;
+ byte write_buffer = reg_addr;
+ byte read_buffer = 0;
- IoRequest req(write_buf, read_buf);
- IoResponse rsp = lsm6dsox.transfer(req);
- /* Do other stuff */
- rsp->wait(); /* Wait for the completion of the IO Request. */
+ 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_buf;
+ return read_buffer;
}
```
-### `transfer_and_wait` **synchronous** threadsafe `Wire` access
+### 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 confusing there's also a synchronous API call combining the request of the transfer and waiting for it's 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 `transfer_and_wait`.
```C++
byte lsm6dsox_read_reg(byte const reg_addr)
{
- byte write_buf = reg_addr;
- byte read_buf = 0;
+ byte write_buffer = reg_addr;
+ byte read_buffer = 0;
- IoRequest req(write_buf, read_buf);
- IoResponse rsp = transfer_and_wait(lsm6dsox, req); /* Transmit IO request for execution and wait for completion of request. */
+ 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_buf;
+ return read_buffer;
}
```
-### `Adafruit_BusIO` style **synchronous** threadsafe `Wire` access
+### `Adafruit_BusIO` style **synchronous** thread-safe `Wire` access
([`examples/Threadsafe_IO/Wire_BusIO`](../examples/Threadsafe_IO/Wire_BusIO))
-For a further simplification [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) style APIs are provided:
+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_buf = 0;
- lsm6dsox.wire().write_then_read(®_addr, 1, &read_buf, 1);
- return read_buf;
+ 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
index f3a139e..c7189b6 100644
--- a/docs/05-threadsafe-spi.md
+++ b/docs/05-threadsafe-spi.md
@@ -1,9 +1,10 @@
-Threadsafe `SPI`
+Thread-safe `SPI`
===============
## Introduction
-`SPI` communication shares the same problems that [have been demonstrated](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.
+`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);
@@ -11,7 +12,7 @@ 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 as has been done for `Wire` this is solved by introducing a `BusDevice` which allows for single-function call, threadsafe, atomic SPI transactions. A `BusDevice` is declared simply by specifying the type of interface and its parameters:
+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() { /* ... */ }
@@ -24,53 +25,56 @@ BusDevice bmp388(SPI, DEVICE_CS_PIN, spi_clock, spi_bit_order, spi_bit_mode);
BusDevice bmp388(SPI, device_cs_select, device_cs_deselect, spi_settings);
```
-### `transfer`/`wait` **asynchronous** threadsafe `SPI` access
-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 traditional Arduino bus APIs `transfer()` is asynchronous and thus won't block execution unless the `wait()` function is called.
-Note that since we are in a parallel programming environment this means that calls to `transfer()` on the same bus from different sketches will be arbitrated and that the `wait()` API will suspend the sketch until the transfer is complete, thus allowing other processes to execute or going to low power state.
+### 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_buf[] = {static_cast(0x80 | reg_addr), 0, 0};
+ byte read_write_buffer[] = {0x80 | reg_addr, 0, 0};
- IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0);
- IoResponse rsp = bmp388.transfer(req);
+ IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0);
+ IoResponse response = bmp388.transfer(request);
/* Do other stuff */
- rsp->wait(); /* Wait for the completion of the IO Request. */
-
- return read_write_buf[2];
+ response->wait(); /* Wait for the completion of the IO Request. */
+ auto value = read_write_buffer[2];
+ return value;
}
```
-### `transfer_and_wait` **synchronous** threadsafe `SPI` access
+### 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 confusing there's also a synchronous API call combining the request of the transfer and waiting for it's 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 `transfer_and_wait`.
```C++
byte bmp388_read_reg(byte const reg_addr)
{
/* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */
- byte read_write_buf[] = {static_cast(0x80 | reg_addr), 0, 0};
+ byte read_write_buffer[] = {0x80 | reg_addr, 0, 0};
- IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0);
- IoResponse rsp = transfer_and_wait(bmp388, req);
+ IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0);
+ IoResponse response = transfer_and_wait(bmp388, request);
- return read_write_buf[2];
+ auto value = read_write_buffer[2];
+ return value;
}
```
-### `Adafruit_BusIO` style **synchronous** threadsafe `SPI` access
+### `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_buf[2] = {static_cast(0x80 | reg_addr), 0};
- byte read_buf = 0;
+ byte write_buffer[2] = {0x80 | reg_addr, 0};
+ byte read_buffer = 0;
- bmp388.spi().write_then_read(write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));
- return read_buf;
+ 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
index 3b6f6c9..7511394 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,16 +2,18 @@
`Arduino_Threads/docs`
======================
-The Arduino threading APIs brings multi-threading to the world of Arduino.
+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 supported:
+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 on Arduino can be achieved by leveraging the [Arduino_Threads](https://github.com/bcmi-labs/Arduino_Threads) library in combination with [arduino-cli](https://github.com/facchinm/arduino-cli/commits/arduino_threads_rebased).
-Download pre-built `arduino-cli`:
+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)
@@ -21,8 +23,11 @@ Download pre-built `arduino-cli`:
* [Windows_64Bit](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Windows_64bit.zip)
### Table of Contents
-* [Threading Basics](01-threading-basics.md)
-* [Data exchange between threads](02-data-exchange.md)
-* [Threadsafe `Serial`](03-threadsafe-serial.md)
-* [Threadsafe `Wire`](04-threadsafe-wire.md)
-* [Threadsafe `SPI`](05-threadsafe-spi.md)
+
+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)