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 SpiDev
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.
|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.
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 with two associated slave select signals. It uses the following pins on the GPIO (P1) header.
|Signal||GPIO pin||Physical pin|
Newer models with a 40 GPIO header have a second SPI bus which has up to three slave select signals.
|Signal||GPIO pin||Physical pin|
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.
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.
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
While "everything is a file in Linux",
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
spidev0.1. Unfortunately the third slave select,
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
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
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
The SPI interface is enabled
<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-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
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.
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
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.
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.
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.
The Richard Hull version of the
spidev-test originally written in C by Anton Vorontsov is used. 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
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
The test works with other hardware SPI channels if in place.
Of course, the
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.
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
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.threewire are unsupported on the Raspberry Pi. I am not positive that the last option remains unsupported, but I have not yet tested that possibility.
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.
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.
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, but I was not able to identify the original author. 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 that allows me 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.
|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
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
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,
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
xfer2 functions fails 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,
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
Here is the command used to capture the above images.
In my opinion, it would make more sense if the
xfer2 functions abided by the buffer size parameter. It is less important that the
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)|
|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
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.