Skip to content

Commit 8469039

Browse files
authored
Merge pull request #28 from bcmi-labs/integrate-io
Integrate Arduino_ThreadsafeIO
2 parents 44c2c74 + d7e9dfe commit 8469039

30 files changed

+2489
-7
lines changed

Diff for: .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

Diff for: .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"

Diff for: .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.

Diff for: 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)

Diff for: examples/Threadsafe_SPI/Threadsafe_SPI.ino

+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+
}
+84
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**************************************************************************************
2+
* INCLUDE
3+
**************************************************************************************/
4+
5+
#include <Arduino_ThreadsafeIO.h>
6+
7+
/**************************************************************************************
8+
* CONSTANTS
9+
**************************************************************************************/
10+
11+
static size_t constexpr NUM_THREADS = 5;
12+
13+
/**************************************************************************************
14+
* FUNCTION DECLARATION
15+
**************************************************************************************/
16+
17+
String serial_log_message_prefix(String const & /* msg */);
18+
String serial_log_message_suffix(String const & prefix, String const & msg);
19+
void serial_thread_func();
20+
21+
/**************************************************************************************
22+
* GLOBAL VARIABLES
23+
**************************************************************************************/
24+
25+
static char thread_name[NUM_THREADS][32];
26+
#undef Serial
27+
#ifdef ARDUINO_PORTENTA_H7_M4
28+
SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
29+
#else
30+
SerialDispatcher Serial(SerialUSB);
31+
#endif
32+
33+
/**************************************************************************************
34+
* SETUP/LOOP
35+
**************************************************************************************/
36+
37+
void setup()
38+
{
39+
Serial.global_prefix(serial_log_message_prefix);
40+
Serial.global_suffix(serial_log_message_suffix);
41+
42+
/* Fire up some threads all accessing the LSM6DSOX */
43+
for(size_t i = 0; i < NUM_THREADS; i++)
44+
{
45+
snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
46+
rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
47+
t->start(serial_thread_func);
48+
}
49+
}
50+
51+
void loop()
52+
{
53+
54+
}
55+
56+
/**************************************************************************************
57+
* FUNCTION DEFINITION
58+
**************************************************************************************/
59+
60+
String serial_log_message_prefix(String const & /* msg */)
61+
{
62+
char msg[32] = {0};
63+
snprintf(msg, sizeof(msg), "[%05lu] ", millis());
64+
return String(msg);
65+
}
66+
67+
String serial_log_message_suffix(String const & prefix, String const & msg)
68+
{
69+
return String("\r\n");
70+
}
71+
72+
void serial_thread_func()
73+
{
74+
Serial.begin(9600);
75+
76+
for(;;)
77+
{
78+
/* Sleep between 5 and 500 ms */
79+
rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
80+
/* Print a unbroken log message including thread name and timestamp as a prefix. */
81+
Serial.block();
82+
Serial.print(rtos::ThisThread::get_name());
83+
Serial.print(": ");
84+
Serial.print("Lorem ipsum ...");
85+
Serial.unblock();
86+
}
87+
}

0 commit comments

Comments
 (0)