Using SPI on the Pyboard with 'machine.SPI'
Contents
What is SPI?
SPI (Serial Peripheral Interface) is a high-speed, synchronous serial communication protocol used primarily for short-distance data exchange between microcontrollers and peripheral devices like sensors, SD cards, memory chips and LCD displays.
SPI typically requires four wires to operate:
- SCK (Serial Clock): The clock signal generated by the master to synchronize data transfer.
- MOSI (Master Out Slave In): The data line for the master to send information to the slave.
- MISO (Master In Slave Out): Data sent from the slave to the master.
- SS/CS (Slave Select/Chip Select): The master selects a specific slave device using this active-low signal.
The SCK, MOSI and MISO lines can be shared by many slaves. However each slave must have its own separate SS/CS line.
The SS/CS line is used to select a specific slave device on the SPI bus for communication with the master. The slave's CS line is held High during inactivity. The master selects the slave by pulling the line Low. Once the SPI communication between master and slave has completed the CS line is pulled High again.
SPI can send and receive data simultaneously, making it faster than half-duplex protocols like I2C. It is synchronous serial communication because there is only a single clock source, maintained by the master, on the SPI bus.
There is no single, official written standard for the Serial Peripheral Interface (SPI) protocol. Unlike other protocols like I2C or USB, which have strict specifications governed by international committees, SPI is a "de facto" standard.
The unfortunate fact there's no rigid SPI international standard can often be a problem. When interfacing a slave to a microcontroller through SPI, the relevant datasheet must be carefully read.
SPI Hardware on the Pyboard
The Pyboard V1 has two separate hardware SPI units, configured as shown in the following table:
| SPI Name | SCK Pin | MISO Pin | MOSI Pin |
|---|---|---|---|
| SPI('X') | X6 | X7 | X8 |
| SPI('Y') | Y6 | Y7 | Y8 |
NOTE: They can also be named SPI(1) and SPI(2) but the official MicroPython documentation[1] prefers the names shown in the table above.
MicroPython machine.SPI Class
This section discusses the machine.SPI class and its methods. This class is generic and supports most microcontrollers with a MicroPython port.
An alternate is the pyb.SPI class which is specific for the Pyboard. For further details see this article, Using SPI on the Pyboard with 'pyb.SPI'. Both classes are easy to use and becomes simply a personal choice.
Initialising the SPI Object
The first step is to declare and initialise an SPI object.
Syntax:
<spi_variable> = machine.SPI(id,
baudrate=1000000,*,
polarity=0,
phase=0)
Creates an SPI object.
Where:
id : One of 'X' or 'Y' based on the above table.
baudrate : The SCL clock rate. It will often not
be exactly as requested.
polarity : can be 0 or 1, and is the level the
idle clock line sits at.
phase : can be 0 or 1 to sample data on the first
or second clock edge respectively.
Note: The polarity and phase are obtained from
the product datasheet.
Syntax:
SPI.deinit()
Turns off the SPI bus.
Examples:
from machine import SPI
spi_1 = SPI('X')
spi_2 = SPI('Y', baudrate=328125,
polarity=1, phase=1)
# Get a description of the SPI object configurations.
print(spi_1)
print()
print(spi_2)
spi_2.deinit()
Output:
SPI(1, baudrate=328125, polarity=0, phase=0, bits=8)
SPI(2, baudrate=328125, polarity=1, phase=1, bits=8)
SPI Methods
The SPI object provides basic methods to read and write to the bus.
Syntax:
SPI.read(nbytes)
Where:
nbytes : Number of bytes to receive.
Returns: Returns a bytes object with the data that was read.
Syntax:
SPI.write(buf)
Where:
buf : Write the bytes contained in buf. Returns None.
Note: The sent or received data must be one or more bytes; each byte having a value between 0 and 255 inclusive. This is usually a string or bytes object or bytearray object.
Reading the BMP280 Sensor's Chip ID
The BMP280 is a barometric pressure sensor that remains popular though it is no longer being actively manufactured by Bosch Sensortec.
As with most Bosch Sensortec sensors the BMP280 has a chip ID burnt to non-volatile memory at point of manufacture. The example MicroPython program will read and report this ID value. The algorithm is simple. Write the ID register address of 0xD0 to the BMP280 then read back the ID as a single byte.
The Pyboard's SPI('Y') will be used. The hookup to the BMP280 is relatively simple:
| Pyboard | BMP280 |
|---|---|
| 3.3V | VCC |
| GND | GND |
| Pin Y5 | CSB (CS) |
| Pin Y6 | SCL (SCK) |
| Pin Y7 | SDO (MISO) |
| Pin Y8 | SDA (MOSI) |
Copy the following code to a suitable IDE such as Thonny and flash to the Pyboard.
Code:
# A BMP280 barometric pressure sensor is attached to the
# 'Y' SPI bus of the Pyboard.
# Then the BMP280 is queried for its chip ID. This should
# return 0x58.
from machine import Pin, SPI
CMD_ID = bytes([0xD0])
cs = Pin('Y5', Pin.OPEN_DRAIN, value=1)
spi = SPI('Y', polarity=0, phase=0)
# Get chip ID
cs.off()
spi.write(CMD_ID)
buf = spi.read(1)
cs.on()
# Report chip ID
chipID = hex(buf[0])
print('BMP280 Chip ID:', chipID)
Output:
BMP280 Chip ID: 0x58
A chip ID of 0x58 was returned. According to the BMP280 datasheet this is the correct value.
The BMP280 datasheet advises that the CS line is pulled high with a pullup resistor. Therefore in the program the CS pin is declared output with open drain control.
The BMP280 datasheet also specifies that SPI mode can be '00' (polarity=phase=0) or '11' (polarity=phase=1). This example program uses mode '00'.