SPI (serial peripheral interface bus)

  1. General Introduction
  2. API Overview
  3. Initializing and configuring SPI
  4. Transmitting Data over SPI
  5. Transmitting Data Full code Example
  6. Receiving Data over SPI
  7. Receiving Data Full Code Example

General Introduction

SPI is short for Serial Peripheral Interface and is a synchronous serial bus specification to transfer data between integrated circuits. It uses four lines for the data transmission and integrates the roles of master and slave. With the selection through a chip select line, multiple slave devices can be connected to one master. The data transmission is a full-duplex synchronous communication between the master and multiple slaves.

The serial lines of SPI are named and have a certain functionality as follows:

  • Clock: Serial Clock controlled by the master device and provided to the slave device for synchronized data exchange.
  • CS (Chip Select): Chip select is controlled by the master device and as active low line an indication that the master is sending data to or requesting data from the corresponding slave device.
  • MOSI (Master Out Slave In): Data transmission from the master device to the slave device. The MOSI line on the master device needs to be connected to the MOSI line on the slave device.
  • MISO (Master In Slave Out): Data transmission from the slave device to the master device. The MISO line on the master device needs to be connected to the MISO line on the slave device.

Possible SPI data rates are between 600 and 230400 bps.

Additional configurable parameters for SPI are:

  • Baud rate: The Baud rate is a measure of the speed of data transfer at which data can be transferred and incoming data is read. It is expressed in bits per second (bps).
  • SPI mode: The SPI mode allows manipulation over the clock polarity and the rising or falling edge to modify when incoming or outgoing data is sampled. For a detailed information about which modes are available on the XDK, please refer to the interface BSP_ExtensionPort.h.
  • SPI bit order: The SPI bit order parameter allows the configuration about the sampled byte order of outgoing or incoming data stream is represented with the most significant bit or the least significant bit first.

API Overview

It is generally recommended to develop an application based on the highest API level the XDK framework supports, although it is possible to access deeper API levels if the highest level does not provide the functionality required for a specific purpose.

As with most XDK functionalities, there is an API allowing simple access to necessary functions for developing applications that use the serial bus protocols on the Extension Bus. For that the BSP and Utils API provided by the Platform layer will be used.

The picture below illustrates the interfaces for UART, SPI and I2C from the corresponding API.

Image

Initializing and configuring SPI

This section describes the steps necessary to configure and initialize the SPI module to transmit and receive data. It also covers the necessary configuration for the baud rate, the SPI mode and the bit order. For that, the following interfaces will be used.

#include "BCDS_MCU_SPI.h"
#include "BSP_ExtensionPort.h"
#include "semphr.h"

The BSP_ExtensionPort.h is mainly used to connect and configure the SPI interface on the Extension Bus. With it, the configuration of the Baud rate, the SPI mode and the SPI bit order will be implemented.

The following Table shows an excerpt of the functions used to implement a working SPI connection.

FunctionDescription
BSP_ExtensionPort_Connect()This function is called to enable the power control for the extension bus and disables all GPIO pins to preserve power
BSP_ExtensionPort_ConnectSpi()This function is called to configure the corresponding SPI MOSI, MISO and Clock pins on the extension bus
BSP_ExtensionPort_SetSpiConfig()This function is called to configure SPI settings, such as Baud rate, SPI mode and SPI bit order
BSP_ExtensionPort_GetSpiHandle()This function returns the handle for the SPI communication holding all relevant configuration data
BSP_ExtensionPort_EnableSpi()This function is called to enable the SPI module for transmitting and reading data
BSP_ExtensionPort_ConnectGpio()This function is called to connect to a certain GPIO pin on the extension bus
BSP_ExtensionPort_SetGpioConfig()This function is called to set configuration for a specific GPIO pin on the extension bus
BSP_ExtensionPort_EnableGpio()This function is called to enable a specific GPIO pin on the extension bus
BSP_ExtensionPort_ClearGpio()This function is called to clear a state on a certain GPIO pin
BSP_ExtensionPort_SetGpio()This function is called to set a state on a certain GPIO pin

The interface BCDS_MCU_SPI.h will be used to initialize SPI on driver level and to transmit and to receive data.

FunctionDescription
MCU_SPI_Initialize()This function is used to initialize the SPI interface on driver level
MCU_SPI_Send()This function is used to transmit data over the SPI interface
MCU_SPI_Receive()This function is used to receive data from the SPI interface

Furthermore, the interface semphr.h is used to add control logic to the implementation of SPI, which will be shown in the next chapter, via the functions xSemaphoreTake(), xSemaphoreGiveFromISR() and xSemaphoreCreateBinary().

The code below shows an example for configuring SPI with the BSP_ExtensionPort.h.

BSP_ExtensionPort_Connect();
BSP_ExtensionPort_ConnectSpi();
BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_BAUDRATE, UINT32_C(2000000), NULL);
BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_MODE, BSP_EXTENSIONPORT_SPI_MODE0, NULL);
BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_BIT_ORDER, BSP_EXTENSIONPORT_SPI_MSB_FIRST, NULL);

First, the function BSP_ExtensionPort_Connect() is called to enable the power control on the Extension Bus and to disable all pins on the Extension Bus. Afterwards, the function BSP_ExtensionPort_ConnectSpi() configures the SPI pins PB4, PB3 and PB5 on the Extension Bus to be used as MISO, MOSI and Clock pins.

Afterwards, BSP_ExtensionPort_SetSpiConfig() is called to configure the baud rate, the SPI mode and the SPI bit order As for this outline, a baud rate of 2000000, the SPI mode 0 and the bit order with the most significant bit first is chosen.

Now the initialization of the SPI module at the driver level can begin. For that, a global variable of the type HWHandle_T and SemaphoreHandle_t, as shown in the following code, need to be declared.

HWHandle_T SpiHandle = NULL;
static SemaphoreHandle_t spiCompleteSync = NULL;

The global variable of the type HWHandle_t will be used to store all SPI related configuration and initialization data. The global variable of the type SemaphoreHandle_t will be used for the control logic for sending and receiving data.

Afterwards, the callback function for the SPI initialization can be implemented. For that, a callback function as shown in the code below can be used.

void SpiAppCallback(SPI_T spi, struct MCU_SPI_Event_S event){
  BCDS_UNUSED(spi);
  if ((event.TxComplete) || (event.RxComplete)){
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    if (xSemaphoreGiveFromISR(spiCompleteSync, &xHigherPriorityTaskWoken) == pdTRUE){
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
  }
}

The code adds control logic to the triggered events, when a SPI data frame is transmitted or received via the SPI module. A semaphore is inserted for controlled receiving and transmitting of data. After the semaphore spiCompleteSync is released, new data can be transmitted or received. This will be further explained and cleared up in the context of a later code snippet.

Now, the SPI module on driver level can be initialized. The following shows how this can be done using the previously implemented callback.

SpiHandle = BSP_ExtensionPort_GetSpiHandle();
MCU_SPI_Initialize(SpiHandle, SpiAppCallback);

First, the function BSP_ExtensionPort_GetSpiHandle() is used to store all configurations made with the BSP_ExtensionPort.h interface in the global variable SpiHandle. Then this variable and the callback function, which was declared in the previous code snippet, are passed into the function MCU_SPI_Initialize() to initialize the SPI module.

Now, the SPI module itself is configured, including the MISO, MOSI and Clock line. Only the CS line is left to be configured. For that, configuration applied to the Extension Bus pin PB8 needs to be done. The following code shows outlines how to do that.

BSP_ExtensionPort_ConnectGpio(BSP_EXTENSIONPORT_GPIO_PD8);
BSP_ExtensionPort_SetGpioConfig(
        BSP_EXTENSIONPORT_GPIO_PD8,
        BSP_EXTENSIONPORT_GPIO_PINMODE,
        (uint32_t) BSP_EXTENSIONPORT_PUSHPULL, NULL);
BSP_ExtensionPort_EnableGpio(BSP_EXTENSIONPORT_GPIO_PD8);

First, we connect to the GPIO pin PD8 on the Extension Bus with the function BSP_ExtensionPort_ConnectGpio(). After that, configuration to that pin is applied via the function BSP_ExtensionPort_SetGpioConfig() to act as pushpull output.

Afterwards, the pin is enabled by using the function BSP_ExtensionPort_EnableGpio().

Now the pin can be used as chip select line to enable the data transmission between the master and slave.

For that, two functions are implemented to set the chip select level to high and low. The next two code snippets show an outline of the corresponding implementations.

void SpiSetCSLow(void){
  BSP_ExtensionPort_ClearGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

The function SpiSetCSLow is structured simply and only acts as a wrapper for the function call BSP_ExtensionPort_ClearGpio(). The function BSP_ExtensionPort_ClearGpio() on sets the voltage level on the GPIO pin PD8 to active low.

void SpiSetCSHigh(void){
  BSP_ExtensionPort_SetGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

As before, the function SpiSetCSHigh acts as a wrapper for the function BSP_ExtensionPort_SetGpio(). This sets the voltage level on the GPIO pin PD8 to high.

Both of these functions from the two previous code snippets will be used in the upcoming two chapters for transmitting and receiving data via the SPI module.

Transmitting Data over SPI

Assuming that all configuration and initialization is done, data can be transmitted via SPI. For that, a function is implemented, which can work with sensors or other slave devices attached to the Extension Bus.

void SpiWriteRegister(uint8_t regAddr, uint8_t *writeVal, uint32_t writeLength){

  Retcode_T sendReturn;
  SpiSetCSLow();
  if ((writeLength > 0) && (spiCompleteSync != NULL)){
    sendReturn = MCU_SPI_Send(SpiHandle, &regAddr, writeLength);
    if(sendReturn == RETCODE_OK){
      xSemaphoreTake(spiCompleteSync, UINT32_C(0));
    }

    sendReturn = MCU_SPI_Send(SpiHandle, writeVal, writeLength);
    if(sendReturn == RETCODE_OK){
      xSemaphoreTake(spiCompleteSync, UINT32_C(0));
    }
  }
  SpiSetCSHigh();
}

The function outlined in Code 23 takes three parameters, a register address called regAddr, a pointer to the buffer containing the data to be transmitted called writeVal, and the length of the data buffer called writeLength.

The body of the function defines first a variable of the type Retcode_T, this variable will be used to store the return value of the function MCU_SPI_Send(). Afterwards, the function SpiSetCSLow(), which was implemented in Code 21, is called to set the voltage level of the chip select line to low. This informs the connected slave device that the master is about to send or read data to or from it. Afterwards, an if condition is used to check if the length of the transmit data is is greater than zero and if the spiCompleteSync semaphore is not null.

Then the register address itself and its length is send via the function SPI_MCU_Send(). The function takes a handle from the type SPI_T containing all information regarding the SPI interface, the send byte or buffer, and its length as parameters.

Please note: since only one byte is sent in both write attempts, the write length is identical.

Afterwards, the return value of the function SPI_MCU_Send() is evaluated. If the evaluation is okay, then the function xSemaphoreTake() is called using the spiCompleteSync semaphore with a second parameter, which is used as timeout. In this case here, the parameter given to the function is zero to indicate that no timeout is used.

By taking the semaphore, it is ensured that no previous transmission over SPI is still processed because the semaphore would not be available if that would be the case. The semaphore is then release by the implemented event for event.TxComplete in the function SpiAppCallback(). Then the procedure is done once more to send the write value.

Afterwards, the chip select line is raised to a high voltage level by using the function SpiSetCSHigh(), as implemented at the end of chapter Initializing and Configuring SPI.

The function SpiWriteRegister can now be called to transmit data via SPI. An example for this is shown in the following implementation.

void appInitSystem(void * CmdProcessorHandle, uint32_t param2)
{
  if (CmdProcessorHandle == NULL)
  {
    printf("Command processor handle is null \n\r");
    assert(false);
  }
  BCDS_UNUSED(param2);
  vTaskDelay(5000);

  // This code omits initialization and configuration for brevity
  // Please ensure that SPI is initialized and configured correctly here

  // CS must be high initially
  SpiSetCSHigh();
  uint8_t regAddr = 0x01;
  uint8_t regWriteVal = 0x09;

  uint8_t dataSize = UINT8_C(1);
  SpiWriteRegister(regAddr, &regWriteVal, dataSize);
}

Here the function SpiWriteRegister() is simply called, by passing a register address, a value written to the register and the length of the value into it.

An alternative approach to send data to the slave device would be achieved by lowering the chip select line to a low voltage level and then call MCU_SPI_Send() to transmit then desired data to the slave device. Afterwards, only raising the voltage level on the chip select line to high would be required.

Transmitting Data Full code Example

Note: The full code example is intended for XDK-Workbench versions 3.4.0 and higher.

/* --------------------------------------------------------------------------- |
 * INCLUDES & DEFINES ******************************************************** |
 * -------------------------------------------------------------------------- */

/* own header files */
#include "XdkAppInfo.h"
#undef BCDS_MODULE_ID  /* Module ID define before including Basics package*/
#define BCDS_MODULE_ID XDK_APP_MODULE_ID_APP_CONTROLLER

/* own header files */
#include "AppController.h"

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"
#include "task.h"

#include "BCDS_MCU_SPI.h"
#include "BSP_ExtensionPort.h"
#include "semphr.h"

/* --------------------------------------------------------------------------- |
 * HANDLES ******************************************************************* |
 * -------------------------------------------------------------------------- */

static CmdProcessor_T * AppCmdProcessor;
static xTaskHandle AppControllerHandle = NULL;

HWHandle_T SpiHandle = NULL;
static SemaphoreHandle_t spiCompleteSync = NULL;

/* --------------------------------------------------------------------------- |
 * EXECUTING FUNCTIONS ******************************************************* |
 * -------------------------------------------------------------------------- */

void SpiAppCallback(SPI_T spi, struct MCU_SPI_Event_S event){
  BCDS_UNUSED(spi);
  if ((event.TxComplete) || (event.RxComplete)){
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    if (xSemaphoreGiveFromISR(spiCompleteSync, &xHigherPriorityTaskWoken) == pdTRUE){
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
  }
}

void SpiDriver_Init(void){
    SpiHandle = BSP_ExtensionPort_GetSpiHandle();
    MCU_SPI_Initialize(SpiHandle, SpiAppCallback);
}

void SpiPinPB8(void){
    BSP_ExtensionPort_ConnectGpio(BSP_EXTENSIONPORT_GPIO_PD8);
    BSP_ExtensionPort_SetGpioConfig(
            BSP_EXTENSIONPORT_GPIO_PD8,
            BSP_EXTENSIONPORT_GPIO_PINMODE,
            (uint32_t) BSP_EXTENSIONPORT_PUSHPULL, NULL);
    BSP_ExtensionPort_EnableGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

void SpiSetCSLow(void){
  BSP_ExtensionPort_ClearGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

void SpiSetCSHigh(void){
  BSP_ExtensionPort_SetGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

void SpiWriteRegister(uint8_t regAddr, uint8_t *writeVal, uint32_t writeLength){

  Retcode_T sendReturn;
  SpiSetCSLow();
  if ((writeLength > 0) && (spiCompleteSync != NULL)){
    sendReturn = MCU_SPI_Send(SpiHandle, &regAddr, writeLength);
    if(sendReturn == RETCODE_OK){
      xSemaphoreTake(spiCompleteSync, UINT32_C(0));
    }

    sendReturn = MCU_SPI_Send(SpiHandle, writeVal, writeLength);
    if(sendReturn == RETCODE_OK){
      xSemaphoreTake(spiCompleteSync, UINT32_C(0));
    }
  }
  SpiSetCSHigh();
}

static void AppControllerFire(void* pvParameters)
{
    BCDS_UNUSED(pvParameters);

    vTaskDelay(5000);
    // CS must be high initially
    SpiSetCSHigh();
    uint8_t regAddr = 0x01;
    uint8_t regWriteVal = 0x09;

    uint8_t dataSize = UINT8_C(1);
    SpiWriteRegister(regAddr, &regWriteVal, dataSize);

    vTaskDelete(NULL);
}

/* --------------------------------------------------------------------------- |
 * BOOTING- AND SETUP FUNCTIONS ********************************************** |
 * -------------------------------------------------------------------------- */

static void AppControllerEnable(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    if (RETCODE_OK == retcode)
    {
        if (pdPASS != xTaskCreate(AppControllerFire, (const char * const ) "AppController", TASK_STACK_SIZE_APP_CONTROLLER, NULL, TASK_PRIO_APP_CONTROLLER, &AppControllerHandle))
        {
            retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES);
        }
    }

    if (RETCODE_OK != retcode) {
        printf("AppControllerEnable : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

static void AppControllerSetup(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    BSP_ExtensionPort_Connect();
    BSP_ExtensionPort_ConnectSpi();
    BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_BAUDRATE, UINT32_C(2000000), NULL);
    BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_MODE, BSP_EXTENSIONPORT_SPI_MODE0, NULL);
    BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_BIT_ORDER, BSP_EXTENSIONPORT_SPI_MSB_FIRST, NULL);

    SpiDriver_Init();
    SpiPinPB8();

    retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerEnable, NULL, UINT32_C(0));

    if (RETCODE_OK != retcode) {
        printf("AppControllerSetup : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

void AppController_Init(void * cmdProcessorHandle, uint32_t param2)
{
    BCDS_UNUSED(param2);

    Retcode_T retcode = RETCODE_OK;

    if (cmdProcessorHandle == NULL)
    {
        printf("AppController_Init : Command processor handle is NULL \r\n");
        retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER);
    }
    else
    {
        AppCmdProcessor = (CmdProcessor_T *) cmdProcessorHandle;
        retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerSetup, NULL, UINT32_C(0));
    }

    if (RETCODE_OK != retcode)
    {
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

Receiving Data over SPI

Assuming that all configuration and initialization is done, data can be received via SPI. For that, a function called SpiReadRegister() is implemented as follows.

void SpiReadRegister(uint8_t regAddr, uint8_t *readVal, uint32_t readLength){
  // CS pin Set to Low
  SpiSetCSLow();

  Retcode_T recvReturn;
  uint8_t readData = 0xFF;
  if ((readLength > 0) && (spiCompleteSync != NULL)){
    recvReturn = MCU_SPI_Send(SpiHandle, &regAddr, readLength);
    if (recvReturn == RETCODE_OK){
      xSemaphoreTake(spiCompleteSync, SPI_DATA_TRANSFER_TIMEOUT_MILLISEC);
    }

    recvReturn = MCU_SPI_Receive(SpiHandle, &readData, readLength);
    if (recvReturn == RETCODE_OK) {
      xSemaphoreTake(spiCompleteSync, SPI_DATA_TRANSFER_TIMEOUT_MILLISEC);
      *readVal = readData;
    }
  }
  // CS pin Set to High
  SpiSetCSHigh();
}

The function takes three parameters: A register address from where the data is read called regAddr, a pointer which will store the read data called readVal, and the length of read data called readLength. The function body itself calls the function SpiSetCSLow() to drive the chip select line to low to inform the slave device that a data transmission is initiated.

First, the local variable readData is declared and initialized. This variable will temporarily hold the incoming data received over SPI. Then, an if condition is used to check if the read length is greater than zero and if the spiCompleteSync semaphore is not null. After that, the function MCU_SPI_Send() is called to initiate data transmission. As inputs, the SPI handle, the register address and the read length are used, to inform the slave device from which register a value should be read.

Afterwards, the return value of this function call is evaluated. If the return value corresponds to RETCODE_OK, then the function xSemaphoreTake() is called on spiCompleteSync to block the function until sending is completed. If sending is not completed within 10ms - as specified by the macro SPI_DATA_TRANSFER_TIMEOUT_MILLISEC - a timeout is issued.

After sending is completed, either successfully or due to the timeout, the function MCU_SPI_Receive() is called after the semaphore spiCompleteSync had been released by a TX complete event. This function receives the SPI handle, the local variable readData for storing the incoming data, and the read length.

Again, the return value is evaluated and if RETCODE_OK is returned, xSemaphoreTake() is called on the semaphore spiCompleteSync.

Afterwards, the value of the variable readData, which now contains the data received by the SPI module, is stored in readVal, as a return value of the function. Finally, the chip select pin is raised back to a high voltage level by calling SpiSetCSHigh(), which indicates that the SPI transmission is over.

A call of the function SpiReadRegister() could then be used as shown in the following code snippet.

void appInitSystem(void * CmdProcessorHandle, uint32_t param2)
{
  if (CmdProcessorHandle == NULL)
  {
    printf("Command processor handle is null \n\r");
    assert(false);
  }
  BCDS_UNUSED(param2);
  vTaskDelay(5000);

  // This code omits initialization and configuration for brevity
  // Please ensure that SPI is initialized and configured correctly here

  // CS must be high initially
  SpiSetCSHigh();
  uint8_t regAddr = 0x02;
  uint8_t regReadVal = 0x00;

  uint8_t dataSize = UINT8_C(1);
  SpiReadRegister(regAddr, &regReadVal, dataSize);
}

Here the function SpiReadRegister() is called, passing a register address, a pointer to a variable that stores the incoming data from the register, the length of the data to be read as inputs.

An alternative approach to read incoming data from the slave device would be easily achieved, by only lowering the chip select line to a low voltage level and then call MCU_SPI_Receive() to read the incoming data from the slave device. Afterwards only raising the voltage level on the chip select line to high would be required.

While this example uses read / write buffers with a size of one byte, the functions MCU_SPI_Send() and MCU_SPI_Receive() also allow that multiple bytes are read and written within one call. The code must be adapted accordingly.

Receiving Data Full Code Example

Note: The full code example is intended for XDK-Workbench versions 3.4.0 and higher.

/* --------------------------------------------------------------------------- |
 * INCLUDES & DEFINES ******************************************************** |
 * -------------------------------------------------------------------------- */

/* own header files */
#include "XdkAppInfo.h"
#undef BCDS_MODULE_ID  /* Module ID define before including Basics package*/
#define BCDS_MODULE_ID XDK_APP_MODULE_ID_APP_CONTROLLER

/* own header files */
#include "AppController.h"

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"
#include "task.h"

#include "BCDS_MCU_SPI.h"
#include "BSP_ExtensionPort.h"
#include "semphr.h"

/* --------------------------------------------------------------------------- |
 * HANDLES ******************************************************************* |
 * -------------------------------------------------------------------------- */

static CmdProcessor_T * AppCmdProcessor;/**< Handle to store the main Command processor handle to be used by run-time event driven threads */

static xTaskHandle AppControllerHandle = NULL;/**< OS thread handle for Application controller to be used by run-time blocking threads */

HWHandle_T SpiHandle = NULL;
static SemaphoreHandle_t spiCompleteSync = NULL;

/* --------------------------------------------------------------------------- |
 * VARIABLES ***************************************************************** |
 * -------------------------------------------------------------------------- */

#define SPI_DATA_TRANSFER_TIMEOUT_MILLISEC 10;

/* --------------------------------------------------------------------------- |
 * EXECUTING FUNCTIONS ******************************************************* |
 * -------------------------------------------------------------------------- */

void SpiAppCallback(SPI_T spi, struct MCU_SPI_Event_S event){
  BCDS_UNUSED(spi);
  if ((event.TxComplete) || (event.RxComplete)){
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    if (xSemaphoreGiveFromISR(spiCompleteSync, &xHigherPriorityTaskWoken) == pdTRUE){
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
  }
}

void SpiDriver_Init(void){
    SpiHandle = BSP_ExtensionPort_GetSpiHandle();
    MCU_SPI_Initialize(SpiHandle, SpiAppCallback);
}

void SpiPinPB8(void){
    BSP_ExtensionPort_ConnectGpio(BSP_EXTENSIONPORT_GPIO_PD8);
    BSP_ExtensionPort_SetGpioConfig(
            BSP_EXTENSIONPORT_GPIO_PD8,
            BSP_EXTENSIONPORT_GPIO_PINMODE,
            (uint32_t) BSP_EXTENSIONPORT_PUSHPULL, NULL);
    BSP_ExtensionPort_EnableGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

void SpiSetCSLow(void){
  BSP_ExtensionPort_ClearGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

void SpiSetCSHigh(void){
  BSP_ExtensionPort_SetGpio(BSP_EXTENSIONPORT_GPIO_PD8);
}

void SpiReadRegister(uint8_t regAddr, uint8_t *readVal, uint32_t readLength){
  // CS pin Set to Low
  SpiSetCSLow();

  Retcode_T recvReturn;
  uint8_t readData = 0xFF;
  if ((readLength > 0) && (spiCompleteSync != NULL)){
    recvReturn = MCU_SPI_Send(SpiHandle, &regAddr, readLength);
    if (recvReturn == RETCODE_OK){
      xSemaphoreTake(spiCompleteSync, SPI_DATA_TRANSFER_TIMEOUT_MILLISEC);
    }

    recvReturn = MCU_SPI_Receive(SpiHandle, &readData, readLength);
    if (recvReturn == RETCODE_OK) {
      xSemaphoreTake(spiCompleteSync, SPI_DATA_TRANSFER_TIMEOUT_MILLISEC);
      *readVal = readData;
    }
  }
  // CS pin Set to High
  SpiSetCSHigh();
}

static void AppControllerFire(void* pvParameters)
{
    BCDS_UNUSED(pvParameters);

    vTaskDelay(5000);
    // CS must be high initially
    SpiSetCSHigh();
    uint8_t regAddr = 0x02;
    uint8_t regReadVal = 0x00;

    uint8_t dataSize = UINT8_C(1);
    SpiReadRegister(regAddr, &regReadVal, dataSize);

    vTaskDelete(NULL);
}

/* --------------------------------------------------------------------------- |
 * BOOTING- AND SETUP FUNCTIONS ********************************************** |
 * -------------------------------------------------------------------------- */

static void AppControllerEnable(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    if (RETCODE_OK == retcode)
    {
        if (pdPASS != xTaskCreate(AppControllerFire, (const char * const ) "AppController", TASK_STACK_SIZE_APP_CONTROLLER, NULL, TASK_PRIO_APP_CONTROLLER, &AppControllerHandle))
        {
            retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES);
        }
    }

    if (RETCODE_OK != retcode) {
        printf("AppControllerEnable : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

static void AppControllerSetup(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    BSP_ExtensionPort_Connect();
    BSP_ExtensionPort_ConnectSpi();
    BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_BAUDRATE, UINT32_C(2000000), NULL);
    BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_MODE, BSP_EXTENSIONPORT_SPI_MODE0, NULL);
    BSP_ExtensionPort_SetSpiConfig(BSP_EXTENSIONPORT_SPI_BIT_ORDER, BSP_EXTENSIONPORT_SPI_MSB_FIRST, NULL);

    SpiDriver_Init();
    SpiPinPB8();

    retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerEnable, NULL, UINT32_C(0));

    if (RETCODE_OK != retcode) {
        printf("AppControllerSetup : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

void AppController_Init(void * cmdProcessorHandle, uint32_t param2) {

    BCDS_UNUSED(param2);

    Retcode_T retcode = RETCODE_OK;

    if (cmdProcessorHandle == NULL)
    {
        printf("AppController_Init : Command processor handle is NULL \r\n");
        retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER);
    }
    else
    {
        AppCmdProcessor = (CmdProcessor_T *) cmdProcessorHandle;
        retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerSetup, NULL, UINT32_C(0));
    }

    if (RETCODE_OK != retcode)
    {
        Retcode_RaiseError(retcode);
        assert(0);
    }
}