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.
Consider this a draft, because some parts will certainly need to be updated and there may very well be some additional material in the future. Changes will have to wait until I have a bit more time and until the box of microcontrollers carefully put aside when preparing to move the office is found. Question: What does one do during a pandemic induce lockdown? Answer: Switch offices with one's spouse. That may sound like a small job, but a lot of things have accumulated over the years. That included books which occupy 32 metres (105 feet) of shelves that had to be brought from an upstairs office to a basement office and workshop. Moving them was not much fun, but organizing them is some sort of reasonable order is even less enjoyable. The final result should prove that the effort was worthwhile.
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
Raspbian Buster Lite. That can be done on a one-off basis with the
Interpret these files ("everything is a file in Linux") 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
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. 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
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.
Some overlays are obviously drivers for SPI devices such as an Ethernet controller, a real-time clock and flash memory. 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 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. So what happens if one tries to install one of those drivers on a Raspberry Pi 3 B?
The installation will fail and it may be flagged as such on the console 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.
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, it does not matter which slave select pin is used for this test as can be seen from the two commands above.
If you do not want to use a virtual environment, I believe that the required
spidev module can be loaded into the default Python 3 installation with the bash command sudo apt install python3-spydev based on the following, but I have not tested that.
Then I used
nano to create the following script.
The spi_loopback_test.py script can be executed invoking the Python 3 interpreter directly.
Or, the script can be marked as executable and
bash will start the interpreter automatically because of the first "shebang" line of the script.
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 ball-park 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. After making it executable, I printed out the help 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|
There's a vexing problem with the SpiDev module. Just 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. As far as I can tell, the buffer size is 4,096 bytes. The three functions
xfer2 write out the any list of values of that 4,096 bytes 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 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. The functions are identical.
The maximum size of the SPI buffer is seemingly specified in the file
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 would fail with any list bigger than 4,096 bytes just as before. As expected,
xfer3 would work with bigger lists. The strange thing is that these functions broke the large list in chunks of 8,192 bytes. The first three functions ignore the buffer size parameter while the last two abide by it even if it is less important in their case.
A first draft of my revised SpiDev Documentation 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, an ESP8266 perhaps?