SPI and I2C are the common serial communication protocols used by sensors to communicate with microcontrollers and microcomputers. Both protocols are master-slave protocols. Sensors are often part of a slave device within an embedded ecosystem. Most of the time, a sensor only requires sending data to the microcontroller/microcomputer. This remains true even if a given sensor is interrupt enabled.
Many embedded devices include many sensors that require full-duplex communication with the controller. These are usually sophisticated sensors and embedded modules with their own set of explicit commands and a wider configuration scope or those capable of transmitting a wide range of varied data. These sensors and embedded modules rely on the full-duplex SPI protocol — which allows the controller to exchange data with multiple devices on a common bus — for data communication with the controller. SPI is also a synchronous communication protocol that enables a high-speed chip-to-chip link. The data speed on the SPI bus can reach a maximum of 10 Mbps.
SPI vs. I2C
SPI stands for Serial Peripheral Interface. It is a full-duplex serial communication protocol in contrast to I2C, a half-duplex serial protocol. By full-duplex it means that the SPI bus simultaneously allows the transmission and reception of data between the controller and the slave. There are separate data lines on the SPI bus for transferring data from controller to controller. Unlike this, I2C has a single data line, so at a time either controller can send or receive data, but not both. While the I2C bus has only two lines – a data line and a clock line, an SPI bus has three or four lines. There are two data lines, MISO (for slave-to-controller data transfer) and MOSI (for controller-to-slave data transfer), an SCK clock line, and a chip select or CS slave select line /SS. In I2C protocol, multiple slaves can be connected to two lines where the slaves are recognized and selected for data communication using an addressing mechanism. In SPI, if multiple slaves are connected, the Chip Select line is required. Each slave has its own chip selection line. This increases the number of data pins involved in the controller as more slave devices are added to the SPI bus. While the I2C protocol guarantees successful data transmission using acknowledgments, the SPI protocol does not have such a mechanism. Data transmission is not guaranteed in the SPI protocol.
Although the requirement for additional chip selection lines and lack of data verification are the main disadvantages of the SPI protocol. It has a lot to offer in the embedded setting. It enables full-duplex data communication at high speeds of up to 10 Mbps. The data transmission speed on the I2C bus can reach a maximum of 3.4 Mbps. The SPI bus requires less power for data communication and is highly efficient over short distances. If an embedded block needs to be integrated into the controller, SPI will be slightly above I2C.
MicroPython Machine Module
The MicroPython machine module is written to handle hardware functions of supported ports. The module includes classes to control digital input/output, control output signals from external devices, pulse width modulation, analog to digital conversion, control peripherals ADC, UART, SPI, I2C, I2S, timer, RTC, Watchdog timer and SD card management. It has SPI class for I2C hardware management and SoftSPI for SPI software implementation on supported ports.
SPI Class
The SPI class of the machine module is responsible for implementing the SPI protocol on ports supported by MicroPython. The SPI bus has three lines – MOSI, MISO and SCK. The synchronous protocol requires an additional CS line per slave, as long as multiple slaves are connected to the SPI bus. The CS signal is managed with the help of the PIN class of the machine module. Hardware and software SPI are available in MicroPython. Hardware SPI uses the underlying SPI peripheral of the supported ports. The SPI pins on most ports are fixed. Although not as efficient, SPI software can be applied to all output-capable pins of a port. The SPI class is included in a MicroPython script using the following statement.
Import Machine SPI, Pin
After importing the SPI and PIN class, an object of the SPI class must be instantiated using the constructor method. The constructor method of the SPI class has the following prototype.
class machine.SPI(id,…)
A call to the constructor method essentially requires one argument, the id. The id is the identification of the hardware SPI block. It can be a number or string depending on the specific port. Typically, SPI blocks are identified by numbers such as 0, 1, 2, etc. The other arguments that can be passed in a constructor method call are configuration parameters. The configuration parameters defined in the constructor method include baud rate, polarity, phase, bits, first bit, sck, mosi, miso, and pins. The baud rate is the SCK clock rate. The maximum baud rate that can be set depends on the specific port. Polarity defines the level of the idle clock line. Can be set to 0 or 1. Phase determines whether data will be sampled on the first or second clock edge. If phase is set to 0, data will be sampled at the first clock edge; if set to 1, data will be sampled on the second clock edge. Firstbit determines whether the first bit in the data is LSB or MSB. Can be defined as SPI.LSB or SPI.MSB. Some ports allow alternate pins for MOSI, MISO, and SCK. The mosi, miso, and sck arguments define the MOSI, MISO, and SCK pins, respectively, on such ports. If not specified, the default MOSI, MISO and SCK pins will be defined. The pins argument is only allowed for the WiPy port. Allows you to specify a tuple to define the MOSI, MISO and SCK pins. Some of the valid examples of creating SPI objects are as follows.
spi = SPI(0)
spi = SPI(0, baud rate=400000)
hspi = SPI(1, 10000000)
hspi = SPI(1, 10000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
vspi = SPI(2, baud rate=80000000, polarity=0, phase=0, bits=8, first bit=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
After creating an SPI object, it can be initialized using the SPI.init method. It takes the same arguments as the constructor method, except the id. The SPI.init method has the following prototype.
SPI.init(baud rate=1000000, *, polarity=0, phase=0, bits=8, first bit=SPI.MSB, sck=None, mosi=None, miso=None, pins=(SCK, MOSI, MISO))
The other methods available in the SPI class are as follows:
SPI.deinit : This method disables the SPI bus.
SPI.read(nbytes, write=0x00) : This method reads the number of bytes nbytes by writing a single byte specified by the write argument to the MOSI line. It returns a byte object containing the bytes read from the MISO line.
SPI.readinto(buf, write=0x00) : This method reads bytes from the MISO line into a buffer object while writing a single byte specified by the write argument to the MOSI line. The read bytes are stored in the buffer object. The number of bytes read depends on the size of the buffer object. The method returns nothing.
SPI.write(buf) : This method writes the bytes stored in a buffy buffer object to the MOSI line. It returns nothing.
SPI.write_readinto(write_buf, read_buf) : This method writes bytes from a write_buf buffer object to the MOSI line and reads bytes into a read_buf buffer object. Both buffer objects can be the same, but they must be the same length. The method returns nothing. The number of bytes in write_buf are written to the bus, and the number of bytes equal to the length of read_buf are read and stored in the read_buf object.
SoftSPI Class
SPI software is implemented by bit-banging. The SoftSPI class is imported into a MicroPython script using the following statement.
Machine Import Pin, SoftSPI
SoftSPI has all the same methods as the hardware SPI class. The SoftSPI constructor method has the following prototype.
class machine.SoftSPI(baudrate=500000, *, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None)
The following is a valid example of software SPI object instantiation.
spi = SoftSPI(baud rate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4))
Hardware SPI in ESP8266
There are two SPI blocks in ESP8266 – SPI0 and SPI1. SPI0 remains reserved for Flash ROM and cannot be used in the user program. SPI1 has MISO, MOSI and SCK pins fixed to GPIO12, GPIO13 and GPIO14 respectively.

Standard SPI pins on ESP8266
The maximum clock frequency can be set to 80 MHz. Following is a valid example of creating a hardware SPI object in ESP8266.
Pin machine import, SPI
hspi = SPI(1, baud rate=80000000, polarity=0, phase=0)
SPI Software on ESP8266
SPI Software is one of two SPI drivers available on the ESP8266. SPI software can be implemented on all pins of the ESP8266. Following is a valid example of using MicroPython SPI software on ESP8266.
Machine Import Pin, SoftSPI
spi = SoftSPI(baud rate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4))
spi.init(baud rate=800000)
spi.read(3) # read 3 bytes in MISO
spi.read(3, 0xff) # reads 3 bytes while generating 0xff in MOSI
buf = bytearray(10) # buffer object
spi.readinto(buf) # read 10 bytes into the given buffer
spi.readinto(buf, 0xff) # reads 10 bytes into the provided buffer and outputs 0xff in MOSI
spi.write(b'12345′) # writes the specified buffer object to MOSI
buf = bytearray(5) # create a buffer
spi.write_readinto(b'12345′, buf) # writes to MOSI and reads from MISO into the buffer
spi.write_readinto(buf, buf) # write buf to MOSI and read MISO back to buf
Hardware SPI on ESP32
There are two hardware SPI blocks in the ESP32 – HSPI (SPI1) and VSPI (SPI2). The default pins for MOSI, MISO, and SCK of HSPI are GPIO13, GPIO12, and GPIO14, respectively. The default pins for VSPI's MOSI, MISO, and SCK are GPIO23, GPIO19, and GPIO18, respectively. The maximum SPI clock frequency can be 80 MHz on the standard pins. ESP32 allows GPIO multiplexing of hardware SPI blocks, as long as the pins are enabled for input or output according to the SPI line requirements. However, the maximum SPI clock frequency on multiplexed pins can only be 40 MHz.

Standard SPI pins on ESP32
Following is a valid example of creating a hardware SPI object in ESP32.
Pin machine import, SPI
hspi = SPI(1, 80000000)
hspi = SPI(1, 40000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
vspi = SPI(2, baud rate=80000000, polarity=0, phase=0, bits=8, first bit=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
Software SPI on ESP32
SPI software is available on all pins of the ESP32. The maximum clock frequency of SPI software can be 40 MHz. Following is a valid example of software SPI on ESP32.
Machine Import Pin, SoftSPI
spi = SoftSPI(baud rate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4))
spi.init(baud rate=800000)
spi.read(3) # read 3 bytes in MISO
spi.read(3, 0xff) # reads 3 bytes while generating 0xff in MOSI
buf = bytearray(10) # buffer object
spi.readinto(buf) # read 10 bytes into the given buffer
spi.readinto(buf, 0xff) # reads 10 bytes into the provided buffer and outputs 0xff in MOSI
spi.write(b'12345′) # writes the specified buffer object to MOSI
buf = bytearray(5) # create a buffer
spi.write_readinto(b'12345′, buf) # writes to MOSI and reads from MISO into the buffer
spi.write_readinto(buf, buf) # write buf to MOSI and read MISO back to buf
Some examples of SPI
Some good examples of using the MicroPython SPI class can be found in the following projects.
- Project – MicroPython SSD1351 by author rdagger
- Project – MicroPython nanoGUI by author peterhinch
- Project – uPySensors by author lemariva
- Project – uble by author dmazzella
Check out the embedded projects above and explore their source code to learn how the MicroPython machine module SPI class is used.