Skip to content

Commit 5a21e7f

Browse files
committed
Merge branch 'main' of /home/alex/projects/arduino/libraries/Arduino_ThreadsafeIO into integrate-io
2 parents 44c2c74 + 40e2855 commit 5a21e7f

31 files changed

+2586
-7
lines changed

.codespellrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
[codespell]
33
# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here:
44
ignore-words-list = inot
5-
skip = ./.git
65
builtin = clear,informal,en-GB_to_en-US
76
check-filenames =
87
check-hidden =
8+
skip = ./.git

.github/dependabot.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# See: https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates#about-the-dependabotyml-file
2+
version: 2
3+
4+
updates:
5+
# Configure check for outdated GitHub Actions actions in workflows.
6+
# See: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-actions-up-to-date-with-dependabot
7+
- package-ecosystem: github-actions
8+
directory: / # Check the repository's workflows under /.github/workflows/
9+
schedule:
10+
interval: daily
11+
labels:
12+
- "topic: infrastructure"

.github/workflows/check-arduino.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Arduino Lint
2222
uses: arduino/arduino-lint-action@v1
2323
with:
24-
compliance: strict
24+
compliance: specification
2525
# Change this to "update" once the library is added to the index.
2626
library-manager: submit
2727
# Always use this setting for official repositories. Remove for 3rd party projects.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Compile Examples
2+
3+
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
4+
on:
5+
push:
6+
paths:
7+
- ".github/workflows/compile-examples-private.ya?ml"
8+
- "library.properties"
9+
- "examples/**"
10+
- "src/**"
11+
pull_request:
12+
paths:
13+
- ".github/workflows/compile-examples-private.ya?ml"
14+
- "library.properties"
15+
- "examples/**"
16+
- "src/**"
17+
schedule:
18+
# Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms).
19+
- cron: "0 8 * * TUE"
20+
workflow_dispatch:
21+
repository_dispatch:
22+
23+
env:
24+
SKETCHES_REPORTS_PATH: sketches-reports
25+
SKETCHES_REPORTS_ARTIFACT_NAME: sketches-reports
26+
27+
jobs:
28+
build:
29+
name: ${{ matrix.board.fqbn }}
30+
runs-on: ubuntu-latest
31+
32+
strategy:
33+
fail-fast: false
34+
35+
matrix:
36+
board:
37+
- fqbn: arduino:mbed_nano:nano33ble
38+
platforms: |
39+
- name: arduino:mbed_nano
40+
- fqbn: arduino:mbed_nano:nanorp2040connect
41+
platforms: |
42+
- name: arduino:mbed_nano
43+
- fqbn: arduino:mbed_portenta:envie_m4
44+
platforms: |
45+
- name: arduino:mbed_portenta
46+
- fqbn: arduino:mbed_portenta:envie_m7
47+
platforms: |
48+
- name: arduino:mbed_portenta
49+
50+
steps:
51+
- name: Checkout repository
52+
uses: actions/checkout@v2
53+
54+
- name: Compile examples
55+
uses: arduino/compile-sketches@v1
56+
with:
57+
github-token: ${{ secrets.GITHUB_TOKEN }}
58+
fqbn: ${{ matrix.board.fqbn }}
59+
platforms: ${{ matrix.board.platforms }}
60+
libraries: |
61+
# Install the library from the local path.
62+
- source-path: ./
63+
# Additional library dependencies can be listed here.
64+
# See: https://github.com/arduino/compile-sketches#libraries
65+
sketch-paths: |
66+
- examples
67+
enable-deltas-report: true
68+
sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }}
69+
70+
- name: Save sketches report as workflow artifact
71+
uses: actions/upload-artifact@v2
72+
with:
73+
if-no-files-found: error
74+
path: ${{ env.SKETCHES_REPORTS_PATH }}
75+
name: ${{ env.SKETCHES_REPORTS_ARTIFACT_NAME }}
76+
77+
report-size-deltas:
78+
needs: build
79+
# Run even if some compilations failed.
80+
if: always() && github.event_name == 'pull_request'
81+
runs-on: ubuntu-latest
82+
83+
steps:
84+
- name: Download sketches reports artifact
85+
id: download-artifact
86+
continue-on-error: true # If compilation failed for all boards then there are no artifacts
87+
uses: actions/download-artifact@v2
88+
with:
89+
name: ${{ env.SKETCHES_REPORTS_ARTIFACT_NAME }}
90+
path: ${{ env.SKETCHES_REPORTS_PATH }}
91+
92+
- name: Comment size deltas report to PR
93+
uses: arduino/report-size-deltas@v1
94+
# If actions/download-artifact failed, there are no artifacts to report from.
95+
if: steps.download-artifact.outcome == 'success'
96+
with:
97+
sketches-reports-source: ${{ env.SKETCHES_REPORTS_PATH }}

README.md

+43-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
1+
<img src="https://content.arduino.cc/website/Arduino_logo_teal.svg" height="100" align="right" />
2+
13
`Arduino_Threads`
24
=================
35

46
[![Compile Examples status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/compile-examples.yml)
57
[![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)
68
[![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)
79

8-
This library makes it easy to use the multi-threading capability of [Arduino](https://www.arduino.cc/) boards that use an [Mbed OS](https://os.mbed.com/docs/mbed-os/latest/introduction/index.html)-based core library.
10+
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.
911

1012
## :zap: Features
11-
### :thread: Multi-threaded sketch execution
13+
### :thread: Multi-Threading
14+
#### :thread: Multi-threaded sketch execution
1215
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.
1316

14-
### :calling: Easy communication between multiple threads
17+
#### :calling: Easy communication between multiple threads
1518
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.
1619

20+
### :thread: Threadsafe IO
21+
#### :thread: Threadsafe
22+
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:
23+
24+
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:
25+
26+
```C++
27+
/* Wire Write Access */
28+
Wire.beginTransmission(addr);
29+
Wire.write(val);
30+
Wire.endTransmission();
31+
32+
/* Wire Read Access */
33+
Wire.beginTransmission(addr);
34+
Wire.write(val);
35+
Wire.endTransmission();
36+
Wire.requestFrom(addr, bytes)
37+
while(Wire.available()) {
38+
int val = Wire.read();
39+
}
40+
```
41+
42+
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.
43+
44+
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:.
45+
46+
`Arduino_ThreadsafeIO` 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.
47+
48+
#### :zzz: Asynchronous
49+
50+
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_SPI/Threadsafe_SPI.ino)).
51+
52+
#### :sparkling_heart: Convenient API
53+
54+
Although you are free to directly manipulate IO requests and responses (e.g. [Threadsafe_Wire](examples/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_Wire_BusIO/Threadsafe_Wire_BusIO.ino)).
55+
56+
1757
## :mag_right: Resources
1858

1959
* [How to install a library](https://www.arduino.cc/en/guide/libraries)
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**************************************************************************************
2+
* INCLUDE
3+
**************************************************************************************/
4+
5+
#include <Arduino_ThreadsafeIO.h>
6+
7+
/**************************************************************************************
8+
* CONSTANTS
9+
**************************************************************************************/
10+
11+
static int const BMP388_CS_PIN = 2;
12+
static int const BMP388_INT_PIN = 6;
13+
static byte const BMP388_CHIP_ID_REG_ADDR = 0x00;
14+
15+
static size_t constexpr NUM_THREADS = 20;
16+
17+
/**************************************************************************************
18+
* FUNCTION DECLARATION
19+
**************************************************************************************/
20+
21+
byte bmp388_read_reg(byte const reg_addr);
22+
void bmp388_thread_func();
23+
24+
/**************************************************************************************
25+
* GLOBAL VARIABLES
26+
**************************************************************************************/
27+
28+
BusDevice bmp388(SPI, BMP388_CS_PIN, 1000000, MSBFIRST, SPI_MODE0);
29+
30+
static char thread_name[NUM_THREADS][32];
31+
32+
/**************************************************************************************
33+
* SETUP/LOOP
34+
**************************************************************************************/
35+
36+
void setup()
37+
{
38+
Serial.begin(9600);
39+
while (!Serial) { }
40+
41+
pinMode(BMP388_CS_PIN, OUTPUT);
42+
digitalWrite(BMP388_CS_PIN, HIGH);
43+
44+
for(size_t i = 0; i < NUM_THREADS; i++)
45+
{
46+
snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
47+
rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
48+
t->start(bmp388_thread_func);
49+
}
50+
}
51+
52+
void loop()
53+
{
54+
55+
}
56+
57+
/**************************************************************************************
58+
* FUNCTION DEFINITION
59+
**************************************************************************************/
60+
61+
byte bmp388_read_reg(byte const reg_addr)
62+
{
63+
/* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */
64+
byte read_write_buf[] = {static_cast<byte>(0x80 | reg_addr), 0, 0};
65+
66+
IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0);
67+
IoResponse rsp = bmp388.transfer(req);
68+
69+
/* Do other stuff */
70+
71+
rsp->wait();
72+
73+
return read_write_buf[2];
74+
}
75+
76+
void bmp388_thread_func()
77+
{
78+
for(;;)
79+
{
80+
/* Sleep between 5 and 500 ms */
81+
rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
82+
/* Try to read some data from the BMP3888. */
83+
byte const chip_id = bmp388_read_reg(BMP388_CHIP_ID_REG_ADDR);
84+
/* Print thread id and chip id value to serial. */
85+
char msg[64] = {0};
86+
snprintf(msg, sizeof(msg), "%s: Chip ID = 0x%X", rtos::ThisThread::get_name(), chip_id);
87+
Serial.println(msg);
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**************************************************************************************
2+
* INCLUDE
3+
**************************************************************************************/
4+
5+
#include <Arduino_ThreadsafeIO.h>
6+
7+
/**************************************************************************************
8+
* CONSTANTS
9+
**************************************************************************************/
10+
11+
static int const BMP388_CS_PIN = 2;
12+
static int const BMP388_INT_PIN = 6;
13+
static byte const BMP388_CHIP_ID_REG_ADDR = 0x00;
14+
15+
static size_t constexpr NUM_THREADS = 20;
16+
17+
/**************************************************************************************
18+
* FUNCTION DECLARATION
19+
**************************************************************************************/
20+
21+
byte bmp388_read_reg(byte const reg_addr);
22+
void bmp388_thread_func();
23+
24+
/**************************************************************************************
25+
* GLOBAL VARIABLES
26+
**************************************************************************************/
27+
28+
BusDevice bmp388(SPI, BMP388_CS_PIN, 1000000, MSBFIRST, SPI_MODE0);
29+
30+
static char thread_name[NUM_THREADS][32];
31+
32+
/**************************************************************************************
33+
* SETUP/LOOP
34+
**************************************************************************************/
35+
36+
void setup()
37+
{
38+
Serial.begin(9600);
39+
while (!Serial) { }
40+
41+
pinMode(BMP388_CS_PIN, OUTPUT);
42+
digitalWrite(BMP388_CS_PIN, HIGH);
43+
44+
for(size_t i = 0; i < NUM_THREADS; i++)
45+
{
46+
snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
47+
rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
48+
t->start(bmp388_thread_func);
49+
}
50+
}
51+
52+
void loop()
53+
{
54+
55+
}
56+
57+
/**************************************************************************************
58+
* FUNCTION DEFINITION
59+
**************************************************************************************/
60+
61+
byte bmp388_read_reg(byte const reg_addr)
62+
{
63+
/* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */
64+
byte write_buf[2] = {static_cast<byte>(0x80 | reg_addr), 0};
65+
byte read_buf = 0;
66+
67+
bmp388.spi().write_then_read(write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));
68+
return read_buf;
69+
}
70+
71+
void bmp388_thread_func()
72+
{
73+
for(;;)
74+
{
75+
/* Sleep between 5 and 500 ms */
76+
rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
77+
/* Try to read some data from the BMP3888. */
78+
byte const chip_id = bmp388_read_reg(BMP388_CHIP_ID_REG_ADDR);
79+
/* Print thread id and chip id value to serial. */
80+
char msg[64] = {0};
81+
snprintf(msg, sizeof(msg), "%s: Chip ID = 0x%X", rtos::ThisThread::get_name(), chip_id);
82+
Serial.println(msg);
83+
}
84+
}

0 commit comments

Comments
 (0)