> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/wokwi/wokwi-docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SPI Device API

> Implement SPI peripheral devices in custom chips

To create an SPI device, first call `spi_init`, passing in an `spi_config_t` struct. This struct defines the clock and MOSI/MISO pins, the SPI mode, and the `done` callback.

## spi\_init

```c theme={null}
spi_dev_t spi_init(spi_config_t *config)
```

Initializes an SPI device interface. The `config` argument defines the pins, mode, and callbacks for the SPI device. It contains the following fields:

<ParamField path="sck" type="pin_t">
  The clock pin
</ParamField>

<ParamField path="mosi" type="pin_t">
  The MOSI data pin (or `NO_PIN` to disable MOSI)
</ParamField>

<ParamField path="miso" type="pin_t">
  The MISO data pin (or `NO_PIN` to disable MISO)
</ParamField>

<ParamField path="mode" type="uint32_t">
  SPI mode: `0`, `1`, `2`, or `3` (default: `0`)
</ParamField>

<ParamField path="done" type="callback">
  Called when an SPI transaction finishes (see below)
</ParamField>

<ParamField path="user_data" type="void *">
  Data that will be passed in the first argument of the `done` callback
</ParamField>

The API does not support a CS/SS pin: it is up to the user to select/deselect the SPI interface by calling `spi_start()` and `spi_stop()`.

<Warning>
  Note: `spi_init` can only be called from `chip_init()`. Do not call it at a later time.
</Warning>

### Example

```c theme={null}
const spi_config_t spi1 = {
  .sck = pin_init("SCK", INPUT),
  .mosi = pin_init("MOSI", INPUT),
  .miso = pin_init("MISO", INPUT),
  .mode = 0,
  .done = chip_spi_done, // See the example below
  .user_data = chip,
};
```

## spi\_start

```c theme={null}
void spi_start(spi_dev_t spi, uint8_t *buffer, uint32_t count)
```

Starts an SPI transaction, sending and receiving `count` bytes to/from the given `buffer`.

You will usually listen for the CS (chip select) pin with `pin_watch`. Call `spi_start()` when the CS pin goes low, and `spi_stop()` when the CS pin goes high.

When creating a device that transfers large amounts of data (e.g. an LCD display), it's recommended to use a large buffer size (few kilobytes). The simulator can use the larger buffer to optimize DMA-controlled SPI transfer and speed up the simulation.

For simple devices that transfer small amounts of data, you can use a single-byte buffer, and process each byte as it arrives in the `done` callback.

## spi\_stop

```c theme={null}
void spi_stop(spi_dev_t spi)
```

Stops the SPI interface. Usually, you'd call this method when the CS pin goes high.

## The done callback

The signature for the `done` callback is as follows:

```c theme={null}
static void chip_spi_done(void *user_data, uint8_t *buffer, uint32_t count) {
  // 1. process the received data (optional)
  // 2. if the CS pin is still low, schedule the next SPI transaction using `spi_start`
}
```

The `done` callback runs when an SPI transaction finishes: either when the buffer provided to `spi_start` is full, or when `spi_stop` was called. The `buffer` contains the data received (it is the same buffer given to `spi_start`), and `count` is the number of bytes that have been transferred (or `0` if `spi_stop` was called before a complete byte has been transferred).

Your `done` callback should check the status of the CS pin, and if it is still low, it should call `spi_start()` again to receive the next chunk of data from the microcontroller.

## Simulator examples

* [SPI ROT13 Chip Example](https://wokwi.com/projects/330669951756010068)
