SPI Communication

Learn how to communicate with SPI devices using the PoKeys SPI master interface

What You'll Learn

  • • Configure SPI master interface with custom settings
  • • Communicate with SPI devices using chip select pins
  • • Read data from SPI sensors and peripherals
  • • Handle SPI timing and protocol requirements

Hardware Setup

Required Connections

SPI Bus Pins (Fixed)

  • • Pin 23: MOSI (Master Out, Slave In)
  • • Pin 24: MISO (Master In, Slave Out)
  • • Pin 25: CLK (Clock)

Chip Select Pins

  • • Any available pin (1-55)
  • • Connect to CS/SS pin of SPI device
  • • Multiple devices need separate CS pins

Basic SPI Communication

use pokeys_lib::*;
use std::thread;
use std::time::Duration;

fn main() -> Result<()> {
    // Connect to device
    let device_count = enumerate_usb_devices()?;
    if device_count == 0 {
        println!("No PoKeys devices found");
        return Ok(());
    }

    let mut device = connect_to_device(0)?;
    println!("Connected to PoKeys device");

    // Configure SPI with 1MHz clock
    let spi_config = SpiConfiguration {
        clock_frequency: 1_000_000, // 1 MHz
        mode: SpiMode::Mode0,       // CPOL=0, CPHA=0
        bit_order: SpiBitOrder::MsbFirst,
    };

    device.configure_spi(spi_config)?;
    println!("SPI configured: 1MHz, Mode 0, MSB first");

    // Configure chip select pin
    let cs_pin = 10;
    device.set_pin_function(cs_pin, PinFunction::DigitalOutput)?;
    device.set_digital_output(cs_pin, true)?; // CS idle high

    // Example: Read device ID from SPI sensor
    let device_id = read_spi_device_id(&mut device, cs_pin)?;
    println!("SPI Device ID: 0x{:02X}", device_id);

    Ok(())
}

fn read_spi_device_id(device: &mut PoKeysDevice, cs_pin: u8) -> Result<u8> {
    // Pull CS low to start transaction
    device.set_digital_output(cs_pin, false)?;
    thread::sleep(Duration::from_micros(10));

    // Send read ID command (example: 0x9F for many flash chips)
    let tx_data = vec![0x9F, 0x00]; // Command + dummy byte
    let rx_data = device.spi_transfer(&tx_data)?;

    // Pull CS high to end transaction
    device.set_digital_output(cs_pin, true)?;

    // Return device ID (second byte in response)
    Ok(rx_data[1])
}

Advanced: Multiple SPI Devices

use pokeys_lib::*;
use std::collections::HashMap;

struct SpiDevice {
    cs_pin: u8,
    name: String,
}

fn main() -> Result<()> {
    let mut device = connect_to_device(0)?;

    // Configure SPI bus
    let spi_config = SpiConfiguration {
        clock_frequency: 2_000_000, // 2 MHz
        mode: SpiMode::Mode0,
        bit_order: SpiBitOrder::MsbFirst,
    };
    device.configure_spi(spi_config)?;

    // Define multiple SPI devices
    let mut spi_devices = HashMap::new();
    spi_devices.insert("flash", SpiDevice { cs_pin: 10, name: "Flash Memory".to_string() });
    spi_devices.insert("adc", SpiDevice { cs_pin: 11, name: "ADC Converter".to_string() });
    spi_devices.insert("dac", SpiDevice { cs_pin: 12, name: "DAC Output".to_string() });

    // Configure all CS pins
    for spi_dev in spi_devices.values() {
        device.set_pin_function(spi_dev.cs_pin, PinFunction::DigitalOutput)?;
        device.set_digital_output(spi_dev.cs_pin, true)?; // CS idle high
        println!("Configured CS pin {} for {}", spi_dev.cs_pin, spi_dev.name);
    }

    // Communicate with each device
    for (key, spi_dev) in &spi_devices {
        match key {
            "flash" => {
                let id = read_flash_id(&mut device, spi_dev.cs_pin)?;
                println!("Flash ID: 0x{:04X}", id);
            },
            "adc" => {
                let value = read_adc_channel(&mut device, spi_dev.cs_pin, 0)?;
                println!("ADC Channel 0: {}", value);
            },
            "dac" => {
                write_dac_value(&mut device, spi_dev.cs_pin, 2048)?;
                println!("DAC set to mid-scale");
            },
            _ => {}
        }
    }

    Ok(())
}

fn read_flash_id(device: &mut PoKeysDevice, cs_pin: u8) -> Result<u16> {
    device.set_digital_output(cs_pin, false)?;
    let tx_data = vec![0x90, 0x00, 0x00, 0x00, 0x00]; // Read ID command
    let rx_data = device.spi_transfer(&tx_data)?;
    device.set_digital_output(cs_pin, true)?;

    Ok(((rx_data[3] as u16) << 8) | (rx_data[4] as u16))
}

fn read_adc_channel(device: &mut PoKeysDevice, cs_pin: u8, channel: u8) -> Result<u16> {
    device.set_digital_output(cs_pin, false)?;
    let tx_data = vec![0x01, (channel << 4) | 0x80, 0x00]; // Start bit + channel
    let rx_data = device.spi_transfer(&tx_data)?;
    device.set_digital_output(cs_pin, true)?;

    Ok(((rx_data[1] as u16 & 0x0F) << 8) | (rx_data[2] as u16))
}

fn write_dac_value(device: &mut PoKeysDevice, cs_pin: u8, value: u16) -> Result<()> {
    device.set_digital_output(cs_pin, false)?;
    let tx_data = vec![
        0x30 | ((value >> 8) & 0x0F) as u8, // Command + upper 4 bits
        (value & 0xFF) as u8,                    // Lower 8 bits
    ];
    device.spi_transfer(&tx_data)?;
    device.set_digital_output(cs_pin, true)?;

    Ok(())
}

SPI Modes and Configuration

SPI Clock Modes

Mode 0 (CPOL=0, CPHA=0)

Clock idle low, data sampled on rising edge

Mode 1 (CPOL=0, CPHA=1)

Clock idle low, data sampled on falling edge

Mode 2 (CPOL=1, CPHA=0)

Clock idle high, data sampled on falling edge

Mode 3 (CPOL=1, CPHA=1)

Clock idle high, data sampled on rising edge

// Configure different SPI modes
let configs = [
    SpiConfiguration { mode: SpiMode::Mode0, clock_frequency: 1_000_000, bit_order: SpiBitOrder::MsbFirst },
    SpiConfiguration { mode: SpiMode::Mode1, clock_frequency: 500_000, bit_order: SpiBitOrder::LsbFirst },
    SpiConfiguration { mode: SpiMode::Mode2, clock_frequency: 2_000_000, bit_order: SpiBitOrder::MsbFirst },
    SpiConfiguration { mode: SpiMode::Mode3, clock_frequency: 100_000, bit_order: SpiBitOrder::MsbFirst },
];

// Apply configuration based on device requirements
device.configure_spi(configs[0])?; // Most common: Mode 0

Troubleshooting

No Response from SPI Device

  • • Check wiring: MOSI, MISO, CLK, and CS connections
  • • Verify SPI mode matches device requirements
  • • Ensure clock frequency is within device limits
  • • Check CS pin polarity (active low vs active high)

Data Corruption

  • • Reduce clock frequency for long wires
  • • Check bit order (MSB vs LSB first)
  • • Add delays between CS transitions
  • • Verify power supply stability

Pin Conflicts

  • • Pins 23, 24, 25 are reserved for SPI bus
  • • Choose CS pins that support digital output
  • • Avoid pins used for other functions
  • • Check device model pin capabilities