SPI is less popular than I2C, but still you'll see lots of sensors and chips use it. Unlike I2C, you don't have everything share two wires. Instead, there's three shared wires (clock, data in, data out) and then a unique chip select line for each chip.

The nice thing about SPI is you can have as many chips as you like, even the same kind, all share the three SPI wires, as long as each one has a unique chip select pin.

The formal/technical names for the 4 pins used are:

  • SPI clock - called SCLK, SCK or CLK
  • SPI data out - called MOSI for Microcomputer Out Serial In. This is the wire that takes data from the Linux computer to the sensor/chip. Sometimes marked SDI or DI on chips
  • SPI data in - called MISO for Microcomputer In Serial Out. This is the wire that takes data to the Linux computer from the sensor/chip. Sometimes marked SDO or DO on chips
  • SPI chip select - called CS or CE

Remember, connect all SCK, MOSI and MISO pins together (unless there's some specific reason/instruction not to) and a unique CS pin for each device.

WARNING! SPI on Linux/Jetson Nano WARNING!

SPI on microcontrollers is fairly simple, you have an SPI peripheral and you can transfer data on it with some low level command. Its 'your job' as a programmer to control the CS lines with a GPIO. That's how CircuitPython is structured as well. busio does just the SPI transmit/receive part and busdevice handles the chip select pin as well.

Linux, on the other hand, doesn't let you send data to SPI without a CS line, and the CS lines are fixed in hardware as well. For example, on the Jetson Nano there are only two CS pins available for use as hardware SPI pins - CE0 and CE1 - and you have to use them. (In theory there's an ioctl option called no_cs but this does not actually work)

The upshot here is to let you use more than 2 peripherals on SPI, we decided to let you use any CS pins you like, CircuitPython will toggle it the way you expect. But when we transfer SPI data, we always tell the kernel to use CE0. CE0 will toggle like a CS pin, but if we leave it disconnected, it's no big deal

The downside here is basically never connect anything to CE0 (or CE1 for that matter). Use whatever chip select pin you define in CircuitPython and just leave the CE pins alone, it will toggle as if it is the chip select line, completely on its own, so you shouldn't try to use it as a digital input/output/whatever.

Don't forget you have to enable SPI with jetson-io!

Using the Second SPI Port

The Jetson Nano has a 'main' SPI port, but not a lot of people know there's a second one too! This is handy if you are using the main SPI port for a PiTFT or other kernel-driven device. You can enable the second SPI port by using the Jetson-IO utility and also selecting spi2. See the Initial Setup Page for more details.

Here's the wiring for SPI #2:

  • SCK_1 on GPIO #27 (Pin 13)
  • MOSI_1 on GPIO #26 (Pin 37)
  • MISO_1 on GPIO #25 (Pin 22)
  • SPI #2 CS0 on GPIO #24 (Pin 18)
  • SPI #2 CS1 on GPIO #23 (Pin 16)

Like the main SPI, we'll use CE0 as our default but don't connect to it! Use any other pin and leave that one unused. Then update your scripts to use

spi = busio.SPI(board.SCK_1, MOSI=board.MOSI_1, MISO=board.MISO_1)

Testing SPI with FRAM

OK now that we've gone thru the warning, let's wire up an SPI FRAM Memory breakout. This will allow us to test both reading  and writing.

Parts Used

FRAM, or Ferroelectric Ram, is the coolest new data storage method that all the fashion magazines are talking about. Oh...
$5.95
In Stock

We recommend using a breadboard and some female-male wires.

Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of ten rainbow...
$3.95
In Stock

You can use a Cobbler to make this a little easier, the pins are then labeled!

The Raspberry Pi B+ has landed on the Maker World like a 40-GPIO pinned, quad-USB ported, credit card sized bomb of DIY joy. And while you can use most of our great Model B accessories...
Out of Stock
This is the assembled version of the Pi T-Cobbler Plus.  It only works with the Raspberry Pi Model Zero, A+, B+, Pi 2, Pi 3 & Pi 4! (Any Pi with 2x20...
$7.95
In Stock

Wiring

There's no Jetson Nano Fritzing object, so we'll show using a Raspberry Pi which has the same pinout
  • Connect the Raspberry Pi 3.3V power pin to Vin
  • Connect the Raspberry Pi GND pin to GND
  • Connect the Pi SCLK pin to the FRAM SCK
  • Connect the Pi MISO pin to to the FRAM MISO
  • Connect the Pi MOSI pin to to the FRAM MOSI
  • Connect the Pi GPIO 5 pin to to the FRAM CS

Double-check you have the right wires connected to the right location. It can be tough to keep track of GPIO pins as there are forty of them!

Install the CircuitPython FRAM Library

OK, onto the good stuff. You can now install the Adafruit FRAM CircuitPython library.

As of this writing, not all libraries are up on PyPI so you may want to search before trying to install. Look for circuitpython and then the driver you want.

Once you know the name, install it with

pip3 install adafruit-circuitpython-fram

You'll notice we also installed a few other dependencies called spidev, adafruit-pureio, adafruit-circuitpython-busdevice and more. This is a great thing about pip, if you have other required libraries they'll get installed too!

We also recommend an adafruit_blinka update, in case we've fixed bugs:

pip3 install --upgrade adafruit_blinka

Run that code!

The finish line is right up ahead. You can now run one of the (many in some cases) example scripts we've written for you.

Check out the examples for your library by visiting the repository for the library and looking in the example folder. In this case, it would be https://github.com/adafruit/Adafruit_CircuitPython_FRAM/tree/master/examples

As of this writing there's only one example that runs on SPI:

## Simple Example For CircuitPython/Python SPI FRAM Library

import board
import busio
import digitalio
import adafruit_fram

## Create a FRAM object.
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = digitalio.DigitalInOut(board.D5)
fram = adafruit_fram.FRAM_SPI(spi, cs)

## Write a single-byte value to register address '0'

fram[0] = 1

## Read that byte to ensure a proper write.
## Note: 'read()' returns a bytearray

print(fram[0])

## Or write a sequential value, then read the values back.
## Note: 'read()' returns a bytearray. It also allocates
##       a buffer the size of 'length', which may cause
##       problems on memory-constrained platforms.

# values = list(range(100)) # or bytearray or tuple
# fram[0] = values
# print(fram[0:99])

Save this code to your Jetson Nano by copying and pasting it into a text file, downloading it directly from the Jetson Nano, etc.

Then in your command line run:

python3 fram_spi_simpletest.py

The code will write a value of 1 to the first address of the FRAM memory and then read it back:

Feel free to edit the test script and change the value if you want to test it more thoroughly.

Now if you want to read the documentation on the library, what each function does in depth, visit our readthedocs documentation at:

https://circuitpython.readthedocs.io/projects/fram/en/latest/

Using a TFT

Now that we've done a simple test for reading and writing just a little data, let's test out a TFT which uses much more data, but only uses it one direction. In this example, we're going to use a PiTFT.

Parts

The cute PiTFT got even more adorable with this little primary display for Raspberry Pi in HAT form! It features a 2.2" display with 320x240 16-bit color pixels. The HAT uses the...
$24.95
In Stock

Wiring

One of the reasons we wanted to use the PiTFT for this section is because it's so easy since you just press it onto the Jetson Nano's GPIO pins. The 40-pin GPIO header is backwards from a Pi, so the PiTFT just sits off to the side.

Install the CircuitPython RGB Display Library

Just like with the previous example, you'll want to install the RGB Display library. The command to install it is:

pip3 install adafruit-circuitpython-display

If you haven't already updated adafruit_blinka be sure to do that now:

pip3 install --upgrade adafruit_blinka

Install Pillow

We're going to show you an example that uses Pillow, a popular fork of the Python Imaging Library. First we need to install it:

sudo apt install python3-pil

Run that Code!

You can now run one of the many example scripts for displays that we've written for you.

Check out the examples for your library by visiting the repository for the library and looking in the example folder. In this case, it would be https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display/tree/master/examples

We'll just go over one example here as other scripts are covered in other guides. Here's the stats example:

"""
This will show some Linux Statistics on the attached display. Be sure to adjust
to the display you have connected. Be sure to check the learn guides for more
usage information.

This example is for use on (Linux) computers that are using CPython with
Adafruit Blinka to support CircuitPython libraries. CircuitPython does
not support PIL/pillow (python imaging library)!
"""

import time
import subprocess
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
import adafruit_rgb_display.ili9341 as ili9341
import adafruit_rgb_display.st7789 as st7789  # pylint: disable=unused-import
import adafruit_rgb_display.hx8357 as hx8357  # pylint: disable=unused-import
import adafruit_rgb_display.st7735 as st7735  # pylint: disable=unused-import
import adafruit_rgb_display.ssd1351 as ssd1351  # pylint: disable=unused-import
import adafruit_rgb_display.ssd1331 as ssd1331  # pylint: disable=unused-import

# Configuration for CS and DC pins (these are PiTFT defaults):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = digitalio.DigitalInOut(board.D24)

# Config for display baudrate (default max is 24mhz):
BAUDRATE = 24000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# pylint: disable=line-too-long
# Create the display:
# disp = st7789.ST7789(spi, rotation=90,                            # 2.0" ST7789
# disp = st7789.ST7789(spi, height=240, y_offset=80, rotation=180,  # 1.3", 1.54" ST7789
# disp = st7789.ST7789(spi, rotation=90, width=135, height=240, x_offset=53, y_offset=40, # 1.14" ST7789
# disp = hx8357.HX8357(spi, rotation=180,                           # 3.5" HX8357
# disp = st7735.ST7735R(spi, rotation=90,                           # 1.8" ST7735R
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3,   # 1.44" ST7735R
# disp = st7735.ST7735R(spi, rotation=90, bgr=True,                 # 0.96" MiniTFT ST7735R
# disp = ssd1351.SSD1351(spi, rotation=180,                         # 1.5" SSD1351
# disp = ssd1351.SSD1351(spi, height=96, y_offset=32, rotation=180, # 1.27" SSD1351
# disp = ssd1331.SSD1331(spi, rotation=180,                         # 0.96" SSD1331
disp = ili9341.ILI9341(
    spi,
    rotation=90,  # 2.2", 2.4", 2.8", 3.2" ILI9341
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
)
# pylint: enable=line-too-long

# Create blank image for drawing.
# Make sure to create image with mode 'RGB' for full color.
if disp.rotation % 180 == 90:
    height = disp.width  # we swap height/width to rotate it to landscape!
    width = disp.height
else:
    width = disp.width  # we swap height/width to rotate it to landscape!
    height = disp.height

image = Image.new("RGB", (width, height))

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image)

# First define some constants to allow easy positioning of text.
padding = -2
x = 0

# Load a TTF font.  Make sure the .ttf font file is in the
# same directory as the python script!
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24)

while True:
    # Draw a black filled box to clear the image.
    draw.rectangle((0, 0, width, height), outline=0, fill=0)

    # Shell scripts for system monitoring from here:
    # https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load
    cmd = "hostname -I | cut -d' ' -f1"
    IP = "IP: " + subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
    CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB  %.2f%%\", $3,$2,$3*100/$2 }'"
    MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = 'df -h | awk \'$NF=="/"{printf "Disk: %d/%d GB  %s", $3,$2,$5}\''
    Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "cat /sys/class/thermal/thermal_zone0/temp |  awk '{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}'"  # pylint: disable=line-too-long
    Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")

    # Write four lines of text.
    y = padding
    draw.text((x, y), IP, font=font, fill="#FFFFFF")
    y += font.getsize(IP)[1]
    draw.text((x, y), CPU, font=font, fill="#FFFF00")
    y += font.getsize(CPU)[1]
    draw.text((x, y), MemUsage, font=font, fill="#00FF00")
    y += font.getsize(MemUsage)[1]
    draw.text((x, y), Disk, font=font, fill="#0000FF")
    y += font.getsize(Disk)[1]
    draw.text((x, y), Temp, font=font, fill="#FF00FF")

    # Display image.
    disp.image(image)
    time.sleep(0.1)

Save this code to your Jetson Nano by copying and pasting it into a text file, downloading it directly from the Jetson Nano, etc.

Then in your command line run:

python3 rgb_display_pillow_stats.py

You should see some stats showing up:

That's it! Now if you want to read the documentation on the library, what each function does in depth, visit our readthedocs documentation at:

https://circuitpython.readthedocs.io/projects/rgb_display/en/latest/

This guide was first published on Sep 10, 2019. It was last updated on Sep 10, 2019.

This page (SPI Sensors & Devices) was last updated on Nov 06, 2020.