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.