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 "hw_wkup.h"

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

5.2. System Init Code

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

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

/* OS signals used for synchronizing OS tasks*/
static OS_EVENT siganl_i2c_eeprom;
static OS_EVENT signal_i2c_eeprom_async;

/*
 * Status flag used for indicating whether a read
 * or write I2C operation will be performed.
 */
volatile static bool i2c_status = 0;

/* I2C task priority */
#define mainI2C_TASK_PRIORITY             ( OS_TASK_PRIORITY_NORMAL )

/*
 * I2C application tasks - Function prototype
 */
static void prvI2CTask_EEPROM ( void *pvParameters );
static void poll_ack (i2c_device dev);

static void system_init( void *pvParameters )
{
        OS_TASK task_h = NULL;
        OS_TASK i2c_eeprom = 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


        /* Initialize the OS event signals */
        OS_EVENT_CREATE(siganl_i2c_eeprom);
        OS_EVENT_CREATE(signal_i2c_eeprom_async);

        /* 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 I2C task responsible for controlling the
         * externally connected 24LC256 EEPROM module.
         */
        OS_TASK_CREATE("EPPROM_24LC256",

                        prvI2CTask_EEPROM,
                        NULL,
                        200 * OS_STACK_WORD_SIZE,

                        mainI2C_TASK_PRIORITY,
                        i2c_eeprom );
        OS_ASSERT(i2c_eeprom);

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

       /*
        * Toggle I2C status:
        *
        * 1: a Write transaction will be performed
        * 0: a Read transaction will be performed
        */
        i2c_status ^= 1;

        /*
         * Notify [prvI2CTask_EEPROM] that time for
         * performing I2C operations has elapsed.
         */
        OS_EVENT_SIGNAL_FROM_ISR(siganl_i2c_eeprom);
}

/*
 * 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]
         * disables hardware debouncing mechanism. 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 to configure pins after a power-up/wake-up cycle. Please note that every time the system enters sleep, it loses all its pin configurations.

/* I2C 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_4, HW_GPIO_PIN_6, OUTPUT, I2C_SCL, true),

        // Bidirectional signal both for sending and receiving data
        HW_GPIO_PINCONFIG(HW_GPIO_PORT_4, HW_GPIO_PIN_7, INPUT,  I2C_SDA, true),

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

};

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

        /* This is a shortcut to configure multiple GPIOs in one call */
        hw_gpio_configure(gpio_cfg);
}

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

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

5.5. EEPROM ACK Code

Code snippet of the EEPROM polling ACK routine. In main.c, add the following code (after system_init()):

/*
 * Function for polling the EEPROM status. This function should be invoked
 * after a successful I2C write operation to check when the EEEPROM device
 * is ready to accept the next write/read cycle.
 */
static void poll_ack(i2c_device dev)
{
        bool no_ack;

        /* Get hardware ID */
        HW_I2C_ID id = ad_i2c_get_hw_i2c_id(dev);

        ad_i2c_device_acquire(dev);
        ad_i2c_bus_acquire(dev);

        do {

                /*
                 * Make sure TX ABORT interrupt status flag is reset.
                 * At the beginning, EEPROM will not ACK the address
                 * byte (the first byte sent by the master controller).
                 * So, a TX ABORT will be issued in I2C controller.
                 */
                hw_i2c_reset_int_tx_abort(id);

                /*
                 * Clear STOP interrupt status flag that is used
                 * for waiting ACK or NO ACK.
                 */
                hw_i2c_reset_int_stop_detected(id);

                /*
                 * We only need to check if EEPROM returns ACK for address byte, however we
                 * cannot simply send address byte since it's the controller who takes care
                 * of this once TX FIFO is filled with data. A simple solution is to send a
                 * dummy byte which  will be ignored by EEPROM but will make the I2C controller
                 * to generate a START condition and send the address byte.
                 */
                hw_i2c_write_byte(id, 0xAA);

                /*
                 * Wait until a STOP condition is detected and
                 * the corresponding status bit is asserted.
                 */
                while ((hw_i2c_get_raw_int_state(id) & HW_I2C_INT_STOP_DETECTED) == 0);

                /*
                 * Check whether transmission was successful. If EEPROM did not
                 * ACK the address byte, the appropriate abort source will be set.
                 */
                no_ack = (hw_i2c_get_abort_source(id) & HW_I2C_ABORT_7B_ADDR_NO_ACK);


        } while (no_ack);

        ad_i2c_bus_release(dev);
        ad_i2c_device_release(dev);
}

5.6. Task Code for 24LC256

Code snippet of the prvI2CTask_EEPROM task responsible for interacting with the 24LC256 EEPROM module, externally connected on I2C1 bus. In main.c, add the following code (after system_init()):

/*
 * Page size of the selected EEPROM module. This is device-specific information!
 * I2C operations must be restricted within a page size. For example,
 * 0x0000 - 0x0040, 0x0041 to 0x0080, 0x0081 to 0x00C0 etc.
 */
#define PAGE_SIZE  64

/*
 * Error code returned after an I2C operation. It
 * can be used to identify the reason of a failure.
 */
HW_I2C_ABORT_SOURCE error_code;


#if I2C_ASYNC_EN == 1
/*
 * Callback function for the I2C asynchronous transactions:
 *
 * \param [in] error      Error code returned at the end of an I2C transaction.
 * \param [in] user_data  User data that can be passed and used within the function.
 */
void i2c_eeprom_cb(void *user_data, HW_I2C_ABORT_SOURCE error)
{
        /* Read the error status code */
        error_code = error;

        /*
         * Signal the [prvI2CTask_EEPROM] task that time
         * for resuming has elapsed.
         */
        OS_EVENT_SIGNAL_FROM_ISR(signal_i2c_eeprom_async);
}
#endif


/* Task responsible for controlling the 24LC256 module */
static void prvI2CTask_EEPROM ( void *pvParamters )
{
        i2c_device i2c_dev;

        /* Starting address (2 bytes) */
        uint8_t starting_addr[2] = {0x00, 0x00}; // or {0x00, 0x41} or {0x00, 0x81}

        /*
         * Data to be transferred: 64 bytes raw data + 2 bytes for the starting address
         */
        uint8_t eeprom_wd[PAGE_SIZE + 2];

        /* Buffer for storing the received data */
        uint8_t eeprom_rd[PAGE_SIZE];

       /*
        * Half of data (32 bytes) is set to [0x55] and half to [0xAA].
        * Here you can declare your own preferred values.
        */
        memcpy(eeprom_wd,                                         starting_addr, sizeof(starting_addr));
        memset(eeprom_wd + sizeof(starting_addr),                 0x55,          (PAGE_SIZE/2));
        memset(eeprom_wd + sizeof(starting_addr) + (PAGE_SIZE/2), 0xAA,          (PAGE_SIZE/2));


        /*
         * I2C adapter initialization should be done once at the beginning. Alternatively,
         * this function could be called during system initialization in system_init().
         */
        ad_i2c_init();


        for (;;) {

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

                /*
                 * 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 I2C bus.
                 */
                i2c_dev =  ad_i2c_open(MEM_24LC256);


                /*
                 * Write a whole page (64 bytes) in EEPROM.
                 */
                if (i2c_status == 1) {

                        printf("\n\rI2C write...\n\r");

                        /*
                         * Write some data in EEPROM, starting from physical address 0x0000
                         */
                        error_code = ad_i2c_write(i2c_dev, eeprom_wd, sizeof(eeprom_wd));


                        /* Check the status of the I2C write operation */
                        if (error_code == 0) {
                                printf("\n\rSuccessful write operation!\n\r\n\r");

                              /*
                               * Wait until data is actually written in EEPROM cells!
                               */
                                poll_ack(i2c_dev);
                        } else {
                                printf("\n\rError in write operation with error code: %d!\n\r\n\r",
                                                                                       error_code);
                        }

                /*
                 * Read a whole page (64 bytes) from EEPROM
                 */
                } else {

                        printf("\n\rI2C read...\n\r");
/*
 * Perform the read operation synchronously!
 */
#if I2C_ASYNC_EN == 0
                        /*
                         * This function performs a write followed by a read transaction. An
                         * operation which is typical when reading data from I2C peripherals,
                         * where an address needs to be specified through a write before
                         * reading data.
                         */
                        error_code = ad_i2c_transact(i2c_dev, starting_addr,
                                          sizeof(starting_addr), eeprom_rd, sizeof(eeprom_rd));

/*
 * Perform the read operation asynchronously!
 */
#else
                       /* Perform an I2C read operation asynchronously */
                       ad_i2c_async_transact(i2c_dev,
                                             I2C_SND(starting_addr, sizeof(starting_addr)),
                                             I2C_RCV(eeprom_rd, sizeof(eeprom_rd)),
                                             I2C_CB(i2c_eeprom_cb),
                                             I2C_END
                                           );

                       /* Wait until the current I2C operation is finished */
                       OS_EVENT_WAIT(signal_i2c_eeprom_async, OS_EVENT_FOREVER);
#endif

                       /* Check the status of the I2C read operation */
                       if (error_code == 0) {
                               printf("\n\rSuccessful read transaction!\n\r");

                               /*
                                * Check if read data match written data. If there is a match
                                * [strncmp] returns [0]
                                */
                               if (!strncmp(((char *)eeprom_wd + 2), (char *)eeprom_rd, PAGE_SIZE)) {
                                       printf("\n\rRead data match written data!\n\r\n\r");
                               } else {
                                       printf("\n\rRead data do not match written data!\n\r\n\r");
                               }

                       } else {
                               printf("\n\rUnsuccessful read transaction with error code: %d!\n\r\n\r",
                                                                                            error_code);
                       }


                } // end of else()


                /* Close the already opened device */
                ad_i2c_close(i2c_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.7. Macro Definitions

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

/*
 * Enable the preferred devices, declared in "platform_devices.h"
 */
#define CONFIG_24LC256


/*
 * Macros for enabling I2C operations using Adapters
 */
#define dg_configUSE_HW_I2C                     (1)
#define dg_configI2C_ADAPTER                    (1)

Note

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