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 "ad_i2c.h"
#include "peripheral_setup.h"
#include "platform_devices.h"

5.2. System Init Code

In main.c file, add the task initialization code in the system_init() routine:

/* Start main task(s) here (text menu available via UART1 to control application) */
      OS_TASK_CREATE( "I2C master",            /* The text name assigned to the task, for
                                                 debug only; not used by the kernel. */
              i2c_master_task,                /* The function that implements the task. */
              NULL,                           /* The parameter passed to the task. */
              256 * 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. */
              i2c_task_m );                       /* The task handle */
      OS_ASSERT(i2c_task_m);

      OS_TASK_CREATE( "I2C slave",            /* The text name assigned to the task, for
                                                   debug only; not used by the kernel. */
              i2c_slave_task,                /* The function that implements the task. */
              NULL,                           /* The parameter passed to the task. */
              256 * 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. */
              i2c_task_s );                       /* The task handle */
      OS_ASSERT(i2c_task_s);
      /* the work of the SysInit task is done */
      OS_TASK_DELETE( xHandle );

5.3. 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 )
 {
     /* Init hardware */
     pm_system_init(periph_init);
     ad_i2c_io_config(((ad_i2c_controller_conf_t *)I2C_MASTER_DEVICE)->id,
                      ((ad_i2c_controller_conf_t *)I2C_MASTER_DEVICE)->io, AD_IO_CONF_ON);
     ad_i2c_io_config(((ad_i2c_controller_conf_t *)I2C_SLAVE_DEVICE)->id,
                      ((ad_i2c_controller_conf_t *)I2C_SLAVE_DEVICE)->io, AD_IO_CONF_ON);
  }

5.4. Macro Definitions

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

/*******************************************************
 * Peripheral specific config
 */
#define dg_configI2C_ADAPTER                    (1)
#define dg_configUSE_HW_I2C                     (1)
#define dg_configI2C_ADAPTER_SLAVE_SUPPORT      (1)

5.5. Device Configurations

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

/* I2X Interface */
 #define I2C_GPIO_LEVEL HW_GPIO_POWER_V33


 /* I2C */
 #define I2C_PORT    HW_GPIO_PORT_0

 #define I2C_MASTER_SCL_PIN  HW_GPIO_PIN_30
 #define I2C_MASTER_SDA_PIN  HW_GPIO_PIN_31

 #define I2C_SLAVE_SCL_PIN   HW_GPIO_PIN_18
 #define I2C_SLAVE_SDA_PIN   HW_GPIO_PIN_19

 #define I2C_SLAVE_ADDRESS    ( 0xA )

 #define I2C_AD_CONFIG_MASTER_I2C_CTRL       (HW_I2C1)
 #define I2C_AD_CONFIG_MASTER_DMA_CH         (HW_DMA_CHANNEL_2)
 #define I2C_AD_CONFIG_SLAVE_I2C_CTRL        (HW_I2C2)
 #define I2C_AD_CONFIG_SLAVE_DMA_CH          (HW_DMA_CHANNEL_4)

In platform_devices.h file, add the following definitions:

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

/**
 * \brief I2C device handle
 */
typedef const void* i2c_device;


/*
 * I2C DEVICES
 *****************************************************************************************
 */
#if dg_configI2C_ADAPTER || dg_configUSE_HW_I2C

/**
 * \brief EEPROM 24FC256 device
 */
extern i2c_device EEPROM_24FC256;

#endif /* dg_configI2C_ADAPTER || dg_configUSE_HW_I2C */

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

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

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

#if dg_configI2C_ADAPTER

/* I2C I/O configuration */
const ad_i2c_io_conf_t i2c_master_io = {
        .scl = {
                .port = I2C_PORT, .pin = I2C_MASTER_SCL_PIN,
                .on =  { HW_GPIO_MODE_OUTPUT_OPEN_DRAIN, HW_GPIO_FUNC_I2C_SCL,   false },
                .off = { HW_GPIO_MODE_INPUT,             HW_GPIO_FUNC_GPIO,      true  }
        },
        .sda = {
                .port = I2C_PORT, .pin = I2C_MASTER_SDA_PIN,
                .on =  { HW_GPIO_MODE_OUTPUT_OPEN_DRAIN, HW_GPIO_FUNC_I2C_SDA,   false },
                .off = { HW_GPIO_MODE_INPUT,             HW_GPIO_FUNC_GPIO,      true  }
        },
        .voltage_level = I2C_GPIO_LEVEL
};

const ad_i2c_io_conf_t i2c_slave_io = {
        .scl = {
                .port = I2C_PORT, .pin = I2C_SLAVE_SCL_PIN,
                .on =  { HW_GPIO_MODE_OUTPUT_OPEN_DRAIN, HW_GPIO_FUNC_I2C_SCL,   false },
                .off = { HW_GPIO_MODE_INPUT,             HW_GPIO_FUNC_GPIO,      true  }
        },
        .sda = {
                .port = I2C_PORT, .pin = I2C_SLAVE_SDA_PIN,
                .on =  { HW_GPIO_MODE_OUTPUT_OPEN_DRAIN, HW_GPIO_FUNC_I2C_SDA,   false },
                .off = { HW_GPIO_MODE_INPUT,             HW_GPIO_FUNC_GPIO,      true  }
        },
        .voltage_level = I2C_GPIO_LEVEL
};

/*
 * PLATFORM PERIPHERALS CONTROLLER CONFIGURATION
 *******************************************************************************  **********
 */

const ad_i2c_driver_conf_t master_driver_config = {
                I2C_DEFAULT_CLK_CFG,
                .i2c.speed = HW_I2C_SPEED_STANDARD,
                .i2c.mode = HW_I2C_MODE_MASTER,
                .i2c.addr_mode = HW_I2C_ADDRESSING_7B,
                .i2c.address = I2C_SLAVE_ADDRESS,
                .i2c.event_cb = NULL,
                .dma_channel = HW_DMA_CHANNEL_INVALID
                /**
                 * I2C master is not configured to use DMA because in case we   are connecting the
                 * same board in loopback, the DMA controller will block in   case there are blocking
                 * transactions being handled from both the I2C master and   slave controller at the same time.
                 *
                 * In case we are connecting two boards we can use DMA for the   master as well.
                 */
};

const ad_i2c_driver_conf_t slave_driver_config = {
                I2C_DEFAULT_CLK_CFG,
                .i2c.speed = HW_I2C_SPEED_STANDARD,
                .i2c.mode = HW_I2C_MODE_SLAVE,
                .i2c.addr_mode = HW_I2C_ADDRESSING_7B,
                .i2c.address = I2C_SLAVE_ADDRESS,
                .i2c.event_cb = NULL,
                .dma_channel = I2C_AD_CONFIG_SLAVE_DMA_CH
};

/* I2C controller configuration */
const ad_i2c_controller_conf_t master_config = {
        .id = I2C_AD_CONFIG_MASTER_I2C_CTRL,
        .io = &i2c_master_io,
        .drv = &master_driver_config
};

const ad_i2c_controller_conf_t slave_config = {
        .id = I2C_AD_CONFIG_SLAVE_I2C_CTRL,
        .io = &i2c_slave_io,
        .drv = &slave_driver_config
};

i2c_device I2C_MASTER_DEVICE = &master_config;
i2c_device I2C_SLAVE_DEVICE = &slave_config;

5.6. Task Code for I2C Master

/*
 * Callback function for the I2C master asynchronous transactions:
 *
 * \param  error      Error code returned at the end of an I2C transaction.
 * \param  user_data  User data that can be passed and used within the function.
 */
       void _i2c_master_async_read_done(void *user_data, HW_I2C_ABORT_SOURCE error)
       {
               /* Read the error status code */
               I2C_error_code = error;

               /* Signal the master task that time for resuming has elapsed. */
               OS_TASK_NOTIFY_FROM_ISR(i2c_task_m, I2C_DATA_SENT_NOTIFY, eSetBits); //notify slave that data was sent
       }
 /**
  * @brief I2C master task: sends requests, reads responses and prints them
  */
       void i2c_master_task( void *pvParameters )
       {

               unsigned char resp[I2C_RESPONSE_SIZE];
               ad_i2c_handle_t _master_handle = ad_i2c_open(I2C_MASTER_DEVICE);

               OS_TICK_TIME xNextWakeTime;
               static uint32_t loop_counter=0;
               static uint32_t transaction_counter=0;

               /* Initialise xNextWakeTime - this only needs to be done once. */
               xNextWakeTime = OS_GET_TICK_COUNT();

               for ( ;; ) {
                       uint32_t notif = 0;
                       vTaskDelayUntil( &xNextWakeTime, COUNTER_FREQUENCY_MS );
                       loop_counter++;
                       if (loop_counter % (1000 / OS_TICKS_2_MS(COUNTER_FREQUENCY_MS)) == 0) {
                               transaction_counter++;
                               unsigned char * _req = (unsigned char *)"callbacks?";
                               printf("Write I2C [%s] : \n", _req);

       #if (I2C_ASYNC_EN)

                       I2C_error_code = ad_i2c_write_read_async(_master_handle,
                                                _req,
                                                I2C_REQUEST_SIZE,
                                                resp,
                                                I2C_RESPONSE_SIZE,
                                                _i2c_master_async_read_done,
                                                NULL,
                                                HW_I2C_F_ADD_STOP);

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

        /* Wait here until the current asynchronous I2C operation is done.
                       OS_EVENT_WAIT(signal_i2c_async_done, OS_EVENT_FOREVER);*/
                       OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS, &notif, OS_TASK_NOTIFY_FOREVER);

       #else
                I2C_error_code = ad_i2c_write_read(_master_handle,
                                                  _req,
                                                  I2C_REQUEST_SIZE,//reduce this to simulate incomplete send
                                                  resp,
                                                  I2C_RESPONSE_SIZE,
                                                  HW_I2C_F_ADD_STOP);
       #endif
                       if(0 == I2C_error_code){
                               printf("Successfully read I2C:  [%s] \n", resp);

                       } else {
                               printf("i2c read error(%d)\n", I2C_error_code);
                       }
                       fflush(stdout);
               }
       }
       ad_i2c_close(_master_handle, true);
       OS_TASK_DELETE( NULL ); //should never get here
       }

5.7. Slave task code

 /*
  * Callback function on data received from master finished:
  *
  * \param  p            I2C handle.
  * \param  len          Transaction length.
  * \param  success      Error code returned at the end of an I2C transaction.
  * \param  user_data    User data that can be passed and used within the function.
  */
  static void _i2c_slave_data_received(ad_i2c_handle_t p, uint16_t len, bool success, void *user_data)
  {
       _count_data_rcvd++;

       memset(_response,0,I2C_RESPONSE_SIZE);
       sprintf((char*)_response, "REQ: %02d RDY: %02d RCVD: %02d SENT: %02d",
               _count_read_req,
               _count_data_rdy,
               _count_data_rcvd,
               _count_data_sent);

       OS_TASK_NOTIFY_FROM_ISR(i2c_task_s, I2C_DATA_RCVD_NOTIFY, eSetBits); //notify slave that data was received
  }

/*
 * Callback function on master data ready for slave:
 *
 * \param  p          I2C handle.
 * \param  user_data  User data that can be passed and used within the function.
 */
 static void _i2c_slave_data_ready(ad_i2c_handle_t p, void *user_data)
 {
       _count_data_rdy++;
       OS_TASK_NOTIFY_FROM_ISR(i2c_task_s, I2C_DATA_RDY_NOTIFY, eSetBits); //notify slave that data is ready from master
 }

/*
 * Callback function on data requested from slave by master :
 *
 * \param  p          I2C handle.
 * \param  user_data  User data that can be passed and used within the function.
 */
static void _i2c_slave_read_request(ad_i2c_handle_t p, void *user_data)
{
       _count_read_req++;
       OS_TASK_NOTIFY_FROM_ISR(i2c_task_s, I2C_READ_REQ_NOTIFY, eSetBits); //notify slave that data was requested
}

 i2c_dev_slave_event_callbacks_t slave_callbacks = {
       .data_sent = _i2c_slave_data_sent,
       .data_received = _i2c_slave_data_received,
       .data_ready = _i2c_slave_data_ready,
       .read_request = _i2c_slave_read_request,
 };
 /**
  * @brief I2C slave task: reads requests, counts callbacks and sends response
  */
 void i2c_slave_task( void *pvParameters )
 {
       memset(_response,0,I2C_RESPONSE_SIZE);

       ad_i2c_handle_t _slave_handle = ad_i2c_open(I2C_SLAVE_DEVICE);
       uint32_t notif = 0;
       unsigned char req[I2C_REQUEST_SIZE];
       for ( ;; ) {

               //wait for master to send request, send response
               ad_i2c_start_slave(_slave_handle, _response, I2C_RESPONSE_SIZE,
                                                req, I2C_REQUEST_SIZE, &slave_callbacks, NULL);
               // wait until data is received
               OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS, &notif, OS_TASK_NOTIFY_FOREVER);
               //wait until data is sent
               OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS, &notif, OS_TASK_NOTIFY_FOREVER);
       }
       ad_i2c_close(_slave_handle, true);
       OS_TASK_DELETE( NULL ); //should never get here
 }