Raspberry Pi Linux User Mode GPIO in C++ - Part 3

Wednesday, 30 Sep 2015 - 12:33 +0100| Tags: Raspberry Pi, Linux, code, C++, GPIO

The previous instalments[1][2] have described creating the rpi-peripherals[3] library to access general purpose input output (GPIO) on a Raspberry Pi running Raspbian Linux in C++ from user space. They covered creating the phymem_ptr class template that utilises RAII (resource acquisition is initialisation[4]) to manage mapped areas of physical memory, setting up the library project and the implementation of support for basic general purpose input and output of single bit Boolean values, clocks and pulse with modulation (PWM).

This final instalment describes the two serial interface peripheral types the library supports and closes with some concluding remarks.

SPI Game

Having added PWM support to the library and played with the Gertboard’s[5] motor controller I turned to the digital to analogue converter (DAC) and analogue to digital converter (ADC) chips. The ADC handles 10-bit samples while the DAC handles 8-bit samples - other Gertboards may have similar DAC chips that handle 10 or 12 bit samples. Both chips use the serial peripheral interface (SPI)[6] to transfer data making the addition of SPI support the next task.

The Raspberry Pi’s Broadcom BCM2835 processor has several peripherals that support SPI. For the older Raspberry Pi models I was targeting the GPIO pins provided for interfacing only allow using the SPI0 peripheral – referred to as ‘SPI’ or ‘SPI Master’ in the documentation[7]. As there are also two instances of a ‘Universal SPI Master’ or ‘mini SPI interface’ peripheral type named SPI1 and SPI2 I decided to unambiguously use ‘spi0’ to refer to SPI0 peripheral related entities.

SPI uses the common master/slave model[8] where a single master device has control over one or more slave devices. In this case SPI0 on the Raspberry Pi is the master device and the ADC and DAC chips are slave devices. As an aside the BCM2835 also supports an SPI slave peripheral, also unavailable on the earlier Raspberry Pi models. The standard communication mode is a ‘3-wire’ protocol, allowing master and slave to send data simultaneously. Two of the three ‘wires’ are for the master to send data to a slave - MOSI (master output, slave input), and for the slave to send data to the master - MISO (master input, slave output). SPI is a synchronous serial interface so the third ‘wire’, SCLK, is the clock signal which is provided by the master. The master only communicates with one slave device at a time, which device being determined by a number of chip enable (or chip select) lines. The SPI0 peripheral provides two such lines - CE0 and CE1.

For added fun SPI0 supports two 2-wire modes which only use SCLK and MOSI for communication: bidirectional mode which calls MOSI MOMI (master output, master input) and LoSSI (low speed serial interface) mode which names MOSI SDA (serial data) and SCLK SCL (serial clock).

Between data transferred between SPI0 and a slave device are a pair of FIFO (first in first out) buffers, one for data to be sent to the slave and the other for data received from the slave. They are 16 32-bit words deep, allowing 64 8-bit bytes to be buffered. The FIFOs are a case where peripheral registers do not behave like regular memory. Both FIFOs are accessed via same register with reads returning the next available value from the receive FIFO and writes writing to the next available slot in the transmit FIFO.

As with other peripherals I only considered support for SPI0 polled use. On the other hand I did want to support all three communication modes along with various parameters that may need tweaking depending on the specifics of various slave devices.

A plurality of pins

Implementing SPI0 support initially followed the pattern discussed in part 2: I started with the spi0_registers class that matched the layout of the SPI0 peripheral’s control registers and allows querying and setting the various fields. I created the spi0_ctrl singleton class which as per the pattern contains the phymem_ptr specialisation phymem_ptr<_volatile_ spi0_registers> member that maps a spi0_registers instance over the SPI0 control registers. As SPI0 only supports a single item the type for the spi0_ctrl is-in-use tracking member is simply a bool.

The pattern dictates there should be a spi0_pin class. SPI0 requires the use of 5 pins, or 4 for the 2-wire modes. Hence the name spi0_pins – plural, was chosen.

It seemed cumbersome to pass in four or five pin value parameters to the spi0_pins constructor, especially considering that for the Raspberry Pi models I was targeting there could only be two possible valid sets of values: either all five of the available pins for SPI0 functions or the four pin subset required for 2 wire bidirectional mode operation. On the other hand I did not want to hard code the pin values as the SPI0 functions are available on a second group of pins and it was possible other BCM2835 based systems could access them.

So the constructor of spi0_pins takes a spi0_pin_set class template specialisation. spi0_pin_set has five integer non-type template parameters – one for each SPI0 pin. The fifth parameter has a default value representing pin-not-used to allow four pin sets to be defined. The class contains no values and only has five member functions – one for each SPI0 pin function – that return the value of the associated template parameter. Two instances are defined: rpi_p1_spi0_full_pin_set that specifies all 5 pins and rpi_p1_spi0_2_wire_only_pin_set that specifies the 4 pin subset required for 2-wire mode operation.

Chip chat

I soon realised that having just a spi0_pins class was not sufficient. Adding functionality to spi0_pins to allow checking the various states the FIFOs could be in was not a problem. It was when I came to implement the read and write functionality that I ran into a problem.

SPI0 can directly select one of two devices to converse with and switch conversations between devices. Each device can use different sets of communication parameters. So one spi0_pins instance has be able to support multiple sets of conversation parameters.

My initial solution was to create a spi0_conversation class instances of which represented conversations with specific devices with the relevant parameters being passed as constructor arguments. To hold a conversation an instance was opened by passing a reference to a spi0_pins object to the open member function. Once successfully opened the conversation could proceed by calling the read and write member functions. When done the close member function was called. Attempting to have more than one open conversation at a time caused an exception to be thrown. Opening a conversation would set the required SPI0 communication parameters, then activate SPI0 data transfers – which were deactivated on conversation close.

This scheme worked but I did not like the low level twiddling with SPI0 registers being split across two classes. Worse, in order to keep track of open conversations the spi0_pins instance had to keep a pointer to the current open spi0_conversation which was set to nullptr on conversation close meaning the spi0_conversation object had to hold a pointer to the spi0_pins object. So instances of each type referenced each other whenever there was an open conversation which seemed somewhat inelegant.

Moving the read and write functionality to spi0_pins solved my first gripe. To address the second gripe I applied conversation contexts to a spi0_pins instance, with member functions spi0_pins::start_conversing and spi0_pins::stop_conversing in place of open and close operations.

This left spi0_conversation with very little to do so I renamed it spi0_slave_context. Instances of spi0_slave_context hold the subset of SPI0 peripheral registers needed to define the context of each conversation. To create the required register values the construction parameters are used to set the relevant field values of a local automatic spi0_registers object from which the required complete register values are copied to instance data members.

To start a conversation a spi0_slave_context instance is passed to spi0_pins::start_conversing which, after stopping any conversation and performing some validation, copies over most of the spi0_slave_context object’s register value members to their live counterparts. Some fields of the control and status register should not be overwritten so a mask is used to apply only the relevant bits.

Read and write à la mode

The read and write operations provided by spi0_pins cater for transferring single and multiple byte values. Single byte operations return a bool indicating whether or not a value was transferred to or from a FIFO. Multi-byte operations return a std::size_t indicating how many bytes were transferred. A transfer may not complete for several reasons: conversing may be stopped or the transmit FIFO is full or the receive FIFO empty.

Which of the three SPI communication modes is used is part of a conversation’s associated spi0_slave_context. The specifics of the protocols used by each mode are mostly hidden behind the read and write member functions, although some modes require extra information for some operations. Luckily these could be supported by appending an additional parameter with a default value to the operations concerned.

The easiest mode to implement, and the only one I could fully test due to the available hardware, was standard 3-wire mode. In standard mode data is written to the transmit FIFO while it is not full and read from the receive FIFO while it is not empty. The only oddity is that in order to read some data you must first write something – anything. So to read two bytes you first write two bytes. As these are transmitted to the slave device its reply is received, appearing in the receive FIFO. Of course serially transferring data takes time so there will be delays involved.

Listing 1 shows an example where a conversation expects two bytes to be received after the slave device has been sent a single byte that setups up the conversation. In the read function, while only one byte is required to be written to setup the conversation the same value is sent twice as we expect to read two bytes. Each byte is then read by calling the read_byte function which waits until data arrives in the receive FIFO, wrapping a call to the single byte overload of spi0_pins::read in a loop with a delay.

Listing 1: SPI0 read example
#include "spi0_pins.h"
#include <array>
#include <thread>
#include <chrono>
#include <iostream>
#include <iomanip>

using namespace dibase::rpi::peripherals;

unsigned char read_byte(spi0_pins & spi0)
{
  constexpr auto a_short_while(std::chrono::microseconds{100});
  unsigned char byte{0U};
  while (!spi0.read(byte))
    std::this_thread::sleep_for(a_short_while);
  return byte;
}

bool read(spi0_pins & spi0,std::array<int,2> & result)
{
  constexpr unsigned char mode{0xd0};
  if (spi0.write(mode) && spi0.write(mode))
  {
    result[0] = read_byte(spi0);
    result[1] = read_byte(spi0);
    return true;
  }
  return false;
}

int main()
{
  try
  {
    constexpr auto f_sclk(megahertz{1});
    spi0_slave_context chip0_context{spi0_slave::chip0, f_sclk};
    spi0_pins spi0{rpi_p1_spi0_full_pin_set};
    for (unsigned i=0; i<10; ++i)
    {
      spi0.start_conversing(chip0_context);
      std::array<int,2> data;
      if (read(spi0,data))
        std::cout << std::hex << std::setfill('0')
                  << std::setw(2) << data[0] << ' '
                  << std::setw(2) << data[1] << '\n';
      else
        std::cout << "## ##\n";
    }
  }
  catch (std::exception & e)
  {
    std::cerr << "Failed because: " << e.what() << '\n';
  }
}

In main the spi0_pins and spi0_slave_context objects are created and ten sets of data obtained from the slave device selected by asserting CE0, specified by passing spi0_slave::chip0 to the context object’s constructor. The device requires that each value read is a separate conversation so the call to spi0_pins::start_conversing is inside the loop. The only other value explicitly specified for the slave context is the frequency of the clock, given in terms of the frequency types initially created for the clock peripherals’ library support described in part 2. To ensure that resources are released should an exception be thrown the whole lot is wrapped in a try-block. The single catch clause for std::exception by reference suffices as the library throws standard library exception types or types derived from them.

SPI standard mode reading and writing is tested using a loop-back configuration connecting the MOSI pin to the MISO pin so each written byte is immediately received back again. The support for reading and writing using the 2-wire modes can at best be termed ‘provisional’ as I have not been able to test them. I did not see how I could use a loopback setup with these modes and had no devices to hand that supported them – nor had I come across any in my very limited search for devices.

I2C – a serial interface by many other names

Having implemented SPI support allowing me to use the DAC and ADC chips on the Gertboard the only remaining device to look at was an Atmel AVR ATmega 8-bit microcontroller which houses a variety of useful interfaces and peripherals. ATmega application programs are stored in non-volatile flash memory and are sent via a supported interface with the microcontroller in a programming mode. On the Gertboard the ATmega microcontroller is programmed over SPI, using SPI0 at the Raspberry Pi end. I contemplated using the microcontroller as a peripheral extender but did not want to dedicate SPI0 permanently to the microcontroller. A browse through the relevant ATmega data sheet[9] revealed a two wire serial interface (TWI) compatible with the Inter-Integrated Circuit (IIC or I2C) interface[10] is supported. Thinking this could be used for Raspberry Pi and microcontroller communication I decided to add I2C support to the peripherals library.

The peripheral documentation for the BCM2835 calls its I2C-like serial interface ‘Broadcom Serial Controller’ (BSC). Three BSC master controller peripherals – BSC0, BSC1 and BSC2 are supported but only BSC0 and BSC1 are available for use via appropriately configured GPIO pins, BSC2 being reserved for use with the HDMI interface.

As you may have inferred I2C/BSC/TWI uses the master/slave model with each BSC controller acting as an I2C master (I2C slave mode is supported by the same peripheral that supports SPI slave mode). I2C only uses two wires – or pins - referred to as serial data (SDA) and serial clock (SCL). Slave devices have an address which is generally in a 7-bit range, but a cunning scheme can allow 10-bit addressing to be used. All transfers consist of serialised 8-bit bytes with the master sending an initial start byte containing the 7-bit address of the slave device the master wishes to converse with and a single bit indicating whether the master is reading or writing. Standard I2C can communicate at up to 100,000 bits per second (100 Kbps). The BSC controllers support I2C fast-mode allowing speeds of up to 400Kbps. Like SPI0 there are various parameters that can be adjusted to ensure master and slaves can communicate – the serial clock frequency value for example. All of these parameters I found could have useful default values but unlike SPI0 they apply to a controller as a whole and not on a per slave device basis.

BSC masters use a single 16 entry 8-bit wide FIFO that is shared by read and write operations as the I2C bus cannot be doing both simultaneously. Primarily only polled usage is supported by the BSC masters although interrupts can be generated for some interesting conditions. As with other peripherals the library only supports polled usage.

As there are only the two pins…

Once again, support for the BSC masters broadly follows the pattern discussed in part 2 with the i2c_registers class matching the layout of the BSC masters’ control registers and allowing querying and setting the various fields. However, the i2c_ctrl singleton class deviated from the pattern because the BSC masters’ register blocks are located sufficiently distant from each other that an array of three phymem_ptr<_volatile_ i2c_registers> was required to map three i2c_registers instances over three distinct BSC master register address blocks. As with SPI0, because I2C requires more than one pin there is the i2c_pins (plural) class.

I thought two pins were few enough that they could be passed directly to i2c_pins constructors as individual parameters. Annoyingly there is a case where the same two pins support two BSC masters on different alternate pin functions. Although the target Raspberry Pi models do not provide access to these pins I wanted to support this case so provided two constructors. One identifies a BSC master from just two passed pin_id values while the other is additionally passed a disambiguating 0 or 1 value to indicate BSC0 or BSC1 directly. The remaining parameters, common to both constructors, define a bunch of communication parameters and all have defaults.

Conversation starter

As I2C communications parameters apply to the whole peripheral the conversation state complexity of SPI0 does not apply. The only thing required to talk to a slave device is its 7-bit address, which is sent by the master at the start of a transaction along with a read/write bit. To cater for these transaction start requirements I added start_write and start_read member functions in addition to write and read member functions.

BSC masters require the data (byte) length - in the range [0, 65535] - to be specified at the start of each transaction. The transaction completes when data-length bytes have been transferred. So the BSC master peripherals require a slave device address and a transaction data length to start a transaction. When starting a write transaction it is useful to pass an initial chunk of data to transfer, however immediately after starting a read transaction there is nothing yet to receive.

Only multi-byte transfers are supported by the read and write operations of i2c_pins which return a std::size_t indicating how many bytes were transferred. A transfer may not complete because either the FIFO is full so no more data can be written to it or it is empty and no more data can be read from it. The pattern is to call start_write or start_read followed by repeated calls to write or read until the transaction completes, preferably with a delay between calls to allow time for data to transfer.

The BSC master peripherals support a variety of status information. The transfer active condition indicates when a transfer is in process and is presented by the i2c_pins::is_busy member function which returns true while data transfer is ongoing. For finer grained control there are various FIFO states that can be queried.

There are a couple of communication errors. A slave device may fail to acknowledge an address and the SCL clock line may timeout. Slaves can stretch clock ticks on SCL within limits – set as one of those communications parameters passed to i2c_pins constructors. A slave does this if it cannot respond quickly enough and needs to slow the master’s outpourings. The error states can be queried with the no_acknowledge and clock_timeout member functions. Plagiarising the C++ standard library IOStreams states I also provided a good state query member function along with two state clear member functions – one clearing both error states, the other clearing specific error states.

Listing 2 shows an example of using i2c_pins to write data to a memory device[11], read the values back and display the written and read values and whether they differ. The memory device’s write operation starts with the initial address to write to. The device has a 512 byte capacity, a 9-bit range, but the initial address is only 8-bits. The device uses two 256 byte pages to access the whole 512 bytes with page 0 having an even slave address and page 1 the following odd address. The first byte of the transferred data is written to the specified address with following bytes written to subsequent addresses. As the device read operation used does not specify a start address it is handy that addresses wrap round to zero after reaching 511. Reads start at the current address and then from each following address. So writing 512 bytes will return the current address to the initial location – address zero in this case, which if followed by a read will read the previously written data starting with the first byte written.

Listing 2: I2C writing and reading example
#include "i2c_pins.h"
#include <array>
#include <thread>
#include <chrono>
#include <iostream>
#include <iomanip>

using namespace dibase::rpi::peripherals;

constexpr int wrap_length{512}; // bytes
constexpr unsigned char memdev_addrs{0x50}; // page 0
typedef std::array<unsigned char,wrap_length> buffer_t;

void quick_doze()
{
  std::this_thread::sleep_for(std::chrono::microseconds{100});
}

void write(i2c_pins & bsc,unsigned char dev_addrs
          , unsigned char start_addrs,buffer_t & data)
{
  bsc.start_write(dev_addrs,data.size()+1, &start_addrs,1);
  unsigned char * write_ptr{&data[0]};
  std::size_t remaining{data.size()};
  while (remaining)
  {
    if (bsc.write_fifo_has_space())
    {
      std::size_t transferred = bsc.write(write_ptr, remaining);
      remaining -= transferred;
      write_ptr += transferred;
    }
   else
    quick_doze();
  }
  while (bsc.is_busy())
    quick_doze();
}

void read(i2c_pins & bsc,unsigned char dev_addrs,buffer_t & data)
{
  bsc.start_read(dev_addrs,data.size());
  unsigned char * read_ptr{&data[0]};
  std::size_t remaining{data.size()};
  while (remaining)
  {
    if (bsc.read_fifo_has_data())
    {
      std::size_t transferred = bsc.read(read_ptr, remaining);
      remaining -= transferred;
      read_ptr += transferred;
    }
    else
      quick_doze();
  }
  while (bsc.is_busy())
    quick_doze();
}

int main()
{
  try
  {
    i2c_pins bsc1{pin_id{2},pin_id{3}};
    buffer_t wb;
    for (int v=0; v!=wrap_length; ++v)
      wb[v] = v;
    write(bsc1,memdev_addrs, 0,wb);
    buffer_t rb = {{}};
    read(bsc1,memdev_addrs, rb);
    std::cout << std::boolalpha << std::setfill('0')
              << std::hex << "Wrote  Read  Same?\n";
    for (int v=0; v!=wrap_length; ++v)
      std::cout <<   "  " << std::setw(2) << int(wb[v])
                << "    " << std::setw(2) << int(rb[v])
                <<  "   " << (rb[v]==wb[v]) << '\n';
  }
  catch (std::exception & e)
  {
    std::cerr << "Failed because: " << e.what() << '\n';
  }
}

In main an i2c_pins instance is created using GPIO pins 2 and 3 for SDA and SCL respectfully (see pin_id and friends[12][1]). This translates to using BSC1 – hence the object’s name.

Using direct GPIO pin numbers in this case means knowing the Raspberry Pi revision as the original model B presents GPIO pins 0 and 1 on their P1 connector’s pins 3 and 5 – which support BSC0, while later model B revisions and subsequent models (excepting the compute module) connect GPIO pins 2 and 3 to this pair of pins, supporting BSC1. The problem could be solved by specifying p1_pin(3) and p1_pin(5) or use the pre-defined objects sda and scl.

A std::array type, under the alias buffer_t, is used as the type for buffers. The write buffer is created and filled with values matching the index of each byte. The program’s write function performs the write operation. It is passed the bsc1 i2c_pins object by reference along with the memory device’s page 0 address, the memory address to start writing to and the write buffer by reference. The write function initiates a write transaction by calling i2c_pins::write_start on the passed i2c_pins object – specifying the passed-in device address, one more than the write buffer length as the number of bytes to transfer to account for the initial memory page start address and passes the address of the memory page start address argument object as the single byte ‘buffer’ to initially transfer.

The following loop writes values from the write buffer to the FIFO. If there is space in the FIFO, determined by a call to i2c_pins::write_fifo_has_space, data is written to it from the write buffer otherwise the thread waits for space to become available by taking a short sleep – as implemented by the quick_doze function. The loop terminates when all the data has been written to the FIFO but the transaction only completes shortly after the FIFO empties. We could call i2c_pins::write_fifo_is_empty but that ‘completes shortly after’ can cause the peripheral to still be busy when the next transaction is attempted so it is best to wait for i2c_pins::is_busy to return false.

The values are then retrieved by calling the program’s read function which takes the same parameters as write except there is no starting memory address value as the read operation used starts reading from the current memory address. A separate read buffer is passed to read so that both written and read values are available for comparison. The workings mirror those of write. The transaction is initiated by calling i2c_pins::start_read passing the memory device’s address and the size of the passed data buffer to read into. Data is read into the buffer in chunks as they appear in the FIFO. Calls to i2c_pins::read_fifo_has_data check there is data to collect otherwise the thread takes a short doze. Finally read waits for i2c_pins::is_busy to return false indicating transaction completion.

The final action of main is to write out the bytes in the write and read buffers and whether each pair of values match.

You might be wondering why, as BSC masters do not have separate read and write FIFOs, why the FIFO checking member functions specify read and write in their names. The same conditions apply to spi0_pins, and SPI0 does have separate read and write FIFOs. Thinking some consistency might be nice I re-used the names.

I’ve not finished so I’ll start

The BSC masters support the I2C repeated start feature allowing a master and slave to conduct multiple transactions without asserting the stop condition and releasing the bus. Repeated start is only practically relevant to read operations where the master has to send some information to the slave device first – a value identifying a device register to be read for example. In such cases the master sends the information on what is requested then enters a start condition, without first going through a stop condition, specifying the same slave address but changing the read/write bit to read.

To support repeated start read operations I added an overload of i2c_pins::read that takes the data to write as an additional std::uint8_t parameter. Repeated starts are normal (read) transactions issued before the preceding (write) transaction has completed. The documentation is not very clear but it seems repeated starts must be issued after the last byte has started transfer but not completed which is difficult if not impossible to detect for multi-byte transactions. So the initially written data is restricted to a single byte allowing the start of the last and only byte’s transfer to be detected by waiting briefly and busily for i2c_pins::is_busy to return true. When it does the repeated start can be initiated – but has to be setup in the short time before the write transfer completes. The i2c_pins::read overload only returns after the byte-write has completed, counter intuitively by waiting for i2c_pins::read_fifo_has_data to return false, otherwise the byte transfer may be incompletely and falsely reported as read data.

Because the code runs on a pre-emptively scheduled system there is a chance the processor wanders off to do something else while waiting for the single byte write transaction to start and the whole transaction could have completed by the time the thread is scheduled to run again. This means the window in which i2c_pins::is_busy returns true is missed and the wait loop will never exit. To prevent this a maximum number of iterations is defined and an iteration count kept. If the count exceeds the maximum false is returned immediately indicating a missed repeated start and the caller should retry. Other error conditions are signalled by throwing an exception.

Listing 3 shows a read function that uses repeated starts with a random read operation of the memory device. It differs from the read function in listing 2 by having an additional mem_addrs parameter which is passed to the repeated-start supporting i2c_pins::start_read member function in a loop which permits a number of retries to account for the possibility of failure due to context-switches as discussed above.

Listing 3: I2C repeated starts example
bool read(i2c_pins & bsc,std::uint8_t dev_addrs
         ,std::uint8_t mem_addrs, buffer_t & data)
{
  int remaining_tries{3};
  bool started{false};
  while (!started)
  {
    started = bsc.start_read(dev_addrs,mem_addrs,data.size());
    if (!started && --remaining_tries==0)
      return false;
  }
  std::uint8_t * read_ptr{&data[0]};
  std::uint32_t remaining{data.size()};
  while (remaining)
  {
    if (bsc.read_fifo_has_data())
      {
        std::uint32_t transferred = bsc.read(read_ptr, remaining);
        remaining -= transferred;
        read_ptr += transferred;
      }
     else
      quick_doze();
  }
  while (bsc.is_busy())
    quick_doze();
  return true;
}

The ten-bit cunning scheme

One use of repeated starts is to support I2C 10-bit addressing. The additional address bits are provided by a byte written before the rest of the transaction, which for write transactions just adds an additional byte to the transaction similar to the listing 2 write function. Read transactions from 10-bit addressed slave devices require the extra address byte to be written followed by a repeated read-transaction start as per the listing 3 read function. You would think the extra 8-bits would give a 15 bit address but the upper 5 bits of the usual 7-bit part of the address use a fixed pattern specifying a reserved range of addresses, leaving only the lower 2 bits to be available for use as the most significant bits of the extended 10 bit address.

Wrapping up

So that’s about it. I think C++, and C++11, features proved useful in providing simple interfaces to GPIO and other peripherals. The resultant overall structure of the library appears to allow easily adding support for other peripherals or even other modes of peripherals having existing support – such as the serialiser mode of the PWM controller.

There are some parts that have not worked out quite as well as they could. The pin and peripheral allocation support is I think the part with the most problems. While tracking which pins and peripherals are in use is a good thing in theory as there is no system wide way of achieving this the partial solutions currently implemented by the library leave quite a lot to be desired.

While developing the initial implementation I intentionally ignored concurrency and synchronisation issues. While the library can with care be used in a multithreaded environment there should be a review of concurrency concerns at the very least – especially as the latest Raspberry Pi 2 model B has a 4 core chip.

Which leads on to supporting the growing list of Raspberry Pi models. The main concern is that the new BCM2836 based Raspberry Pi 2 model B maps peripheral registers to a different base address. Other concerns are supporting the additional GPIO pins on the larger 40 pin connector and detecting which model code is running on. I have added some preliminary support for these things to the pin_id family of classes and for use by the rpi_info type to determine the board revision details.

But before rushing into too much new functionality it is probably a good point to review the interfaces provided for each peripheral as well as the code in general and of course those 2-wire modes of spi0_pins still need to be tested.