Serial Peripheral Interface (SPI) Bus: A Deep Dive

Introduction to the SPI Bus

The Serial Peripheral Interface (SPI) bus is a synchronous, full-duplex communication standard primarily used for data transfer between integrated circuits in electronic equipment. It utilizes a clock line, incoming and outgoing data lines, and a chip select pin to control device communication.

SPI Bus Management in C

Four C functions are associated with the SPI module when the microcontroller hardware selects SSP. These functions provide a software implementation of the SPI module:

1. setup_spi()

void setup_spi(const mode);

This function initializes and configures the SPI, defining its operating mode. Several tags within the microcontroller’s header file (.h) define these modes, categorized into three groups:

a) Mode

  • SPI_MASTER: Sets the device as master.
  • SPI_SLAVE: Configures the device as slave.
  • SPI_SS_DISABLED: Disables the slave select (SS) pin.

b) Flank

Configures SPI transmission modes based on these combinations:

  • SPI_L_TO_H: Low to high flank (rising edge).
  • SPI_H_TO_L: High to low flank (falling edge).

Mode Configuration Table

MotorolaMicrochipCCS
SPI Mode 0,0CKP = 0, CKE = 1SPI_L_TO_H
SPI Mode 0,1CKP = 0, CKE = 0SPI_L_TO_H | SPI_XMIT_L_TO_H
SPI Mode 1,0CKP = 1, CKE = 1SPI_H_TO_L
SPI Mode 1,1CKP = 1, CKE = 0SPI_H_TO_L | SPI_XMIT_L_TO_H

CKP = Clock Polarity Select bit
CKE = Clock Edge Select bit

c) Frequency

Defines the clock frequency. Used only when the device is configured as master (the clock signal generator).

  • SPI_CLK_DIV_4
  • SPI_CLK_DIV_16
  • SPI_CLK_DIV_64
  • SPI_CLK_T2

Combine flags using the OR operator (|). Examples:

  • Master device, Mode B (0,1), specific clock speed: setup_spi(SPI_MASTER | SPI_L_TO_H | SPI_CLK_DIV_16);
  • Slave device, Mode C (1,0), SS pin disabled: setup_spi(SPI_SLAVE | SPI_H_TO_L | SPI_SS_DISABLED);

2. spi_write()

void spi_write(char c);

Writes the next character to the SPI port. Master devices generate a clock signal and send the value immediately. Slave devices send the value upon receiving a clock signal from the master.

3. spi_read()

char spi_read([char c]);

This function’s behavior depends on the device’s role (master or slave). It returns data read through the SPI and may accept a parameter.

Master: Sends the value c while receiving a new value (d = spi_read(c)). If no data needs sending but a read operation is required, pass zero (d = spi_read(0)). If spi_write() was used previously and you want to read data while writing, use this function without parameters (d = spi_read()).

Slave: Waits for the master to send the clock signal and data. While receiving new data, it sends data c to the other device. Use spi_data_is_in() to check for received data.

4. spi_data_is_in()

char spi_data_is_in();

Returns TRUE if data has been received via the SPI port.

Example Code

This example demonstrates communication between a master and a slave. To compile for the master, uncomment #define MASTER_DEVICE.

#include <16f877.h>

#fuses HS, NOWDT // For a Microchip PIC16F877
#use delay(clock=10000000)
#use rs232(baud=9600, parity=N, xmit=PIN_C6, rcv=PIN_C7, bits=8)
#use standard_io(D)

//#define MASTER_DEVICE

#ifdef MASTER_DEVICE
// (Master)
void main() {
    int8 c, d;
    setup_spi(SPI_MASTER | SPI_L_TO_H | SPI_CLK_DIV_16);
    printf("Press any key to begin");
    while (1) {
        c = getchar();
        output_D(0xFF ^ c); // Toggle LEDs corresponding to PORTD

        // Method 1: Separate write and read
        spi_write(c);
        d = spi_read();

        // Method 2: Combined write and read
        //d = spi_read(c);

        putchar(c);
    }
}
#else
// (Slave)
void main() {
    int8 c;
    setup_spi(SPI_SLAVE | SPI_L_TO_H | SPI_CLK_DIV_16);
    while (1) {
        // Flash C2 to indicate activity
        output_low(PIN_C2);
        delay_ms(100);
        output_high(PIN_C2);
        delay_ms(100);

        // Method 1: Check for data before reading
        if (spi_data_is_in()) {
            c = spi_read();
            output_D(0xFF ^ c); // Toggle LEDs on PORTD
            spi_write(c); // Wait for master signal and write data
        }

        // Alternative method: Combined read and write
        //c = spi_read(c);
        //output_D(0xFF ^ c);
    }
}
#endif

More complex libraries offer additional specialized functions for SPI bus management.