spi 0.1.0

Emulator-focused SPI host controller implementation
Documentation
# spi

An emulator-focused SPI host controller implementation in Rust.

This crate provides a cycle-accurate SPI host controller (`SpiHost`) and a trait (`SpiDevice`) for attaching peripheral devices. It is designed for use in hardware emulators where you need to emulate SPI communication between a CPU and one or more peripherals.

## Features

- All four SPI modes (CPOL/CPHA combinations)
- MSB-first and LSB-first transfer endianness
- Up to 8 simultaneously attached devices with chip-select per device
- System clock and external clock inputs
- Fast transfer mode (FRX) for back-to-back transfers
- Interrupt support (transmission complete)
- Tristate MOSI support
- Software reset via control register
- `SpiDevice` blanket impls for `Box<dyn SpiDevice>`, `RefCell<T>`, and `Rc<RefCell<T>>`

## Usage

Add to your `Cargo.toml`:

```toml
[dependencies]
spi = "0.1"
```

### Implementing a device

```rust
use spi::SpiDevice;

struct MyFlash {
    // ...
}

impl SpiDevice for MyFlash {
    fn select(&mut self, sel: bool) {
        // called when chip select changes
    }

    fn clock(&mut self, mosi: bool) -> bool {
        // called once per clock edge with the MOSI bit
        // return the MISO bit
        false
    }
}
```

### Wiring up the host controller

```rust
use spi::SpiHost;

let mut host = SpiHost::new();

// Attach a device; save the returned ID to remove it later
let id = host.add_device(Box::new(MyFlash { /* ... */ })).unwrap();

// In your CPU read/write handlers (4 registers, address bits [1:0]):
//   reg 0 — data (read: RX byte, write: TX byte)
//   reg 1 — control/status
//   reg 2/3 — device select bitmask (one bit per device)
host.write(1, 0b10000000); // software reset
host.write(2, 0b00000001); // select device 0
host.write(0, 0xAB);       // begin transfer of 0xAB

// Drive the system clock from your emulator's clock source
host.sys_clock(true);
host.sys_clock(false);
// ... repeat until transfer completes (STS_TC set in status register)

let rx = host.read(0); // read received byte

// Optionally check for interrupt
if host.int() {
    // handle interrupt
}
```

### Register map

| Addr (bits [1:0]) | Read | Write |
|---|---|---|
| 0 | RX data byte | TX data byte (starts transfer) |
| 1 | Status register | Control register |
| 2–3 | Device select bitmask | Device select bitmask |

**Control register bits (write to reg 1):**

| Bit | Name | Description |
|-----|------|-------------|
| 7 | SR | Software reset (self-clearing) |
| 6 | END | Endianness: 1 = MSB-first, 0 = LSB-first |
| 5 | IER | Interrupt enable |
| 4 | FRX | Fast transfer: auto-reload TX from RX on data read |
| 3 | TMO | Tristate MOSI (output 0 regardless of data) |
| 2 | ECE | External clock enable |
| 1 | CPOL | Clock polarity |
| 0 | CPHA | Clock phase |

**Status register bits (read from reg 1):**

| Bit | Name | Description |
|-----|------|-------------|
| 7 | TC | Transmission complete |
| 6 | BSY | Transfer in progress |
| 5–0 || Mirror of control register bits 5–0 |

## Clock modes

All four standard SPI modes are supported via CPOL and CPHA:

| Mode | CPOL | CPHA | Clock idle | Sample edge |
|------|------|------|------------|-------------|
| 0 | 0 | 0 | Low | Rising |
| 1 | 0 | 1 | Low | Falling |
| 2 | 1 | 0 | High | Falling |
| 3 | 1 | 1 | High | Rising |

## License

Licensed under your choice of MIT or Apache-2.0.