# 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
| 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):**
| 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):**
| 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:
| 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.