Skip to content

Commit

Permalink
Documentation suggestions (#59)
Browse files Browse the repository at this point in the history
* 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 <consulting@lxrobotics.com>

* Update docs/01-threading-basics.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/01-threading-basics.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update README.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/01-threading-basics.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/05-threadsafe-spi.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/04-threadsafe-wire.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/05-threadsafe-spi.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/05-threadsafe-spi.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/05-threadsafe-spi.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/README.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/05-threadsafe-spi.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>

* Update docs/01-threading-basics.md

* Update docs/02-data-exchange.md

Co-authored-by: Alexander Entinger <consulting@lxrobotics.com>
  • Loading branch information
sebromero and aentinger committed Jun 8, 2022
1 parent cb42fed commit 2bc9ccd
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 118 deletions.
57 changes: 31 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 I<sup>2</sup>C 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:

Expand Down
30 changes: 21 additions & 9 deletions docs/01-threading-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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**
Expand Down Expand Up @@ -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.

0 comments on commit 2bc9ccd

Please sign in to comment.