The SPI Protocol on a PIC
SPI trades I2C's shared-wire simplicity for raw speed: a dedicated wire per direction, plus one select line per device.
SPI (Serial Peripheral Interface) is another way for a PIC to talk to nearby chips, favoring speed over wire count. Where I2C shares two wires among every device on the bus, SPI uses four signal lines total, with most of them shared and one — the chip select line — dedicated separately to each device.
| Line | Job |
|---|---|
| SCK | Clock, generated by the controller, shared by all devices |
| MOSI | "Controller out, peripheral in" — data sent from the PIC |
| MISO | "Controller in, peripheral out" — data sent back to the PIC |
| CS / SS | Chip select — one dedicated wire per device, pulled low to "wake up" that device for the transfer |
Because SPI has its own dedicated clock line (unlike UART) and each device gets its own select line (unlike I2C's shared addressing), transfers are simpler to implement correctly and generally much faster — often many times the top speed of I2C. The trade-off is wiring: adding a second SPI device means running one more chip-select wire, whereas adding a second I2C device costs nothing extra as long as its address doesn't collide with another device already on the bus.
A typical SPI transfer pulls the target device's chip-select line low, sends one or more bytes on MOSI while simultaneously reading whatever comes back on MISO — SPI transfers both directions at once, unlike UART or I2C — and then releases chip-select high again when finished.
unsigned char spi_transfer(unsigned char data) {
SSPBUF = data;
while (!SSPSTATbits.BF); // wait for transfer to finish
return SSPBUF; // byte received during the same transfer
}
void send_to_device(unsigned char value) {
LATC0 = 0; // chip select low: device is selected
spi_transfer(value);
LATC0 = 1; // chip select high: transfer done
}