Concepts

Informative

The model behind the SPI driver: its driver- and channel-level state machines, the buffer-set transfer model, chip-select control, and how transaction events are handled and notified. Non-binding; the normative rules live in Spi — Requirements.

State machine

The driver has a driver-wide state and, within the initialized state, a per-channel state. A channel is statically configured as a controller or a target; the two roles share the channel states but move through them with different triggers.

Driver state

        stateDiagram-v2
   [*] --> SPI_DRV_STATE_UNINIT : Reset
   SPI_DRV_STATE_UNINIT --> SPI_DRV_STATE_INIT : Spi_Init()
   SPI_DRV_STATE_INIT --> SPI_DRV_STATE_UNINIT : Spi_DeInit()
    

Driver state

Meaning

API calls accepted

SPI_DRV_STATE_UNINIT

Not initialized (state after reset/startup)

Only Spi_Init()

SPI_DRV_STATE_INIT

Initialized; service APIs are accepted

All

Channel state

Each channel moves through the states of Spi_ChannelStateType. Spi_Init leaves every channel in IDLE; Spi_DeInit returns the driver (and its channels) to SPI_DRV_STATE_UNINIT.

Channel state

Meaning

SPI_CH_STATE_UNINIT

Not initialized.

SPI_CH_STATE_IDLE

Initialized; no transaction requested. (Target: no buffer staged.)

SPI_CH_STATE_READY

Target only — a buffer has been staged for the next controller-initiated transaction.

SPI_CH_STATE_BUSY

A transaction is ongoing.

Controller mode — the integration code starts a transaction; the driver drives it to completion:

        stateDiagram-v2
   direction LR
   [*] --> SPI_CH_STATE_IDLE : Spi_Init()
   SPI_CH_STATE_IDLE --> SPI_CH_STATE_BUSY : Spi_StartTransaction()
   SPI_CH_STATE_BUSY --> SPI_CH_STATE_IDLE : whole buffer transferred<br/>(at Spi_Interrupt / Spi_PollFunction)
    

Note

Calling Spi_DeInit during a transaction can leave the target in an undefined state; it should be called with the channel in SPI_CH_STATE_IDLE.

Target mode — the controller on the bus drives the channel; staging is optional:

        stateDiagram-v2
   direction LR
   [*] --> SPI_CH_STATE_IDLE : Spi_Init()
   SPI_CH_STATE_IDLE --> SPI_CH_STATE_READY : Spi_StartTransaction() (stage buffer)
   SPI_CH_STATE_READY --> SPI_CH_STATE_IDLE : Spi_CancelTransaction()
   SPI_CH_STATE_READY --> SPI_CH_STATE_BUSY : CS activated
   SPI_CH_STATE_IDLE --> SPI_CH_STATE_BUSY : CS activated (no buffer staged)
   SPI_CH_STATE_BUSY --> SPI_CH_STATE_IDLE : CS deactivated<br/>(before Spi_Callout_OnTransactionEnd)
    

Whether CS activation moves a target to BUSY immediately depends on the hardware: if the SPI HW raises a CS-activation interrupt it is evaluated in Spi_Interrupt / Spi_PollFunction; otherwise the state is evaluated from the HW status whenever an API is called.

See Spi — Requirements (CHI-SPI-MUST-04CHI-SPI-MUST-08).

Transfer model: buffer sets

A transaction transfers a buffer set (Spi_BufferSetType) — an array of buffer chunks (Spi_BufferType) sent within one chip-select sequence. TX and RX buffer sets are supplied independently to Spi_StartTransaction:

  • Each Spi_BufferType chunk has a bufferDataPtr and a length (in elements, ≥ 1). Element size is set by the configured data_width: the driver casts Spi_BufferDataType (a uint8) to uint16 for 9–16-bit widths and to uint32 for 17–32-bit widths.

  • A null bufferDataPtr has direction-specific meaning: on TX the driver sends the configured default_data for that length; on RX it discards the received data for that length.

  • Data consistency is the caller’s responsibility — the buffer-set structures and their TX data MUST stay valid and unmodified from Spi_StartTransaction until Spi_Callout_OnTransactionEnd.

The TX and RX buffer sets are independent: they MAY use a different number of buffers and different per-buffer lengths. Only the total transfer length across txBuffSetPtr and rxBuffSetPtr MUST match; a mismatch raises SPI_DEVERR_PARAM_LENGTH (CHI-SPI-MUST-20).

../../../_images/Transaction_Sequence.drawio.png

Transaction sequence within one chip-select frame: the TX data goes out on MOSI and the RX data comes in on MISO, each as its own buffer set (buffSetPtr[0..n]). The number and individual lengths of the buffers may differ between TX and RX, as long as the total transfer length is equal.

In target mode, if the controller requests more than the staged TX buffer, the target sends default_data for the excess; if it requests more than the RX buffer, the target ignores the excess.

Chip-select control

For a controller channel, the chip select is driven either by the SPI hardware or by a GPIO owned by the integration code (configured per external device as ViaHW or ViaGPIO):

  • ViaHW — the SPI HW asserts and releases CS; the driver does nothing extra.

  • ViaGPIO — the driver calls Spi_Callout_CsControl(channelId, csId, TRUE) just before the transfer and Spi_Callout_CsControl(..., FALSE) at the end, so the integration code toggles the CS line. The driver never touches the GPIO itself.

Event handling: interrupt or polling

For each channel, event handling is configured as either interrupt or polling:

  • InterruptSpi_Init enables the SPI interrupt; Spi_Interrupt services it.

  • PollingSpi_PollFunction services the same events by polling the hardware.

Either way the driver notifies transaction completion through Spi_Callout_OnTransactionEnd and (for GPIO chip selects) drives Spi_Callout_CsControl. A target that cannot detect CS deactivation in hardware is notified of it by the integration code through Spi_OnCs.

Event notification timing

The points at which the driver drives Spi_Callout_CsControl and Spi_Callout_OnTransactionEnd differ between controller and target mode (CHI-SPI-MUST-14, CHI-SPI-MUST-15):

../../../_images/Event_Timing_In_Controller.drawio.png

Event notification timing in controller mode.

../../../_images/Event_Timing_In_Target.drawio.png

Event notification timing in target mode.

Controller transaction sequence

The informative view of a controller transfer (CHI-SPI-MUST-07, CHI-SPI-MUST-13, CHI-SPI-MUST-14):

        sequenceDiagram
   participant IC as Integration Code
   participant INTEG as Integration code
   participant DRV as SpiDrv
   participant HW as SPI HW

   INTEG->>DRV: Spi_StartTransaction(ch, csId, txSet, rxSet)
   activate DRV
   opt CS configured as GPIO
      DRV->>IC: Spi_Callout_CsControl(ch, csId, TRUE)
   end
   DRV->>HW: set up buffers, start transfer (channel BUSY)
   DRV-->>INTEG: E_OK
   deactivate DRV

   HW->>DRV: Spi_Interrupt / Spi_PollFunction
   activate DRV
   alt data remaining
      DRV->>HW: transfer next TX / store RX
   else whole buffer transferred
      opt CS configured as GPIO
         DRV->>IC: Spi_Callout_CsControl(ch, csId, FALSE)
      end
      DRV->>INTEG: Spi_Callout_OnTransactionEnd(ch, csId, length) (channel IDLE)
   end
   deactivate DRV
    

Target transaction sequence

The informative view of a target transfer (CHI-SPI-MUST-08, CHI-SPI-MUST-15, CHI-SPI-SHOULD-01):

        sequenceDiagram
   participant IC as Integration Code
   participant INTEG as Integration code
   participant DRV as SpiDrv
   participant HW as SPI HW

   Note over INTEG,HW: stage the response before the controller selects us
   INTEG->>DRV: Spi_StartTransaction(ch, SPI_CS_UNSPECIFIED, txSet, rxSet)
   activate DRV
   DRV->>HW: set up buffers (channel READY)
   DRV-->>INTEG: E_OK
   deactivate DRV

   HW->>DRV: Spi_Interrupt / Spi_PollFunction (CS activated -> BUSY)
   activate DRV
   alt CS deactivation detected in HW
      DRV->>INTEG: Spi_Callout_OnTransactionEnd(ch, csId, length) (channel IDLE)
   end
   deactivate DRV

   opt HW cannot detect CS deactivation
      IC->>DRV: Spi_OnCs(ch, csId, FALSE)
      activate DRV
      DRV->>INTEG: Spi_Callout_OnTransactionEnd(ch, csId, length) (channel IDLE)
      deactivate DRV
   end
    

If Spi_StartTransaction was not called before a transaction begins, the target sends no data and ignores what it receives, but Spi_Callout_OnTransactionEnd is still called (with length = 0).

Key terms

For SPI glossary terms — SPI device, chip select (CS), controller/target node, buffer set, default data — see the glossary in the appendix.