all
Chapter 15 of 20

SSI Interface — Tiva C

Eslam El Hefny Apr 15, 2025 7 min read
75% done

SSI Interface — Tiva C

Overview

The TM4C123GH6PM has four SSI (Synchronous Serial Interface) modules (SSI0–SSI3). SSI is fully compatible with SPI (Serial Peripheral Interface), Freescale SPI, and the Texas Instruments synchronous serial frame format. It is a full-duplex, synchronous protocol widely used for high-speed sensors, flash memory, and display drivers.


Beginner Level — What & Why

What is SSI/SPI?

SSI (called SPI on most other devices) is a synchronous serial bus that uses four wires: SCK (clock), MOSI (master out/slave in), MISO (master in/slave out), and CS/SS (chip select). Unlike I2C, there is no addressing — each device gets its own chip-select line. Data is clocked on each rising or falling edge of SCK.

Real-World Analogy

SSI is like two people playing a card-passing game. The master (TM4C123) hands one card (bit) to the slave while simultaneously receiving one card back — all perfectly synchronised to the rhythm of the clock (SCK). A table flag (CS LOW) shows who is playing.

What Problem Does It Solve?

  • High-speed data transfers (up to 6.67 Mbps on TM4C123 with 80 MHz clock)
  • Full-duplex simultaneous send and receive
  • Industry-standard protocol for SD cards, sensors, DACs, and displays

Key Terms

Term Meaning
SCK Serial Clock — driven by master
MOSI Master Out Slave In — master sends data
MISO Master In Slave Out — slave sends data
CS/SS Chip Select / Slave Select — active LOW
CPOL Clock Polarity: idle SCK level (0 or 1)
CPHA Clock Phase: when data is sampled
Mode 0 CPOL=0, CPHA=0 — most common

Intermediate Level — How It Works

SSI Pins

Module SCK MOSI (Tx) MISO (Rx) CS
SSI0 PA2 PA5 PA4 PA3
SSI1 PF2 PF1 PF0 PF3
SSI2 PB4 PB7 PB6 PB5
SSI3 PD0 PD3 PD2 PD1

Key Registers

Register Offset Description
SSICR0 0x000 Control 0: SCR prescaler, SPH, SPO, FRF, DSS
SSICR1 0x004 Control 1: SOD, MS (master/slave), SSE (enable), LBM
SSIDR 0x008 Data Register (Tx write / Rx read)
SSISR 0x00C Status: TFE, TNF, RNE, RFF, BSY
SSICPSR 0x010 Clock prescale divisor (must be even, 2–254)
SSIIM 0x014 Interrupt mask
SSIRIS 0x018 Raw interrupt status
SSIMIS 0x01C Masked interrupt status
SSIICR 0x020 Interrupt clear

SSISR status bits:

Bit Name Meaning
0 TFE TX FIFO empty
1 TNF TX FIFO not full (can write)
2 RNE RX FIFO not empty (data available)
3 RFF RX FIFO full
4 BSY SSI busy (transfer in progress)

SPI Mode Selection

Mode CPOL CPHA TivaWare Constant
0 0 0 SSI_FRF_MOTO_MODE_0
1 0 1 SSI_FRF_MOTO_MODE_1
2 1 0 SSI_FRF_MOTO_MODE_2
3 1 1 SSI_FRF_MOTO_MODE_3

TivaWare DriverLib API

#include "driverlib/ssi.h"
#include "driverlib/pin_map.h"

/* Enable SSI0 and Port A */
SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0));
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA));

/* Assign alternate functions to PA2-PA5 */
GPIOPinConfigure(GPIO_PA2_SSI0CLK);
GPIOPinConfigure(GPIO_PA3_SSI0FSS);
GPIOPinConfigure(GPIO_PA4_SSI0RX);
GPIOPinConfigure(GPIO_PA5_SSI0TX);
GPIOPinTypeSSI(GPIO_PORTA_BASE,
               GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5);

/* Configure SSI0: master, Mode 0, 1 MHz, 8-bit data */
SSIConfigSetExpClk(SSI0_BASE,
                   SysCtlClockGet(),     // system clock
                   SSI_FRF_MOTO_MODE_0, // SPI Mode 0
                   SSI_MODE_MASTER,      // master mode
                   1000000,              // 1 MHz bit rate
                   8);                   // 8-bit data width

/* Enable SSI0 */
SSIEnable(SSI0_BASE);

/* Send a byte and receive the response */
SSIDataPut(SSI0_BASE, 0x80);     // write to TX FIFO
while (SSIBusy(SSI0_BASE));       // wait for completion
uint32_t rxData;
SSIDataGet(SSI0_BASE, &rxData);   // read from RX FIFO

Advanced Level — Deep Dive

Bare-Metal SSI Configuration

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_ssi.h"

void SSI0_Init_1MHz_Mode0_8bit(void)
{
    /* Enable clocks */
    HWREG(SYSCTL_RCGCSSI)  |= (1U << 0);  // SSI0
    HWREG(SYSCTL_RCGCGPIO) |= (1U << 0);  // Port A
    while (!(HWREG(SYSCTL_PRSSI)  & (1U << 0)));
    while (!(HWREG(SYSCTL_PRGPIO) & (1U << 0)));

    /* Configure PA2(CLK), PA3(FSS), PA4(RX), PA5(TX) alternate function 2 */
    HWREG(GPIO_PORTA_BASE + GPIO_O_AFSEL) |= 0x3C;  // PA2-PA5
    HWREG(GPIO_PORTA_BASE + GPIO_O_PCTL)  =
        (HWREG(GPIO_PORTA_BASE + GPIO_O_PCTL) & ~0x00FFFF00) | 0x00222200;
    HWREG(GPIO_PORTA_BASE + GPIO_O_DEN)   |= 0x3C;

    /* Disable SSI while configuring (SSE bit in CR1) */
    HWREG(SSI0_BASE + SSI_O_CR1) = 0;

    /* Clock prescale: SSICPSR = 80 (80 MHz / 80 = 1 MHz pre-scale)
     * SCR = 0 → bit rate = 80 MHz / (CPSR × (1 + SCR)) = 1 MHz */
    HWREG(SSI0_BASE + SSI_O_CPSR) = 80;

    /* CR0: SCR=0, SPH=0, SPO=0 (Mode 0), FRF=0 (Motorola SPI), DSS=7 (8-bit) */
    HWREG(SSI0_BASE + SSI_O_CR0) = (0 << 8) |  // SCR = 0
                                    (0 << 7) |  // SPH = 0
                                    (0 << 6) |  // SPO = 0
                                    (0 << 4) |  // FRF = Motorola SPI
                                    (7 << 0);   // DSS = 8-bit (7 = 0111)

    /* CR1: master mode, enable SSI */
    HWREG(SSI0_BASE + SSI_O_CR1) = SSI_CR1_SSE;  // MS=0 (master), SSE=1
}

uint8_t SSI0_TransferByte(uint8_t txByte)
{
    /* Wait until TX FIFO is not full */
    while (!(HWREG(SSI0_BASE + SSI_O_SR) & SSI_SR_TNF));
    HWREG(SSI0_BASE + SSI_O_DR) = txByte;

    /* Wait until RX FIFO has data */
    while (!(HWREG(SSI0_BASE + SSI_O_SR) & SSI_SR_RNE));
    return (uint8_t)HWREG(SSI0_BASE + SSI_O_DR);
}

ADXL345 Accelerometer Read

The ADXL345 uses SPI Mode 3 (CPOL=1, CPHA=1) or Mode 0, with CS manually controlled via GPIO:

/* CS pin on PA3 — use GPIO for manual control */
#define CS_LOW()   GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_3, 0)
#define CS_HIGH()  GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_3, GPIO_PIN_3)

/* ADXL345: read register (bit 7 = 1 for read) */
uint8_t ADXL345_ReadReg(uint8_t reg)
{
    CS_LOW();
    SSI0_TransferByte(reg | 0x80);   // read command
    uint8_t val = SSI0_TransferByte(0x00);  // dummy to clock out data
    CS_HIGH();
    return val;
}

/* ADXL345: write register */
void ADXL345_WriteReg(uint8_t reg, uint8_t data)
{
    CS_LOW();
    SSI0_TransferByte(reg & 0x7F);   // write command (bit 7 = 0)
    SSI0_TransferByte(data);
    CS_HIGH();
}

SSI vs Bit-Bang SPI Comparison

Feature Hardware SSI Bit-Bang SPI
Max speed 6.67 Mbps ~1 Mbps (GPIO limited)
CPU load Low (FIFO + DMA possible) High (100% CPU)
Flexibility Fixed modes Any custom timing
Code size Small (DriverLib) More code
Interrupts Built-in Manual

Gotchas

  • SSICPSR must be even (2, 4, 6, …, 254) — odd values are not valid.
  • Bit rate formula: f_bit = f_clk / (CPSR × (1 + SCR)). Set both CPSR and SCR to reach exact frequencies.
  • Flush RX FIFO after configuration — writing test data during setup may leave ghost bytes in the RX FIFO.
  • Mode 1 and 3 (CPHA=1) sample on the second clock edge — many ADXL345 libraries default to Mode 3.
  • SSI FSS (chip select) is controlled automatically in master mode — for manual CS control, configure PA3 as a regular GPIO output, not as SSI FSS.

Step-by-Step Example

/*
 * ssi_adxl345.c
 * Read X, Y, Z acceleration from ADXL345 via SSI0
 * Board  : TM4C123GXL EK LaunchPad
 * SDK    : TivaWare_C_Series-2.2.x
 * Wiring : PA2→CLK, PA5→SDA(MOSI), PA4→SDO(MISO), PA3(GPIO)→CS
 */

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/ssi.h"
#include "driverlib/pin_map.h"
#include "utils/uartstdio.h"
#include "driverlib/uart.h"

#define ADXL345_DEVID     0x00
#define ADXL345_POWER_CTL 0x2D
#define ADXL345_DATAX0    0x32
#define CS_PIN            GPIO_PIN_3

static void CS_Low(void)
{
    GPIOPinWrite(GPIO_PORTA_BASE, CS_PIN, 0);
}
static void CS_High(void)
{
    GPIOPinWrite(GPIO_PORTA_BASE, CS_PIN, CS_PIN);
}

static uint8_t SPI_Transfer(uint8_t byte)
{
    uint32_t rx;
    SSIDataPut(SSI0_BASE, byte);
    while (SSIBusy(SSI0_BASE));
    SSIDataGet(SSI0_BASE, &rx);
    return (uint8_t)rx;
}

static uint8_t ADXL_Read(uint8_t reg)
{
    CS_Low();
    SPI_Transfer(reg | 0x80);   // read bit
    uint8_t v = SPI_Transfer(0x00);
    CS_High();
    return v;
}

static void ADXL_Write(uint8_t reg, uint8_t data)
{
    CS_Low();
    SPI_Transfer(reg & 0x7F);   // write
    SPI_Transfer(data);
    CS_High();
}

int main(void)
{
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
                   SYSCTL_OSC_MAIN   | SYSCTL_XTAL_16MHZ);

    /* UART0 for debug */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0));
    GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTStdioConfig(0, 115200, SysCtlClockGet());

    /* SSI0 setup */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0));

    /* PA2=CLK, PA4=MISO, PA5=MOSI — alternate function */
    GPIOPinConfigure(GPIO_PA2_SSI0CLK);
    GPIOPinConfigure(GPIO_PA4_SSI0RX);
    GPIOPinConfigure(GPIO_PA5_SSI0TX);
    GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5);

    /* PA3 = manual CS (GPIO output) */
    GPIOPinTypeGPIOOutput(GPIO_PORTA_BASE, CS_PIN);
    CS_High();

    SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(),
                       SSI_FRF_MOTO_MODE_3,  // ADXL345 uses Mode 3
                       SSI_MODE_MASTER, 2000000, 8);
    SSIEnable(SSI0_BASE);

    /* Verify device ID */
    uint8_t devId = ADXL_Read(ADXL345_DEVID);
    UARTprintf("ADXL345 Device ID: 0x%02X (expect 0xE5)\r\n", devId);

    /* Wake up ADXL345: set Measure bit */
    ADXL_Write(ADXL345_POWER_CTL, 0x08);

    while (1)
    {
        /* Read 6 bytes of acceleration data (X0,X1,Y0,Y1,Z0,Z1) */
        CS_Low();
        SPI_Transfer(ADXL345_DATAX0 | 0x80 | 0x40);  // read | multi-byte
        uint8_t x0 = SPI_Transfer(0); uint8_t x1 = SPI_Transfer(0);
        uint8_t y0 = SPI_Transfer(0); uint8_t y1 = SPI_Transfer(0);
        uint8_t z0 = SPI_Transfer(0); uint8_t z1 = SPI_Transfer(0);
        CS_High();

        int16_t ax = (int16_t)((x1 << 8) | x0);
        int16_t ay = (int16_t)((y1 << 8) | y0);
        int16_t az = (int16_t)((z1 << 8) | z0);

        UARTprintf("X=%5d  Y=%5d  Z=%5d\r\n", ax, ay, az);
        SysCtlDelay(SysCtlClockGet() / 10);  // ~300 ms
    }
}

Summary

Key Point Details
SSI modules SSI0–SSI3
SSI0 pins PA2(CLK), PA3(FSS), PA4(RX), PA5(TX)
Max bit rate 6.67 Mbps (system clock / 12)
SPI modes Mode 0–3 (CPOL + CPHA combinations)
Data width 4–16 bits
FIFO depth 8 entries (TX and RX)
Status flags TFE, TNF, RNE, RFF, BSY
Bit rate formula f_clk / (CPSR × (1 + SCR))

Next Chapter

Software CRC Module

Share: