SPI

SPI is a four wire serial protocol for communication between devices.

SPI.swift:132SPI.md
final class SPI

Initialize an SPI instance

Let’s initialize an SPI interface:

// Initialize the pin SPI0 for communication with other parameters set to default.
let spi = SPI(Id.SPI0)

An SPI interface consists of a clock line, two lines for sending and reading data respectively and a CS line. In this case, the pins for SPI are SCK0, SDO0, SDI0. The cs pin is not defined, thus you need to configure it manually: set it to low/high to activate/release it.

The devices on an SPI bus are distinguished by a CS line. Before communicating with a specified device, its CS line needs to be activated. Other devices connected on the same bus will ignore all data.

You can also specify the cs pin when initializing an SPI device, so the spi will manage automatically cs when you read or write data.

// Initialize the cs pin for the device.
let cs = DigitalOut(Id.D0)
// Specify the cs pin so it will be set automatically during communication.
let spi = SPI(Id.SPI0, csPin: cs)

What’s more, SPI communication has four modes determined by the CPOL and CPHA. And the bitOrder specifies how data is sent on the bus. They depends on the device your board is communicating with.

Read or write data

SPI uses two data lines: one for sending data and the other for receiving data. After initialization, you can use the write and read method to communicate with the desired devices:

// Read a UInt8 from the device and store it in a variable.
let byte: UInt8 = 0
spi.read(into: &byte)

// Write a UInt8 value to the device.
let value: UInt8 = 0x01
spi.write(value)

Read or write data and handle error

Indeed, communication can fail for various reasons, potentially leading to incorrect data. So methods related to reading or writing data will return results in a Result type. This allows you to handle errors and find alternative solutions.

// Read a byte from the provided address and get the results.
let result = spi.read(into: &byte)

if case .failure(let err) = result {
    // If the communication fails, execute the specified task.

}

If the data is successfully read, it is stored in byte. If the communication fails, the byte may store a wrong value or remain unchanged, anyway, it is useless. You can check the result to know what happens, and furthermore, handle the error.

Example 1: Write data via SPI bus

// Import the SwiftIO to use the related board functions.
import SwiftIO
// Import the MadBoard to decide which pin is used for the specific function.
import MadBoard

// The cs pin is high so that the sensor would be in an inactive state.
let cs = DigitalOut(Id.D0, value: true)
// Initialize the pin SPI0. The cs pin will be controlled by spi automatically.
let spi = SPI(Id.SPI0, csPin: cs)

// Write data to the device.
let result = spi.write([0x00, 0x01])
if case .failure(let err) = result {
    // If the communication fails, execute the specified task.

}

Example 2: Read accelerations using LIS3DH library

import SwiftIO
import MadBoard
import LIS3DH

// The cs pin is high so that the sensor would be in an inactive state.
let cs = DigitalOut(Id.D0, value: true)
// The cs pin will be controlled by SPI. The CPOL and CPHA should be true for the sensor.
let spi = SPI(Id.SPI0, csPin: cs, CPOL: true, CPHA: true)
// Initialize the sensor using the spi instance.
let sensor = LIS3DH(spi)

// Read values from the sensor and print them.
while true {
    print(sensor.readXYZ())
    sleep(ms: 1000)
}

In this example, you just need to initialize the SPI pin and then talk to the sensor without caring about the details of communication. The library LIS3DH has configured the sensor by sending and receiving data via SPI bus. Therefore, you can directly read temperature using the predefined APIs.

Initializer

Reading data

Writing data

Writing and reading data

Configuring SPI