SPI
SPI is a four wire serial protocol for communication between devices.
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
init(Id, speed: Int, csPin: DigitalOut?, CPOL: Bool, CPHA: Bool, bitOrder: BitOrder
) Initializes a specified interface for SPI communication as a master device.
Reading data
func read(into: inout UInt8
) -> Result<(), Errno> Reads a UInt8 from the slave device.
func read(into: inout [UInt8], count: Int?
) -> Result<(), Errno> Reads an array of data from the slave device.
func read<Element>(into: inout [Element], count: Int?
) -> Result<(), Errno> Read an array of binary integer from the slave device.
func read(into: UnsafeMutableRawBufferPointer, count: Int?
) -> Result<(), Errno> Reads the data from the slave device into the specified buffer pointer.
Writing data
func write(UInt8
) -> Result<(), Errno> Writes a UInt8 to the slave device.
func write([UInt8], count: Int?
) -> Result<(), Errno> Writes an array of UInt8 to the slave device.
func write<Element>([Element], count: Int?
) -> Result<(), Errno> Write an array of binary integer to the slave device.
func write(UnsafeRawBufferPointer, count: Int?
) -> Result<(), Errno> Writes a buffer pointer of the data in the underlying storage to the slave device.
Writing and reading data
func transceive(UInt8, into: inout [UInt8], readCount: Int?
) -> Result<(), Errno> Writes a UInt8 to the slave device and then read bytes from it.
func transceive([UInt8], writeCount: Int?, into: inout [UInt8], readCount: Int?
) -> Result<(), Errno> Writes an array of UInt8 to the slave device and then read bytes from it.
Configuring SPI
func getSpeed(
) -> Int Gets the current clock speed of SPI communication.
func getMode(
) -> (CPOL: Bool, CPHA: Bool, bitOrder: BitOrder) Gets the SPI mode.
enum BitOrder
The bit order that the data is sent on SPI bus: MSB or LSB.