SPI Master#

Integrating SPI Into Your Project#

Micrium OS IO-SPI is composed of several components, each of which is a set of files that implement specific functions. To use IO-SPI, you must add these files to your project and populate your RTOS description file .

Starting the IO SPI Module Quickly#

Micrium offers quick example applications that demonstrate how to initialize, add a controller, and start the Micrium OS IO SPI module. We highly recommend that you start with one of these examples.

SPI Configuration#

This section describes the configurations related to Micrium OS IO-SPI.

SPI Core Run-Time Configurations#

This section describes Micrium OS IO-SPI core run-time configurations.These configurations are optional and can usually be ignored until the very late stages of your application design.

Core Configuration#

Optional Pre-Init Configuration#

Pre-init configuration can be performed by calling dedicated configuration functions before IO_Init() is called. If no explicit pre-init configuration is performed, default values will be used. These default values are stored in SPI_InitCfgDflt, which is defined in spi.c.

Table - IO-SPI Optional Pre-Init Configurations#

Configurations

Description

Type

Function to call

Default

Field from default configuration structure

Memory segment

Configures the memory segment where the core internal data structures will be allocated.

MEM_SEG*

SPI_ConfigureMemSeg()

General-purpose heap

.MemSegPtr

Maximum number of slave handles

Configures the maximum number of SPI slaves that will be opened simultaneously.

CPU_SIZE_T

SPI_ConfigureSlaveHandleQty()

Unlimited number of slave handles

.SlaveHandleQty

SPI Slave Configurations#

Opening an SPI slave is done by calling the function SPI_SlaveOpen(). This function takes one configuration argument which is described below.

p_slave_info#

p_slave_info is a pointer to a configuration structure of type SPI_SLAVE_INFO. Its purpose is to provide the SPI module with basic information regarding the slave, such as clock frequency, frame size, etc.

Table - SPI_SLAVE_INFO Configuration Structure in the SPI Slave Configurations page describes each configuration field available in this configuration structure.

Table - SPI_SLAVE_INFO Configuration Structure#

Field

Description

.Mode

SPI communication mode. Allows you to configure the CPHA and CPOL mode. This field is a bitmap. Possible values are: - SERIAL_SPI_BUS_MODE_CPHA_BIT - SERIAL_SPI_BUS_MODE_CPOL_BIT

.BitsPerFrame

Number of bits per frame.

.LSbFirst

Indicates if Least Significant Bits (LSB) must be transferred first.

.SClkFreqMax

Clock frequency, in hertz.

.SlaveID

ID of the slave. Refer to your SPI BSP to find out the ID of the SPI slave you want to use.

.TxDummyByte

When calling SPI_SlaveRx(), dummy bytes must be transferred to the slave to generate a clock. This configures the value of the dummy byte.

.ActiveLow

Indicates if the Slave Select (or Chip Select) signal must be low when the SPI slave is selected. Most SPI slaves are active low so this should generally be set to DEF_YES.

SPI Programming Guide#

Initial Setup of SPI Module#

This section describes the basic steps required to initialize the SPI module and to add an SPI controller.

Initializing the IO-SPI Module#

The first step is to initialize the SPI module core. This is done by calling the function IO_Init().

Listing - Example of Call to SPI_Init() in the Initial Setup of SPI Module page shows an example of a call to SPI_Init().

Listing - Example of Call to SPI_Init()#
RTOS_ERR  err;

SPI_Init(&err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}

Adding Your SPI Bus Controller(s)#

Once you successfully initialized the IO-SPI module, you can start adding your SPI Bus controller(s). This is done by calling the function SPI_BusAdd(). This function must be called for each SPI Bus controller you want to add.

Listing - Example of Call to SPI_BusAdd() in the Initial Setup of SPI Module page shows an example of a call to SPI_BusAdd() using default arguments. In this example, SPI controller "spi0" is added to the IO-SPI module. For more information on how to register an SPI Bus controller (if you have to write your own BSP), see SPI Controller Registration to the Platform Manager .

Listing - Example of Call to SPI_BusAdd()#
RTOS_ERR        err;
SPI_BUS_HANDLE  spi_handle;

spi_handle = SPI_BusAdd("spi0",
                        &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}

Starting Your SPI Bus Controller(s)#

Once you successfully added your SPI Bus controller(s), you must start it/them. This is done by calling the function SPI_BusStart(). This function must be called for each SPI Bus controller you added.

Listing - Example of Call to SPI_BusStart() in the Initial Setup of SPI Module page gives an example of call to SPI_BusStart().

Listing - Example of Call to SPI_BusStart()#
RTOS_ERR  err;

SPI_BusStart(spi_handle,
            &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}

SPI Slaves Management#

This section describes the different operations related to SPI slaves.

Adding a Slave#

Before communicating with a slave, the slave must be opened on the bus that was previously added. Opening a slave is done by calling the function SPI_SlaveOpen().

Listing - Example of Call to SPI_SlaveOpen() in the SPI Slaves Management page shows an example of a call to SPI_SlaveOpen() that will open a slave. For more information on the configuration arguments to pass to SPI_SlaveOpen(), see SPI Slave Configurations .

Listing - Example of Call to SPI_SlaveOpen()#
RTOS_ERR          err;
SPI_SLAVE_HANDLE  slave_handle;
SPI_SLAVE_INFO    slave_info;

slave_info.Mode         = SERIAL_SPI_BUS_MODE_CPHA_BIT | SERIAL_SPI_BUS_MODE_CPOL_BIT;
slave_info.BitsPerFrame = 8u;
slave_info.LSbFirst     = DEF_YES;
slave_info.SClkFreqMax  = 400000u;
slave_info.SlaveID      = 0u;
slave_info.TxDummyByte  = 0xFFu;
slave_info.ActiveLow    = DEF_YES;
slave_handle = SPI_SlaveOpen(bus_handle,
                            &slave_info,
                            &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occured. Error handling should be added here. */
}

Selecting/Deselecting a Slave#

It is possible to explicitly select and deselect a slave. This is done by calling the functions SPI_SlaveSel() and SPI_SlaveDesel().

While a slave is selected, it is not possible to select another slave on the bus, and only the task that selected the slave can communicate with it.

Note that it is not mandatory to explicitly select and deselect a slave via the functions SPI_SlaveSel() and SPI_SlaveDesel() before communicating with it. If the slave has not been selected before, it will automatically be selected temporarily for the time of the communication operation.

Listing - Example of Call to SPI_SlaveSel() and SPI_SlaveDesel() in the SPI Slaves Management page shows an example of a call to SPI_SlaveSel() and SPI_SlaveDesel().

Listing - Example of Call to SPI_SlaveSel() and SPI_SlaveDesel()#
RTOS_ERR  err;

SPI_SlaveSel(slave_handle,
             5000u,                         /* 5 sec timeout. 0 for infinite.           */
             SPI_OPT_NONE,
            &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}

/* ... Communication operations with the slave...   */

SPI_SlaveDesel(slave_handle,
              &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}

Communicating with a Slave#

Receiving Data#

Receiving data from a slave is done by calling the function SPI_SlaveRx(). Listing - Example of Call to SPI_SlaveRx() in the SPI Slaves Management page shows an example of a call to SPI_SlaveRx().

Listing - Example of Call to SPI_SlaveRx()#
RTOS_ERR    err;
CPU_INT08U  rx_buf[64u];

SPI_SlaveRx(slave_handle,
            rx_buf,
            64u,
            5000u,
           &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}
Transmitting Data#

Transmitting data to a slave is done by calling the function SPI_SlaveTx(). Listing - Example of Call to SPI_SlaveTx() in the SPI Slaves Management page shows an example of a call to SPI_SlaveTx().

Listing - Example of Call to SPI_SlaveTx()#
RTOS_ERR    err;
CPU_INT08U  tx_buf[] = "Data to transmit\r\n";

SPI_SlaveTx(slave_handle,
            tx_buf,
            sizeof(tx_buf),
            5000u,
           &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}
Transferring Data#

Transferring data with a slave (transmit and receive data simultaneously) is done by calling the function SPI_SlaveXfer(). Listing - Example of Call to SPI_SlaveXfer() in the SPI Slaves Management page shows an example of a call to SPI_SlaveXfer().

Listing - Example of Call to SPI_SlaveXfer()#
RTOS_ERR    err;
CPU_INT08U  rx_buf[18u];
CPU_INT08U  tx_buf[] = "Data to transmit\r\n";

SPI_SlaveXfer(slave_handle,
              rx_buf,
              tx_buf,
              sizeof(tx_buf),
              5000u,
             &err);
if (err.Code != RTOS_ERR_NONE) {
    /* An error occurred. Error handling should be added here. */
}

SPI Hardware Porting Guide#

The Micrium OS IO-SPI module uses a hardware driver that can be customized for any SPI controller using a Board Support Package (BSP). The BSP has two purposes:

  • It initializes and configures any resources needed by the SPI controller, but which are provided by an external module.

  • It provides hardware information to the SPI driver.

We provide example BSPs for some popular platforms. If one is available for your platform, we recommend that you use it as a starting point. However, if no example BSP is available for your platform, the information in this section will help you understand how to correctly port the SPI module to your platform.

Note that each SPI controller (that you are planning to use) will require a BSP, and all the steps described in this section must be performed for each of them.

SPI BSP Functions Guide#

A Board Support Package contains a set of functions that support your specific hardware platform on Micrium OS. These functions are called by the SPI driver to perform hardware configuration or initialization of IO pins, interrupts, etc. It is your responsibility to either create these functions from scratch, or to modify example code to fit your needs and the specifics of your hardware.

Each of these functions is described below.

Initialize#

The first function you should implement is a generic initialization function. This function is called by the driver only once at initialization. Listing - Init Function Signature in the SPI BSP Functions Guide page shows the signature of this function.

Listing - Init Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_Init (SERIAL_CTRLR_ISR_HANDLE_FNCT   isr_fnct,
                                      SERIAL_DRV                    *p_ser_drv);

Its purpose is to initialize and allocate resources that will be necessary to the SPI controller.

The initialization function receives two arguments: isr_fnct and p_ser_drv, which are used when handling interrupts from the SPI controller. The argument isr_fnct is a pointer to a driver function that must be called when an SPI interrupt occurs, and this driver function takes p_ser_drv as an argument. So it is important to save these as global variables in your BSP.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Clock Configuration#

The clock configure function is called by the driver when the SPI controller is started. Implementing this function is not mandatory if the SPI controller clock does not require configuration. Listing - Clock Configure Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Clock Configure Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_ClkCfg (void);

Its purpose is to configure the source clock of the SPI controller.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

I/O Configuration#

The I/O configure function is called by the driver when the SPI controller is started. Implementing this function is not mandatory if the SPI controller I/O does not require configuration. Listing - IO Configure Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - IO Configure Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_IO_Cfg (void);

Its purpose is to configure the I/O pins for the SPI controller.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Interrupt Configuration#

The interrupt configure function is called by the driver when the SPI controller is started. Listing - Interrupt Configure Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Interrupt Configure Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_IntCfg (void);

Its purpose is to configure the interrupt(s) of the SPI controller.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Power Configuration#

The power configure function is called by the driver when the SPI controller is started. Implementing this function is not mandatory if the SPI controller power supply does not require configuration. Listing - Power Configure Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Power Configure Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_PwrCfg (void);

Its purpose is to configure the power of the SPI controller.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Start#

The start function is called by the driver each time the SPI controller is started. It is always called after all the "configure" functions. Implementing this function is not mandatory if there is nothing further to do when the controller is started. Listing - Start Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Start Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_Start (void);

Its main purpose is to perform any operation required when the SPI controller is started.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Stop#

The stop function is called by the driver when the SPI controller is stopped. Implementing this function is not mandatory if there is nothing further to do when the controller is stopped. Listing - Stop Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Stop Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_Stop (void);

Its purpose is to perform any operation required when the SPI controller is stopped.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Slave Select#

The slave select function is called by the driver when a slave is selected by the application. Listing - Slave Select Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Slave Select Function Signature#
CPU_BOOLEAN  BSP_Serial_<ctrlr>_SlaveSel (const  SERIAL_SLAVE_INFO  *p_slave_info);

Its purpose is to perform any operation required when a slave is selected.

Implementing this function is not mandatory if the SPI's driver supports built-in slave select lines. Otherwise, you have to implement it yourself; for example, by using the GPIO interface to control the slave selection.

The function receives one argument of type SERIAL_SLAVE_INFO which provide information regarding the slave. Table below shows a description of each filed available in this structure.

Field

Description

.Addr

Address/ID of the slave.

.ActiveLow

Flag that indicates if the slave is active when the Slave select line is low.

You must return DEF_OK from this function if it executes successfully, or DEF_FAIL, otherwise.

Slave Deselect#

The slave select function is called by the driver when a slave is deselected by the application. It has the same signature as the slave select function. Refer to the slave select function for more information.

Clock Frequency Get#

The clock frequency get function is called by the driver when a slave clock is configured. It is used to determine the clock divider that must be used to achieve the desired slave clock. The function must always return the clock frequency, in hertz, of the clock that feeds the SPI controller.

Listing - Clock Frequency Get Function Signature in the SPI BSP Functions Guide page shows the signature of the function.

Listing - Clock Frequency Get Function Signature#
CPU_INT32U  BSP_Serial_<ctrlr>_ClkFreqGet (void);

ISR Handling#

Each SPI controller driver has an ISR handler function that must be called each time a SPI interrupt is triggered. However, for most platforms, it will be necessary to implement an intermediate ISR handler in the BSP. This BSP ISR will then call the driver's ISR handler. This is necessary, as some interrupt controllers may require the interrupt status to be cleared each time it is triggered. It is also necessary if your interrupt controller does not support passing an argument to the interrupt vector, as the driver's ISR handler takes the p_ser_drv received in the Init () function of the BSP as an argument.

Also, some driver will require the interrupt event source to be passed to the ISR handler as the second argument. Here are the possible event values:

  • SERIAL_CTRLR_ISR_SRC_NONE

  • SERIAL_CTRLR_ISR_SRC_RX

  • SERIAL_CTRLR_ISR_SRC_TX

  • SERIAL_CTRLR_ISR_SRC_ERR

  • SERIAL_CTRLR_ISR_SRC_DMA_CMPL_RX

  • SERIAL_CTRLR_ISR_SRC_DMA_CMPL_TX

You may browse a Gecko SDK SPI driver demo in Simplicity Studio to view an example of how the ISR handler source event should be handled.

Listing - Example of BSP ISR Implementation in the SPI BSP Functions Guide page shows an example of an ISR handler implemented in the BSP if using a Silicon Labs chip (that follows the CMSIS naming standard).

Listing - Example of BSP ISR Implementation#
void DMA_IRQHandler(void)
{
    /* TODO: Clear interrupt status, if needed. */

    OSIntEnter();
    BSP_Serial_<ctrlr>_DrvISR_Handler(BSP_Serial_<ctrlr>_DrvPtr,
                                      SERIAL_CTRLR_ISR_SRC_NONE);
    OSIntExit();
}

SPI Hardware Information#

The SPI driver requires information about the SPI controller on your MCU, which you provide using a structure of type SERIAL_CTRLR_DRV_INFO. The controller information can be found in the manual for your MCU.

This structure is used by the macro IO_SERIAL_CTRLR_REG() to register the SPI controller with the Platform Manager . For more details about using the macro IO_SERIAL_CTRLR_REG(), refer to the page SPI Controller Registration to the Platform Manager .

The main SPI hardware information structure contain other structures which are explained in the sections below.

Hardware Driver Information#

The structure SERIAL_CTRLR_HW_INFO provides information about the SPI controller's characteristics. This structure is referenced from the main structure SERIAL_CTRLR_DRV_INFO.

Table - SERIAL_CTRLR_HW_INFO Structure in the SPI Hardware Information page describes each configuration field available in this structure.

Table - SERIAL_CTRLR_HW_INFO Structure#

Field

Description

.SupportedMode

Communication mode(s) supported by the controller. Should be set to SERIAL_CTRLR_MODE_SPI.

.BaseAddr

Base address of the SPI controller registers set.

.InfoExtPtr

Pointer to an extended hardware information structure. Some driver may require extra information. The format of this structure is hence specific to your driver. Most of the time this can be set to DEF_NULL.

If need to create an additional information structure, you can browse Simplicity Studio for an example of a Gecko SDK SPI.

For example, the Gecko SDK SPI driver requires that you select the port where the SPI controller is routed. The port selection is set in a custom hardware information structure of type SERIAL_CTRLR_HW_INFO_EXT_SILICONLABS. The structure itself is defined in serial_drv.h. You should refer to your chip's reference manual and to your board's schematic to find out which port you should use.

Below is an example of initializing and assigning an additional information structure.

const  SERIAL_CTRLR_HW_INFO_EXT_SILICONLABS  BSP_Serial_USART0_SPI_ExtHwInfo = {
  .PortLocationRx = 1u,
  .PortLocationTx = 1u,
  .PortLocationClk = 1u,
  .PortLocationCs = 1u
};

const  SERIAL_CTRLR_DRV_INFO  BSP_Serial_SiliconLabs_Bus0_DrvInfo = {
    .HW_Info.SupportedMode =  SERIAL_CTRLR_MODE_SPI,
    .HW_Info.BaseAddr      =  0x4000C000u,
    .HW_Info.InfoExtPtr    = &BSP_Serial_USART0_SPI_ExtHwInfo,
    .BSP_API_Ptr           = &BSP_Serial_USART0_SPI_API,
    .DrvAPI_Ptr            = &Serial_CtrlrDrv_API_SiliconLabsGeckoSDK
};

BSP API Structure#

In order to provide a pointer to the BSP functions for the SPI controller driver, you must create a structure of type SERIAL_CTRLR_BSP_API.

Table - SERIAL_CTRLR_BSP_API Structure in the SPI Hardware Information page describes each field available in this structure.

Table - SERIAL_CTRLR_BSP_API Structure#

Field

Description

.Init

Pointer to the BSP initialization function.

.ClkEn

Pointer to the BSP clock enable function.

.IO_Cfg

Pointer to the BSP I/O configure function.

.IntCfg

Pointer to the BSP interrupt configure function.

.PwrCfg

Pointer to the BSP power configure function.

.Start

Pointer to the BSP start function.

.Stop

Pointer to the BSP stop function.

.SlaveSel

Pointer to the BSP slave select function.

.SlaveDesel

Pointer to the BSP slave deselect function.

.ClkFreqGet

Pointer to the BSP clock frequency get function.

.DMA_API_Ptr

For future considerations. Must always be set to DEF_NULL.

.BSP_API_ExtPtr

Pointer to extended BSP API functions that could be required by your driver. Most of the time this can be set to DEF_NULL .

Listing - Example of BSP API Structure in the SPI Hardware Information page shows an example of a BSP API structure.

Listing - Example of BSP API Structure#
const  SERIAL_CTRLR_BSP_API  BSP_Serial_<ctrlr>_API = {
    .Init           = BSP_Serial_<ctrlr>_Init,
    .ClkEn          = BSP_Serial_<ctrlr>_ClkEn,
    .IO_Cfg         = BSP_Serial_<ctrlr>_IO_Cfg,
    .IntCfg         = BSP_Serial_<ctrlr>_IntCfg,
    .PwrCfg         = BSP_Serial_<ctrlr>_PwrCfg,
    .Start          = BSP_Serial_<ctrlr>_Start,
    .Stop           = BSP_Serial_<ctrlr>_Stop,
    .SlaveSel       = BSP_Serial_<ctrlr>_SlaveSel,
    .SlaveDesel     = BSP_Serial_<ctrlr>_SlaveDesel,
    .ClkFreqGet     = BSP_Serial_<ctrlr>_ClkFreqGet,
    .DMA_API_Ptr    = DEF_NULL,
    .BSP_API_ExtPtr = DEF_NULL
};

Device Hardware Information#

The last step is to create the main device hardware information structure.

Table - SERIAL_CTRLR_DRV_INFO Structure in the SPI Hardware Information page describes each configuration field available in this structure.

Table - SERIAL_CTRLR_DRV_INFO Structure#

Field

Description

.HW_Info

Structure explained at step hardware driver information .

.DrvAPI_Ptr

Pointer to the driver API structure you are using with your driver. Some drivers may provide more than one API structure.

.BSP_API_Ptr

Pointer to the BSP API structure you created at step BSP API Structure .

SPI Controller Registration to the Platform Manager#

Once the hardware information structure for your SPI controller is ready, it must be registered with the Platform Manager . This should typically be done using the BSP_OS_Init() function that is located in the file bsp_os.c.

There is a macro located in the file serial.h that you can call to register an SPI controller. The macro is named IO_SERIAL_CTRLR_REG().

Listing - Example of SPI Controller Registration in the SPI Controller Registration to the Platform Manager page shows an example of how to register a SPI Device controller.

Listing - Example of SPI Controller Registration#
#include  <rtos_description.h>
#ifdef RTOS_MODULE_IO_SERIAL_AVAIL
#include  <rtos/io/include/serial.h>
#endif

#if defined(RTOS_MODULE_IO_SERIAL_AVAIL)                            (1)
BSP_HW_INFO_EXT(const  SERIAL_CTRLR_DRV_INFO, BSP_Serial_<ctrlr>_DrvInfo);
#endif

void  BSP_OS_Init (void)
{

    /* ... */

                                                      /* ------------- REGISTER SPI CONTROLLERS ------------- */
#if defined(RTOS_MODULE_IO_SERIAL_AVAIL)
    IO_SERIAL_CTRLR_REG("spi0",
                        &BSP_Serial_<ctrlr>_DrvInfo);
#endif
}

(1) Since the hardware information global variables are declared in another file, you must declare them as external in your bsp_os.c file. Always use the BSP_HW_INFO_EXT() macro.