6. Code Overview¶
This section provides the code blocks needed to successfully execute this tutorial.
6.1. Header Files¶
In main.c
, add the following header files:
/*
* Required header files for the main.c source file
*/
#include "hw_wkup.h"
#include "sw_rtc_date_time.h"
#include "alert_mechanism.h"
6.2. System Init Code¶
In main.c
, replace system_init()
with the following code:
/* Task priorities */
#define mainRTC_TASK_PRIORITY ( OS_TASK_PRIORITY_NORMAL )
#define mainALERT_TASK_PRIORITY ( OS_TASK_PRIORITY_NORMAL )
/******** Notification Bitmasks *************/
#define SW_RTC_TRIG_NOTIF (1 << 1)
/*
* Macro for outputting the low power (lp) clock on P3.0 pin.
* 1: configures P3.0 pin to output the lp clock.
* 0: nothing
*
* \note: This macro is intended for debugging purposes only in order to measure
* the accuracy of the lp clock used. The device should stay active for
* as long as measurements on P3.0 pin are performed.
*/
#define TEST_LP_CLOCK_OUTPUT 0
/*
* Functions prototypes
*/
void prvRTC_Task( void *pvParameters );
void prvALERT_Task( void *pvParameters );
extern void time_alert_led_blink_stop(void);
/* Task handlers */
static OS_TASK task_rtc_h = NULL;
static OS_TASK task_alert_h = NULL;
static void system_init( void *pvParameters )
{
OS_TASK task_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
#if (TEST_LP_CLOCK_OUTPUT == 1)
/* Set the device to stay in active mode. */
pm_set_sleep_mode(pm_mode_active);
/*
* Select which clock to map when PID = FUNC_CLOCK.
*
* Valid values are:
* [0x00]: When XTAL32K is the selected lp clock
* [0x02]: When RCX is the selected lp clock
*/
REG_SETF(GPIO, GPIO_CLK_SEL, FUNC_CLOCK_SEL, 0x00);
#else
/* Set the desired sleep mode. */
pm_set_sleep_mode(pm_mode_extended_sleep);
#endif
/* 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);
/*
* Task responsible for performing RTC related
* operations
*/
OS_TASK_CREATE( "RTC",
prvRTC_Task,
NULL,
300 * OS_STACK_WORD_SIZE,
mainRTC_TASK_PRIORITY,
task_rtc_h );
OS_ASSERT(task_rtc_h);
/*
* Task responsible for performing alert related
* operations
*/
OS_TASK_CREATE( "ALERT",
prvALERT_Task,
NULL,
300 * OS_STACK_WORD_SIZE,
mainALERT_TASK_PRIORITY,
task_alert_h );
OS_ASSERT(task_alert_h);
/* The work of the SysInit task is done */
OS_TASK_DELETE( xHandle );
}
6.3. Wake-Up Timer Code¶
In main.c
, add the following code for handling external events, after periph_init()
.
/*
* 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-defined
* interrupt callback, to clear the interrupt flag.
*/
hw_wkup_reset_interrupt();
/*
* Notify the [prvRTC_Task] task that time for performing an RTC operation has elapsed
*/
OS_TASK_NOTIFY_FROM_ISR(task_rtc_h, SW_RTC_TRIG_NOTIF,
OS_NOTIFY_SET_BITS);
/*
* Disable breath timer and resume the device to its
* initial state, that is extended sleep mode!
*/
time_alert_led_blink_stop();
}
/*
* 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 on a pin 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);
}
6.4. Hardware Initialization¶
In main.c
, replace both periph_init()
and prvSetupHardware()
with the following code for
configuring pins after power-up/wake-up:
/**
* @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);
#if (TEST_LP_CLOCK_OUTPUT == 1)
// Output the selected lp clock on P3.0 pin - Intended for debugging purposes only.
hw_gpio_set_pin_function(HW_GPIO_PORT_3, HW_GPIO_PIN_0, HW_GPIO_MODE_OUTPUT,
HW_GPIO_FUNC_CLOCK);
#endif
}
/**
* @brief Hardware Initialization
*/
static void prvSetupHardware( void )
{
/* Init hardware */
pm_system_init(periph_init);
init_wkup();
}
6.5. Task Code for RTC Operations¶
Code snippet of prvRTC_Task
task responsible for performing RTC related operations. Create a new source
file, for example sw_rtc_task.c
and add the following code:
#include <string.h>
#include <stdio.h>
#include "sw_rtc_date_time.h"
#include "osal.h"
/******** Notification Bitmasks *************/
#define SW_RTC_TRIG_NOTIF (1 << 1)
/*
* Macro for enabling/disabling debugging messages on the serial console:
*
* 1: enables debugging messages
* 0: disables debugging messages
*/
#define SW_RTC_DBG_MESSAGES_CONSOLE 1
/*
* Structure that holds date/time related info
*/
__RETAINED_RW sw_rtc_date_time_t date_time = {
/*
* Declare all the info related to date.
*
* \note: The weekday is calculated automatically by the app.
*
* \note: The app performs sanity checks to identify values out of boundaries.
* For any invalid value, the code gets stuck in assertions!
*
*/
.date = {
.year = 2018,
.month = 9,
.month_date = 9,
},
/*
* Declare all the info related to time.
*
* \note: The max time resolution is microseconds (us) */
.time = {
.hour = 0,
.min = 0,
.sec = 0,
}
};
/*
* Task responsible for performing RTC operations
*/
void prvRTC_Task( void *pvParameters )
{
char calendar_str[60];
/*
* Initialize the RTC mechanism. This should be the first invoked
* function before any other related SW RTC API.
*/
sw_rtc_init();
/* Set the current date */
sw_rtc_date_write(&date_time.date);
/* Set the current time */
sw_rtc_time_write(&date_time.time);
/*
* Instead of declaring date/time individually, sw_rtc_date_time_write()
* can be used to set both of them at once:
*/
memset((uint8_t *)&date_time, 0x00, sizeof(sw_rtc_date_time_t));
for(;;) {
OS_BASE_TYPE ret;
uint32_t notif;
/*
* Block forever waiting for any of the notification bits,
* then clear them all. In this example at every K1 button
* press (on pro DevKit)
*/
ret = OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS,
¬if, OS_TASK_NOTIFY_FOREVER);
/*
* Thus, the return value must always be OS_OK
*/
OS_ASSERT(ret == OS_OK);
/*
* Has time for updating the calendar elapsed?
*/
if (notif & SW_RTC_TRIG_NOTIF) {
/* Read the current date */
sw_rtc_date_read(&date_time.date);
/* Read the current time */
sw_rtc_time_read(&date_time.time);
/*
* Instead of reading date/time individually, sw_rtc_date_time_read()
* can be used to read both of them at once:
*/
/* If enabled, print the current date/time on the serial console */
if (SW_RTC_DBG_MESSAGES_CONSOLE) {
/* Clear the buffer */
memset(calendar_str, '\0', sizeof(calendar_str));
/*
* Prepare the debugging message (current date/time)
*/
sprintf(calendar_str, "DATE: %lu-%lu-%lu (week day: %lu) TIME: %lu-%lu-%lu-%lu\n\r",
date_time.date.month_date, date_time.date.month, date_time.date.year, date_time.date.week_date,
date_time.time.hour, date_time.time.min, date_time.time.sec, date_time.time.msec );
/* Send the message on the serial console */
printf("%s", calendar_str);
fflush(stdout);
}
}
} // end of main for(;;) loop
} // end of task
6.6. Task Code for Alert Operations¶
Code snippet of prvALERT_Task
task responsible for performing alert related operations. Create a new source file,
for example alert_task.c
and add the following code:
#include <string.h>
#include <stdio.h>
#include "alert_mechanism.h"
#include "osal.h"
#include <stdbool.h>
#include "sys_power_mgr.h"
#include "hw_led.h"
#include "hw_breath.h"
#include "sw_rtc_date_time.h"
/* Handle of [prvALERT_Task] task */
__RETAINED_RW OS_TASK alert_task_h = NULL;
/******** Notification Bitmasks *************/
#define TIME_ALERT_TRIG_NOTIF_H (1 << 1)
#define TIME_ALERT_TRIG_NOTIF_M (1 << 2)
#define TIME_ALERT_TRIG_NOTIF_S (1 << 3)
/*
* Macro for enabling/disabling debugging messages on the serial console:
* 1: enables debugging messages
* 0: disables debugging messages
*/
#define TIME_ALERT_DBG_MESSAGES_CONSOLE 1
/*
* Macro for enabling/disabling blinking functionality upon an alert event:
* 1: enables blinking functionality
* 0: disables blinking functionality
*/
#define TIME_ALERT_BREATH_TIMER_EN 0
/*
* Keep track of device state (active/sleep)
*/
__RETAINED_RW static bool old_alerting = false;
/* Function prototypes */
void time_alert_set_mode(bool new_alerting);
/*
* Turn on LED1 on Pro DevKit (blinking functionality).
* LED1 is driven by breath timer.
*/
void time_alert_led_blink_start(breath_config *breath_cfg)
{
/* Force the device to stay in active mode */
time_alert_set_mode(true);
/* Configure the breath timer */
hw_breath_init(breath_cfg);
/* Configure the white LED1 */
hw_led_set_led1_src(HW_LED_SRC1_BREATH);
hw_led_enable_led1(true);
/* Enable the breath timer */
hw_breath_enable();
}
/* Turn off LED1 on Pro DevKit */
void time_alert_led_blink_stop(void)
{
/* Resume device to its initial state */
time_alert_set_mode(false);
/* Disable breath timer */
hw_breath_disable();
}
/*
* Check whether the device should be forced to stay
* awake or it's time to resume to its initial state.
*
* \note Number of calls to pm_stay_alive() must match the
* number of calls to pm_resume_sleep()!!!!!!!
*/
void time_alert_set_mode(bool new_alerting)
{
/* First check if a new alert flag has been assigned */
if (old_alerting != new_alerting) {
/* If flag is set to true then force the device to stay alive */
if (new_alerting) {
pm_stay_alive();
/*
* Otherwise the new value is zero and thus the device should
* resume to its initial state.
*/
} else {
pm_resume_sleep();
}
/* Make a copy of the new alert flag */
old_alerting = new_alerting;
}
}
/*
* Structure that holds alert related parameters
*
* \note: There are three distinct alert classes which can be
* enabled-disabled-configured individually.
*
*/
__RETAINED_RW time_alert_t my_alert = {
.hour = 4, // Set alerts expressed in hours (every x hours ).
.min = 2, // Set alerts expressed in minutes (every x minutes).
.sec = 5, // Set alerts expressed in seconds (every x seconds).
.repeat_h = false, // If set to false, alerts are triggered once, repeatedly otherwise.
.repeat_m = true, // If set to false, alerts are triggered once, repeatedly otherwise.
.repeat_s = false, // If set to false, alerts are triggered once, repeatedly otherwise.
};
/*
* User-defined callback function, called upon an alert event.
* It should have that specific prototype!
*
* \note: All the alert classes will call the same callback. The \p source parameter
* should be used to identify the source of the event.
*
*/
void my_alert_cb(uint8_t source)
{
/*
* Check whether Hours Alert Class triggered the alert.
* Then notify the main task accordingly.
*/
if (source & ALERT_SOURCE_HOURS_Msk) {
OS_TASK_NOTIFY(alert_task_h, TIME_ALERT_TRIG_NOTIF_H,
OS_NOTIFY_SET_BITS);
}
/*
* Check whether Minutes Alert Class triggered the alert.
* Then notify the main task accordingly.
*/
if (source & ALERT_SOURCE_MINUTES_Msk) {
OS_TASK_NOTIFY(alert_task_h, TIME_ALERT_TRIG_NOTIF_M,
OS_NOTIFY_SET_BITS);
}
/*
* Check whether Seconds Alert Class triggered the alert.
* Then notify the main task accordingly.
*/
if (source & ALERT_SOURCE_SECONDS_Msk) {
OS_TASK_NOTIFY(alert_task_h, TIME_ALERT_TRIG_NOTIF_S,
OS_NOTIFY_SET_BITS);
}
}
/*
* Task responsible for performing alert operations.
*/
void prvALERT_Task( void *pvParameters )
{
/* Get the handler of the current task */
alert_task_h = OS_GET_CURRENT_TASK();
/*
* Initialize the alert mechanism. This function should be invoked
* before any other related API.
*/
time_alert_init();
/*
* Register callback function that will be called upon an alert event.
*/
time_alert_register_cb(my_alert_cb);
/*
* Set alerts, expressed in minutes.
*
* \note If repeat flag is set to true, alerts are triggered every x minutes
*
* \note If a new alert configuration is performed before the execution of the current alert,
* the old alert will be discarded and the new one will be taken into consideration.
*
* \note At any time, alerts can be disabled by calling time_alert_class_clear()
*
*/
time_alert_class_set(my_alert.min, my_alert.repeat_m, SET_ALERT_CLASS_Min);
/*
* Set alerts expressed in hours. Configure an alert to be triggered after 1 hour.
*
* \note If repeat flag is set to true, alerts are triggered every x hours
*
* \note If a new alert configuration is performed before the execution of the current alert,
* the old alert will be discarded and the new one will be taken into consideration.
*
* \note At any time, alerts can be disabled by calling time_alert_class_clear()
*
*/
time_alert_class_set(my_alert.hour, my_alert.repeat_h, SET_ALERT_CLASS_Hour);
/*
* Set alerts expressed in seconds
*
* \note If repeat flag is set to true, alerts are triggered every x seconds
*
* \note If a new alert configuration is performed before the execution of the current alert,
* the old alert will be discarded and the new one will be taken into consideration.
*
* \note At any time, alerts can be disabled by calling time_alert_class_clear()
*
*/
time_alert_class_set(my_alert.sec, my_alert.repeat_s, SET_ALERT_CLASS_Sec);
#if (TIME_ALERT_BREATH_TIMER_EN == 1)
/*
* Breath timer parameters
*/
breath_config breath_cfg = {
.dc_min = 0,
.dc_max = 255,
.freq_div = 255,
.dc_step = 96,
.polarity = HW_BREATH_PWM_POL_POS
};
#endif
for (;;) {
OS_BASE_TYPE ret;
uint32_t notif;
/*
* Block forever waiting for any of the notification bits,
* then clear them all
*/
ret = OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS,
¬if, OS_TASK_NOTIFY_FOREVER);
/*
* Thus, the return value must always be OS_OK
*/
OS_ASSERT(ret == OS_OK);
/*
* This code block is executed upon an alert event
* produced by Hours Alert Class.
*/
if (notif & TIME_ALERT_TRIG_NOTIF_H) {
#if (TIME_ALERT_BREATH_TIMER_EN == 1)
/* Start blinking white LED D1 on pro DevKit */
time_alert_led_blink_start(&breath_cfg);
#endif
/* Print out a debugging message */
if (TIME_ALERT_DBG_MESSAGES_CONSOLE) {
printf("The source of the alert is: 'HOURS'\n\r");
}
}
/*
* This code block is executed upon an alert event
* produced by Minutes Alert Class.
*/
if (notif & TIME_ALERT_TRIG_NOTIF_M) {
#if (TIME_ALERT_BREATH_TIMER_EN == 1)
/* Start blinking white LED D1 on pro DevKit */
time_alert_led_blink_start(&breath_cfg);
#endif
/* Print out a debugging message */
if (TIME_ALERT_DBG_MESSAGES_CONSOLE) {
printf("The source of the alert is: 'MINUTES'\n\r");
}
}
/*
* This code block is executed upon an alert event
* produced by Seconds Alert Class.
*/
if (notif & TIME_ALERT_TRIG_NOTIF_S) {
#if (TIME_ALERT_BREATH_TIMER_EN == 1)
/* Start blinking white LED D1 on pro DevKit */
time_alert_led_blink_start(&breath_cfg);
#endif
/* Print out a debugging message */
if (TIME_ALERT_DBG_MESSAGES_CONSOLE) {
printf("The source of the alert is: 'SECONDS'\n\r");
}
}
}
}
6.7. Date/Time Implementation Source File¶
Code snippet of date/time mechanism implementation. Create a new source file,
for example sw_rtc_date_time.c
and add the following code:
#include "sw_rtc_date_time.h"
#include <stdbool.h>
#include <stdlib.h>
#include <math.h>
#include "osal.h"
#include "sys_rtc.h"
#include "sys_power_mgr.h"
#include <string.h>
#include <stdio.h>
/* Months of a year */
typedef enum {
JANUARY = 1,
FEBRUARY = 2,
MARCH = 3,
APRIL = 4,
MAY = 5,
JUNE = 6,
JULY = 7,
AUGUST = 8,
SEPTEMBER = 9,
OCTOBER = 10,
NOVEMBER = 11,
DECEMBER = 12
} SW_RTC_MONTH;
/*
* Set this macro to perform a forced RTC measurement via an OS Timer.
*/
#define SW_RTC_FORCED_DATE_TIME_EN 1
/* Mutex handle */
__RETAINED_RW static OS_MUTEX sw_rtc_mutex = NULL;
/*
* Time interval, expressed in milliseconds, for triggering a forced RTC operation.
* This macro is used only when SW_RTC_FORCED_DATE_TIME_EN is set. Default value is
* 1 day, that is:
*
* (24 hrs * 60 minutes/hour * 60 seconds/minute * 1000 milliseconds/second) = 86,400,000
*
*/
#define SW_RTC_FORCED_DATE_TIME_IN_MS 86400000
/*
* Variables used to store the lp clocks, using the old/new scheme
*/
__RETAINED_RW static uint64_t sw_rtc_new = 0;
__RETAINED_RW static uint64_t sw_rtc_old = 0;
/*
* SW RTC parameters. This structure holds the current date/time.
*/
__RETAINED_RW static sw_rtc_date_time_t *calendar;
#if (SW_RTC_FORCED_DATE_TIME_EN == 1)
/* OS timer handle */
static OS_TIMER sw_rtc_timer_h;
/* Callback function called after OS timer's expiration */
void sw_rtc_trg_cb(OS_TIMER timer)
{
OS_BASE_TYPE ret;
/*
* Try to get the mutex without blocking. If the mutex was successfully acquired then
* no other tasks perform RTC measurements. If not, another task is already performing
* RTC operations and it's OK to abort the operation.
*
* \warning Blocking within timer's callback is not permitted!!!
*
*/
ret = OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_NO_WAIT);
if (ret == OS_MUTEX_TAKEN) {
/* Allocate memory both date/time */
sw_rtc_date_time_t *date_time = (sw_rtc_date_time_t *) OS_MALLOC(sizeof(sw_rtc_date_time_t));
/* Just perform a read date/time operation */
sw_rtc_date_time_read(date_time);
/* Release the previously allocated memory */
OS_FREE(date_time);
/* Release the already acquired mutex */
OS_MUTEX_PUT(sw_rtc_mutex);
}
}
#endif
/*
* Initialize the SW RTC mechanism. First, memory for the SW RTC parameters
* is allocated and then an OS timer responsible for performing forced RTC
* operations is created.
*
*/
void sw_rtc_init()
{
/* Allocate memory for the calendar (date/time) structure */
calendar = (sw_rtc_date_time_t *) OS_MALLOC(sizeof(sw_rtc_date_time_t));
OS_ASSERT(calendar);
/* Clear the calendar structure */
memset((uint8_t *)calendar, 0x00, sizeof(sw_rtc_date_time_t));
#if (SW_RTC_FORCED_DATE_TIME_EN == 1)
uint64_t timer_ticks = (((uint64_t)SW_RTC_FORCED_DATE_TIME_IN_MS * configTICK_RATE_HZ) / 1000);
/*
* Create an OS timer to trigger a forced RTC operation.
*/
sw_rtc_timer_h = OS_TIMER_CREATE("RTC_Trigger",
(uint32_t)timer_ticks, OS_TIMER_SUCCESS,
(void *) OS_GET_CURRENT_TASK(), sw_rtc_trg_cb);
OS_ASSERT(sw_rtc_timer_h);
/* Start the OS timer */
OS_TIMER_START(sw_rtc_timer_h, OS_TIMER_FOREVER);
#endif
/* Create mutex for RTC operations */
OS_MUTEX_CREATE(sw_rtc_mutex);
OS_ASSERT(sw_rtc_mutex != NULL);
}
/* Reset calendar values to zero */
void sw_rtc_date_time_reset(void)
{
/* Reset all the calendar related parameters */
sw_rtc_new = 0;
sw_rtc_old = 0;
/* Reset date parameters */
calendar->date.year = 0;
calendar->date.month = 1;
calendar->date.month_date = 1;
/* Reset time parameters */
calendar->time.hour = 0;
calendar->time.min = 0;
calendar->time.sec = 0;
calendar->time.msec = 0;
calendar->time.usec = 0;
}
/*
* Get the number of days of a month.
*/
uint8_t sw_rtc_get_days_of_month(void)
{
uint8_t days = 0;
/* Given the current month, calculate its maximum days */
switch(calendar->date.month) {
case JANUARY:
days = 31;
break;
case FEBRUARY:
/*
* Check if the year is a leap year, taking into consideration
* the following three criteria:
* 1. the year can be evenly divided by 4
* 2. the year is divisible by 400
* 3. the year is not divisible by 100
*/
if ((!(calendar->date.year % 4)) ||
((!(calendar->date.year % 400)) && (calendar->date.year % 100))) {
days = 29;
} else {
days = 28;
}
break;
case MARCH:
days = 31;
break;
case APRIL:
days = 30;
break;
case MAY:
days = 31;
break;
case JUNE:
days = 30;
break;
case JULY:
days = 31;
break;
case AUGUST:
days = 31;
break;
case SEPTEMBER:
days = 30;
break;
case OCTOBER:
days = 31;
break;
case NOVEMBER:
days = 30;
break;
case DECEMBER:
days = 31;
break;
default:
OS_ASSERT(0); // Invalid value!
}
return days;
}
/* Check whether the given year is a leap year */
bool sw_rtc_is_leap_year(void)
{
/*
* Check if the year is a leap year, taking into consideration
* the following three criteria:
* 1. the year can be evenly divided by 4
* 2. the year is divisible by 400
* 3. the year is not divisible by 100
*/
if ((!(calendar->date.year % 4)) ||
((!(calendar->date.year % 400)) && (calendar->date.year % 100))) {
return true;
} else {
return false;
}
}
/* Calculate the month code used in weekday computations */
uint8_t sw_rtc_compute_month_code(void)
{
uint8_t month_code;
switch(calendar->date.month) {
case JANUARY:
month_code = 0;
break;
case FEBRUARY:
month_code = 3;
break;
case MARCH:
month_code = 3;
break;
case APRIL:
month_code = 6;
break;
case MAY:
month_code = 1;
break;
case JUNE:
month_code = 4;
break;
case JULY:
month_code = 6;
break;
case AUGUST:
month_code = 2;
break;
case SEPTEMBER:
month_code = 5;
break;
case OCTOBER:
month_code = 0;
break;
case NOVEMBER:
month_code = 3;
break;
case DECEMBER:
month_code = 5;
break;
default:
OS_ASSERT(0); // Invalid value!
}
return month_code;
}
/* Calculate the century code used in weekday computations */
uint8_t sw_rtc_compute_century_code(void)
{
uint8_t century_code = 0;
if (calendar->date.year >= 2000 && calendar->date.year < 2100) {
century_code = 6;
} else if (calendar->date.year >= 2100 && calendar->date.year < 2200) {
century_code = 4;
} else if (calendar->date.year >= 2200 && calendar->date.year < 2300) {
century_code = 2;
} else if (calendar->date.year >= 2300 && calendar->date.year < 2400) {
century_code = 0;
}
return century_code;
}
/* Function responsible for computing the current day of the week */
uint32_t sw_rtc_compute_date_of_week(void)
{
/* Isolate the last two digits from the year e.g. 2018 -> 18 */
uint8_t year_code = (uint8_t)(calendar->date.year % 100);
/* Compute the year code */
year_code = ((year_code + (year_code / 4)) % 7);
/* Compute the month code */
uint8_t month_code = sw_rtc_compute_month_code();
/* Compute the century code */
uint8_t centure_code = sw_rtc_compute_century_code();
/* Check whether the year is a leap year */
uint8_t leap_year = (uint8_t)sw_rtc_is_leap_year();
/* Add all the codes */
uint8_t day_week = (year_code + month_code + centure_code + (uint8_t)calendar->date.month_date);
if (leap_year && ((calendar->date.month == JANUARY) || (calendar->date.month == FEBRUARY)) ) {
return (uint32_t)((day_week - 1) % 7);
} else {
return (uint32_t)(day_week % 7);
}
}
/*
* Function responsible for converting time, expressed in microseconds (us), into full calendar (date/time).
*/
void sw_rtc_convert_time_to_calendar(uint64_t usec_time)
{
calendar->time.usec += usec_time;
/*
* Check if there is an overflow in [microseconds],
* that is greater than 999.
*/
if (calendar->time.usec > 999) {
/* Compute the right next greater unit, that is milliseconds */
calendar->time.msec += (uint32_t)(calendar->time.usec / 1000);
/* Then compute the new value for [microseconds] */
calendar->time.usec %= 1000;
/*
* Check if there is an overflow in [milliseconds],
* that is greater than 999.
*/
if (calendar->time.msec > 999) {
/* Compute the right next greater unit, that is [seconds] */
calendar->time.sec += (calendar->time.msec / 1000);
/* Then compute the new value for [milliseconds] */
calendar->time.msec %= 1000;
/*
* Check if there is an overflow in [seconds],
* that is greater than 59
*/
if (calendar->time.sec > 59) {
/* Compute the right next greater unit, that is [minutes] */
calendar->time.min += (calendar->time.sec / 60);
/* Then compute the new value for [seconds] */
calendar->time.sec %= 60;
/*
* Check is there is an overflow in [minutes],
* that is greater than 59
*/
if (calendar->time.min > 59) {
/* Compute the right next greater unit, that is [hours] */
calendar->time.hour += (calendar->time.min / 60);
/* Then compute the new value for [minutes] */
calendar->time.min %= 60;
/*
* Check if there is an overflow in hours,
* that is greater than 23
*/
if (calendar->time.hour > 23) {
/* Compute the right next greater unit, that is [days] */
calendar->date.month_date += (calendar->time.hour / 24);
/* Then compute the new value for [hours] */
calendar->time.hour %= 24;
/*
* Check if there is an overflow in days,
* that is greater than 28/29/30 or 31
* depending on the current month.
*/
if (calendar->date.month_date >
sw_rtc_get_days_of_month()) {
/* Calculate months and days */
do {
calendar->date.month_date -= sw_rtc_get_days_of_month();
calendar->date.month++;
/*
* Check if there is an overflow in [months],
* that is greater than 12
*/
if (calendar->date.month > 12) {
calendar->date.year += (calendar->date.month / 12);
calendar->date.month %= 12;
}
} while (calendar->date.month_date > sw_rtc_get_days_of_month());
} // end of days monitoring
} // end of hours monitoring
} // end of minutes monitoring
} // end of seconds monitoring
} // end of milliseconds monitoring
} // end of microseconds monitoring
/* Finally compute the day of the week */
calendar->date.week_date = sw_rtc_compute_date_of_week();
} // end of function
/*
* Convert low power (lp) clock cycles into time - expressed in microseconds.
*/
uint64_t sw_rtc_convert_lp_to_time(uint64_t lp_clocks)
{
uint64_t time = 0;
/*
* Check whether the lp clock source is either the external crystal XTAL32K
* or an external clock source.
*/
if ((dg_configUSE_LP_CLK == LP_CLK_32768) ||
(dg_configUSE_LP_CLK == LP_CLK_32000)) {
/*
* Compute the period of the lp clock source - expressed in microseconds (us)
*
* \note Multiply by [1024] to increase accuracy!
*
*/
const uint32_t lp_clk_period = ((1000000UL * 1024)
/configSYSTICK_CLOCK_HZ);
/*
* Compute the time that has elapsed.
*/
time = (lp_clocks * (uint64_t)lp_clk_period);
time = (time >> 10); // divide with 1024
/* Check whether the lp clock source is the internal RCX */
} else if (dg_configUSE_LP_CLK == LP_CLK_RCX) {
/*
* Use [rcx_clock_period] to get the current RCX period in usec.
* Please note that this value is multiplied by [1024 * 1024]
*/
time = (lp_clocks * (uint64_t)rcx_clock_period);
time = (time >> 20); // divide with (1024 * 1024)
} else {
OS_ASSERT(0); // Invalid lp clock source
}
return time;
}
/*
* \callgraph
*
* \brief This function performs a SW RTC measurement using APIs from the RTC mechanism
* (provided by the SDK), to calculate the number of low power clock cycles that
* fit within a given time interval.
*/
void sw_rtc_date_time_read(sw_rtc_date_time_t *val)
{
uint64_t sw_rtc_value_us = 0;
uint64_t sw_rtc_dif = 0;
/* Get the current RTC value expressed in lp clock ticks. */
sw_rtc_new = rtc_get();
/*
* To reduce values used in various calculations, calculate
* only the lp clocks since the last RTC measurement.
* (instead of invoking all the lp clock cycles measured
* from the very beginning of chip's operation)
*/
sw_rtc_dif = sw_rtc_new - sw_rtc_old;
#if (DBG == 1)
printf("RTC diff: %ld\r\n", (uint32_t)sw_rtc_dif);
#endif
/*
* The current RTC value becomes the old for the next RTC measurement.
*/
sw_rtc_old = sw_rtc_new;
/* Convert the lp clock cycles into [microseconds] */
sw_rtc_value_us = sw_rtc_convert_lp_to_time(sw_rtc_dif);
/* Update the calendar using the current time in [microseconds] */
sw_rtc_convert_time_to_calendar(sw_rtc_value_us);
/* Safeguard the structure that holds the current date/time */
OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_FOREVER);
/* Copy the current date/time */
memcpy((uint8_t *)val, (uint8_t *)calendar, sizeof(sw_rtc_date_time_t));
/* Release the previously acquired mutex so that other tasks can use it e.g the daemon task */
OS_MUTEX_PUT(sw_rtc_mutex);
}
/*
* Function that performs an SW RTC measurement and returns the current date.
*/
void sw_rtc_date_read(sw_rtc_date_t *val)
{
/* Allocate memory to hold both date and time */
sw_rtc_date_time_t *date_time = (sw_rtc_date_time_t *) OS_MALLOC(sizeof(sw_rtc_date_time_t));
OS_ASSERT(date_time);
/* Read the current date/time */
sw_rtc_date_time_read(date_time);
/* Safeguard the structure that holds the current date/time */
OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_FOREVER);
/* Copy the current date */
memcpy((uint8_t *)val, (uint8_t *)&date_time->date, sizeof(sw_rtc_date_t));
/* Release the previously acquired mutex so that other tasks can use it e.g the daemon task */
OS_MUTEX_PUT(sw_rtc_mutex);
OS_FREE(date_time);
}
/*
* Function that performs an SW RTC measurement and returns the current time.
*/
void sw_rtc_time_read(sw_rtc_time_t *val)
{
/* Allocate memory to hold both date and time */
sw_rtc_date_time_t *date_time = (sw_rtc_date_time_t *) OS_MALLOC(sizeof(sw_rtc_date_time_t));
OS_ASSERT(date_time);
/* Read the current date/time */
sw_rtc_date_time_read(date_time);
/* Safeguard the structure that holds the current date/time */
OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_FOREVER);
/* Copy the current time */
memcpy((uint8_t *)val, (uint8_t *)&date_time->time, sizeof(sw_rtc_time_t));
/* Release the previously acquired mutex so that other tasks can use it e.g the daemon task */
OS_MUTEX_PUT(sw_rtc_mutex);
OS_FREE(date_time);
}
/* Sanity checks for the date structure */
int sw_rtc_date_write_sanity_check(sw_rtc_date_t *val)
{
if ((val->month_date > 31) || (val->month_date == 0) || (val->month > 12) || (val->month == 0) || (val->week_date > 6)) {
return -1;
} else {
return 0;
}
}
/* Sanity checks for the time structure */
int sw_rtc_time_write_sanity_check(sw_rtc_time_t *val)
{
if ((val->hour > 23) || (val->min > 59) || (val->sec > 59) || (val->msec > 999) || (val->usec > 999)) {
return -1;
} else {
return 0;
}
}
/*
* Function responsible for setting the current date and time
*/
void sw_rtc_date_time_write(sw_rtc_date_time_t *val)
{
int error_date;
int error_time;
/**
* Perform sanity checks on the provided values.
*
* \note If the returned value is -1 then the provided values are out of the boundaries
*
*/
error_date = sw_rtc_date_write_sanity_check(&val->date);
error_time = sw_rtc_time_write_sanity_check(&val->time);
OS_ASSERT((error_date == 0) && (error_time == 0));
/* Safeguard the structure that holds the current date/time */
OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_FOREVER);
/* Update date/time parameters */
memcpy((uint8_t *)calendar, (uint8_t *)val, sizeof(sw_rtc_date_time_t));
/* Release the previously acquired mutex so that other tasks can use it e.g the daemon task */
OS_MUTEX_PUT(sw_rtc_mutex);
}
/*
* Function responsible for setting the current date
*/
void sw_rtc_date_write(sw_rtc_date_t *val)
{
int error;
/**
* Perform sanity checks on the provided values.
*
* \note If the returned value is -1 then the provided values are out of the boundaries
*
*/
error = sw_rtc_date_write_sanity_check(val);
OS_ASSERT(error == 0);
/* Safeguard the structure that holds the current date */
OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_FOREVER);
/* Update date parameters */
memcpy((uint8_t *)&calendar->date, (uint8_t *)val, sizeof(sw_rtc_date_t));
/* Release the previously acquired mutex so that other tasks can use it e.g the daemon task */
OS_MUTEX_PUT(sw_rtc_mutex);
}
/*
* Function responsible for setting the current time
*/
void sw_rtc_time_write(sw_rtc_time_t *val)
{
int error;
/**
* Perform sanity checks on the provided values.
*
* \note If the returned value is -1 then the provided values are out of boundaries
*
*/
error = sw_rtc_time_write_sanity_check(val);
OS_ASSERT(error == 0);
/* Safeguard the structure that holds the current time */
OS_MUTEX_GET(sw_rtc_mutex, OS_MUTEX_FOREVER);
/* Update time parameters */
memcpy((uint8_t *)&calendar->time, (uint8_t *)val, sizeof(sw_rtc_time_t));
/* Release the previously acquired mutex so that other tasks can use it e.g the daemon task */
OS_MUTEX_PUT(sw_rtc_mutex);
}
6.8. Date/Time Implementation Header File¶
Create a new header file, for example sw_rtc_date_time.h
and add the following code:
#include <stdint.h>
#include <stdbool.h>
/* Days of a week */
typedef enum {
WEEK_DATE_SUNDAY = 0,
WEEK_DATE_MONDAY = 1,
WEEK_DATE_TUESDAY = 2,
WEEK_DATE_WEDNESDAY = 3,
WEEK_DATE_THURSDAY = 4,
WEEK_DATE_FRIDAY = 5,
WEEK_DATE_SATURDAY = 6,
} WEEK_DATE;
/*
* Date related parameters
*/
typedef struct sw_rtc_date {
uint32_t year; // Current year: there is no limitation
uint32_t month; // Range: (1..12)
uint32_t month_date; // Range: (1..31) or (1..30) or (1..29) or (1..28)
uint32_t week_date; // Range: (0:6) where 0 = Sunday ... 6 = Saturday
} sw_rtc_date_t;
/*
* Time related parameters
*/
typedef struct sw_rtc_time {
uint32_t hour; // Range: (0..23)
uint32_t min; // Range: (0..59)
uint32_t sec; // Range: (0..59)
uint32_t msec; // Range: (0..999)
uint64_t usec; // Range: (0..999)
} sw_rtc_time_t;
/*
* Date/time (calendar) related parameters
*/
typedef struct sw_rtc_calendar {
sw_rtc_date_t date;
sw_rtc_time_t time;
} sw_rtc_date_time_t;
/**
*
* \brief SW RTC initialization
*
* This function initializes all the resources required for the SW RTC mechanism.
*
* \note It should be the first invoked API before any other SW RTC related operations.
*
*/
void sw_rtc_init();
/**
*
* \brief SW RTC date/time read operation
*
* This function is responsible for reading the current date and time. It should be
* considered as a shortcut function since it combines both sw_rtc_date_read()
* and sw_rtc_time_read().
*
*
* \param [out] val A structure in which the current date/time will be stored
*
* \note This function can be called by any task at any time, to read the current date.
* A protection mechanism against multiple accesses has been implemented.
*
*/
void sw_rtc_date_time_read(sw_rtc_date_time_t *val);
/*
* \brief SW RTC date read operation
*
* This function is responsible for reading the current date.
*
*
* \param [out] val A structure in which the current date will be stored
*
* \note This function can be called by any task at any time, to read the current date.
* A protection mechanism against multiple accesses has been implemented.
*
*/
void sw_rtc_date_read(sw_rtc_date_t *val);
/*
* \brief SW RTC time read operation
*
* This function is responsible for reading the current time.
*
*
* \param [out] val A structure in which the current time will be stored
*
* \note This function can be called by any task at any time, to read the current date.
* A protection mechanism against multiple accesses has been implemented.
*
*/
void sw_rtc_time_read(sw_rtc_time_t *val);
/*
* \brief SW RTC date/time write operation
*
* This function is responsible for setting the current date and time. It should be
* considered as a shortcut function since it combines both sw_rtc_date_write()
* and sw_rtc_time_write()
*
*
* \param[in] val The preferred values for date/time.
*
* \note The weekday is calculated automatically by the app.
*
* \note The code performs sanity checks to identify values out of boundaries.
* For any invalid value, the code gets stuck in assertions!
*
* \note This function can be called by any task at any time, to set date/time.
* A protection mechanism against multiple accesses has been implemented.
*
*/
void sw_rtc_date_time_write(sw_rtc_date_time_t *val);
/*
* \brief SW RTC date write operation
*
* This function is responsible for setting the current time.
*
*
* \param[in] val The preferred values for time
*
* \note The code performs sanity checks to identify values out of boundaries.
* For any invalid value, the code gets stuck in assertions!
*
* \note This function can be called by any task at any time, to set date/time.
* A protection mechanism against multiple accesses has been implemented.
*
*/
void sw_rtc_time_write(sw_rtc_time_t *val);
/*
* \brief SW RTC time write operation
*
* This function responsible for setting the current time.
*
*
* \param[in] val The preferred values for date.
*
* \note The weekday is calculated automatically by the code.
*
* \note The code performs sanity checks to identify values out of boundaries.
* For any invalid value, the code gets stuck in assertions!
*
* \note This function can be called by any task at any time, to set date/time.
* A protection mechanism against multiple accesses has been implemented.
*
*/
void sw_rtc_date_write(sw_rtc_date_t *val);
6.9. Alert Implementation Source File¶
Code snippet of alert mechanism implementation. Create a new source file, for example alert_mechanism.c
and add the following code:
#include "alert_mechanism.h"
#include <string.h>
#include "osal.h"
/*
* Masks for positioning alert sources (alert class)
*/
#define ALERT_SOURCE_HOURS_Pos (0x00)
#define ALERT_SOURCE_MINUTES_Pos (0x01)
#define ALERT_SOURCE_SECONDS_Pos (0x02)
/* Handles of OS timers - One for each ALERT class */
__RETAINED_RW static OS_TIMER timer_h = NULL;
__RETAINED_RW static OS_TIMER timer_m = NULL;
__RETAINED_RW static OS_TIMER timer_s = NULL;
/*
* Hold the current user-defined callback function
*/
__RETAINED_RW static alert_cb cb = NULL;
/*
* Hold the current alert configurations.
*/
__RETAINED_RW static time_alert_t *alert_cfg = NULL;
/*
* OS timer callback, dedicated for Hours Alert Class.
* This callback is called upon timer's expiration.
*/
static void timer_h_cb(OS_TIMER timer)
{
// Check whether the [repeat] flag is set, then restart timer.
if (alert_cfg->repeat_h == pdTRUE) {
OS_TIMER_START(timer, OS_TIMER_FOREVER);
}
// Check whether a user-defined function has been declared.
if (cb) {
cb((uint8_t)(1 << ALERT_SOURCE_HOURS_Pos));
}
}
/*
* OS timer callback, dedicated for Minutes Alert Class.
* This callback is called upon timer's expiration.
*/
void timer_m_cb(OS_TIMER timer)
{
// Check whether the [repeat] flag is set, then restart timer.
if (alert_cfg->repeat_m == pdTRUE) {
OS_TIMER_START(timer, OS_TIMER_FOREVER);
}
// Check whether a user-defined function has been declared.
if (cb) {
cb((uint8_t)(1 << ALERT_SOURCE_MINUTES_Pos));
}
}
/*
* OS timer callback, dedicated for Seconds Alert Class.
* This callback is called upon timer's expiration.
*/
void timer_s_cb(OS_TIMER timer)
{
// Check whether the [repeat] flag is set, then restart timer.
if (alert_cfg->repeat_s == pdTRUE) {
OS_TIMER_START(timer, OS_TIMER_FOREVER);
}
// Check whether a user-defined function has been declared.
if (cb) {
cb((uint8_t)(1 << ALERT_SOURCE_SECONDS_Pos));
}
}
/*
* Initialize the alert mechanism. Three OS timers are being created,
* one for each ALERT class, without enabling them.
*/
void time_alert_init(void)
{
/* Allocate memory for the alert parameters */
alert_cfg = (time_alert_t *) OS_MALLOC(sizeof(time_alert_t));
OS_ASSERT(alert_cfg);
/* Clear the alert structure */
memset((uint8_t *)alert_cfg, 0x00, sizeof(time_alert_t));
/*
* Create one-shot OS timer dedicated for Hours Alert Class.
*
* \note Timer period - expressed in OS ticks -
* must be greater than 1, otherwise an assertion is issued!
*/
timer_h = OS_TIMER_CREATE("HOURS", OS_MS_2_TICKS(10),
OS_TIMER_FAIL, (void *)0, timer_h_cb);
OS_ASSERT(timer_h);
/*
* Create one-shot OS timer dedicated for Minutes Alert Class.
*
* \note: Timer's period - expressed in OS ticks -
* must be greater than 1, otherwise an assertion is issued!
*/
timer_m = OS_TIMER_CREATE("MINUTES", OS_MS_2_TICKS(10),
OS_TIMER_FAIL, (void *)0, timer_m_cb);
OS_ASSERT(timer_m);
/*
* Create one-shot OS timer dedicated for Seconds Alert Class.
*
* \note: Timer's period - expressed in OS ticks -
* must be greater than 1, otherwise an assertion is issued.
*/
timer_s = OS_TIMER_CREATE("SECONDS", OS_MS_2_TICKS(10),
OS_TIMER_FAIL, (void *)0, timer_s_cb);
OS_ASSERT(timer_s);
}
/*
* Function responsible for setting an alert
*/
void time_alert_class_set(uint16_t value, bool repeat, SET_ALERT_CLASS alert)
{
OS_BASE_TYPE ret = 0;
uint64_t timer_ticks = 0;
switch(alert) {
/* Set alerts for Hours Alert Class */
case SET_ALERT_CLASS_Hour:
/* Then check whether the new value assigned is non-zero */
if (value > 0) {
/*
* Recalculate timer period, that is,
* (hours * 60 minutes/hour * 60 seconds/minute * 1000 milliseconds/second)
*
* Note: Changing the period of a dormant timer will also
* start the timer!
*/
timer_ticks = (((uint64_t)value * 3600000 * configTICK_RATE_HZ) / 1000);
/* Perform a sanity check for 32-bit max number */
if (timer_ticks >= (uint64_t)4294967295UL) {
OS_ASSERT(0);
}
ret = OS_TIMER_CHANGE_PERIOD(timer_h,
(uint32_t)timer_ticks, OS_TIMER_FOREVER);
OS_ASSERT(ret == OS_OK);
/* Otherwise the new value is zero and hence timer should be halted */
} else {
ret = OS_TIMER_STOP(timer_h, OS_TIMER_FOREVER);
OS_ASSERT(ret == OS_OK);
}
/* Store the new parameters for Hours Alert Class */
alert_cfg->hour = value;
alert_cfg->repeat_h = repeat;
break;
/* Set alerts for Minutes Alert Class */
case SET_ALERT_CLASS_Min:
/* Then check whether the new value assigned is non-zero */
if (value > 0) {
/*
* Recalculate timer period, that is,
* (minutes * 60 seconds/minute * 1000 milliseconds/second)
*
* Note: changing the period of a dormant timer will also
* start the timer!
*/
timer_ticks = (((uint64_t)value * 60000 * configTICK_RATE_HZ) / 1000);
/* Perform a sanity check for 32-bit max number */
if (timer_ticks >= (uint64_t)4294967295UL) {
OS_ASSERT(0);
}
ret = OS_TIMER_CHANGE_PERIOD(timer_m,
(uint32_t)timer_ticks, OS_TIMER_FOREVER);
OS_ASSERT(ret == OS_OK);
/* Otherwise the new value is zero and hence timer should be halted */
} else {
ret = OS_TIMER_STOP(timer_m, OS_TIMER_FOREVER);
OS_ASSERT(ret == OS_OK);
}
/* Store the new parameters for Minutes Alert Class */
alert_cfg->min = value;
alert_cfg->repeat_m = repeat;
break;
/* Set alerts for Seconds Alert Class */
case SET_ALERT_CLASS_Sec:
/* Then check whether the new value assigned is non-zero */
if (value > 0) {
/*
* Recalculate timer period, that is,
* (seconds * 1000 milliseconds/second)
*
* Note: changing the period of a dormant timer will also
* start the timer!
*/
timer_ticks = (((uint64_t)value * 1000 * configTICK_RATE_HZ) / 1000);
/* Perform a sanity check for 32-bit max number */
if (timer_ticks >= (uint64_t)4294967295UL) {
OS_ASSERT(0);
}
ret = OS_TIMER_CHANGE_PERIOD(timer_s,
(uint32_t)timer_ticks, OS_TIMER_FOREVER);
OS_ASSERT(ret == OS_OK);
/* Otherwise the new value is zero and hence timer should be halted */
} else {
ret = OS_TIMER_STOP(timer_s, OS_TIMER_FOREVER);
OS_ASSERT(ret == OS_OK);
}
/* Store the new parameters for Seconds Alert Class */
alert_cfg->sec = value;
alert_cfg->repeat_s = repeat;
break;
default:
OS_ASSERT(0);
}
}
/*
* Function responsible for settings alerts of all the available alert classes
*/
void time_alert_class_set_all(time_alert_t *alert)
{
time_alert_class_set(alert->hour, alert->repeat_h, SET_ALERT_CLASS_Hour);
time_alert_class_set(alert->min, alert->repeat_m, SET_ALERT_CLASS_Min);
time_alert_class_set(alert->sec, alert->repeat_s, SET_ALERT_CLASS_Sec);
}
/*
* Function responsible for clearing alerts
*/
void time_alert_class_clear(CLEAR_ALERT_CLASS alert)
{
switch(alert) {
/* Disable alerts for Hours Alert Class */
case CLEAR_ALERT_CLASS_Hour:
/* Clear the associated variables */
alert_cfg->hour = 0;
alert_cfg->repeat_h = false;
/*Stop the associated OS timer */
OS_TIMER_STOP(timer_h, OS_TIMER_FOREVER);
break;
/* Disable alerts for Minutes Alert Class */
case CLEAR_ALERT_CLASS_Min:
/* Clear the associated variables */
alert_cfg->min = 0;
alert_cfg->repeat_m = false;
/*Stop the associated OS timer */
OS_TIMER_STOP(timer_m, OS_TIMER_FOREVER);
break;
/* Disable alerts for Seconds Alert Class */
case CLEAR_ALERT_CLASS_Sec:
/* Clear the associated variables */
alert_cfg->sec = 0;
alert_cfg->repeat_s = false;
/*Stop the associated OS timer */
OS_TIMER_STOP(timer_s, OS_TIMER_FOREVER);
break;
/* Disable alerts for all classes */
case CLEAR_ALERT_CLASS_All:
/* Clear all the alert variables */
memset((uint8_t *)alert_cfg, 0x00, sizeof(time_alert_t));
/*Stop all the associated OS timers */
OS_TIMER_STOP(timer_h, OS_TIMER_FOREVER);
OS_TIMER_STOP(timer_m, OS_TIMER_FOREVER);
OS_TIMER_STOP(timer_s, OS_TIMER_FOREVER);
break;
default:
OS_ASSERT(0);
}
}
/*
* Function responsible for registering the user-defined callback function
*/
void time_alert_register_cb(alert_cb user_cb)
{
cb = user_cb;
}
/*
* Function responsible for deleting the user-defined callback function
*/
void time_alert_unregister_cb(void)
{
cb = NULL;
}
6.10. Alert Implementation Header File¶
Create a new header file, for example alert_mechanism.h
and add the following code:
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
/**
*
* \brief Alert related parameters.
*
* \note There are three distinct ALERT classes that can be configured individually.
*
*/
typedef struct time_alert {
/*
* A value equal to zero means that the corresponding alert class is disabled.
*/
uint16_t hour;
uint16_t min;
uint16_t sec;
/*
* Define whether an alert will be repeatable. Valid values are:
* true: for repeatable alarms, false otherwise.
*/
bool repeat_h;
bool repeat_m;
bool repeat_s;
} time_alert_t;
/**
*
* \brief Clear an alert class
*
*/
typedef enum {
CLEAR_ALERT_CLASS_Hour = 0,
CLEAR_ALERT_CLASS_Min = 1,
CLEAR_ALERT_CLASS_Sec = 2,
CLEAR_ALERT_CLASS_All = 3,
} CLEAR_ALERT_CLASS;
/**
*
* \brief Set an alert class
*
*/
typedef enum {
SET_ALERT_CLASS_Hour = 0,
SET_ALERT_CLASS_Min = 1,
SET_ALERT_CLASS_Sec = 2,
} SET_ALERT_CLASS;
/**
*
* \brief Alert Class Masks
*
* These masks should be used within callback functions to identify
* the source of an alert event. This is necessary since all the
* ALERT classes will call the same callback function.
*
*/
#define ALERT_SOURCE_HOURS_Msk (0x01)
#define ALERT_SOURCE_MINUTES_Msk (0x02)
#define ALERT_SOURCE_SECONDS_Msk (0x04)
/**
*
* \brief User-defined callback function
*
* This function will be called upon an alert event. It should have that specific prototype!
*
* \note All the alert classes will call the same callback. User is responsible to identify
* the source of an alert event. Following the recommended callback usage:
*
* \code{.c}
* void my_alert_cb(uint8_t source)
* {
* if (source & ALERT_SOURCE_HOURS_Msk) {
* // Do something...
* }
*
* if (source & ALERT_SOURCE_MINUTES_Msk) {
* // Do something...
* }
*
* if (source & ALERT_SOURCE_SECONDS_Msk) {
* // Do something...
* }
* }
* \endcode
*/
typedef void (*alert_cb)(uint8_t source);
/**
*
* \brief Alert mechanism initialization
*
* This function initializes all the resources required for the alert mechanism.
*
* \note It should be the first invoked API before any other alert related operation.
*
*/
void time_alert_init(void);
/**
*
* \brief Callback function registration
*
*
* This function should be used for registering a callback function, called upon an alert event.
*
*
* \param [in] user_cb The user-defined callback function.
*
* \note If \p user_cb is set to NULL, none callback function will be called upon an alert event.
*
*/
void time_alert_register_cb(alert_cb user_cb);
/**
*
* \brief Callback function deletion
*
* This function should be used for unregistering any previously declared callback.
*
*/
void time_alert_unregister_cb(void);
/**
*
* \brief Set alerts for a specific ALERT class
*
* This function should be used for declaring alerts of a specific ALERT class.
* There are three distinct alert classes that can be configured individually.
*
* The following code demonstrates alert configurations, expressed in minutes. Assuming we
* want alerts to be triggered every 5 minutes (triggered repeatedly).
*
* \code{.c}
* time_alert_set_class(5, true, SET_ALERT_CLASS_Min)
* \endcode
*
* The following code demonstrates alert configurations, expressed in seconds. Assuming we
* want an alert after 5 minutes (triggered once)
*
* \code{.c}
* time_alert_set_class(5, false, SET_ALERT_CLASS_Min)
* \endcode
*
*
* \param [in] value Time interval of alert events.
* \param [in] repeat If set to false, alerts will be triggered once, repeatedly otherwise.
* \param [in] alert One of the three alert classes.
*
*
* \note If \p value is set to zero, alerts for that specific alert class will be disabled.
*
*/
void time_alert_class_set(uint16_t value, bool repeat, SET_ALERT_CLASS alert);
/**
* \brief Set alert events for all the three alert classes
*
* This is a shortcut function since it can configure all the three alert classes.
*
* \param [in] alert Parameters for all the tree alert classes.
*
*/
void time_alert_class_set_all(time_alert_t *alert);
/**
*
* \brief Clear alert events
*
* This function should be used for disabling alert events of an individual or all the three alert classes
*
* \param [in] alert The alert class
*
*/
void time_alert_class_clear(CLEAR_ALERT_CLASS alert);