Skip to content

Latest commit

 

History

History
84 lines (67 loc) · 4.04 KB

threadsafe-wire.md

File metadata and controls

84 lines (67 loc) · 4.04 KB

Thread-safe Wire

Introduction

A common 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 these problems:

Imagine an embedded system where multiple Wire client devices are physically connected to a single Wire server. Each Wire client device is managed by a dedicated software thread. Each thread polls its Wire client device periodically. Access to the Wire bus is managed via the Wire library and typically follows the pattern described below:

/* Wire Write Access */
Wire.beginTransmission(address);
Wire.write(value);
Wire.endTransmission();

/* Wire Read Access */
Wire.requestFrom(address, bytes)
while(Wire.available()) {
  int value = Wire.read();
}

Since we are using the preemptive RTOS ARM Mbed OS with a tick time of 10 ms for achieving multi-tasking capability and under the assumption that all threads share the same priority (which leads to a round-robin scheduling) it can easily happen that one thread is half-way through its Wire access when the scheduler interrupts it and schedules the next thread which in turn starts/continues/ends its own Wire access.

As a result this interruption by the scheduler will break Wire access for both devices and leave the Wire controller in an undefined state.

In Arduino Parallela we introduced the concept of BusDevices which are meant to unify the way sketches access peripherals through heterogeneous busses such as Wire, SPI and Serial. A BusDevice is declared simply by specifying the type of interface and its parameters:

BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS);
/* or */
BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, true /* restart */);
/* or */
BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, false /* restart */, true, /* stop */);

Asynchronous thread-safe Wire access with transfer/wait

Once a BusDevice is declared it can be used to transfer data to and from the peripheral by means of the transfer() API. As opposed to the traditional Arduino bus APIs, transfer() is asynchronous and thus won't block execution unless the wait() function is called. Note that we are in a parallel programming environment which means that calls to transfer() on the same bus from different sketches will be arbitrated.

byte lsm6dsox_read_reg(byte const reg_addr)
{
  byte write_buffer = reg_addr;
  byte read_buffer  = 0;

  IoRequest  request(write_buffer, read_buffer);
  IoResponse response = lsm6dsox.transfer(request);

  /* Wait for the completion of the IO Request.
     Allows other threads to run */
  response->wait();

  return read_buffer;
}

Synchronous thread-safe Wire access with transferAndWait

(examples/Threadsafe_IO/Wire)

As the use of the transfer API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using transferAndWait.

byte lsm6dsox_read_reg(byte const reg_addr)
{
  byte write_buffer = reg_addr;
  byte read_buffer  = 0;

  IoRequest  request(write_buffer, read_buffer);
  IoResponse response = transferAndWait(lsm6dsox, request); /* Transmit IO request for execution and wait for completion of request. */

  return read_buffer;
}

Adafruit_BusIO style synchronous thread-safe Wire access

(examples/Threadsafe_IO/Wire_BusIO)

For further simplification Adafruit_BusIO style APIs are provided:

byte lsm6dsox_read_reg(byte reg_addr)
{
  byte read_buffer = 0;
  lsm6dsox.wire().writeThenRead(&reg_addr, 1, &read_buffer, 1);
  return read_buffer;
}