The golang.org/x/exp/io/spi package provides a comprehensive interface for reading from and writing to SPI (Serial Peripheral Interface) devices. It includes both the main spi package for device communication and the spi/driver package that defines interfaces for SPI driver implementations.
go get golang.org/x/exp/io/spiimport (
"golang.org/x/exp/io/spi"
"golang.org/x/exp/io/spi/driver"
"time"
)package main
import (
"fmt"
"golang.org/x/exp/io/spi"
"golang.org/x/exp/io/spi/driver"
"time"
)
func main() {
// Create a devfs opener for Linux /dev/spidev interface
opener := &spi.Devfs{
Dev: "/dev/spidev0.0",
Mode: spi.Mode0,
MaxSpeed: 1000000,
}
// Open the SPI device
device, err := spi.Open(opener)
if err != nil {
fmt.Println("Error opening device:", err)
return
}
defer device.Close()
// Configure the device
device.SetBitsPerWord(8)
device.SetMaxSpeed(1000000)
device.SetDelay(time.Microsecond)
// Perform a duplex transaction (write and read simultaneously)
write := []byte{0xAA, 0xBB, 0xCC}
read := make([]byte, len(write))
if err := device.Tx(write, read); err != nil {
fmt.Println("Error in transaction:", err)
return
}
fmt.Printf("Sent: %v\n", write)
fmt.Printf("Received: %v\n", read)
}The spi package follows a driver-based architecture with two main layers:
spi package): Provides Device struct and configuration methods for end-user applicationsspi/driver package): Defines interfaces (Conn, Opener) that SPI driver implementations must satisfyThe architecture supports multiple SPI implementations through the driver interface:
Devfs: Linux devfs-based driver for /dev/spidev* devicesdriver.Opener and driver.Conn interfacesCommunication with SPI devices through full-duplex transactions (simultaneous read and write).
package spi
func Open(o driver.Opener) (*Device, error)Opens an SPI device using the provided driver opener and returns a Device instance.
Parameters:
o (driver.Opener): The driver opener that implements the interface for opening SPI connectionsReturns:
(*Device, error): A pointer to the Device or an error if opening failsConfiguration methods to set SPI communication parameters on an open Device.
func (d *Device) SetMode(mode Mode) errorSetMode sets the SPI mode. SPI mode is a combination of polarity and phases. CPOL (clock polarity) is the high order bit, CPHA (clock phase) is the low order bit. Pre-computed mode values are Mode0, Mode1, Mode2 and Mode3. The value can be changed by the SPI device's driver.
func (d *Device) SetMaxSpeed(speed int) errorSetMaxSpeed sets the maximum clock speed in Hz. The value can be overridden by the SPI device's driver.
func (d *Device) SetBitsPerWord(bits int) errorSetBitsPerWord sets how many bits it takes to represent a word, for example, 8 represents 8-bit words. The default is 8 bits per word.
func (d *Device) SetBitOrder(o Order) errorSetBitOrder sets the bit justification used to transfer SPI words. Valid values are MSBFirst and LSBFirst.
func (d *Device) SetDelay(t time.Duration) errorSetDelay sets the amount of pause that will be added after each frame write. Some SPI devices require a minimum amount of wait time after each frame transmission.
func (d *Device) SetCSChange(leaveEnabled bool) errorSetCSChange sets whether to leave the chipselect enabled after a Tx operation. When true, the chipselect remains active after a transaction completes.
Perform duplex SPI transactions (simultaneous read and write operations).
func (d *Device) Tx(w, r []byte) errorTx performs a duplex transmission to write w to the SPI device and read len(r) bytes into r. The user should not mutate w and r until this call returns. Both write and read buffers are processed simultaneously.
Parameters:
w ([]byte): Data to write to the SPI device. If nil, no data is written.r ([]byte): Buffer to read data into. If nil, no data is read. Must be equal length to w.Returns:
error: An error if the transaction failsProperly close and release SPI device resources.
func (d *Device) Close() errorClose closes the SPI device and releases the related resources. Should be called when the device is no longer needed.
type Mode int
const (
Mode0 = Mode(0)
Mode1 = Mode(1)
Mode2 = Mode(2)
Mode3 = Mode(3)
)Mode represents the SPI mode number where clock polarity (CPOL) is the high order bit and clock edge (CPHA) is the low order bit. The four modes are:
type Order int
const (
MSBFirst = Order(0)
LSBFirst = Order(1)
)Order is the bit justification to be used while transferring words to the SPI device. MSB-first encoding is more popular than LSB-first.
type Device struct {
// Has unexported fields.
}Device represents an open SPI device connection. It provides methods to configure the SPI device and perform transactions.
type Devfs struct {
Dev string
Mode Mode
MaxSpeed int64
}Devfs is an SPI driver that works against the Linux devfs. You need to have loaded the "spidev" Linux kernel module to use this driver.
Fields:
Dev (string): The device to be opened. Device name is usually in the /dev/spidev<bus>.<chip> format. Required.Mode (Mode): The SPI mode. SPI mode is a combination of polarity and phases. CPOL is the high order bit, CPHA is the low order. Pre-computed mode values are Mode0, Mode1, Mode2 and Mode3. The value of the mode argument can be overridden by the device's driver. Required.MaxSpeed (int64): The max clock speed in Hz and can be overridden by the device's driver. Required.func (d *Devfs) Open() (driver.Conn, error)Open opens the provided device with the specified options and returns a connection implementing the driver.Conn interface.
The golang.org/x/exp/io/spi/driver package defines the interfaces that SPI driver implementations must satisfy.
const (
Mode = iota
Bits
MaxSpeed
Order
Delay
CSChange
)These constants represent configuration keys used with the Configure method:
Mode: The SPI mode (valid values are 0, 1, 2 and 3)Bits: Bits per word (default is 8-bit per word)MaxSpeed: The max clock speed (in Hz)Order: Bit order to be used in transfers. Zero value represents MSB-first, non-zero values represent LSB-first encodingDelay: The pause time between frames (in microseconds)CSChange: Whether to leave the device's chipselect active after a Txtype Conn interface {
Configure(k, v int) error
Tx(w, r []byte) error
Close() error
}Conn is a connection to an SPI device. It must be implemented by SPI driver implementations.
Methods:
Configure(k, v int) error: Configures the SPI device with configuration keys and values. Available configuration keys are Mode, Bits, MaxSpeed, Order, Delay, and CSChange. SPI devices can override these values. Returns an error if configuration fails.Tx(w, r []byte) error: Performs a SPI transaction: w is written if not nil, the result is put into r if not nil. len(w) must be equal to len(r), otherwise the driver should return an error.Close() error: Frees the underlying resources and closes the connection.type Opener interface {
Open() (Conn, error)
}Opener is an interface to be implemented by the SPI driver to open a connection to an SPI device. Returns a Conn implementation and an error if opening fails.
package main
import (
"fmt"
"golang.org/x/exp/io/spi"
"time"
)
func configureSPIDevice() {
opener := &spi.Devfs{
Dev: "/dev/spidev0.0",
Mode: spi.Mode3,
MaxSpeed: 5000000,
}
device, err := spi.Open(opener)
if err != nil {
fmt.Println("Error:", err)
return
}
defer device.Close()
// Set 16-bit word size (useful for some sensors/devices)
device.SetBitsPerWord(16)
// Set clock speed to 5 MHz
device.SetMaxSpeed(5000000)
// Add 10 microsecond delay between frames
device.SetDelay(10 * time.Microsecond)
// Keep chipselect active between transactions
device.SetCSChange(true)
// Use LSB-first bit order
device.SetBitOrder(spi.LSBFirst)
fmt.Println("Device configured successfully")
}package main
import (
"fmt"
"golang.org/x/exp/io/spi"
)
func writeRegisters(device *spi.Device, registers []byte) error {
// SPI registers are typically read/write in single transaction
response := make([]byte, len(registers))
err := device.Tx(registers, response)
if err != nil {
return fmt.Errorf("register write failed: %w", err)
}
fmt.Printf("Wrote registers: %v\n", registers)
fmt.Printf("Response: %v\n", response)
return nil
}package main
import (
"fmt"
"golang.org/x/exp/io/spi"
)
func readSensorData(device *spi.Device) ([]byte, error) {
// Send dummy bytes to read data (SPI requires simultaneous write/read)
command := []byte{0x00, 0x00, 0x00, 0x00}
response := make([]byte, len(command))
err := device.Tx(command, response)
if err != nil {
return nil, fmt.Errorf("sensor read failed: %w", err)
}
return response, nil
}The spi package returns errors in the following scenarios:
Open() if the device cannot be opened or the opener failsTx() if the SPI transaction failsClose() if resource cleanup failsExample error handling:
if err := device.SetMaxSpeed(speed); err != nil {
fmt.Printf("Failed to set SPI speed: %v\n", err)
}
if err := device.Tx(write, read); err != nil {
fmt.Printf("SPI transaction failed: %v\n", err)
}To create a custom SPI driver, implement the driver.Opener and driver.Conn interfaces:
package customspi
import (
"golang.org/x/exp/io/spi/driver"
)
type CustomOpener struct {
// Custom configuration fields
}
func (o *CustomOpener) Open() (driver.Conn, error) {
// Implementation to open connection
return &CustomConn{}, nil
}
type CustomConn struct {
// Connection state
}
func (c *CustomConn) Configure(k, v int) error {
// Implement configuration handling
return nil
}
func (c *CustomConn) Tx(w, r []byte) error {
// Implement SPI transaction
if len(w) != len(r) {
return fmt.Errorf("write and read buffer lengths must match")
}
// Perform actual SPI transfer
return nil
}
func (c *CustomConn) Close() error {
// Implement resource cleanup
return nil
}