5. Code Overview

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

5.1. Header Files

In main.c file, add the following header files:

#include "hw_pdc.h"
#include "ad_spi.h"
#include "hw_wkup.h"
#include "hw_sys.h"
#include <stdlib.h>
#include "peripheral_setup.h"
#include "platform_devices.h"

5.2. System Init Code

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

/* Task priorities */
#define  mainSPI_TASK_PRIORITY      ( OS_TASK_PRIORITY_NORMAL )

/* Enable/disable asynchronous SPI operations */
#define SPI_ASYNC_EN                (1)


/* Retained symbols */
__RETAINED static OS_EVENT signal_MCP4822;
__RETAINED static OS_EVENT signal_MCP4822_async;

/* Error code returned upon an SPI operation */
__RETAINED static int SPI_error_code;

/* SPI task handle */
__RETAINED static OS_TASK prvSPITask_h;


uint32_t pdc_wkup_combo_id  __attribute__((unused));

/*
 * Task functions
 */
static void prvSPITask_MCP_4822(void *pvParameters);

static void system_init(void *pvParameters)
{
        OS_BASE_TYPE status;

        REG_SETF(GPREG, DEBUG_REG, SYS_CPU_FREEZE_EN, 0);

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

        /*
         * 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 XTAL32M has
         * settled and, maybe, the PLL is locked.
         */
        cm_sys_clk_init(sysclk_XTAL32M);
        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();

#if defined CONFIG_RETARGET
        retarget_init();
#endif /* CONFIG_RETARGET */


        OS_EVENT_CREATE(signal_MCP4822);
        OS_EVENT_CREATE(signal_MCP4822_async);

        /*
         * Upon a wakeup cycle, wait for the XTAL32M crystal to settle.
         * BLE, USB and UART blocks require the XTAL32M to be up and
         * running to work properly.
         */
        pm_set_wakeup_mode(true);


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

        /*
         * Set the desired wakeup mode.
         *
         * \warning When set is Ultra-Fast wakeup mode, sleep voltage should be 0.9V
         *          and not less than that.
         *
         **/
        pm_set_sys_wakeup_mode(pm_sys_wakeup_mode_fast);

        /* SPI task  */
        status = OS_TASK_CREATE("SPI",      /* The text name assigned to the task, for
                                               debug only; not used by the kernel. */
                        prvSPITask_MCP_4822,  /* The function that implements the task. */
                        NULL,                 /* The parameter passed to the task. */
                        1024 * OS_STACK_WORD_SIZE, /* Stack size allocated for the task in
                                                      bytes. */
                        mainSPI_TASK_PRIORITY,  /* The priority assigned to the task. */
                        prvSPITask_h );         /* The task handle. */
        OS_ASSERT(status == OS_TASK_CREATE_SUCCESS);

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

5.3. Task Code for MCP4822 DAC Module

In main.c file and after system_init(), add the following code used for interacting with the MCP4822 DAC module externally connected on SPI1 bus:

/*
 * MCP4822 DAC Module Configuration Macros
 ***********************************************************************************
 */

/* MCP4822 control bits masks */
#define MCP4822_AB_CONTROL_BIT_Msk     (0x8000)
#define MCP4822_GA_CONTROL_BIT_Msk     (0x2000)
#define MCP4822_SHDN_CONTROL_BIT_Msk   (0x1000)


/* Get the mask of an MCP48822 control bit  */
#define MCP4822_GET_MSK(x)  MCP4822_ ## x ## _CONTROL_BIT_Msk


/* MCP4822 control bits */
typedef enum {
        MCP4822_AB_CONTROL_BIT_RESET   = 0, /* Select DACA channel */
        MCP4822_GA_CONTROL_BIT_RESET   = 0, /* Output gain 1x */
        MCP4822_SHDN_CONTROL_BIT_RESET = 0, /* Shutdown the selected DAC channel  */
        MCP4822_AB_CONTROL_BIT_SET     = MCP4822_GET_MSK(AB),  /* Select DACB channel */
        MCP4822_GA_CONTROL_BIT_SET     = MCP4822_GET_MSK(GA),  /* Output gain 2x */
        /* Activate the selected DAC channel */
        MCP4822_SHDN_CONTROL_BIT_SET   = MCP4822_GET_MSK(SHDN)
} MCP4822_CONTROL_BITS;


/* Set the MCP4822 2-byte register */
#define MCP4822_SET_REG(AB_BIT, GA_BIT, SHDN_BIT, DATA, VAR)  VAR = ((VAR & 0x0) | \
                                                            ((DATA & 0xFFF) | \
                                                            (AB_BIT | GA_BIT | SHDN_BIT)))


#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, uint16_t num_of_data)
{
       /* Signal [prvSPITask_MCP_4822] that time for resuming has elapsed */
       OS_EVENT_SIGNAL_FROM_ISR(signal_MCP4822_async);
}
#endif


/* Function used for writing data to the DAC module */
static void dac_data_writer(spi_device dev, uint16_t val)
{

        /* Open the SPI device */
        ad_spi_handle_t spi_dev = ad_spi_open((ad_spi_controller_conf_t *)dev);

        /* Enable the target SPI device */
        ad_spi_activate_cs(spi_dev);


#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.
         */
        SPI_error_code = ad_spi_write(spi_dev, (const uint8_t *)&reg_val,
                                                            sizeof(uint16_t));
#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
         */
        SPI_error_code = ad_spi_write_async(spi_dev, (const uint8_t *)&val,
                                                sizeof(uint16_t), spi_mcp_4822_cb, NULL);

        /*
         * 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_MCP4822_async, OS_EVENT_FOREVER);
#endif

         /* Disable the target SPI device */
         ad_spi_deactivate_cs(spi_dev);

         /* Close the SPI device */
         ad_spi_close(spi_dev, true);


         /*  Print on the serial console the status of the SPI operation */
         if (SPI_error_code == 0) {
                 printf("Value sent %04X\n\r", val);
         } else {
                 printf("\n\rUnsuccessful SPI write operation with error code: %d\n\r",
                                                                         SPI_error_code);
         }
}

/**
 * @brief Task responsible for performing SPI related operations
 */
static void prvSPITask_MCP_4822(void *pvParameters)
{
        uint16_t reg_val;
        uint16_t analog_output = 0;

        printf("\n\r***SPI Demonstration Example***\n\r");

        /*
         * 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();

        /* Configure the MCP4822 DAC module  */
        MCP4822_SET_REG(MCP4822_AB_CONTROL_BIT_SET, /* Select DAC channel B (bit 15) */
                        /* Maximum analog output: 2048 mV (bit 13) */
                        MCP4822_GA_CONTROL_BIT_SET,
                        /* Activate the selected DAC channel (bit 12) */
                        MCP4822_SHDN_CONTROL_BIT_SET,
                        /* Analog output of the selected DAC channel (bits 11-0) */
                        analog_output,
                        reg_val); /* Returned value used for setting the MCP4822 module */

        for (;;) {

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

                /* Perform an SPI write operation */
                dac_data_writer(DAC_MC4822_DEVICE, reg_val);

                /*
                 * Select an arbitrary value for the analog output of the selected
                 * DAC channel.
                 */
                analog_output = (uint16_t)rand();

                /* Configure the MCP4822 DAC module */
                MCP4822_SET_REG(MCP4822_AB_CONTROL_BIT_SET,
                                MCP4822_GA_CONTROL_BIT_SET,
                                MCP4822_SHDN_CONTROL_BIT_SET,
                                analog_output, reg_val);
        }
}

5.4. Wake-Up Timer Code

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

/* WKUP KEY interrupt handler */
static void wkup_cb(void)
{

        /* Clear the WKUP interrupt flag!!! */
        hw_wkup_reset_interrupt();


        /*
         * Avoid using printf() within ISR context!!! It may crash your code.
         * Instead, use notifications to notify a task to perform an action!!
         */

        /*
         * Notify [prvSPITask_MCP_4822] task that time for performing SPI operations
         * has elapsed.
         */
        OS_EVENT_SIGNAL_FROM_ISR(signal_MCP4822);

}


/* Initialize the WKUP controller */
static void wkup_init(void)
{

        /* Initialize the WKUP controller */
        hw_wkup_init(NULL);

        /*
         * Set debounce time expressed in ms. Maximum allowable value is 63 ms.
         * A value set to 0 disables the debounce functionality.
         */
        hw_wkup_set_debounce_time(10);

        /*
         * Enable interrupts produced by the KEY block of the wakeup controller (debounce
         * circuitry) and register a callback function to hit following a KEY event.
         */
        hw_wkup_register_key_interrupt(wkup_cb, 1);


        /*
         * Set the polarity (rising/falling edge) that triggers the WKUP controller.
         *
         * \note The polarity is applied both to KEY and GPIO blocks of the controller
         *
         */
        hw_wkup_configure_pin(KEY1_PORT, KEY1_PIN, 1, HW_WKUP_PIN_STATE_LOW);


        /* Enable interrupts of WKUP controller */
        hw_wkup_enable_irq();
}

5.5. Hardware Initialization

In main.c file, replace the prvSetupHardware() routine with the following code used for configuring the pins utilized by the wake-up controller.

/**
 * @brief Hardware Initialization
 */
static void prvSetupHardware(void)
{
        /*
         * The IRQ produced by the KEY sub block of the wakeup controller (debounced
         * IO IRQ) is multiplexed with other trigger sources (VBUS IRQ, SYS2CMAC IRQ,
         * JTAG present) in a single PDC peripheral trigger ID
         * (HW_PDC_PERIPH_TRIG_ID_COMBO).
         */
#if !defined(CONFIG_USE_BLE) && (!dg_configENABLE_DEBUGGER) && (!dg_configUSE_SYS_CHARGER)

        pdc_wkup_combo_id = hw_pdc_add_entry(HW_PDC_LUT_ENTRY_VAL(
                                                            HW_PDC_TRIG_SELECT_PERIPHERAL,
                                                            HW_PDC_PERIPH_TRIG_ID_COMBO,
                                                            HW_PDC_MASTER_CM33, 0));
        OS_ASSERT(pdc_wkup_combo_id != HW_PDC_INVALID_LUT_INDEX);

        /*  */
        hw_pdc_set_pending(pdc_wkup_combo_id);
        hw_pdc_acknowledge(pdc_wkup_combo_id);
#endif

        wkup_init();

        /* Init hardware */
        pm_system_init(periph_init);


        /* Enable the COM power domain before handling any GPIO pin */
        hw_sys_pd_com_enable();

        ad_spi_io_config(((ad_spi_controller_conf_t *)DAC_MC4822_DEVICE)->id,
                ((ad_spi_controller_conf_t *)DAC_MC4822_DEVICE)->io, AD_IO_CONF_OFF);


        /* Configure the KEY1 push button on Pro DevKit */
        HW_GPIO_SET_PIN_FUNCTION(KEY1);
        HW_GPIO_PAD_LATCH_ENABLE(KEY1);

        /* Lock the mode of the target GPIO pin */
        HW_GPIO_PAD_LATCH_DISABLE(KEY1);

        /* Disable the COM power domain after handling the GPIO pins */
        hw_sys_pd_com_disable();
}

5.6. Macro Definitions

In config/custom_config_xxx.h file, add the following macro definitions to enable the SPI related API:

/*******************************************************
 * Peripheral specific config
 */
#define dg_configSPI_ADAPTER                    (1)
#define dg_configUSE_HW_SPI                     (1)

5.7. Device Configurations

In peripheral_setup.h file, add macro definitions for the GPIO pins used by the target application:

/**
 * SPI 1 configuration
 */
#define DAC_MC4822_DO_PORT      ( HW_GPIO_PORT_0 )
#define DAC_MC4822_DO_PIN       ( HW_GPIO_PIN_26 )

#define DAC_MC4822_DI_PORT      ( HW_GPIO_PORT_0 )
#define DAC_MC4822_DI_PIN       ( HW_GPIO_PIN_27 )

#define DAC_MC4822_CLK_PORT     ( HW_GPIO_PORT_0 )
#define DAC_MC4822_CLK_PIN      ( HW_GPIO_PIN_28 )


/* SPI chip-select pin(s) */
#define DAC_MC4822_CS_PORT      ( HW_GPIO_PORT_0 )
#define DAC_MC4822_CS_PIN       ( HW_GPIO_PIN_29 )


/**
 * SPI 2 configuration
 */

In platform_devices.c file, add IO bus as well as driver configurations for the SPI device(s) used. These settings will be applied when an application task attempts to interact with the target SPI slave device.

#include <ad_spi.h>
#include "peripheral_setup.h"
#include "platform_devices.h"

/*
 * PLATFORM PERIPHERALS GPIO CONFIGURATION
 *****************************************************************************************
 */

#if dg_configSPI_ADAPTER || dg_configUSE_HW_SPI

/* SPI chip-select pins */
static const ad_io_conf_t spi_master_cs[] = {{

        .port = DAC_MC4822_CS_PORT,
        .pin  = DAC_MC4822_CS_PIN,
        .on = {
                .mode     = HW_GPIO_MODE_OUTPUT_PUSH_PULL,
                .function = HW_GPIO_FUNC_SPI_EN,
                .high     = true
        },
        .off = {
                .mode     = HW_GPIO_MODE_OUTPUT_PUSH_PULL,
                .function = HW_GPIO_FUNC_SPI_EN,
                .high     = true
        }},
};

/* SPI1 IO */
const ad_spi_io_conf_t bus_SPI1 = {

        .spi_do = {
                .port = DAC_MC4822_DO_PORT,
                .pin  = DAC_MC4822_DO_PIN,
                .on   = {HW_GPIO_MODE_OUTPUT_PUSH_PULL, HW_GPIO_FUNC_SPI_DO, false},
                .off  = {HW_GPIO_MODE_INPUT,            HW_GPIO_FUNC_GPIO,   true},
        },
        .spi_di = {
                .port = DAC_MC4822_DI_PORT,
                .pin  = DAC_MC4822_DI_PIN,
                .on   = {HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_SPI_DI, false},
                .off  = {HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_GPIO,   true},
        },
        .spi_clk = {
                .port = DAC_MC4822_CLK_PORT,
                .pin  = DAC_MC4822_CLK_PIN,
                .on   = {HW_GPIO_MODE_OUTPUT_PUSH_PULL, HW_GPIO_FUNC_SPI_CLK, false},
                .off  = {HW_GPIO_MODE_INPUT,            HW_GPIO_FUNC_GPIO,    true},
        },

        /*
         * The number of pins in spi_master_cs array.
         *
         * \warning When the SPI bus is used by SNC \p cs_cnt must be always 1
         */
        .cs_cnt = 1,
        .spi_cs = spi_master_cs,

        .voltage_level = HW_GPIO_POWER_V33
};


/* External sensor/module SPI driver */
const ad_spi_driver_conf_t drv_SPI1 = {
        .spi = {
                .cs_pad = {DAC_MC4822_CS_PORT, DAC_MC4822_CS_PIN},
                .word_mode = HW_SPI_WORD_16BIT, /* 2-byte mode */
                .smn_role  = HW_SPI_MODE_MASTER,
                .polarity_mode = HW_SPI_POL_LOW,
                .phase_mode    = HW_SPI_PHA_MODE_0,
                .mint_mode = HW_SPI_MINT_DISABLE,
                .xtal_freq = HW_SPI_FREQ_DIV_8,
                .fifo_mode = HW_SPI_FIFO_RX_TX,
                .disabled  = 0, /* Should be disabled during initialization phase */
                .ignore_cs = false,
                .use_dma   = true,
                .rx_dma_channel = HW_DMA_CHANNEL_0,
                .tx_dma_channel = HW_DMA_CHANNEL_1
        }
};


/* Sensor/module device configuration */
const ad_spi_controller_conf_t dev_SPI_CUSTOM_DEVICE = {
        .id  = HW_SPI1,
        .io  = &bus_SPI1,
        .drv = &drv_SPI1
};



spi_device DAC_MC4822_DEVICE = &dev_SPI_CUSTOM_DEVICE;

#endif /* dg_configSPI_ADAPTER || dg_configUSE_HW_SPI */

In platform_devices.h file, add the following definitions:

#include <ad_spi.h>
#include "peripheral_setup.h"

#ifdef __cplusplus
extern "C" {
#endif

#define _SPI_CUSTOM_DEVICE_


#if dg_configSPI_ADAPTER || dg_configUSE_HW_SPI


/**
 * \brief SPI device handle
 */
typedef const void* spi_device;


#endif /* dg_configSPI_ADAPTER || dg_configUSE_HW_SPI */



/* List of devices */
extern spi_device DAC_MC4822_DEVICE;


#ifdef __cplusplus
}
#endif