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 @@
+<img src="https://content.arduino.cc/website/Arduino_logo_teal.svg" height="100" align="right" />
+
 `Arduino_Threads`
 =================
 
@@ -5,15 +7,53 @@
 [![Check Arduino status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml)
 [![Spell Check status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/spell-check.yml)
 
-This library makes it easy to use the multi-threading capability of [Arduino](https://www.arduino.cc/) boards that use an [Mbed OS](https://os.mbed.com/docs/mbed-os/latest/introduction/index.html)-based core library.
+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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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<byte>(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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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<byte>(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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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<uint8_t>(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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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 <Arduino_ThreadsafeIO.h>
+
+/**************************************************************************************
+ * 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(&reg_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<SpiBusDevice *>(_dev.get());
+}
+
+WireBusDevice & BusDevice::wire()
+{
+  return *reinterpret_cast<WireBusDevice *>(_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<BusDeviceBase> _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 <Arduino.h>
+
+#include <mbed.h>
+
+#include <SharedPtr.h>
+
+/**************************************************************************************
+ * 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<impl::IoResponse> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> 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<rtos::Mutex> lock(_mutex);
+  _global_prefix_callback = func;
+}
+
+void SerialDispatcher::global_suffix(SuffixInjectorCallbackFunc func)
+{
+  mbed::ScopedLock<rtos::Mutex> 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<rtos::Mutex> 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<char>(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<SerialDispatcher::ThreadCustomerData>::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<ThreadCustomerData>::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 <mbed.h>
+
+#include <list>
+#include <functional>
+
+#include <SharedPtr.h>
+
+/**************************************************************************************
+ * 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<String(String const &)> PrefixInjectorCallbackFunc;
+  typedef std::function<String(String const &, String const &)>  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<THREADSAFE_SERIAL_TRANSMIT_RINGBUFFER_SIZE> SerialTransmitRingbuffer;
+
+  typedef struct
+  {
+    osThreadId_t thread_id;
+    SerialTransmitRingbuffer tx_buffer;
+    bool block_tx_buffer;
+    mbed::SharedPtr<arduino::RingBuffer> 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<ThreadCustomerData> _thread_customer_list;
+
+  void threadFunc();
+  std::list<ThreadCustomerData>::iterator findThreadCustomerDataById(osThreadId_t const thread_id);
+  void prepareSerialReader(std::list<ThreadCustomerData>::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 <Arduino.h>
+
+#include <SPI.h>
+
+#include <functional>
+
+/**************************************************************************************
+ * CLASS DECLARATION
+ **************************************************************************************/
+
+class SpiBusDeviceConfig
+{
+public:
+
+  typedef std::function<void(void)> SpiSelectFunc;
+  typedef std::function<void(void)> 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 <SPI.h>
+
+/**************************************************************************************
+ * 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<rtos::Mutex> lock(_mutex);
+  if (!_p_instance) {
+    _p_instance = new SpiDispatcher();
+  }
+  return *_p_instance;
+}
+
+void SpiDispatcher::destroy()
+{
+  mbed::ScopedLock<rtos::Mutex> lock(_mutex);
+  delete _p_instance;
+  _p_instance = nullptr;
+}
+
+IoResponse SpiDispatcher::dispatch(IoRequest * req, SpiBusDeviceConfig * config)
+{
+  mbed::ScopedLock<rtos::Mutex> 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 <mbed.h>
+
+#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<SpiIoTransaction, REQUEST_QUEUE_SIZE> _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 <Arduino.h>
+
+#include <Wire.h>
+
+/**************************************************************************************
+ * 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 <Wire.h>
+
+/**************************************************************************************
+ * 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<rtos::Mutex> lock(_mutex);
+  if (!_p_instance) {
+    _p_instance = new WireDispatcher();
+  }
+  return *_p_instance;
+}
+
+void WireDispatcher::destroy()
+{
+  mbed::ScopedLock<rtos::Mutex> lock(_mutex);
+  delete _p_instance;
+  _p_instance = nullptr;
+}
+
+IoResponse WireDispatcher::dispatch(IoRequest * req, WireBusDeviceConfig * config)
+{
+  mbed::ScopedLock<rtos::Mutex> 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<int>(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 <mbed.h>
+
+#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<WireIoTransaction, REQUEST_QUEUE_SIZE> _wire_io_transaction_mailbox;
+
+   WireDispatcher();
+  ~WireDispatcher();
+
+  void begin();
+  void end();
+  void threadFunc();
+  void processWireIoRequest(WireIoTransaction * wire_io_transaction);
+};
+
+#endif /* WIRE_DISPATCHER_H_ */