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
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)