diff --git a/.codespellrc b/.codespellrc
index 5af8717..9e62ae5 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -2,7 +2,7 @@
[codespell]
# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here:
ignore-words-list = inot
-skip = ./.git
builtin = clear,informal,en-GB_to_en-US
check-filenames =
check-hidden =
+skip = ./.git
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..b93afff
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# See: https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates#about-the-dependabotyml-file
+version: 2
+
+updates:
+ # Configure check for outdated GitHub Actions actions in workflows.
+ # See: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-actions-up-to-date-with-dependabot
+ - package-ecosystem: github-actions
+ directory: / # Check the repository's workflows under /.github/workflows/
+ schedule:
+ interval: daily
+ labels:
+ - "topic: infrastructure"
diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml
index 6e3035d..0d03f48 100644
--- a/.github/workflows/check-arduino.yml
+++ b/.github/workflows/check-arduino.yml
@@ -21,7 +21,7 @@ jobs:
- name: Arduino Lint
uses: arduino/arduino-lint-action@v1
with:
- compliance: strict
+ compliance: specification
# Change this to "update" once the library is added to the index.
library-manager: submit
# Always use this setting for official repositories. Remove for 3rd party projects.
diff --git a/README.md b/README.md
index 46907a7..e5e7c93 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
+
`Arduino_Threads`
=================
@@ -5,15 +7,53 @@
[](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.
+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
-### :thread: Multi-threaded sketch execution
+### :thread: Multi-Threading
+#### :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.
-### :calling: Easy communication between multiple threads
+#### :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.
+### :thread: Threadsafe IO
+#### :thread: 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:
+
+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:
+
+```C++
+/* Wire Write Access */
+Wire.beginTransmission(addr);
+Wire.write(val);
+Wire.endTransmission();
+
+/* Wire Read Access */
+Wire.beginTransmission(addr);
+Wire.write(val);
+Wire.endTransmission();
+Wire.requestFrom(addr, bytes)
+while(Wire.available()) {
+ int val = 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.
+
+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:.
+
+`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.
+
+#### :zzz: 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_SPI/Threadsafe_SPI.ino)).
+
+#### :sparkling_heart: Convenient API
+
+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)).
+
+
## :mag_right: Resources
* [How to install a library](https://www.arduino.cc/en/guide/libraries)
diff --git a/examples/Threadsafe_SPI/Threadsafe_SPI.ino b/examples/Threadsafe_SPI/Threadsafe_SPI.ino
new file mode 100644
index 0000000..ddd7b8c
--- /dev/null
+++ b/examples/Threadsafe_SPI/Threadsafe_SPI.ino
@@ -0,0 +1,89 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static int const BMP388_CS_PIN = 2;
+static int const BMP388_INT_PIN = 6;
+static byte const BMP388_CHIP_ID_REG_ADDR = 0x00;
+
+static size_t constexpr NUM_THREADS = 20;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+byte bmp388_read_reg(byte const reg_addr);
+void bmp388_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+BusDevice bmp388(SPI, BMP388_CS_PIN, 1000000, MSBFIRST, SPI_MODE0);
+
+static char thread_name[NUM_THREADS][32];
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ Serial.begin(9600);
+ while (!Serial) { }
+
+ pinMode(BMP388_CS_PIN, OUTPUT);
+ digitalWrite(BMP388_CS_PIN, HIGH);
+
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(bmp388_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+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};
+
+ IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0);
+ IoResponse rsp = bmp388.transfer(req);
+
+ /* Do other stuff */
+
+ rsp->wait();
+
+ return read_write_buf[2];
+}
+
+void bmp388_thread_func()
+{
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+ /* Try to read some data from the BMP3888. */
+ byte const chip_id = bmp388_read_reg(BMP388_CHIP_ID_REG_ADDR);
+ /* Print thread id and chip id value to serial. */
+ char msg[64] = {0};
+ snprintf(msg, sizeof(msg), "%s: Chip ID = 0x%X", rtos::ThisThread::get_name(), chip_id);
+ Serial.println(msg);
+ }
+}
diff --git a/examples/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino b/examples/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino
new file mode 100644
index 0000000..6d5f0c5
--- /dev/null
+++ b/examples/Threadsafe_SPI_BusIO/Threadsafe_SPI_BusIO.ino
@@ -0,0 +1,84 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static int const BMP388_CS_PIN = 2;
+static int const BMP388_INT_PIN = 6;
+static byte const BMP388_CHIP_ID_REG_ADDR = 0x00;
+
+static size_t constexpr NUM_THREADS = 20;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+byte bmp388_read_reg(byte const reg_addr);
+void bmp388_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+BusDevice bmp388(SPI, BMP388_CS_PIN, 1000000, MSBFIRST, SPI_MODE0);
+
+static char thread_name[NUM_THREADS][32];
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ Serial.begin(9600);
+ while (!Serial) { }
+
+ pinMode(BMP388_CS_PIN, OUTPUT);
+ digitalWrite(BMP388_CS_PIN, HIGH);
+
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(bmp388_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+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;
+
+ bmp388.spi().write_then_read(write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));
+ return read_buf;
+}
+
+void bmp388_thread_func()
+{
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+ /* Try to read some data from the BMP3888. */
+ byte const chip_id = bmp388_read_reg(BMP388_CHIP_ID_REG_ADDR);
+ /* Print thread id and chip id value to serial. */
+ char msg[64] = {0};
+ snprintf(msg, sizeof(msg), "%s: Chip ID = 0x%X", rtos::ThisThread::get_name(), chip_id);
+ Serial.println(msg);
+ }
+}
diff --git a/examples/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino b/examples/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino
new file mode 100644
index 0000000..a22fe04
--- /dev/null
+++ b/examples/Threadsafe_Serial_GlobalPrefixSuffix/Threadsafe_Serial_GlobalPrefixSuffix.ino
@@ -0,0 +1,87 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static size_t constexpr NUM_THREADS = 5;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+String serial_log_message_prefix(String const & /* msg */);
+String serial_log_message_suffix(String const & prefix, String const & msg);
+void serial_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+static char thread_name[NUM_THREADS][32];
+#undef Serial
+#ifdef ARDUINO_PORTENTA_H7_M4
+ SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
+#else
+ SerialDispatcher Serial(SerialUSB);
+#endif
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ Serial.global_prefix(serial_log_message_prefix);
+ Serial.global_suffix(serial_log_message_suffix);
+
+ /* Fire up some threads all accessing the LSM6DSOX */
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(serial_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+String serial_log_message_prefix(String const & /* msg */)
+{
+ char msg[32] = {0};
+ snprintf(msg, sizeof(msg), "[%05lu] ", millis());
+ return String(msg);
+}
+
+String serial_log_message_suffix(String const & prefix, String const & msg)
+{
+ return String("\r\n");
+}
+
+void serial_thread_func()
+{
+ Serial.begin(9600);
+
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+ /* Print a unbroken log message including thread name and timestamp as a prefix. */
+ Serial.block();
+ Serial.print(rtos::ThisThread::get_name());
+ Serial.print(": ");
+ Serial.print("Lorem ipsum ...");
+ Serial.unblock();
+ }
+}
diff --git a/examples/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino b/examples/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino
new file mode 100644
index 0000000..e6d7549
--- /dev/null
+++ b/examples/Threadsafe_Serial_ProtocolWrapping/Threadsafe_Serial_ProtocolWrapping.ino
@@ -0,0 +1,110 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static size_t constexpr NUM_THREADS = 5;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+void serial_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+static char thread_name[NUM_THREADS][32];
+#undef Serial
+#ifdef ARDUINO_PORTENTA_H7_M4
+ SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
+#else
+ SerialDispatcher Serial(SerialUSB);
+#endif
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ /* Fire up some threads all accessing the LSM6DSOX */
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(serial_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+String nmea_message_prefix(String const & /* msg */)
+{
+ return String("$");
+}
+
+String nmea_message_suffix(String const & prefix, String const & msg)
+{
+ /* NMEA checksum is calculated over the complete message
+ * starting with '$' and ending with the end of the message.
+ */
+ byte checksum = 0;
+ std::for_each(msg.c_str(),
+ msg.c_str() + msg.length(),
+ [&checksum](char const c)
+ {
+ checksum ^= static_cast(c);
+ });
+ /* Assemble the footer of the NMEA message. */
+ char footer[16] = {0};
+ snprintf(footer, sizeof(footer), "*%02X\r\n", checksum);
+ return String(footer);
+}
+
+void serial_thread_func()
+{
+ Serial.begin(9600);
+
+ Serial.prefix(nmea_message_prefix);
+ Serial.suffix(nmea_message_suffix);
+
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+
+ /* Print a fake NMEA GPRMC message:
+ * $GPRMC,062101.714,A,5001.869,N,01912.114,E,955535.7,116.2,290520,000.0,W*45\r\n
+ */
+ Serial.block();
+
+ Serial.print("GPRMC,");
+ Serial.print(millis());
+ Serial.print(",A,");
+ Serial.print("5001.869,");
+ Serial.print("N,");
+ Serial.print("01912.114,");
+ Serial.print("E,");
+ Serial.print("955535.7,");
+ Serial.print("116.2,");
+ Serial.print("290520,");
+ Serial.print("000.0,");
+ Serial.print("W");
+
+ Serial.unblock();
+ }
+}
diff --git a/examples/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino b/examples/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino
new file mode 100644
index 0000000..643675f
--- /dev/null
+++ b/examples/Threadsafe_Serial_Reader/Threadsafe_Serial_Reader.ino
@@ -0,0 +1,79 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static size_t constexpr NUM_THREADS = 5;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+void serial_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+static char thread_name[NUM_THREADS][32];
+#undef Serial
+#ifdef ARDUINO_PORTENTA_H7_M4
+ SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
+#else
+ SerialDispatcher Serial(SerialUSB);
+#endif
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ /* Fire up some threads all accessing the LSM6DSOX */
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(serial_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+void serial_thread_func()
+{
+ Serial.begin(9600);
+
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+
+ /* Read data from the serial interface into a String. */
+ String serial_msg;
+ while (Serial.available())
+ serial_msg += (char)Serial.read();
+
+ /* Print thread id and chip id value to serial. */
+ if (serial_msg.length())
+ {
+ char msg[64] = {0};
+ snprintf(msg, sizeof(msg), "[%05lu] %s: %s ...", millis(), rtos::ThisThread::get_name(), serial_msg.c_str());
+ Serial.block();
+ Serial.println(msg);
+ Serial.unblock();
+ }
+ }
+}
diff --git a/examples/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino b/examples/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino
new file mode 100644
index 0000000..42ae35d
--- /dev/null
+++ b/examples/Threadsafe_Serial_Writer/Threadsafe_Serial_Writer.ino
@@ -0,0 +1,74 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static size_t constexpr NUM_THREADS = 5;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+void serial_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+static char thread_name[NUM_THREADS][32];
+#undef Serial
+#ifdef ARDUINO_PORTENTA_H7_M4
+ SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */
+#else
+ SerialDispatcher Serial(SerialUSB);
+#endif
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ /* Fire up some threads all accessing the LSM6DSOX */
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(serial_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+void serial_thread_func()
+{
+ Serial.begin(9600);
+
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+ /* Print thread id and chip id value to serial. */
+ char msg[64] = {0};
+ snprintf(msg, sizeof(msg), "[%05lu] %s: Lorem ipsum ...", millis(), rtos::ThisThread::get_name());
+ /* Every Serial.print/println() encapsulated between
+ * block/unblock statements will only be printed after
+ * a block statement has occurred.
+ */
+ Serial.block();
+ Serial.println(msg);
+ Serial.unblock();
+ }
+}
diff --git a/examples/Threadsafe_Wire/Threadsafe_Wire.ino b/examples/Threadsafe_Wire/Threadsafe_Wire.ino
new file mode 100644
index 0000000..0ebba48
--- /dev/null
+++ b/examples/Threadsafe_Wire/Threadsafe_Wire.ino
@@ -0,0 +1,90 @@
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static byte constexpr LSM6DSOX_ADDRESS = 0x6A;
+static byte constexpr LSM6DSOX_WHO_AM_I_REG = 0x0F;
+
+static size_t constexpr NUM_THREADS = 20;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+byte lsm6dsox_read_reg(byte const reg_addr);
+void lsm6dsox_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS);
+
+static char thread_name[NUM_THREADS][32];
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ Serial.begin(9600);
+ while (!Serial) { }
+
+ /* Fire up some threads all accessing the LSM6DSOX */
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(lsm6dsox_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+byte lsm6dsox_read_reg(byte const reg_addr)
+{
+ /* As we need only 1 byte large write/read buffers for this IO transaction
+ * the buffers are not arrays but rather simple variables.
+ */
+ byte write_buf = reg_addr;
+ byte read_buf = 0;
+
+ IoRequest req(write_buf, read_buf);
+ IoResponse rsp = lsm6dsox.transfer(req);
+
+ /* Optionally do other stuff */
+
+ rsp->wait();
+
+ return read_buf;
+}
+
+
+void lsm6dsox_thread_func()
+{
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+ /* Try to read some data from the LSM6DSOX. */
+ byte const who_am_i = lsm6dsox_read_reg(LSM6DSOX_WHO_AM_I_REG);
+ /* Print thread id and chip id value to serial. */
+ char msg[64] = {0};
+ snprintf(msg, sizeof(msg), "%s: LSM6DSOX[WHO_AM_I] = 0x%X", rtos::ThisThread::get_name(), who_am_i);
+ Serial.println(msg);
+ }
+}
diff --git a/examples/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino b/examples/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino
new file mode 100644
index 0000000..ae934a4
--- /dev/null
+++ b/examples/Threadsafe_Wire_BusIO/Threadsafe_Wire_BusIO.ino
@@ -0,0 +1,78 @@
+ /**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+/**************************************************************************************
+ * CONSTANTS
+ **************************************************************************************/
+
+static byte constexpr LSM6DSOX_ADDRESS = 0x6A;
+static byte constexpr LSM6DSOX_WHO_AM_I_REG = 0x0F;
+
+static size_t constexpr NUM_THREADS = 20;
+
+/**************************************************************************************
+ * FUNCTION DECLARATION
+ **************************************************************************************/
+
+byte lsm6dsox_read_reg(byte const reg_addr);
+void lsm6dsox_thread_func();
+
+/**************************************************************************************
+ * GLOBAL VARIABLES
+ **************************************************************************************/
+
+BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS);
+
+static char thread_name[NUM_THREADS][32];
+
+/**************************************************************************************
+ * SETUP/LOOP
+ **************************************************************************************/
+
+void setup()
+{
+ Serial.begin(9600);
+ while (!Serial) { }
+
+ /* Fire up some threads all accessing the LSM6DSOX */
+ for(size_t i = 0; i < NUM_THREADS; i++)
+ {
+ snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i);
+ rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]);
+ t->start(lsm6dsox_thread_func);
+ }
+}
+
+void loop()
+{
+
+}
+
+/**************************************************************************************
+ * FUNCTION DEFINITION
+ **************************************************************************************/
+
+byte lsm6dsox_read_reg(byte reg_addr)
+{
+ byte read_buf = 0;
+ lsm6dsox.wire().write_then_read(®_addr, 1, &read_buf, 1);
+ return read_buf;
+}
+
+void lsm6dsox_thread_func()
+{
+ for(;;)
+ {
+ /* Sleep between 5 and 500 ms */
+ rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500)));
+ /* Try to read some data from the LSM6DSOX. */
+ byte const who_am_i = lsm6dsox_read_reg(LSM6DSOX_WHO_AM_I_REG);
+ /* Print thread id and chip id value to serial. */
+ char msg[64] = {0};
+ snprintf(msg, sizeof(msg), "%s: LSM6DSOX[WHO_AM_I] = 0x%X", rtos::ThisThread::get_name(), who_am_i);
+ Serial.println(msg);
+ }
+}
diff --git a/keywords.txt b/keywords.txt
index 099b24d..f871002 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -1,5 +1,5 @@
#######################################
-# Syntax Coloring Map For Arduino_Threads
+# Syntax Coloring Map for Arduino_Threads
#######################################
#######################################
@@ -8,6 +8,14 @@
Shared KEYWORD1
+IoRequest KEYWORD1
+IoResponse KEYWORD1
+SpiBusDevice KEYWORD1
+SpiBusDeviceConfig KEYWORD1
+WireBusDevice KEYWORD1
+WireBusDeviceConfig KEYWORD1
+BusDevice KEYWORD1
+
#######################################
# Methods and Functions (KEYWORD2)
#######################################
@@ -18,6 +26,20 @@ broadcastEvent KEYWORD2
sendEvent KEYWORD2
setLoopDelay KEYWORD2
+transfer KEYWORD2
+create KEYWORD2
+block KEYWORD2
+unblock KEYWORD2
+prefix KEYWORD2
+suffix KEYWORD2
+global_prefix KEYWORD2
+global_suffix KEYWORD2
+spi KEYWORD2
+wire KEYWORD2
+read KEYWORD2
+write KEYWORD2
+write_then_read KEYWORD2
+
#######################################
# Constants (LITERAL1)
#######################################
diff --git a/library.properties b/library.properties
index 05de5ff..aca4467 100644
--- a/library.properties
+++ b/library.properties
@@ -6,5 +6,5 @@ sentence=Easy multi-threading for your Mbed OS-based Arduino.
paragraph=This library allows an easy access to the multi-threading capability inherent in all Mbed OS-based Arduino boards.
category=Other
url=https://github.com/bcmi-labs/Arduino_Threads
-architectures=mbed
+architectures=mbed,mbed_portenta,mbed_nano
includes=Arduino_Threads.h
diff --git a/src/Arduino_ThreadsafeIO.h b/src/Arduino_ThreadsafeIO.h
new file mode 100644
index 0000000..46bbb72
--- /dev/null
+++ b/src/Arduino_ThreadsafeIO.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef ARDUINO_THREADSAFE_IO_H_
+#define ARDUINO_THREADSAFE_IO_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "BusDevice.h"
+#include "spi/SpiBusDevice.h"
+#include "wire/WireBusDevice.h"
+#include "serial/SerialDispatcher.h"
+
+#endif /* ARDUINO_THREADSAFE_IO_H_ */
diff --git a/src/BusDevice.cpp b/src/BusDevice.cpp
new file mode 100644
index 0000000..6278aa0
--- /dev/null
+++ b/src/BusDevice.cpp
@@ -0,0 +1,121 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "BusDevice.h"
+
+#include "spi/SpiBusDevice.h"
+#include "wire/WireBusDevice.h"
+
+/**************************************************************************************
+ * BusDeviceBase PUBLIC MEMBER FUNCTIONS
+ **************************************************************************************/
+
+BusDevice BusDeviceBase::create(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol)
+{
+ return BusDevice(new SpiBusDevice(SpiBusDeviceConfig{spi,
+ spi_settings,
+ cs_pin,
+ fill_symbol
+ }));
+}
+
+BusDevice BusDeviceBase::create(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol)
+{
+ return BusDevice(new SpiBusDevice(SpiBusDeviceConfig{spi,
+ SPISettings(spi_clock, spi_bit_order, spi_bit_mode),
+ cs_pin,
+ fill_symbol
+ }));
+}
+
+BusDevice BusDeviceBase::create(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol)
+{
+ return BusDevice(new SpiBusDevice(SpiBusDeviceConfig{spi, spi_settings, spi_select, spi_deselect, fill_symbol}));
+}
+
+BusDevice BusDeviceBase::create(arduino::HardwareI2C & wire, byte const slave_addr)
+{
+ return create(wire, slave_addr, true, true);
+}
+
+BusDevice BusDeviceBase::create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart)
+{
+ return create(wire, slave_addr, restart, true);
+}
+
+BusDevice BusDeviceBase::create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop)
+{
+ return BusDevice(new WireBusDevice(WireBusDeviceConfig{wire, slave_addr, restart, stop}));
+}
+
+/**************************************************************************************
+ * BusDevice CTOR/DTOR
+ **************************************************************************************/
+
+BusDevice::BusDevice(BusDeviceBase * dev)
+: _dev{dev}
+{ }
+
+BusDevice::BusDevice(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol)
+{
+ *this = BusDeviceBase::create(spi, cs_pin, spi_settings, fill_symbol);
+}
+
+BusDevice::BusDevice(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol)
+{
+ *this = BusDeviceBase::create(spi, cs_pin, spi_clock, spi_bit_order, spi_bit_mode, fill_symbol);
+}
+
+BusDevice::BusDevice(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol)
+{
+ *this = BusDeviceBase::create(spi, spi_select, spi_deselect, spi_settings, fill_symbol);
+}
+
+BusDevice::BusDevice(arduino::HardwareI2C & wire, byte const slave_addr)
+{
+ *this = BusDeviceBase::create(wire, slave_addr);
+}
+
+BusDevice::BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart)
+{
+ *this = BusDeviceBase::create(wire, slave_addr, restart);
+}
+
+BusDevice::BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop)
+{
+ *this = BusDeviceBase::create(wire, slave_addr, restart, stop);
+}
+
+IoResponse BusDevice::transfer(IoRequest & req)
+{
+ return _dev->transfer(req);
+}
+
+SpiBusDevice & BusDevice::spi()
+{
+ return *reinterpret_cast(_dev.get());
+}
+
+WireBusDevice & BusDevice::wire()
+{
+ return *reinterpret_cast(_dev.get());
+}
diff --git a/src/BusDevice.h b/src/BusDevice.h
new file mode 100644
index 0000000..3767a51
--- /dev/null
+++ b/src/BusDevice.h
@@ -0,0 +1,94 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef BUS_DEVICE_H_
+#define BUS_DEVICE_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "IoTransaction.h"
+
+#include "spi/SpiBusDeviceConfig.h"
+
+/**************************************************************************************
+ * FORWARD DECLARATION
+ **************************************************************************************/
+
+namespace arduino
+{
+ class HardwareSPI;
+ class HardwareI2C;
+}
+
+class BusDevice;
+class SpiBusDevice;
+class WireBusDevice;
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class BusDeviceBase
+{
+public:
+
+ virtual ~BusDeviceBase() { }
+
+ virtual IoResponse transfer(IoRequest & req) = 0;
+
+
+ static BusDevice create(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol = 0xFF);
+ static BusDevice create(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol = 0xFF);
+ static BusDevice create(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol = 0xFF);
+
+ static BusDevice create(arduino::HardwareI2C & wire, byte const slave_addr);
+ static BusDevice create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart);
+ static BusDevice create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop);
+
+};
+
+class BusDevice
+{
+public:
+
+ BusDevice(BusDeviceBase * dev);
+
+ BusDevice(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol = 0xFF);
+ BusDevice(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol = 0xFF);
+ BusDevice(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol = 0xFF);
+
+ BusDevice(arduino::HardwareI2C & wire, byte const slave_addr);
+ BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart);
+ BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop);
+
+ IoResponse transfer(IoRequest & req);
+
+
+ SpiBusDevice & spi();
+ WireBusDevice & wire();
+
+
+private:
+
+ mbed::SharedPtr _dev;
+
+};
+
+#endif /* BUS_DEVICE_H_ */
diff --git a/src/IoTransaction.h b/src/IoTransaction.h
new file mode 100644
index 0000000..812f3f5
--- /dev/null
+++ b/src/IoTransaction.h
@@ -0,0 +1,125 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef IO_TRANSACTION_H_
+#define IO_TRANSACTION_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+#include
+
+#include
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+/**************************************************************************************
+ * IoRequest
+ **************************************************************************************/
+
+class IoRequest
+{
+public:
+
+ IoRequest(byte * write_buf_, size_t const bytes_to_write_, byte * read_buf_, size_t const bytes_to_read_)
+ : write_buf{write_buf_}
+ , bytes_to_write{bytes_to_write_}
+ , read_buf{read_buf_}
+ , bytes_to_read{bytes_to_read_}
+ { }
+
+ IoRequest(byte & write_buf_, byte & read_buf_)
+ : IoRequest{&write_buf_, 1, &read_buf_, 1}
+ { }
+
+ byte * write_buf{nullptr};
+ size_t const bytes_to_write{0};
+ byte * read_buf{nullptr};
+ size_t const bytes_to_read{0};
+
+};
+
+/**************************************************************************************
+ * IoResponse
+ **************************************************************************************/
+
+namespace impl
+{
+
+class IoResponse
+{
+public:
+
+ IoResponse()
+ : bytes_written{0}
+ , bytes_read{0}
+ , _cond{_mutex}
+ , _is_done{false}
+ { }
+
+ size_t bytes_written{0};
+ size_t bytes_read{0};
+
+ void done()
+ {
+ _mutex.lock();
+ _is_done = true;
+ _cond.notify_all();
+ _mutex.unlock();
+ }
+
+ void wait()
+ {
+ _mutex.lock();
+ while (!_is_done) {
+ _cond.wait();
+ }
+ _mutex.unlock();
+ }
+
+private:
+
+ rtos::Mutex _mutex;
+ rtos::ConditionVariable _cond;
+ bool _is_done{false};
+
+};
+
+} /* namespace impl */
+
+typedef mbed::SharedPtr IoResponse;
+
+/**************************************************************************************
+ * IoTransaction
+ **************************************************************************************/
+
+class IoTransaction
+{
+public:
+
+ IoTransaction(IoRequest * q, IoResponse * p) : req{q}, rsp{p} { }
+ IoRequest * req{nullptr};
+ IoResponse * rsp{nullptr};
+};
+
+#endif /* IO_TRANSACTION_H_ */
diff --git a/src/serial/SerialDispatcher.cpp b/src/serial/SerialDispatcher.cpp
new file mode 100644
index 0000000..7fb6dea
--- /dev/null
+++ b/src/serial/SerialDispatcher.cpp
@@ -0,0 +1,334 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "SerialDispatcher.h"
+
+/**************************************************************************************
+ * CTOR/DTOR
+ **************************************************************************************/
+
+SerialDispatcher::SerialDispatcher(arduino::HardwareSerial & serial)
+: _is_initialized{false}
+, _mutex{}
+, _cond{_mutex}
+, _serial{serial}
+, _thread(osPriorityRealtime, 4096, nullptr, "SerialDispatcher")
+, _has_tread_started{false}
+, _terminate_thread{false}
+, _global_prefix_callback{nullptr}
+, _global_suffix_callback{nullptr}
+{
+
+}
+
+/**************************************************************************************
+ * PUBLIC MEMBER FUNCTIONS
+ **************************************************************************************/
+
+void SerialDispatcher::begin(unsigned long baudrate)
+{
+ begin(baudrate, SERIAL_8N1);
+}
+
+void SerialDispatcher::begin(unsigned long baudrate, uint16_t config)
+{
+ mbed::ScopedLock lock(_mutex);
+
+ if (!_is_initialized)
+ {
+ _serial.begin(baudrate, config);
+ _is_initialized = true;
+ _thread.start(mbed::callback(this, &SerialDispatcher::threadFunc)); /* TODO: Check return code */
+ while (!_has_tread_started) { }
+ }
+
+ /* Check if the thread calling begin is already in the list. */
+ osThreadId_t const current_thread_id = rtos::ThisThread::get_id();
+ if (findThreadCustomerDataById(rtos::ThisThread::get_id()) == std::end(_thread_customer_list))
+ {
+ /* Since the thread is not in the list yet we are
+ * going to create a new entry to the list.
+ */
+ ThreadCustomerData data;
+ data.thread_id = current_thread_id;
+ data.block_tx_buffer = false;
+ data.prefix_func = nullptr;
+ data.suffix_func = nullptr;
+ _thread_customer_list.push_back(data);
+ }
+}
+
+void SerialDispatcher::end()
+{
+ mbed::ScopedLock lock(_mutex);
+
+ /* Retrieve the current thread id and remove
+ * the thread data from the thread data list.
+ */
+ osThreadId_t const current_thread_id = rtos::ThisThread::get_id();
+ std::remove_if(std::begin(_thread_customer_list),
+ std::end (_thread_customer_list),
+ [current_thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == current_thread_id); });
+
+ /* If no thread consumers are left also end
+ * the serial device altogether.
+ */
+ if (_thread_customer_list.size() == 0)
+ {
+ _terminate_thread = true;
+ _thread.join();
+ _serial.end();
+ }
+}
+
+int SerialDispatcher::available()
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return 0;
+
+ prepareSerialReader(iter);
+ handleSerialReader();
+
+ return iter->rx_buffer->available();
+}
+
+int SerialDispatcher::peek()
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return 0;
+
+ prepareSerialReader(iter);
+ handleSerialReader();
+
+ return iter->rx_buffer->peek();
+}
+
+int SerialDispatcher::read()
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return 0;
+
+ prepareSerialReader(iter);
+ handleSerialReader();
+
+ return iter->rx_buffer->read_char();
+}
+
+void SerialDispatcher::flush()
+{
+ mbed::ScopedLock lock(_mutex);
+ _serial.flush();
+}
+
+size_t SerialDispatcher::write(uint8_t const b)
+{
+ return write(&b, 1);
+}
+
+size_t SerialDispatcher::write(const uint8_t * data, size_t len)
+{
+ mbed::ScopedLock lock(_mutex);
+
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+
+ /* If this thread hasn't registered yet
+ * with the SerialDispatcher via 'begin'.
+ */
+ if (iter == std::end(_thread_customer_list))
+ return 0;
+
+ size_t bytes_written = 0;
+ for (; (bytes_written < len) && iter->tx_buffer.availableForStore(); bytes_written++)
+ iter->tx_buffer.store_char(data[bytes_written]);
+
+ /* Inform the worker thread that new data has
+ * been written to a Serial transmit buffer.
+ */
+ _cond.notify_one();
+
+ return bytes_written;
+}
+
+void SerialDispatcher::block()
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return;
+ iter->block_tx_buffer = true;
+}
+
+void SerialDispatcher::unblock()
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return;
+ iter->block_tx_buffer = false;
+ _cond.notify_one();
+}
+
+void SerialDispatcher::prefix(PrefixInjectorCallbackFunc func)
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return;
+ iter->prefix_func = func;
+}
+
+void SerialDispatcher::suffix(SuffixInjectorCallbackFunc func)
+{
+ mbed::ScopedLock lock(_mutex);
+ auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id());
+ if (iter == std::end(_thread_customer_list)) return;
+ iter->suffix_func = func;
+}
+
+void SerialDispatcher::global_prefix(PrefixInjectorCallbackFunc func)
+{
+ mbed::ScopedLock lock(_mutex);
+ _global_prefix_callback = func;
+}
+
+void SerialDispatcher::global_suffix(SuffixInjectorCallbackFunc func)
+{
+ mbed::ScopedLock lock(_mutex);
+ _global_suffix_callback = func;
+}
+
+/**************************************************************************************
+ * PRIVATE MEMBER FUNCTIONS
+ **************************************************************************************/
+
+void SerialDispatcher::threadFunc()
+{
+ _has_tread_started = true;
+
+ while(!_terminate_thread)
+ {
+ /* Prevent race conditions by multi-threaded
+ * access to shared data.
+ */
+ mbed::ScopedLock lock(_mutex);
+ /* Wait for new data to be available */
+ _cond.wait();
+ /* Iterate over all list entries. */
+ std::for_each(std::begin(_thread_customer_list),
+ std::end (_thread_customer_list),
+ [this](ThreadCustomerData & d)
+ {
+ if (d.block_tx_buffer)
+ return;
+
+ /* Return if there's no data to be written to the
+ * serial interface. This statement is necessary
+ * because otherwise the prefix/suffix functions
+ * will be invoked and will be printing something,
+ * even though no data is actually to be printed for
+ * most threads.
+ */
+ if (!d.tx_buffer.available())
+ return;
+
+ /* Retrieve all data stored in the transmit ringbuffer
+ * and store it into a String for usage by both suffix
+ * prefix callback functions.
+ */
+ String msg;
+ while(d.tx_buffer.available())
+ msg += static_cast(d.tx_buffer.read_char());
+
+ /* The prefix callback function allows the
+ * user to insert a custom message before
+ * a new message is written to the serial
+ * driver. This is useful e.g. for wrapping
+ * protocol (e.g. the 'AT' protocol) or providing
+ * a timestamp, a log level, ...
+ */
+ String prefix;
+ if (d.prefix_func)
+ prefix = d.prefix_func(msg);
+ /* A prefix callback function defined per thread
+ * takes precedence over a globally defined prefix
+ * callback function.
+ */
+ else if (_global_prefix_callback)
+ prefix = _global_prefix_callback(msg);
+
+ /* Similar to the prefix function this callback
+ * allows the user to specify a specific message
+ * to be appended to each message, e.g. '\r\n'.
+ */
+ String suffix;
+ if (d.suffix_func)
+ suffix = d.suffix_func(prefix, msg);
+ /* A suffix callback function defined per thread
+ * takes precedence over a globally defined suffix
+ * callback function.
+ */
+ else if (_global_suffix_callback)
+ suffix = _global_suffix_callback(prefix, msg);
+
+ /* Now it's time to actually write the message
+ * conveyed by the user via Serial.print/println.
+ */
+ _serial.write(prefix.c_str());
+ _serial.write(msg.c_str());
+ _serial.write(suffix.c_str());
+ });
+ }
+}
+
+std::list::iterator SerialDispatcher::findThreadCustomerDataById(osThreadId_t const thread_id)
+{
+ return std::find_if(std::begin(_thread_customer_list),
+ std::end (_thread_customer_list),
+ [thread_id](ThreadCustomerData const d) -> bool { return (d.thread_id == thread_id); });
+}
+
+void SerialDispatcher::prepareSerialReader(std::list::iterator & iter)
+{
+ if (!iter->rx_buffer)
+ iter->rx_buffer.reset(new arduino::RingBuffer());
+}
+
+void SerialDispatcher::handleSerialReader()
+{
+ while (_serial.available())
+ {
+ int const c = _serial.read();
+
+ std::for_each(std::begin(_thread_customer_list),
+ std::end (_thread_customer_list),
+ [c](ThreadCustomerData & d)
+ {
+ if (!d.rx_buffer)
+ return;
+
+ if (!d.rx_buffer->availableForStore())
+ return;
+
+ d.rx_buffer->store_char(c);
+ });
+ }
+}
diff --git a/src/serial/SerialDispatcher.h b/src/serial/SerialDispatcher.h
new file mode 100644
index 0000000..ca17793
--- /dev/null
+++ b/src/serial/SerialDispatcher.h
@@ -0,0 +1,105 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SERIAL_DISPATCHER_H_
+#define SERIAL_DISPATCHER_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "api/HardwareSerial.h"
+
+#include
+
+#include
+#include
+
+#include
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class SerialDispatcher : public arduino::HardwareSerial
+{
+
+public:
+
+ SerialDispatcher(arduino::HardwareSerial & serial);
+
+
+ virtual void begin(unsigned long baudrate) override;
+ virtual void begin(unsigned long baudrate, uint16_t config) override;
+ virtual void end() override;
+ virtual int available() override;
+ virtual int peek() override;
+ virtual int read() override;
+ virtual void flush() override;
+ virtual size_t write(uint8_t const b) override;
+ virtual size_t write(const uint8_t * data, size_t len) override;
+ using Print::write;
+ virtual operator bool() override { return _serial; }
+
+ void block();
+ void unblock();
+
+ typedef std::function PrefixInjectorCallbackFunc;
+ typedef std::function SuffixInjectorCallbackFunc;
+ void prefix(PrefixInjectorCallbackFunc func);
+ void suffix(SuffixInjectorCallbackFunc func);
+ void global_prefix(PrefixInjectorCallbackFunc func);
+ void global_suffix(SuffixInjectorCallbackFunc func);
+
+
+private:
+
+ bool _is_initialized;
+ rtos::Mutex _mutex;
+ rtos::ConditionVariable _cond;
+ arduino::HardwareSerial & _serial;
+
+ rtos::Thread _thread;
+ bool _has_tread_started;
+ bool _terminate_thread;
+
+ PrefixInjectorCallbackFunc _global_prefix_callback;
+ SuffixInjectorCallbackFunc _global_suffix_callback;
+
+ static int constexpr THREADSAFE_SERIAL_TRANSMIT_RINGBUFFER_SIZE = 128;
+ typedef arduino::RingBufferN SerialTransmitRingbuffer;
+
+ typedef struct
+ {
+ osThreadId_t thread_id;
+ SerialTransmitRingbuffer tx_buffer;
+ bool block_tx_buffer;
+ mbed::SharedPtr rx_buffer; /* Only when a thread has expressed interested to read from serial a receive ringbuffer is allocated. */
+ PrefixInjectorCallbackFunc prefix_func;
+ SuffixInjectorCallbackFunc suffix_func;
+ } ThreadCustomerData;
+
+ std::list _thread_customer_list;
+
+ void threadFunc();
+ std::list::iterator findThreadCustomerDataById(osThreadId_t const thread_id);
+ void prepareSerialReader(std::list::iterator & iter);
+ void handleSerialReader();
+};
+
+#endif /* SERIAL_DISPATCHER_H_ */
diff --git a/src/spi/SpiBusDevice.cpp b/src/spi/SpiBusDevice.cpp
new file mode 100644
index 0000000..42dd09b
--- /dev/null
+++ b/src/spi/SpiBusDevice.cpp
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "SpiBusDevice.h"
+
+#include "SpiDispatcher.h"
+
+/**************************************************************************************
+ * CTOR/DTOR
+ **************************************************************************************/
+
+SpiBusDevice::SpiBusDevice(SpiBusDeviceConfig const & config)
+: _config{config}
+{
+
+}
+
+/**************************************************************************************
+ * PUBLIC MEMBER FUNCTIONS
+ **************************************************************************************/
+
+IoResponse SpiBusDevice::transfer(IoRequest & req)
+{
+ return SpiDispatcher::instance().dispatch(&req, &_config);
+}
+
+bool SpiBusDevice::read(uint8_t * buffer, size_t len, uint8_t sendvalue)
+{
+ SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.select_func(), _config.deselect_func(), sendvalue);
+ IoRequest req(nullptr, 0, buffer, len);
+ IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &config);
+ rsp->wait();
+ return true;
+}
+
+bool SpiBusDevice::write(uint8_t * buffer, size_t len)
+{
+ IoRequest req(buffer, len, nullptr, 0);
+ IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &_config);
+ rsp->wait();
+ return true;
+}
+
+bool SpiBusDevice::write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue)
+{
+ SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.select_func(), _config.deselect_func(), sendvalue);
+ IoRequest req(write_buffer, write_len, read_buffer, read_len);
+ IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &config);
+ rsp->wait();
+ return true;
+}
diff --git a/src/spi/SpiBusDevice.h b/src/spi/SpiBusDevice.h
new file mode 100644
index 0000000..f16347b
--- /dev/null
+++ b/src/spi/SpiBusDevice.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SPI_BUS_DEVICE_H_
+#define SPI_BUS_DEVICE_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "../BusDevice.h"
+
+#include "SpiBusDeviceConfig.h"
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class SpiBusDevice : public BusDeviceBase
+{
+public:
+
+ SpiBusDevice(SpiBusDeviceConfig const & config);
+ virtual ~SpiBusDevice() { }
+
+
+ virtual IoResponse transfer(IoRequest & req) override;
+
+
+ bool read(uint8_t * buffer, size_t len, uint8_t sendvalue = 0xFF);
+ bool write(uint8_t * buffer, size_t len);
+ bool write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue = 0xFF);
+
+
+private:
+
+ SpiBusDeviceConfig _config;
+
+};
+
+#endif /* SPI_BUS_DEVICE_H_ */
diff --git a/src/spi/SpiBusDeviceConfig.h b/src/spi/SpiBusDeviceConfig.h
new file mode 100644
index 0000000..196fc43
--- /dev/null
+++ b/src/spi/SpiBusDeviceConfig.h
@@ -0,0 +1,82 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SPI_BUS_DEVICE_CONFIG_H_
+#define SPI_BUS_DEVICE_CONFIG_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+#include
+
+#include
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class SpiBusDeviceConfig
+{
+public:
+
+ typedef std::function SpiSelectFunc;
+ typedef std::function SpiDeselectFunc;
+
+
+ SpiBusDeviceConfig(arduino::HardwareSPI & spi, SPISettings const & spi_settings, SpiSelectFunc spi_select, SpiDeselectFunc spi_deselect, byte const fill_symbol = 0xFF)
+ : _spi{spi}
+ , _spi_settings{spi_settings}
+ , _spi_select{spi_select}
+ , _spi_deselect{spi_deselect}
+ , _fill_symbol{fill_symbol}
+ { }
+
+ SpiBusDeviceConfig(arduino::HardwareSPI & spi, SPISettings const & spi_settings, int const cs_pin, byte const fill_symbol = 0xFF)
+ : SpiBusDeviceConfig
+ {spi,
+ spi_settings,
+ [cs_pin](){ digitalWrite(cs_pin, LOW); },
+ [cs_pin](){ digitalWrite(cs_pin, HIGH); },
+ fill_symbol
+ }
+ { }
+
+
+ arduino::HardwareSPI & spi() { return _spi; }
+ SPISettings settings () const { return _spi_settings; }
+ void select () const { if (_spi_select) _spi_select(); }
+ void deselect () const { if (_spi_deselect) _spi_deselect(); }
+ byte fill_symbol() const { return _fill_symbol; }
+
+ SpiSelectFunc select_func () const { return _spi_select; }
+ SpiDeselectFunc deselect_func() const { return _spi_deselect; }
+
+private:
+
+ arduino::HardwareSPI & _spi;
+ SPISettings _spi_settings;
+ SpiSelectFunc _spi_select{nullptr};
+ SpiDeselectFunc _spi_deselect{nullptr};
+ byte _fill_symbol{0xFF};
+
+};
+
+#endif /* SPI_BUS_DEVICE_CONFIG_H_ */
diff --git a/src/spi/SpiDispatcher.cpp b/src/spi/SpiDispatcher.cpp
new file mode 100644
index 0000000..a5ab024
--- /dev/null
+++ b/src/spi/SpiDispatcher.cpp
@@ -0,0 +1,175 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "SpiDispatcher.h"
+
+#include
+
+/**************************************************************************************
+ * STATIC MEMBER DEFINITION
+ **************************************************************************************/
+
+SpiDispatcher * SpiDispatcher::_p_instance{nullptr};
+rtos::Mutex SpiDispatcher::_mutex;
+
+/**************************************************************************************
+ * CTOR/DTOR
+ **************************************************************************************/
+
+SpiDispatcher::SpiDispatcher()
+: _thread(osPriorityRealtime, 4096, nullptr, "SpiDispatcher")
+, _has_tread_started{false}
+, _terminate_thread{false}
+{
+ begin();
+}
+
+SpiDispatcher::~SpiDispatcher()
+{
+ end();
+}
+
+/**************************************************************************************
+ * PUBLIC MEMBER FUNCTIONS
+ **************************************************************************************/
+
+SpiDispatcher & SpiDispatcher::instance()
+{
+ mbed::ScopedLock lock(_mutex);
+ if (!_p_instance) {
+ _p_instance = new SpiDispatcher();
+ }
+ return *_p_instance;
+}
+
+void SpiDispatcher::destroy()
+{
+ mbed::ScopedLock lock(_mutex);
+ delete _p_instance;
+ _p_instance = nullptr;
+}
+
+IoResponse SpiDispatcher::dispatch(IoRequest * req, SpiBusDeviceConfig * config)
+{
+ mbed::ScopedLock lock(_mutex);
+
+ SpiIoTransaction * spi_io_transaction = _spi_io_transaction_mailbox.try_alloc();
+ if (!spi_io_transaction)
+ return nullptr;
+
+ IoResponse rsp(new impl::IoResponse());
+
+ spi_io_transaction->req = req;
+ spi_io_transaction->rsp = rsp;
+ spi_io_transaction->config = config;
+
+ _spi_io_transaction_mailbox.put(spi_io_transaction);
+
+ return rsp;
+}
+
+/**************************************************************************************
+ * PRIVATE MEMBER FUNCTIONS
+ **************************************************************************************/
+
+void SpiDispatcher::begin()
+{
+ SPI.begin();
+ _thread.start(mbed::callback(this, &SpiDispatcher::threadFunc)); /* TODO: Check return code */
+ /* It is necessary to wait until the SpiDispatcher::threadFunc()
+ * has started, otherwise other threads might trigger IO requests
+ * before this thread is actually running.
+ */
+ while (!_has_tread_started) { }
+}
+
+void SpiDispatcher::end()
+{
+ _terminate_thread = true;
+ _thread.join(); /* TODO: Check return code */
+ SPI.end();
+}
+
+void SpiDispatcher::threadFunc()
+{
+ _has_tread_started = true;
+
+ while(!_terminate_thread)
+ {
+ /* Wait blocking for the next IO transaction
+ * request to be posted to the mailbox.
+ */
+ SpiIoTransaction * spi_io_transaction = _spi_io_transaction_mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever);
+ if (spi_io_transaction)
+ {
+ processSpiIoRequest(spi_io_transaction);
+ /* Free the allocated memory (memory allocated
+ * during dispatch(...)
+ */
+ _spi_io_transaction_mailbox.free(spi_io_transaction);
+ }
+ }
+}
+
+void SpiDispatcher::processSpiIoRequest(SpiIoTransaction * spi_io_transaction)
+{
+ IoRequest * io_request = spi_io_transaction->req;
+ IoResponse io_response = spi_io_transaction->rsp;
+ SpiBusDeviceConfig * config = spi_io_transaction->config;
+
+ config->select();
+
+ config->spi().beginTransaction(config->settings());
+
+ /* In a first step transmit the complete write buffer and
+ * write back the receive data directly into the write buffer
+ */
+ size_t bytes_sent = 0;
+ for(; bytes_sent < io_request->bytes_to_write; bytes_sent++)
+ {
+ uint8_t const tx_byte = io_request->write_buf[bytes_sent];
+ uint8_t const rx_byte = config->spi().transfer(tx_byte);
+
+ io_request->write_buf[bytes_sent] = rx_byte;
+ }
+
+ /* In a second step, transmit the fill symbol and write the
+ * received data into the read buffer.
+ */
+ size_t bytes_received = 0;
+ for(; bytes_received < io_request->bytes_to_read; bytes_received++)
+ {
+ uint8_t const tx_byte = config->fill_symbol();
+ uint8_t const rx_byte = config->spi().transfer(tx_byte);
+
+ io_request->read_buf[bytes_received] = rx_byte;
+ }
+
+ config->spi().endTransaction();
+
+ config->deselect();
+
+ io_response->bytes_written = bytes_sent;
+ io_response->bytes_read = bytes_received;
+
+ io_response->done();
+}
diff --git a/src/spi/SpiDispatcher.h b/src/spi/SpiDispatcher.h
new file mode 100644
index 0000000..c42adf2
--- /dev/null
+++ b/src/spi/SpiDispatcher.h
@@ -0,0 +1,76 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef SPI_DISPATCHER_H_
+#define SPI_DISPATCHER_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+#include "../IoTransaction.h"
+
+#include "SpiBusDeviceConfig.h"
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class SpiDispatcher
+{
+public:
+
+ SpiDispatcher(SpiDispatcher &) = delete;
+ void operator = (SpiDispatcher &) = delete;
+
+ static SpiDispatcher & instance();
+ static void destroy();
+
+ IoResponse dispatch(IoRequest * req, SpiBusDeviceConfig * config);
+
+private:
+
+ static SpiDispatcher * _p_instance;
+ static rtos::Mutex _mutex;
+
+ rtos::Thread _thread;
+ bool _has_tread_started;
+ bool _terminate_thread;
+
+ typedef struct
+ {
+ IoRequest * req;
+ IoResponse rsp;
+ SpiBusDeviceConfig * config;
+ } SpiIoTransaction;
+
+ static size_t constexpr REQUEST_QUEUE_SIZE = 32;
+ rtos::Mail _spi_io_transaction_mailbox;
+
+ SpiDispatcher();
+ ~SpiDispatcher();
+
+ void begin();
+ void end();
+ void threadFunc();
+ void processSpiIoRequest(SpiIoTransaction * spi_io_transaction);
+};
+
+#endif /* SPI_DISPATCHER_H_ */
diff --git a/src/wire/WireBusDevice.cpp b/src/wire/WireBusDevice.cpp
new file mode 100644
index 0000000..19477fb
--- /dev/null
+++ b/src/wire/WireBusDevice.cpp
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "WireBusDevice.h"
+
+#include "WireDispatcher.h"
+
+/**************************************************************************************
+ * CTOR/DTOR
+ **************************************************************************************/
+
+WireBusDevice::WireBusDevice(WireBusDeviceConfig const & config)
+: _config{config}
+{
+
+}
+
+/**************************************************************************************
+ * PUBLIC MEMBER FUNCTIONS
+ **************************************************************************************/
+
+IoResponse WireBusDevice::transfer(IoRequest & req)
+{
+ return WireDispatcher::instance().dispatch(&req, &_config);
+}
+
+bool WireBusDevice::read(uint8_t * buffer, size_t len, bool stop)
+{
+ WireBusDeviceConfig config(_config.wire(), _config.slave_addr(), _config.restart(), stop);
+ IoRequest req(nullptr, 0, buffer, len);
+ IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config);
+ rsp->wait();
+ return true;
+}
+
+bool WireBusDevice::write(uint8_t * buffer, size_t len, bool stop)
+{
+ bool const restart = !stop;
+ WireBusDeviceConfig config(_config.wire(), _config.slave_addr(), restart, _config.stop());
+ IoRequest req(buffer, len, nullptr, 0);
+ IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config);
+ rsp->wait();
+ return true;
+}
+
+bool WireBusDevice::write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop)
+{
+ /* Copy the Wire parameters from the device and modify only those
+ * which can be modified via the parameters of this function.
+ */
+ bool const restart = !stop;
+ WireBusDeviceConfig config(_config.wire(), _config.slave_addr(), restart, _config.stop());
+ /* Fire off the IO request and await its response. */
+ IoRequest req(write_buffer, write_len, read_buffer, read_len);
+ IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config);
+ rsp->wait();
+ /* TODO: Introduce error codes within the IoResponse and evaluate
+ * them here.
+ */
+ return true;
+}
diff --git a/src/wire/WireBusDevice.h b/src/wire/WireBusDevice.h
new file mode 100644
index 0000000..6c9f8bf
--- /dev/null
+++ b/src/wire/WireBusDevice.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef WIRE_BUS_DEVICE_H_
+#define WIRE_BUS_DEVICE_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "../BusDevice.h"
+
+#include "WireBusDeviceConfig.h"
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class WireBusDevice : public BusDeviceBase
+{
+public:
+
+ WireBusDevice(WireBusDeviceConfig const & config);
+ virtual ~WireBusDevice() { }
+
+
+ virtual IoResponse transfer(IoRequest & req) override;
+
+
+ bool read(uint8_t * buffer, size_t len, bool stop = true);
+ bool write(uint8_t * buffer, size_t len, bool stop = true);
+ bool write_then_read(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop = false);
+
+
+private:
+
+ WireBusDeviceConfig _config;
+
+};
+
+#endif /* WIRE_BUS_DEVICE_H_ */
diff --git a/src/wire/WireBusDeviceConfig.h b/src/wire/WireBusDeviceConfig.h
new file mode 100644
index 0000000..c58e5b9
--- /dev/null
+++ b/src/wire/WireBusDeviceConfig.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef WIRE_BUS_DEVICE_CONFIG_H_
+#define WIRE_BUS_DEVICE_CONFIG_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+#include
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class WireBusDeviceConfig
+{
+public:
+
+ WireBusDeviceConfig(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop)
+ : _wire{wire}
+ , _slave_addr{slave_addr}
+ , _restart{restart}
+ , _stop{stop}
+ { }
+
+
+ inline arduino::HardwareI2C & wire() { return _wire; }
+ inline byte slave_addr() const { return _slave_addr; }
+ inline bool restart() const { return _restart; }
+ inline bool stop() const { return _stop; }
+
+
+private:
+
+ arduino::HardwareI2C & _wire;
+ byte _slave_addr{0x00};
+ bool _restart{true}, _stop{true};
+
+};
+
+#endif /* WIRE_BUS_DEVICE_CONFIG_H_ */
diff --git a/src/wire/WireDispatcher.cpp b/src/wire/WireDispatcher.cpp
new file mode 100644
index 0000000..9a8a664
--- /dev/null
+++ b/src/wire/WireDispatcher.cpp
@@ -0,0 +1,174 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include "WireDispatcher.h"
+
+#include
+
+/**************************************************************************************
+ * STATIC MEMBER DEFINITION
+ **************************************************************************************/
+
+WireDispatcher * WireDispatcher::_p_instance{nullptr};
+rtos::Mutex WireDispatcher::_mutex;
+
+/**************************************************************************************
+ * CTOR/DTOR
+ **************************************************************************************/
+
+WireDispatcher::WireDispatcher()
+: _thread(osPriorityRealtime, 4096, nullptr, "WireDispatcher")
+, _has_tread_started{false}
+, _terminate_thread{false}
+{
+ begin();
+}
+
+WireDispatcher::~WireDispatcher()
+{
+ end();
+}
+
+/**************************************************************************************
+ * PUBLIC MEMBER FUNCTIONS
+ **************************************************************************************/
+
+WireDispatcher & WireDispatcher::instance()
+{
+ mbed::ScopedLock lock(_mutex);
+ if (!_p_instance) {
+ _p_instance = new WireDispatcher();
+ }
+ return *_p_instance;
+}
+
+void WireDispatcher::destroy()
+{
+ mbed::ScopedLock lock(_mutex);
+ delete _p_instance;
+ _p_instance = nullptr;
+}
+
+IoResponse WireDispatcher::dispatch(IoRequest * req, WireBusDeviceConfig * config)
+{
+ mbed::ScopedLock lock(_mutex);
+
+ WireIoTransaction * wire_io_transaction = _wire_io_transaction_mailbox.try_alloc();
+ if (!wire_io_transaction)
+ return nullptr;
+
+ IoResponse rsp(new impl::IoResponse());
+
+ wire_io_transaction->req = req;
+ wire_io_transaction->rsp = rsp;
+ wire_io_transaction->config = config;
+
+ _wire_io_transaction_mailbox.put(wire_io_transaction);
+
+ return rsp;
+}
+
+/**************************************************************************************
+ * PRIVATE MEMBER FUNCTIONS
+ **************************************************************************************/
+
+void WireDispatcher::begin()
+{
+ Wire.begin();
+ _thread.start(mbed::callback(this, &WireDispatcher::threadFunc)); /* TODO: Check return code */
+ /* It is necessary to wait until the WireDispatcher::threadFunc()
+ * has started, otherwise other threads might trigger IO requests
+ * before this thread is actually running.
+ */
+ while (!_has_tread_started) { }
+}
+
+void WireDispatcher::end()
+{
+ _terminate_thread = true;
+ _thread.join(); /* TODO: Check return code */
+ Wire.end();
+}
+
+void WireDispatcher::threadFunc()
+{
+ _has_tread_started = true;
+
+ while(!_terminate_thread)
+ {
+ /* Wait blocking for the next IO transaction
+ * request to be posted to the mailbox.
+ */
+ WireIoTransaction * wire_io_transaction = _wire_io_transaction_mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever);
+ if (wire_io_transaction)
+ {
+ processWireIoRequest(wire_io_transaction);
+ /* Free the allocated memory (memory allocated
+ * during dispatch(...)
+ */
+ _wire_io_transaction_mailbox.free(wire_io_transaction);
+ }
+ }
+}
+
+void WireDispatcher::processWireIoRequest(WireIoTransaction * wire_io_transaction)
+{
+ IoRequest * io_request = wire_io_transaction->req;
+ IoResponse io_response = wire_io_transaction->rsp;
+ WireBusDeviceConfig * config = wire_io_transaction->config;
+
+ if (io_request->bytes_to_write > 0)
+ {
+ config->wire().beginTransmission(config->slave_addr());
+
+ size_t bytes_written = 0;
+ for (; bytes_written < io_request->bytes_to_write; bytes_written++)
+ {
+ config->wire().write(io_request->write_buf[bytes_written]);
+ }
+ io_response->bytes_written = bytes_written;
+
+ if (config->restart() && (io_request->bytes_to_read > 0))
+ config->wire().endTransmission(false /* stop */);
+ else
+ config->wire().endTransmission(true /* stop */);
+ }
+
+ if (io_request->bytes_to_read > 0)
+ {
+ config->wire().requestFrom(config->slave_addr(), io_request->bytes_to_read, config->stop());
+
+ while(config->wire().available() != static_cast(io_request->bytes_to_read))
+ {
+ /* TODO: Insert a timeout. */
+ }
+
+ size_t bytes_read = 0;
+ for (; bytes_read < io_request->bytes_to_read; bytes_read++)
+ {
+ io_request->read_buf[bytes_read] = config->wire().read();
+ }
+ io_response->bytes_read = bytes_read;
+ }
+
+ io_response->done();
+}
diff --git a/src/wire/WireDispatcher.h b/src/wire/WireDispatcher.h
new file mode 100644
index 0000000..87fc88f
--- /dev/null
+++ b/src/wire/WireDispatcher.h
@@ -0,0 +1,78 @@
+/*
+ * This file is part of the Arduino_ThreadsafeIO library.
+ * Copyright (c) 2021 Arduino SA.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef WIRE_DISPATCHER_H_
+#define WIRE_DISPATCHER_H_
+
+/**************************************************************************************
+ * INCLUDE
+ **************************************************************************************/
+
+#include
+
+#include "../IoTransaction.h"
+
+#include "WireBusDeviceConfig.h"
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class WireDispatcher
+{
+public:
+
+ WireDispatcher(WireDispatcher &) = delete;
+ void operator = (WireDispatcher &) = delete;
+
+ static WireDispatcher & instance();
+ static void destroy();
+
+
+ IoResponse dispatch(IoRequest * req, WireBusDeviceConfig * config);
+
+
+private:
+
+ static WireDispatcher * _p_instance;
+ static rtos::Mutex _mutex;
+
+ rtos::Thread _thread;
+ bool _has_tread_started;
+ bool _terminate_thread;
+
+ typedef struct
+ {
+ IoRequest * req;
+ IoResponse rsp;
+ WireBusDeviceConfig * config;
+ } WireIoTransaction;
+
+ static size_t constexpr REQUEST_QUEUE_SIZE = 32;
+ rtos::Mail _wire_io_transaction_mailbox;
+
+ WireDispatcher();
+ ~WireDispatcher();
+
+ void begin();
+ void end();
+ void threadFunc();
+ void processWireIoRequest(WireIoTransaction * wire_io_transaction);
+};
+
+#endif /* WIRE_DISPATCHER_H_ */