Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

I2C and SPI

I2C

micro/i2c provides I2C bus master support for sensors, displays, and other peripherals.

Initialization

use micro/i2c
use micro/gpio

let mut bus = i2c.bus(0)
    .sda(gpio.pin(4))
    .scl(gpio.pin(5))
    .freq(400_000)     // 400 kHz fast mode
    .init()?

Bus 0 and Bus 1 correspond to the hardware I2C peripherals. init() is the fallible step.

Reading and Writing

// Write to device address 0x48
bus.write(0x48, b"\x00")?         // write register address

// Read 2 bytes from device
let mut buf: [u8; 2] = [0; 2]
bus.read(0x48, buf)?               // read into buf

// Write then read (common for register reads)
bus.write_read(0x68, b"\x3B", buf)?

I2C transactions are fallible; the device may NACK or the bus may not acknowledge.

Common Pattern: Sensor Register Read

use micro/i2c
use micro/gpio
use std/time

fn read_temperature(mut bus: i2c.Bus) -> Result<i16, Error> {
    // Write register address
    bus.write(0x48, b"\x00")?

    // Read 2 bytes
    let mut buf: [u8; 2] = [0; 2]
    bus.read(0x48, buf)?

    // Combine bytes (big-endian)
    let raw = i16.from(u16.from(buf[0]) << 8 | u16.from(buf[1]))
    return Ok(raw >> 4)    // TMP102 12-bit temperature
}

SPI

micro/spi provides SPI bus master support for displays, flash chips, SD cards, and RF modules.

Initialization

use micro/spi
use micro/gpio

let mut bus = spi.bus(0)
    .mosi(gpio.pin(19))
    .miso(gpio.pin(16))
    .sck(gpio.pin(18))
    .freq(10_000_000)    // 10 MHz
    .mode(spi.Mode.Mode0)
    .init()?

// Chip select is managed manually or through a device handle
let mut cs = gpio.pin(17).into_output()
cs.high()    // deselect by default

Reading and Writing

// Transfer: write and read simultaneously
let mut rx: [u8; 4] = [0; 4]
let tx: [u8; 4] = [0x03, 0x00, 0x00, 0x00]

cs.low()                          // select device
bus.transfer(tx, rx)?             // send tx, receive into rx
cs.high()                         // deselect device

// Write only
cs.low()
bus.write(b"\x02\x00\x00\x00")?
cs.high()

// Read only (sends zeros)
cs.low()
bus.read(buf)?
cs.high()

Do and Don’t

// Do: handle I2C/SPI errors; bus transactions can fail
bus.write(addr, data)?

// Do: use write_read for register reads in one operation
bus.write_read(addr, &[reg], result_buf)?

// Do: drive CS manually and use defer for safe deselect
cs.low()
defer cs.high()
bus.transfer(tx, rx)?

// Avoid: leaving CS asserted if an error occurs
// The defer pattern above ensures cs.high() even on error paths

// Avoid: using I2C/SPI in ISRs; use DMA or buffered transfers instead