-
Notifications
You must be signed in to change notification settings - Fork 356
SPI
This section assumes you are familiar with the concepts of Digital I/O
Serial Peripheral Interface (SPI) is a common interface used to communicate between integrated circuits. A single SPI bus enables a single master device to communicate with one or more slave devices at typical rates of up to tens of Mbit/sec. SPI is a full-duplex interface, meaning that transmission and reception can are taking place concurrently. SPI requires 3 wires shared by the master and all slaves and an additional wire between the master and each slave.
The interface is very simple. The master transmits pulses on the CLK line, to which all slaves listen. On each CLK pulse, the master writes one bit to the MOSI (master-out-slave-in) line, and reads one bit from the MISO (master-in-slave-out) line. Only a single slave may enabled at any given time, preventing the possibility of concurrent write to the MISO line. The enabled slave is selected by a SS (slave-select) pin, which the master controls. The master will never enable more than one slave at a time. A slave that is not selected using its SS pin must never affect the MISO line and also typically ignores the MOSI line. The MISO line is always accessed in open-drain mode, with a single pull-up resistor making sure 0xFF is read whenever no slave is writing on this line.
At a higher level, bits are usually grouped into 8-bit words (bytes), and individual slaves specify different protocols for accessing their various features. Most commonly (but not always), these protocols are comprised of fixed-length transactions, in which the master sends a byte sequence of a known length, and the slave responds with a known-length message, starting at a known lag (in bytes) after the beginning of the transmission. Because there is a common clock for both transmission and reception, the physical number of bytes transmitted and the number of bytes received are always equal. However, if is very common for the master's transmitted message to be padded with trailing "garbage" bytes (0xFF) and for the slave's transmitted message to be padded with leading "garbage" bytes (0xFF). That way, the actual data we care about, may have arbitrary lengths for the request and response and an arbitrary lag between them. It is important to know that not all SPI-based protocols have this fixed-length structure. A notable example is the MMC protocol, used for SD cards.
The following illustration shows a typical SPI transaction, in which the master sends 5 bytes (0x01, 0x02, 0x03, 0x04, 0x05) and receives 4 bytes (0x0A, 0x0B, 0x0C, 0x0D) with a lag of 3. The total transaction length is thus 7 bytes. Time goes from left to write:
Master | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0xFF | 0xFF |
---|---|---|---|---|---|---|---|
Slave | 0xFF | 0xFF | 0xFF | 0x0A | 0x0B | 0x0C | 0x0D |
IOIO can act as an SPI master on up to 3 concurrent buses, at data rates of up to 8MHz. The CLK and MOSI lines can be mapped to any pins supporting peripheral output. The MISO line can be mapped to any pin supporting peripheral input. SS lines can be mapped to any pin. Peripheral outputs are those pins designated with "P" at the bottom of the board. Peripheral inputs are also the pins marked as "P" as well as the pin marked as "Pi". Note that on the first revision of boards the letter "P" has unfortunately been omitted from some of the pins that support peripheral input and output. These are pins 34-40 and 45-48. You can see a complete list of pin functions in the table at the bottom of this page. Both inputs and outputs can support 5V logic in the standard way, as described here. When using 3.3V logic, the MISO line can be conveniently opened in pull-up mode, saving the need for an external pull-up resistor.
At present, the IOIOLib API supports only fixed-length messages, as described above. Variable-length protocols can always be implemented on top of this (at the worst case by always sending and receiving a single-byte message), but this would result in limited throughput, as each transaction needs to go back and forth between the IOIO and the Android device before the next transaction can take place. However, this is not a physical limitation, and the API can be expanded in the future to support such protocols. Also not supported is the option of avoiding the need for the SS pin in a single-slave environment. Most available SPI slaves do not support this mode, so it seemed useless to support. However, it is possible to change, if the need arises.
A few options for the interface's clock and timing modes are important to match between master and slaves. First, the clock polarity can be inverted. Second, the MOSI and MISO can be required to be valid either on the leading clock edge or on the trailing clock edge. This results in a total of 4 possible combinations. If you are not sure, your best bet is non-inverted clock polarity (i.e. clock pulse is HIGH and idle is LOW) and sampling on leading edge.
Using the IOIO SPI modules is done via the SpiMaster
interface. An instance of this interface corresponds to a single SPI module on the board, as well as to the pins it uses for CLK, MOSI, MISO and SS (one or more). SpiMaster
instances are obtained by calling one of the overloads of IOIO.openSpiMaster()
. The simplest form is:
SpiMaster spi = ioio.openSpiMaster(misoPin, mosiPin, clkPin, ssPins, SpiMaster.Rate.RATE_1M);
This opens a SPI module in master mode, using pin number misoPin
as MISO with internal pull-up, pin number mosiPin
as MOSI in normal mode, pin number clkPin
as CLK in normal mode, and the pins whose numbers are in the ssPins
int array as slave select. The SPI module will run at 1MHz. In the single-slave scenario, openSpiMaster()
has an overload that accepts a scalar int as slave select pin. The clock polarity will be non-inverted and sampling will be done on the leading clock edge. It is required that at the time of call, those pins are not being used for anything else, and that there is at least one free SPI module. In order to open the pins in mode other than their default (e.g. for using 5V inputs/outputs), the other versions of IOIO.openSpi()
should be used. See here for more details on digital pin modes.
Once an instance of SpiMaster
is obtained, requests can be sent by using:
byte[] request = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
byte[] response = new byte[4];
spi.writeRead(0, request, request.length, 7, response, response.length);
This will issue the exact transaction that is used in the example above. The first argument designates that slave number 0 should be used. This number is the index in the ssPins
array that was passed on creation. It can be omitted in the one-slave case. The second and third arguments are the request buffer and it length. The Fourth argument is the total transaction length, the fifth and sixth arguments are the response buffer and the desired response length.
The writeRead()
method will block until a response is received, and then the response can be read from the response buffer. If blocking is not desired, there is also an asynchronous version:
SpiMaster.Result result = spi.writeReadAsync(0, request, request.length, 7, response, response.length);
// ...do some stuff while the transaction is taking place...
result.waitReady(); // blocks until response is available
doSomethingWithResponse(response);
When you are done using the SPI module, call:
spi.close();
in order to return the pins to a "floating" state and possibly be able to re-open them in the same or in a different mode, as well as free the SPI module. The SpiMaster
instance becomes useless after this call - it is illegal to do anything with it.