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.
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.
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.

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

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

- 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:
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT ## 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:100] = values # print(fram[0:100])
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.

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:
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT """ 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 from adafruit_rgb_display import ili9341 from adafruit_rgb_display import st7789 # pylint: disable=unused-import from adafruit_rgb_display import hx8357 # pylint: disable=unused-import from adafruit_rgb_display import st7735 # pylint: disable=unused-import from adafruit_rgb_display import ssd1351 # pylint: disable=unused-import from adafruit_rgb_display import 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 = st7789.ST7789(spi, rotation=90, width=172, height=320, x_offset=34, # 1.47" ST7789 # disp = st7789.ST7789(spi, rotation=270, width=170, height=320, x_offset=35, # 1.9" 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, width=80, # 0.96" MiniTFT Rev A ST7735R # disp = st7735.ST7735R(spi, rotation=90, invert=True, width=80, # 0.96" MiniTFT Rev B ST7735R # x_offset=26, y_offset=1, # 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/
Page last edited January 22, 2025
Text editor powered by tinymce.