Skip to main content

SPI

The SPI (Serial Peripheral Interface) interface provides high-speed synchronous serial communication. Supports configurable clock speed, voltage levels from 1.8 V to 3.3 V, and SPI modes (0-3) for clock polarity and phase.

Only master mode

Please note that the AT1000 device only supports master mode for SPI communication. This means that it can initiate communication with slave devices, but it cannot act as a slave itself.

Multiplexing

Some interfaces share the same GPIO pins, so only one can be used at a time. Check out the pin alternate functions page for more details.

The PWM pins use the same channels: enabling SPI fails with 409 CONFIGURATION_CONFLICT while PWM is enabled on a pin of the same channel (DA17/DA18 for SPI 0, DA21/DA22 for SPI 1).

Enabling SPI also fails with 409 CONFIGURATION_CONFLICT while its MOSI, MISO or SCK pin is configured as a GPIO. Reset the pin to its default analog input (reset() on the pin) before enabling SPI.

Pin-voltage check

Enabling SPI, or changing vcc while it is enabled, first measures its MOSI, MISO and SCK pins. If any of them reads below −0.5 V or above vcc + 0.5 V, the request fails with 409 UNSAFE_PIN_VOLTAGE and the interface stays disabled. The error message names the pin and the voltage measured. Reconfiguring with an unchanged vcc does not repeat the check.

Quick Start​

The example code below shows how to enable the SPI bus, set the baud rate, and transmit/receive data.

Note about CS lines

CS lines are handled as regular I/Os. This means that you can use any free GPIO pin as a CS line. However, you need to manually control the CS line before and after the SPI transaction, as shown in the examples below.

import { AT1000 } from '@ikalogic/at1000';
let devices = await AT1000.findDevices(); // Find devices (returns host strings)
let tester = await AT1000.open(devices[0]); // Open the first detected device
await tester.reset(); // Reset the device to a known state
let spi = tester.com.spi(0); // Select the first SPI bus
var cs_pin = tester.gpio.digital(5); // GPIO 5 on master device
await cs_pin.configure_output({
voh: 3.3,
vol: 0,
vih: 1.2,
vil: 0.8,
value: true
}); // VIH = 3.3V, VIL = 0V, set to high level

// Enable SPI bus with a target VCC of 3.3V
// Effectively connecting the SPI pins the dedicated IO pins
await spi.configure({
vcc: 3.3,
baud_rate: 500000, // Initial baud rate of 500kHz
enabled: true
});

// Do an SPI transaction
await cs_pin.write(false); // Set CS line to low
let rx_data = await spi.trx([0xDE, 0xAD, 0xBE, 0xEF]); // Send and receive 4 data bytes
await cs_pin.write(true); // Set CS line to high
console.log('Received data:', rx_data);

API Reference​

Accessing SPI​

const spi = tester.com.spi(id);

Parameters:

  • id (number / int / usize): The SPI interface identifier (0-based index)

Returns: Spi instance / Result<Spi, At1000Error> in Rust


Methods​

configure(config)​

Configures the SPI interface parameters without changing the enabled state.

await spi.configure({
baud_rate: 1000000,
vcc: 3.3,
mode: 0
});

Parameters:

  • config (SpiConfigPatch): Configuration object with optional properties:
    • enabled (boolean / bool / Option<bool>, optional): Enable or disable the interface
    • baud_rate (number / int / Option<u32>, optional): SPI clock frequency in Hz (1000 to 20000000)
    • vcc (number / float / Option<f64>, optional): Logic level voltage in volts (1.8 to 3.3)
    • mode (number / int / Option<SpiMode>, optional): SPI mode (0, 1, 2, or 3; SpiMode::Mode0 through SpiMode::Mode3 in Rust)

SPI Modes:

  • Mode 0: CPOL=0, CPHA=0 (clock idle low, sample on rising edge)
  • Mode 1: CPOL=0, CPHA=1 (clock idle low, sample on falling edge)
  • Mode 2: CPOL=1, CPHA=0 (clock idle high, sample on falling edge)
  • Mode 3: CPOL=1, CPHA=1 (clock idle high, sample on rising edge)

In modes 1 and 3, the clock is generated with three-phase clocking: SCK does not have a 50 % duty cycle, but the bit rate stays at the configured baud_rate.

Default configuration (after tester.reset()): disabled, baud_rate 1000000, vcc 3.3 V, mode 0.

Returns: Promise<SpiConfig> / SpiConfig / Result<SpiConfig, At1000Error> - The updated configuration

Exceptions:

  • Throws validation error if baud_rate is outside the range [1000, 20000000]
  • Throws validation error if vcc is outside the range [1.8, 3.3]
  • Throws validation error if mode is outside the range [0, 3]
  • Throws error 409 UNSAFE_PIN_VOLTAGE if enabling, or changing vcc while enabled, finds MOSI, MISO or SCK below −0.5 V or above vcc + 0.5 V
  • Throws error 409 CONFIGURATION_CONFLICT when enabling while MOSI, MISO or SCK is configured as a GPIO (not an analog input), or while I2C, UART or PWM uses the same channel

enable([config])​

Enables the SPI interface and optionally applies configuration.

await spi.enable({
baud_rate: 1000000,
vcc: 3.3,
mode: 0
});

Parameters:

  • config (SpiEnableConfig / SpiEnableConfig / SpiConfigPatch, optional): Optional configuration to apply. In Rust the patch is required (pass SpiConfigPatch::default() to keep the current settings), and enable sets enabled: true itself:
    • baud_rate (number / int / Option<u32>, optional): SPI clock frequency (1000 to 20000000)
    • vcc (number / float / Option<f64>, optional): Logic level voltage (1.8 to 3.3)
    • mode (number / int / Option<SpiMode>, optional): SPI mode (0-3)

Returns: Promise<SpiConfig> / SpiConfig / Result<SpiConfig, At1000Error> - The updated configuration with enabled: true

Exceptions:

  • Throws validation error if parameters are outside valid ranges
  • Throws error 409 UNSAFE_PIN_VOLTAGE if MOSI, MISO or SCK reads below −0.5 V or above vcc + 0.5 V
  • Throws error 409 CONFIGURATION_CONFLICT while MOSI, MISO or SCK is configured as a GPIO (not an analog input), or while I2C, UART or PWM uses the same channel

disable()​

Disables the SPI interface.

await spi.disable();

Returns: Promise<SpiConfig> / SpiConfig / Result<SpiConfig, At1000Error> - The updated configuration with enabled: false


trx(data)​

Transmits data over SPI and simultaneously receives the response (full-duplex).

// Send command and receive response
const response = await spi.trx([0x03, 0x00, 0x00, 0x00]);
console.log("Received:", response);

// Send string
const response2 = await spi.trx("READ");

Parameters:

  • data (SerialDataInput / SerialDataInput / &[u8]): Data to transmit, either:
    • string: Text data (1 to 65536 characters)
    • number[]: Byte array (each byte 0-255, 1 to 65536 bytes)
    • Rust takes a byte slice only, from 1 through 65536 bytes: &[0x03, 0x00] or b"READ"

Returns: Promise<SerialData> / SerialData / Result<Vec<u8>, At1000Error> - Received data as byte array (number[] / Vec<u8>)

Note: SPI is full-duplex, so data is received simultaneously with transmission. The response length equals the transmission length.

Exceptions:

  • All three SDKs reject empty data and data exceeding the limits above locally, before sending a request.
  • Throws validation error if any byte value is outside the range [0, 255]
  • Throws error 400 INVALID_CONFIGURATION if the interface is disabled

Types​

SpiConfig​

Complete configuration object returned by configuration methods.

{
enabled: boolean,
baud_rate: number, // 1000 to 20000000
vcc: number, // 1.8 to 3.3
mode: number // 0, 1, 2, or 3
}

Rust: ikalogic_at1000::SpiConfig

pub struct SpiConfig {
pub enabled: bool,
pub baud_rate: u32,
pub vcc: f64,
pub mode: SpiMode,
}

pub enum SpiMode {
Mode0,
Mode1,
Mode2,
Mode3,
}

SpiConfigPatch​

Partial configuration for the configure() method. All properties are optional.

{
enabled?: boolean,
baud_rate?: number, // 1000 to 20000000
vcc?: number, // 1.8 to 3.3
mode?: number // 0, 1, 2, or 3
}

Rust: ikalogic_at1000::SpiConfigPatch (derives Default, so unset fields can be left to ..Default::default())

pub struct SpiConfigPatch {
pub enabled: Option<bool>,
pub baud_rate: Option<u32>,
pub vcc: Option<f64>,
pub mode: Option<SpiMode>,
}

SpiEnableConfig​

Optional configuration for the enable() method.

{
baud_rate?: number, // 1000 to 20000000
vcc?: number, // 1.8 to 3.3
mode?: number // 0, 1, 2, or 3
}

Rust has no separate enable type: enable takes SpiConfigPatch and sets enabled: true itself.

SerialDataInput​

Input data type for transmission.

string | number[]
  • string: 1 to 65536 characters
  • number[]: 1 to 65536 elements, each byte in range [0, 255]

Rust has no equivalent union: trx() takes a byte slice, &[u8], from 1 through 65536 bytes.

SerialData​

Output data type, always returned as a byte array.

number[] // Array of bytes (0-255)

In Rust, data is a Vec<u8>.


Usage Example​

import { AT1000 } from '@ikalogic/at1000';

// Find and connect to device
const devices = await AT1000.findDevices();
const tester = await AT1000.open(devices[0]);
await tester.reset(); // Reset the device to a known state

// Access SPI interface 0
const spi = tester.com.spi(0);

// Enable with 1 MHz clock, 3.3V logic, Mode 0
await spi.enable({
baud_rate: 1000000,
vcc: 3.3,
mode: 0
});

// Read from SPI device (e.g., read 4 bytes from address 0x1000)
const readCmd = [0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
const response = await spi.trx(readCmd);
console.log("Data:", response.slice(4)); // Skip command bytes

// Disable interface
await spi.disable();

Common Patterns​

Reading from SPI Flash​

// Configure for typical SPI flash (Mode 0, 3.3V, 10 MHz)
await spi.enable({
baud_rate: 10000000,
vcc: 3.3,
mode: 0
});

// Read JEDEC ID (0x9F command)
const jedecCmd = [0x9F, 0x00, 0x00, 0x00];
const jedecId = await spi.trx(jedecCmd);
console.log("Manufacturer:", jedecId[1].toString(16));
console.log("Device ID:", jedecId[2].toString(16), jedecId[3].toString(16));

// Read 256 bytes from address 0x000000
const readCmd = [0x03, 0x00, 0x00, 0x00, ...new Array(256).fill(0)];
const data = await spi.trx(readCmd);
const pageData = data.slice(4); // Skip 4 command bytes

SPI Sensor Communication​

// Configure for sensor (e.g., accelerometer)
await spi.enable({
baud_rate: 5000000,
vcc: 3.3,
mode: 3 // Some sensors use Mode 3
});

// Read register 0x0F (WHO_AM_I)
const whoAmI = await spi.trx([0x8F, 0x00]); // 0x80 = read bit
console.log("Device ID:", whoAmI[1].toString(16));

// Write to register 0x20
await spi.trx([0x20, 0x47]); // Write 0x47 to register 0x20

// Read multiple registers (burst read)
const burst = await spi.trx([0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
const values = burst.slice(1); // Skip command byte

Mode Selection Guide​

// Mode 0 (most common): Flash memory, SD cards, many sensors
await spi.configure({ mode: 0 });

// Mode 1: Some LCD controllers
await spi.configure({ mode: 1 });

// Mode 2: Rare, some specialized devices
await spi.configure({ mode: 2 });

// Mode 3: Some accelerometers, gyroscopes
await spi.configure({ mode: 3 });

Multi-Byte Transfer​

// Prepare large data transfer
const txData = new Array(1024).fill(0).map((_, i) => i & 0xFF);

// Send and receive
const rxData = await spi.trx(txData);

// Process response
console.log(`Transferred ${rxData.length} bytes`);

Voltage Level Configuration​

// 3.3V logic devices (most common)
await spi.configure({ vcc: 3.3 });

// 1.8V logic devices
await spi.configure({ vcc: 1.8 });

Notes​

  • SPI is a full-duplex protocol: data is transmitted and received simultaneously
  • The chip select (CS) signal must be managed manually using GPIO pins
  • MOSI (Master Out Slave In): Data from AT1000 to device
  • MISO (Master In Slave Out): Data from device to AT1000
  • SCK (Serial Clock): Clock signal generated by AT1000
  • Response length always equals transmission length (SPI characteristic)
  • In Rust, Spi implements the embedded-hal 1.0 SpiBus trait, so SPI driver crates can use it; see embedded-hal support on the I2C page