As is often the case on this site, this post is a summary of what was learned as I have become a little more familiar with a new-to-me technology. So far, my only use of the Serial Peripheral Interface (SPI) had been a frustrating but ultimately successful attempt at connecting a relatively uncommon SPI LCD display with an ESP8266, then an Arduino Uno and, lately, an Orange Pi. This time, I wanted to look into using the SPI bus much as I used an I²C bus for arbitrary communication between a Raspberry Pi and a microcontroller. I haven't reached that goal yet, concentrating for the time being on "SPI on the Pi". The only devices harmed in the course of writing this post were a Raspberry Pi 3 B and a cheap, Saleae Logic knockoff, 8 channel logic analyzer. And ever mindful to conserve bits, only 3 channels of the latter were used.
The Raspberry Pi was running under the last version of Raspbian Buster when the original version of this post was written in early May 2020. Luckily, not much has changed with the newest version of the OS which now goes under the Raspberry Pi OS moniker. This revision remains a draft, because some parts may need to be updated and there may very well be some additional material in the future.
Table of contents
- Serial Peripheral Interface - SPI
- SPI Buses on the Raspberry Pi
- SPI Loopback Test
- The SPI Protocol in Action
- Documenting the Python SpiDev Module
Serial Peripheral Interface - SPI
Here is how two devices, an Lolin/Wemos D1 Mini and a colour LCD, could be connected to the Raspberry Pi using the SPI.
The table summarizes the logic signals.
Label | Function | Alternate labels... |
---|---|---|
SCLK | Serial Clock (output from master) | SCK, CLK |
MOSI | Master Output Slave Input, or Master Out Slave In (data output from master) | SDI, DIN, SI |
MISO | Master Input Slave Output, or Master In Slave Out (data output from slave) | SDO, DOUT, SO |
SS | Slave Select (often active low, output from master) | CE, SSEL, CS |
There will be no further mention of the LCD or microcontroller in this post, but I thought it would be valuable to show the typical topology of a SPI bus where there can be only one master and one or more slaves. To communicate with a particular slave, the master must enable it with a dedicated signal (slave select, often called chip select).There is no concept of a bus address as in the I²C protocol. The master also supplies the clock to all the slaves. With each pulse of the clock, the master sends a bit of data down to the slave on the MOSI signal line and, simultaneously, the slave sends a bit of data up to the master on the MISO line. Thus SPI is a full-duplex communication protocol. Conceivably, a multi-function peripheral would not know what to send initially so that its data could be garbage until it has received some instructions from the master. And as can be seen above, some peripherals don't even have a slave output signal going to the master.
As one can imagine, there are a lot of details that need to be settled for communications to occur.
- Should the clock signal be a 0 to 1 pulse or a 1 to 0 pulse? That is called its polarity.
- Should the trailing or leading edge of the clock pulse be used to latch the data? That is called the clock phase.
- What is the bit order? Least significant bit first or last ?
- How many bits in a single word transmitted ?
- How fast should the clock signal be?
- Is the slave select active low or can it be high?
- Is the slave select signal needed when there is only one slave?
With those questions in mind, I feel comfortable enough to move on. Seriously, the article Serial Peripheral Interface is informative, thorough and nevertheless easily understood. It is, in fact, among the best technical articles I have read on Wikipedia.
SPI on the Raspberry Pi
Detailed information about Broadcom systems on chip (SoC) is hard to find. Here is what I have been able to piece together. The BCM2835 (used on the Raspberry Pi Model 1 and the Zero), the BCM2836 (used on Model 2) and the BCM2837 (used on Model 2 ver 1.2 and Model 3) systems on chip have 2 SPI master controllers each with 3 independent slave select signals (BCM2835 ARM Peripheral, Broadcom, 2012 p. 20). I have no information on the BCM2837B0 used on Models 3B+, 3A+ and the Compute Module 3+. The DATASHEET, Raspberry Pi Compute Module 3+, Raspberry Pi Compute Module 3+ Lite, Release 1, January 2019 lists 2xSPI as available peripherals (p. 6), which entails that the SoC must have at least two SPI controllers. The BCM2711, used with the Model 4, is more flexible and has 5 SPI Master interfaces SPI0, SPI3, SPI4, SPI5, SPI6 and two mini SPI interfaces, SP1 and SPI2 (BCM2711 ARM Peripherals, Raspberry Pi (Trading) Ltd., Version 1, 5th February 2020, p. 168).
Of course, not every SoC peripheral is connected to a header on the Raspberry Pi. However, all Raspberry Pi models have at least one hardware SPI bus (SPI0) with two associated slave select signals. It uses the following pins on the GPIO (P1) header.
Signal | GPIO pin | Physical pin |
---|---|---|
SPI0_MOSI | 10 | 19 |
SPI0_MISO | 9 | 21 |
SPI0_SCLK | 11 | 23 |
SPI0_CEO_N | 8 | 24 |
SPI0_CE1_N | 7 | 26 |
Newer models with a 40 GPIO header have a second SPI bus (SPI1) which has up to three slave select signals.
Signal | GPIO pin | Physical pin |
---|---|---|
SPI1_MOSI | 20 | 38 |
SPI1_MISO | 19 | 35 |
SPI1_SCLK | 21 | 40 |
SPI1_CEO_N | 18 | 22 |
SPI1_CE1_N | 17 | 11 |
SPI1_CE2_N | 16 | 36 |
Bus SPI2 is available on the Compute module only.
I do not have a Model 4 so cannot experiment with the other SPI interfaces on that model. The pinout for these other interfaces can be found in section 5.3 Alternative Function Assignment on page 98 of the data sheet. There is a clear presentation of the pinout for SPI3, SPI4, SPI5 and SPI6 on the 40 pin header of the Model 4 on the SPI documentation at the Raspberry Pi Foundation.
In BCM2835 ARM Peripheral, there is mention of a BSC/SPI slave controller on page 160 (BSC for Broadcom Serial Controller appears to be the Broadcom implementation of the I2C protocol). It shows up in the BCM2711 ARM Peripherals as the ALT3 function of GPIO8, 9, 10 and 11 pins (pages 98 and 99). To the best of my knowledge, drivers for that peripheral were never developed. There seems to be a consensus that the available hardware SPI interfaces on the Raspberry Pi function exclusively in master mode.
spidev0
By default, the SPI kernel drivers are not loaded in the lite
versions of Raspbian Buster or its more recent replacement Raspberry Pi OS. That can be done on a one-off basis with the dtparam
utility.
While "everything is a file in Linux", spidev0.0
and spidev0.1
are called "user space device nodes" in the help files. Interpret them as the SPI0
controller with slave select SPIO0_CEO_N
which is GPIO8
in the case of spidev0.0
and the same SPI0
controller with slave select SPI0_CE1_N
, GPIO7
for spidev0.1
. Unfortunately the third slave select, SPIO_CE2_N
on GPIO45
is not available on the GPIO header.
There are two ways to enable the SPI0
hardware interface permanently. I prefer to edit the hardware configuration file /boot/config.txt
.
I found the following bit in the file.
As instructed, I removed the leading "#" at the start of the #dtparam=spi=on
line to enable SPI0. This change will take effect when the Raspberry Pi is rebooted. The other way to accomplish the same thing is to use the raspi-config
utility.
Here is the list of menu choices that have to be made.
5 Interfacing Options Configure connections to peripherals
P4 SPI Enable/Disable automatic loading of SPI kernel module
Would you like the SPI interface to be enabled?
select<Yes>
The SPI interface is enabled
select<Ok>
<Finish>
to leave the utility
To be accurate, using the configuration utility not only modifies the boot/config.txt
to enable the SPI interface at boot time, but it also loads the overlay and drivers immediately.
Additionally, there are two device-tree overlays, spi0-cs
and spi0-hw-cs
, that can be used to load the SPI0
kernel driver.
These overlays can be used to load the kernel module as with dtparam spi
, but with more flexibility such as selecting which pins to use for slave select signals.
Below I will install the driver using GPIO23
and GPIO24
(physical pins 16 and 18 respectively) as the slave select pins
As shown above there are four ways to set up the first SPI bus on any Raspberry Pi model.
spidev1 and more
Lets use the dtoverlay
utility to list all SPI related overlays.
Modules marked with an asterisk were added in the newer Raspberry Pi OS. Some overlays are obviously drivers for SPI devices such as an Ethernet controller, a real-time clock, flash memory and display controllers. The spi-gpioXX-YY
overlays are to "move [the] SPI function block to GPIO[XX-YY]". Since these GPIO pins are not available on the Raspberry Pi, I assume they are for the Compute module. The spi0-xxx
overlays have already been seen. The spiB-Ncs
overlays are pretty much self-explanatory: it is a driver for SPI bus B with N slave select signals. Let's look at the documentation for one of them and install it (after spi0-cs
was installed).
Of course the above will install the kernel driver temporarily, and when the Pi is rebooted /dev/spidev1.x
will no longer exist. The /boot/config.txt
file has to be modified to load the driver automatically at boot time.
Shown is bold red are the changes to the file that will load two SPI controllers and create four devices whenever the Raspberry Pi is booted.
There is were no SPI2
to SPI6
interfaces before the Raspberry 4 (and Compute Module). So one should not be surprised if the installation of any of these fail on a Raspberry Pi 3 B or earlier model.
The failure may be flagged on the console as shown above or it may not be reported at all.
Number of SPI Slave Devices
All Raspberry Pi with a 40 pin GPIO header can handle up to five SPI slave devices as shown in the following figure.
In parentheses are the GPIO pin number of each Raspberry Pi signal, while the physical header number of each signal is in square brackets. PJRC would call that a simple but poor SPI bus design, see Better SPI Bus Design in 3 Steps.
It will be possible to have even more SPI slaves connected to a Compute module and to a Raspberry Pi 4. Without these devices, I can't say more with any certainty. Using the loop back tests described in the next section, it will be easy to verify if a kernel module is correctly loaded.
SPI Loopback Test
Being a full-duplex communication protocol, it should be possible to perform a loop back test with a SPI interface. The only prerequisite for such tests is to connect the MOSI and MISO lines together. There are plenty of references to a C program called spidev-test
, so I first tried that. Then I moved on to a similar test but using a Python script.
SPI Loopback Test in C
In my experience, not all versions of spidev-test
originally written in C by Anton Vorontsov that can be found on the Web work on the Raspberry Pi. Presumably the version provided by Raspberry Pi team does function, but I used the Richard Hull version. I have not compared these versions. The test is first run without the loopback connection and then again after making the connection to see the difference.
Obviously, the data send out of the MOSI
signal line was not read. Connect the MOSI
and
MISO
pins (GPIO 10 and 9 which are header pins 19 and 21 respectively) together and rerun the script.
It should not be necessary to use the sudo
prefix to obtain root privileges to run the test because the default user should be a member of the spi
group.
The test works with other hardware SPI channels if in place.
Of course, the MOSI
and MISO
SPI1 signals (GPIO 20 and 19 respectively) have to be connected for this test to work as shown. Also it does not matter which slave select pin is used for this test as can be seen from the two commands above.
SPI Loopback Test in Python
Let's do something similar with a Python script instead of a C/C++ program. First I created a virtual Python3 environment and then installed the Python spidev
module by Stephen Caudle (doceme).
Then I used nano
to create the following script.
The spi_loopback_test.py script can be executed invoking the Python 3 interpreter directly.
If you do not want to use a virtual Python environment, the required spidev
module can be loaded into the default Python 3 installation and then the script can be executed using the Python 3 interpreter.
It is also possible to mark the script as executable and bash
will start the interpreter automatically if the correct "shebang" line is added to the script. In the the virtual environment that first line should be
while it should be
if the default Python3 interpreter is used. No matter how the script is started, this is its output to the console if everything works correctly.
Use the CtrlC key combination to exit the loop.
It was not easy to get that script to work, which forced me to look at spidev
with more attention. Unfortunately, I could not find a "SPI on the Pi with Python" tutorial suitable for a newbie like me, so I had to rely on the README.md on the project GitHub (or project description at pypi.org) and the bits of the source code that I could fathom. Not unexpectedly, the SpiDev
class has a number of settings or attributes. Here they are with their values after instantiation and initialization of a SpiDev
object.
Attribute | spidev.SpiDev() | spidev.SpiDev(0,x) |
---|---|---|
bits_per_word | 0 | 8 |
cshigh | False | False |
loop | False | False |
lsbfirst | False | False |
max_speed_hz | 0 | 125000000 |
mode | 0 | 0 |
no_cs | False | False |
threewire | False | False |
At first I was misled by the loop
setting, which I thought needed to be set to true in a loop back test. It was a logical conclusion but it was wrong. After some searching I found the following issues Setting lsbfirst = True breaks access to GPIO on Raspberry Pi 1/2 with 3.18 kernel #18 and spi.lsbfirst = True fails with [Errno 22] Invalid argument #49 about a similar problem. In the last comment of the last issue, Gadgetoid confirms that spi.lsbfirst
, spi.loop
, and spi.threewire
are unsupported on the Raspberry Pi. I am not positive that the last option remains unsupported especially given the Raspberry Pi SPI page. Of course, even if the Linux driver has some capability, it does not necessarily follow that the Python module supports it. Further testing is needed.
After looking at numerous examples on connecting analogue to digital converters using SPI, I finally twigged on the fact that the default speed of 125 MHz was considerably higher than the typical 1 Mhz in those examples. So I added the spi.max_speed_hz=1000000
and the script worked! A short script gives a ballpark indication of the maximum SPI speed.
The spi_loopback_speed.py script output showed that a 32MHz frequency was possible but not 64MHz.
The SPI Protocol in Action
Here is an even simpler Python script that will create a SpiDev object, have it open the /dev/spidev0.1
file and write a byte (0x3A) on the MOSI signal line every tenth of a second until the CtrlC keyboard combination is pressed.
Three SPI signals were captured with a logic analyzer. The following image is a screen capture of the transmission of one byte as decoded by the analyzer.
As can be seen the data was sent bit by bit (which is what is meant by serial protocol) starting with the most significant bit. The eight clock cycles needed to send a byte of data took about 32 microseconds which translates to the set clock frequency of 250,000 Hz, give or take the measurement error.
The active low slave select signal was asserted about 7 microseconds before the transmission started, and remained active for approximately 24 microseconds after the last bit was sent.
Documenting the Python SpiDev Module
There is an hour-long video by Tony DiCola from Adafruit, Raspberry Pi & Python SPI Deep Dive with TonyD! @adafruit LIVE which is informative even if a little dated (February 2016). At around the 23-minute mark of the video, Mr DiCola refers to a PDF file "that someone put together" to document the SpiDev
module. Unfortunately the link to it is no longer valid. I was able to find SpiDev_Doc.pdf on the Wayback Machine. According to the document properties, it was written by Thomas Baumann and it dates from Dec 21, 2013. As could be expected, the document was not up to date. For reasons which remain mysterious to me and my analyst, I decided to update it. In order to do that, I wrote a Python script to test the various functions and settings of the library.
As usual, this script (spi_explore.py) is running in a virtual Python3 environment although that is not mandatory as explained above. However this script will not work in Python 2.x which, in any case, is deprecated. Here is the help screen which hopefully is self-explanatory.
By default the script will write 4 bytes to spidev0.0
using the writebytes
function. None of the SpiDev
attributes will be changed from their default value except for the speed which is set at 1 MHz for the reason explained above. In the following commands a different slave select is chosen, and the stream of bytes is first written in SPI mode 0 and then again in SPI mode 2.
This verifies that in mode 0 (and mode 1) the clock pulses are transitions from low to high while in mode 2 (and 3) the pulses are transitions from high to low. Here is a somewhat more detailed explanation of the mode.
Mode | CPOL | CPHA | Description | |
---|---|---|---|---|
0 | 0 | 0 | data is sampled at the leading rising edge of the clock | |
1 | 0 | 1 | data is sampled on the trailing falling edge of the clock | |
2 | 1 | 0 | data is sampled on the leading falling edge of the clock | |
3 | 1 | 1 | data is sampled on the trailing rising edge of the clock |
The script shows that certain attributes cannot be changed.
There's a vexing problem with the SpiDev module. Exactly what is the difference between the two transfer functions xfer
and xfer2
? The GitHub documentation says that it is at the level of the chip select signal: xfer
releases the signal between blocks while xfer2
asserts the signal throughout the complete transaction. That raises the question of what is a block of data. The default buffer size is 4,096 bytes. The three functions writebytes
, xfer
and xfer2
write out any list of values of 4,096 bytes or less in one chunk and the chip select signal is asserted throughout the transmission. Any attempt to transmit a list of bytes longer than 4,096 bytes with these three functions fails. In other words, xfer
and xfer2
only ever send out a single block of data, so there is never a reason to release the chip select signal. As far as I can tell, these functions are identical.
The value stored in /sys/module/spidev/parameters/bufsiz
is apparently the maximum size of the SPI buffer.
This value is easily changed by added a spidev.bufsiz=xxxx
option (where xxxx
is the desired buffer size) in the /boot/cmdline.txt
file. Root privileges are needed to edit this file and remember to keep the content of the file on a single line. The system has to be rebooted for this change to take effect. Doubling the size of the buffer limit had surprising consequences.
Even after this change, the writebytes
, xfer
and xfer2
functions fail with any list bigger than 4,096 bytes just as before.
In fact, the three functions completely ignore the spidev.bufsize
parameter and it remains possible to send lists containing 4096 bytes using any one of them even when buffer size parameter is set to a smaller value such as 1024.
On the other hand, writebytes2
and xfer3
which work with arbitrarily sized lists, will break large list in chunks sized according to the value of the spidev.bufsize
parameter. The folowing three images show how the 12,000 bytes were broken up into chuncks of the specified spidev.bufsize
by the xfer3
functions.
Here is the command used to capture the above images.
In my opinion, it would make more sense if the writebytes
, xfer
and xfer2
functions abided by the buffer size parameter. It is less important that the writebytes2
and xfer3
functions follow the buffer size parameter since they can handle any size list with a fixed buffer size of 4096. As can be seen, these latter two functions do release the chip select signals between blocks of data. In other words, xfer3
behaves as xfer2
should behave according to the library documentation.
The graph, which has a completely wrong horizontal scale, and the table shows the default timing of the chip select(/SS
) signal when sending a block of 1024 bytes of data and when a 250 microsecond delay is specified.
Chip (Slave) Select | Time (microseconds) | ||
---|---|---|---|
Phase | Signal | Default | Delay |
1. before the data block | LOW (0 V) | 7 | 6 |
2. transmission of the data block | LOW (0 V) | 8209 | 8201 |
3. after the data block | LOW (0 V) | 16 | 298 |
4. release period | HIGH (3.3V) | 191 | 183 |
The following command was used to add the 250 microsecond delay to generate the second set of data. These measurements, done by hand and only once, are not very accurate but they do show where the delay is added when specified in a xfer3
command.
A second draft of my revised SpiDev Documentation (2020-07-12) is available. This is necessarily a draft as I have done nothing with the incoming data stream. For that I need a slave SPI device that I can control.