5. Code Overview

This section provides the code blocks needed to successfully execute this tutorial.

5.1. Header Files

In main.c, add the following header files:

#include "ad_spi.h"

#include "hw_wkup.h"
#include <platform_devices.h>

5.2. System Init Code

In main.c, replace system_init() with the following code:

/*
 * Macro for enabling asynchronous SPI transactions.
 *
 * Valid values:
 * 0: SPI transactions will follow a synchronous scheme
 * 1: SPI transactions will follow an asynchronous scheme
 *
 */
#define SPI_ASYNC_EN     (0)

/* OS signals used for synchronizing OS tasks*/
static OS_EVENT signal_echo;
static OS_EVENT signal_mcp_4822;

/* SPI Task priority */
#define mainSPI_TASK_PRIORITY       ( OS_TASK_PRIORITY_NORMAL )

/*
 * SPI application tasks - Function prototype
 */
static void prvSPITask_ECHO( void *pvParameters );
static void prvSPITask_MCP_4822( void *pvParameter );

static void system_init( void *pvParameters )
{
        OS_TASK task_h = NULL;

        /* Handler for SPI freeRTOS tasks */
        OS_TASK echo_h    = NULL;
        OS_TASK mcp4822_h = NULL;

#if defined CONFIG_RETARGET
        extern void retarget_init(void);
#endif

        /*
         * Prepare clocks. Note: cm_cpu_clk_set() and cm_sys_clk_set() can be called only from a
         * task since they will suspend the task until the XTAL16M has settled and, maybe, the PLL
         * is locked.
         */
        cm_sys_clk_init(sysclk_XTAL16M);
        cm_apb_set_clock_divider(apb_div1);
        cm_ahb_set_clock_divider(ahb_div1);
        cm_lp_clk_init();

        /* Prepare the hardware to run this demo. */
        prvSetupHardware();

        /* init resources */
        resource_init();

#if defined CONFIG_RETARGET
        retarget_init();
#endif

        /* Set the desired sleep mode. */
        pm_set_sleep_mode(pm_mode_extended_sleep);


        /* Initialize the OS event signals */
        OS_EVENT_CREATE(signal_echo);
        OS_EVENT_CREATE(signal_mcp_4822);

        /* Start main task here */
        OS_TASK_CREATE( "Template",                     /* The text name assigned to the task, for
                                                           debug only; not used by the kernel. */
                        prvTemplateTask,                /* The function that implements the task. */
                        NULL,                           /* The parameter passed to the task. */
                        200 * OS_STACK_WORD_SIZE,       /* The number of bytes to allocate to the
                                                           stack of the task. */
                        mainTEMPLATE_TASK_PRIORITY,     /* The priority assigned to the task. */
                        task_h );                       /* The task handle */
        OS_ASSERT(task_h);

        /* Suspend task execution */
        OS_TASK_SUSPEND(task_h);

        /*
         * Create an SPI task responsible for SPI loopback tests
         */
        OS_TASK_CREATE( "SPI_ECHO",

                         prvSPITask_ECHO,
                         NULL,
                         200 * OS_STACK_WORD_SIZE,

                         mainSPI_TASK_PRIORITY,
                         echo_h );
        OS_ASSERT(echo_h);

        /*
         * Create an SPI task responsible for controlling the
         * externally connected MCP4822 module.
         */
        OS_TASK_CREATE( "SPI_MCP_4822",

                         prvSPITask_MCP_4822,
                         NULL,
                         200 * OS_STACK_WORD_SIZE,

                         mainSPI_TASK_PRIORITY,
                         mcp4822_h );
        OS_ASSERT(mcp4822_h);

        /* the work of the SysInit task is done */
        OS_TASK_DELETE( xHandle );

}

5.3. Wake-Up Timer Code

In main.c, after system_init(), add the following code for handling external events via the wake-up controller:

/*
 * Callback function to be called after an external event is generated,
 * that is, after K1 button on the Pro DevKit is pressed.
 */
void wkup_cb(void)
{
        /*
         * This function must be called by any user-specified
         * interrupt callback, to clear the interrupt flag.
         */
        hw_wkup_reset_interrupt();


        /*
         * Notify the [prvSPITask_ECHO] task that time for performing
         * a loopback test has elapsed.
         */
        OS_EVENT_SIGNAL_FROM_ISR(signal_echo);
}

/*
 * Function which makes all the necessary initializations for the
 * wake-up controller
 */
static void init_wkup(void)
{
        /*
         * This function must be called first and is responsible
         * for the initialization of the hardware block.
         */
        hw_wkup_init(NULL);

        /*
         * Configure the pin(s) that can trigger the device to wake up while
         * in sleep mode. The last input parameter determines the triggering
         * edge of the pulse (event)
         */
        hw_wkup_configure_pin(HW_GPIO_PORT_1, HW_GPIO_PIN_6, true,
                                                       HW_WKUP_PIN_STATE_LOW);


        /*
         * This function defines a delay between the moment at which
         * a trigger event is present and the moment at which the controller
         * takes this event into consideration. Setting debounce time to [0]
         * hardware debouncing mechanism is disabled. Maximum debounce time is 63 ms.
         */
        hw_wkup_set_debounce_time(10);

// Check if the chip is either DA14680 or 81
#if dg_configBLACK_ORCA_IC_REV == BLACK_ORCA_IC_REV_A

        /*
         * Set threshold for event counter. Interrupt is generated after
         * the event counter reaches the configured value. This function
         * is only supported in DA14680/1 chips.
         */
        hw_wkup_set_counter_threshold(1);
#endif

        /* Register interrupt handler */
        hw_wkup_register_interrupt(wkup_cb, 1);
}

5.4. Hardware Initialization

In main.c, replace both periph_init() and prvSetupHardware() with the following code responsible for configuring pins after a power-up/wake-up cycle. Please note that every time the system enters sleep, it loses all its pin configurations.

/* SPI pin configurations */
static const gpio_config gpio_cfg[] = {

        // The system is set to [Master], so it outputs the clock signal
        HW_GPIO_PINCONFIG(HW_GPIO_PORT_3, HW_GPIO_PIN_0, OUTPUT, SPI_CLK, true),

        // Pin for capturing data
        HW_GPIO_PINCONFIG(HW_GPIO_PORT_3, HW_GPIO_PIN_1, INPUT,  SPI_DI,  true),

        // Pin for outputting data
        HW_GPIO_PINCONFIG(HW_GPIO_PORT_3, HW_GPIO_PIN_2, OUTPUT, SPI_DO,  true),

        /*
         * CS pin used when performing the loopback tests. Since the system is set
         * to [Master], it drives this line.
         */
        HW_GPIO_PINCONFIG(HW_GPIO_PORT_3, HW_GPIO_PIN_3, OUTPUT, GPIO,    true),

        /*
         * CS pin used when performing transactions with the MCP4822 module.
         * Since the system is set to [Master], it drives this line.
         */
        HW_GPIO_PINCONFIG(HW_GPIO_PORT_3, HW_GPIO_PIN_4, OUTPUT, GPIO,    true),

        HW_GPIO_PINCONFIG_END // This is critical for the correct termination of the structure
};

/**
 * @brief Initialize the peripherals domain after power-up.
 *
 */
static void periph_init(void)
{
#       if dg_configBLACK_ORCA_MB_REV == BLACK_ORCA_MB_REV_D
#               define UART_TX_PORT    HW_GPIO_PORT_1
#               define UART_TX_PIN     HW_GPIO_PIN_3
#               define UART_RX_PORT    HW_GPIO_PORT_2
#               define UART_RX_PIN     HW_GPIO_PIN_3
#       else
#               error "Unknown value for dg_configBLACK_ORCA_MB_REV!"
#       endif


        hw_gpio_set_pin_function(UART_TX_PORT, UART_TX_PIN, HW_GPIO_MODE_OUTPUT,
                        HW_GPIO_FUNC_UART_TX);
        hw_gpio_set_pin_function(UART_RX_PORT, UART_RX_PIN, HW_GPIO_MODE_INPUT,
                        HW_GPIO_FUNC_UART_RX);

        /* LED D2 on ProDev Kit for debugging purposes */
        hw_gpio_set_pin_function(HW_GPIO_PORT_1, HW_GPIO_PIN_5, HW_GPIO_MODE_OUTPUT,
                                                                      HW_GPIO_FUNC_GPIO);

        /* Configure the SPI pins */
        hw_gpio_configure(gpio_cfg);
}

/**
 * @brief Hardware Initialization
 */
static void prvSetupHardware( void )
{

        /* Init hardware */
        pm_system_init(periph_init);
        init_wkup();
 }

5.5. Task Code for MCP4822

Code snippet of the prvSPITask_MCP_4822 task responsible for interacting with the MCP4822 DAC module, externally connected on SPI1 bus. In main.c, add the following code (after system_init()):

/*
 * Data structure for the DAC4822 module
 */
static struct spi_mcp_4822 {

        // DAC4822 consists of a 2-byte register
        uint16_t data;

        // Control bits mask
        uint16_t mask;

        // Device handle
        spi_device spi_dev;

} spi_mcp_4822_t = {  // Create an instance of the above structure

        .data = 0x0000,

        // Control bits occupy the 4 MSbits and are set to '1'
        .mask = 0xF000,

        .spi_dev = NULL,
};

#if SPI_ASYNC_EN == 1

/*
 * Callback function called upon an SPI asynchronous transaction.
 *
 * \param[in] user_data  User data that can be passed and used within the function
 */
void spi_mcp_4822_cb( void *user_data )
{
       /*
        * Just to show how parameters can be passed
        * within callback functions!
        */
       struct spi_mcp_4822 *spi_param = (struct spi_mcp_4822 *)user_data;

       /*
        * Increment the analog output value of the DAC module. When data reaches
        * its maximum value, it wraps around starting from zero value.
        */
       spi_param->data += 10;


       /* Signal [prvSPITask_MCP_4822] that time for resuming has elapsed */
       OS_EVENT_SIGNAL_FROM_ISR(signal_mcp_4822);
}
#endif


/* Task responsible for controlling the MCP4822 module */
static void prvSPITask_MCP_4822(  void *pvParameters )
{
        /*
         * SPI adapter initialization should be done once at the beginning. Alternatively,
         * this function could be called during system initialization in system_init().
         */
        ad_spi_init();


        for (;;) {

                /* Suspend task's execution for 100 ms */
                OS_DELAY(OS_MS_2_TICKS(100));


                /*
                 * Set DAC's control bits using a mask.
                 */
                spi_mcp_4822_t.data |= spi_mcp_4822_t.mask;


                /*
                 * Turn on LED D2 on ProDev Kit indicating the start of a process
                 */
                hw_gpio_set_active(HW_GPIO_PORT_1, HW_GPIO_PIN_5);


                /*
                 * Open the device that will access the SPI bus
                 */
                spi_mcp_4822_t.spi_dev = ad_spi_open(MCP_4822);

#if SPI_ASYNC_EN == 0
                /*
                 * Perform a synchronous SPI write operation, that is, the task is blocking
                 * waiting for the transaction to finish. Upon transaction completion,
                 * the blocked task unblocks and resumes its operation.
                 */
                ad_spi_write(spi_mcp_4822_t.spi_dev, (uint8_t *)&spi_mcp_4822_t.data,
                                                                      sizeof(uint16_t));

                /*
                 * Increment the analog output value of the DAC module. When data reaches
                 * its maximum value, it wraps around starting from zero value.
                 */
                 spi_mcp_4822_t.data += 10;

#else
                /*
                 * Perform an asynchronous SPI write operation, that is, the task does not
                 * block waiting for the transaction to finish. Upon transaction completion
                 * callback function is triggered indicating the completion of the SPI operation
                 */
                 ad_spi_async_transact(spi_mcp_4822_t.spi_dev,
                           SPI_CSA, // Activate CS signal

                           /* Send some data... */
                           SPI_SND((uint8_t *)&spi_mcp_4822_t.data, sizeof(uint16_t)),

                           /* Declare callback function and data to be passed inside it. */
                           SPI_CB1(spi_mcp_4822_cb, &spi_mcp_4822_t),

                           SPI_CSD, // Deactivate CS signal
                           SPI_END
                      );

                 /*
                  * In the meantime and while SPI transactions are performed in the background,
                  * application task can proceed to other operations/calculation.
                  * It is essential that, the new operations do not involve SPI transactions
                  * on the already occupied bus!!!
                  */

                 /*
                  * Make sure that the current SPI operation has finished,
                  * blocking here forever.
                  */
                 OS_EVENT_WAIT(signal_mcp_4822, OS_EVENT_FOREVER);
#endif

                 /* Close the already opened device */
                 ad_spi_close(spi_mcp_4822_t.spi_dev);

                 /*
                  * Turn off LED D2 on ProDev Kit indicating the end of a process
                  */
                 hw_gpio_set_inactive(HW_GPIO_PORT_1, HW_GPIO_PIN_5);


        } // end of for()
} // end of task

5.6. Task Code for Loopback Test

Code snippet of the prvSPITask_ECHO task responsible for performing loopback tests. In main.c, add the following code (after prvSPITask_MCP_4822()):

/* Number of transmitted/received data */
#define BUFFER_SIZE 5

/*
 * Create an SPI task responsible for SPI loopback tests
 */
static void prvSPITask_ECHO( void *pvParamters )
{
        /* Buffer for storing the transmitted data */
        uint8_t wbuf[BUFFER_SIZE];

        /*  Buffer for storing the received data */
        uint8_t rbuf[BUFFER_SIZE];


        /* Configure the transfer scheme used during the SPI duplex transaction */
        spi_transfer_data spi_transfer_cfg = {
                .wbuf = (void *)wbuf,
                .rbuf = (void *)rbuf,
                .length = BUFFER_SIZE,
        };

        /* Initialize transmitted data - All data are set to 0x55 */
        memset(wbuf, 0x55, BUFFER_SIZE);

        /*
         * Initialization should be done once at the beginning. Alternatively,
         * this function could be called during system initialization in [system_init]
         * function.
         */
        ad_spi_init();

        spi_device spi_dev;

        for (;;) {

                /*
                 * Suspend task execution - As soon as WKUP callback function
                 * is triggered the task resumes its execution.
                 */
                OS_EVENT_WAIT(signal_echo, OS_EVENT_FOREVER);


                /* Open the device that will access the SPI bus */
                spi_dev = ad_spi_open(ECHO_LOOP);

                printf("\n\rSPI loopback operation...\n\r");

                /* Perform a duplex SPI transaction */
                ad_spi_complex_transact(spi_dev, &spi_transfer_cfg, 1);

                /* Close the already opened device */
                ad_spi_close(spi_dev);

                /*
                 * Check whether the read data matches written data. If there is a match
                 * [strncmp] returns [0].
                 */
                if (!strncmp((char *)spi_transfer_cfg.rbuf, (char *)spi_transfer_cfg.wbuf,
                                                                              BUFFER_SIZE)) {
                        printf("\n\rRead data matches written data!\n\r\n\r");
                } else {
                        printf("\n\rRead data does not match written data!\n\r\n\r");
                }

                /* Print out the received data */
                for (int i = 0; i < 5; i++) {
                        printf("\n\rRBUF[%d] = 0x%X\n\r", i,
                                            *( ((uint8_t *)spi_transfer_cfg.rbuf) + i) );
                }
                fflush(stdout);


        } // end of for()
} // end of task

5.7. Macro Definitions

In config/custom_config_qspi.h, add the following macro definitions:

/*
 * Enable the preferred devices connected on SPI bus, declared in [platform_devices.h]
 */
#define SPI_ECHO
#define SPI_MCP_4822

/*
 * Macros for enabling SPI operations using Adapters
 */
#define dg_configUSE_HW_SPI                     (1)
#define dg_configSPI_ADAPTER                    (1)

5.8. SPI Bus Configuration Macros

In the newly created platform_devices.h, add the following device configuration between SPI_BUS(SPI1) and SPI_BUS_END:

#ifdef SPI_ECHO
        SPI_SLAVE_DEVICE(SPI1, ECHO_LOOP, HW_GPIO_PORT_3, HW_GPIO_PIN_3, HW_SPI_WORD_8BIT,
                                                HW_SPI_POL_LOW, HW_SPI_PHA_MODE_0, HW_SPI_FREQ_DIV_2, -1);
#endif

#ifdef SPI_MCP_4822
        SPI_SLAVE_DEVICE(SPI1, MCP_4822, HW_GPIO_PORT_3, HW_GPIO_PIN_4, HW_SPI_WORD_16BIT,
                                                HW_SPI_POL_LOW, HW_SPI_PHA_MODE_0, HW_SPI_FREQ_DIV_2, -1);
#endif

Note

By default, the SDK comes with a few predefined device configurations in the platform_devices.h header file. Therefore, the developer should check whether an entry matches with a device connected to the controller.