spider-bot/fw/nrf52/nrf5_sdk/components/libraries/timer/app_timer2.c

638 lines
20 KiB
C

/**
* Copyright (c) 2018 - 2019, Nordic Semiconductor ASA
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form, except as embedded into a Nordic
* Semiconductor ASA integrated circuit in a product or a software update for
* such product, must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* 4. This software, with or without modification, must only be used with a
* Nordic Semiconductor ASA integrated circuit.
*
* 5. Any software provided in binary form under this license must not be reverse
* engineered, decompiled, modified and/or disassembled.
*
* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "app_timer.h"
#include "nrf_atfifo.h"
#include "nrf_sortlist.h"
#include "nrf_delay.h"
#if APP_TIMER_WITH_PROFILER
#include "app_util_platform.h"
#endif
#if APP_TIMER_CONFIG_USE_SCHEDULER
#include "app_scheduler.h"
#endif
#include <stddef.h>
#define NRF_LOG_MODULE_NAME APP_TIMER_LOG_NAME
#if APP_TIMER_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL APP_TIMER_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR APP_TIMER_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR APP_TIMER_CONFIG_DEBUG_COLOR
#else //APP_TIMER_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL 0
#endif //APP_TIMER_CONFIG_LOG_ENABLED
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();
#include "drv_rtc.h"
/**
* Maximum possible relative value is limited by safe window to detect cases when requested
* compare event has already occured.
*/
#define APP_TIMER_SAFE_WINDOW APP_TIMER_TICKS(APP_TIMER_SAFE_WINDOW_MS)
#define APP_TIMER_RTC_MAX_VALUE (DRV_RTC_MAX_CNT - APP_TIMER_SAFE_WINDOW)
static drv_rtc_t m_rtc_inst = DRV_RTC_INSTANCE(1);
#if APP_TIMER_WITH_PROFILER
static uint8_t m_max_user_op_queue_utilization; /**< Maximum observed timer user operations queue utilization. */
static uint8_t m_current_user_op_queue_utilization; /**< Currently observed timer user operations queue utilization. */
#endif /* APP_TIMER_WITH_PROFILER */
/**
* @brief Timer requests types.
*/
typedef enum
{
TIMER_REQ_START,
TIMER_REQ_STOP,
TIMER_REQ_STOP_ALL
} app_timer_req_type_t;
/**
* @brief Operation request structure.
*/
typedef struct
{
app_timer_req_type_t type; /**< Request type. */
app_timer_t * p_timer; /**< Timer instance. */
} timer_req_t;
static app_timer_t * volatile mp_active_timer; /**< Timer currently handled by RTC driver. */
static bool m_global_active; /**< Flag used to globally disable all timers. */
static uint64_t m_base_counter;
static uint64_t m_stamp64;
/* Request FIFO instance. */
NRF_ATFIFO_DEF(m_req_fifo, timer_req_t, APP_TIMER_CONFIG_OP_QUEUE_SIZE);
/* Sortlist instance. */
static bool compare_func(nrf_sortlist_item_t * p_item0, nrf_sortlist_item_t *p_item1);
NRF_SORTLIST_DEF(m_app_timer_sortlist, compare_func); /**< Sortlist used for storing queued timers. */
/**
* @brief Return current 64 bit timestamp
*/
static uint64_t get_now(void)
{
uint64_t now = m_base_counter + drv_rtc_counter_get(&m_rtc_inst);
/* it is possible that base was not updated and overflow occured, in that case 'now' will be
* 24bit value behind. Additional timestamp updated on every 24 bit period is used to detect
* that case. Apart from that 'now' should never be behind previously read timestamp.
*/
if (now < m_stamp64) {
now += (DRV_RTC_MAX_CNT + 1);
}
return now;
}
/**
* @brief Function used for comparing items in sorted list.
*/
static inline bool compare_func(nrf_sortlist_item_t * p_item0, nrf_sortlist_item_t *p_item1)
{
app_timer_t * p0 = CONTAINER_OF(p_item0, app_timer_t, list_item);
app_timer_t * p1 = CONTAINER_OF(p_item1, app_timer_t, list_item);
uint64_t p0_end = p0->end_val;
uint64_t p1_end = p1->end_val;
return (p0_end <= p1_end) ? true : false;
}
#if APP_TIMER_CONFIG_USE_SCHEDULER
static void scheduled_timeout_handler(void * p_event_data, uint16_t event_size)
{
ASSERT(event_size == sizeof(app_timer_event_t));
app_timer_event_t const * p_timer_event = (app_timer_event_t *)p_event_data;
p_timer_event->timeout_handler(p_timer_event->p_context);
}
#endif
/**
* @brief Function called on timer expiration
* If end value is not reached it is assumed that it was partial expiration and time is put back
* into the list. Otherwise function calls user handler if timer was not stopped before. If timer
* is in repeated mode then timer is rescheduled.
*
* @param p_timer Timer instance.
*
* @return True if reevaluation of sortlist needed (becasue it was updated).
*/
static bool timer_expire(app_timer_t * p_timer)
{
ASSERT(p_timer->handler);
bool ret = false;
if ((m_global_active == true) && (p_timer != NULL) && (p_timer->active))
{
if (get_now() >= p_timer->end_val) {
/* timer expired */
if (p_timer->repeat_period == 0)
{
p_timer->active = false;
}
#if APP_TIMER_CONFIG_USE_SCHEDULER
app_timer_event_t timer_event;
timer_event.timeout_handler = p_timer->handler;
timer_event.p_context = p_timer->p_context;
uint32_t err_code = app_sched_event_put(&timer_event,
sizeof(timer_event),
scheduled_timeout_handler);
APP_ERROR_CHECK(err_code);
#else
NRF_LOG_DEBUG("Timer expired (context: %d)", (uint32_t)p_timer->p_context)
p_timer->handler(p_timer->p_context);
#endif
/* check active flag as it may have been stopped in the user handler */
if ((p_timer->repeat_period) && (p_timer->active))
{
p_timer->end_val += p_timer->repeat_period;
nrf_sortlist_add(&m_app_timer_sortlist, &p_timer->list_item);
ret = true;
}
}
else
{
nrf_sortlist_add(&m_app_timer_sortlist, &p_timer->list_item);
ret = true;
}
}
return ret;
}
/**
* @brief Function is configuring RTC driver to trigger timeout interrupt for given timer.
*
* It is possible that RTC driver will indicate that timeout already occured. In that case timer
* expires and function indicates that RTC was not configured.
*
* @param p_timer Timer instance.
* @param [in,out] p_rerun Flag indicating that sortlist reevaluation is required.
*
* @return True if RTC was successfully configured, false if timer already expired and RTC was not
* configured.
*
*/
static bool rtc_schedule(app_timer_t * p_timer, bool * p_rerun)
{
ret_code_t ret = NRF_ERROR_TIMEOUT;
*p_rerun = false;
int64_t remaining = (int64_t)(p_timer->end_val - get_now());
if (remaining > 0) {
uint32_t cc_val = ((uint32_t)remaining > APP_TIMER_RTC_MAX_VALUE) ?
(app_timer_cnt_get() + APP_TIMER_RTC_MAX_VALUE) : p_timer->end_val;
ret = drv_rtc_windowed_compare_set(&m_rtc_inst, 0, cc_val, APP_TIMER_SAFE_WINDOW);
NRF_LOG_DEBUG("Setting CC to 0x%08x (err: %d)", cc_val & DRV_RTC_MAX_CNT, ret);
if (ret == NRF_SUCCESS)
{
return true;
}
}
else
{
drv_rtc_compare_disable(&m_rtc_inst, 0);
}
if (ret == NRF_ERROR_TIMEOUT)
{
*p_rerun = timer_expire(p_timer);
}
else
{
NRF_LOG_ERROR("Unexpected error: %d", ret);
ASSERT(0);
}
return false;
}
static inline app_timer_t * sortlist_pop(void)
{
nrf_sortlist_item_t * p_next_item = nrf_sortlist_pop(&m_app_timer_sortlist);
return p_next_item ? CONTAINER_OF(p_next_item, app_timer_t, list_item) : NULL;
}
static inline app_timer_t * sortlist_peek(void)
{
nrf_sortlist_item_t const * p_next_item = nrf_sortlist_peek(&m_app_timer_sortlist);
return p_next_item ? CONTAINER_OF(p_next_item, app_timer_t, list_item) : NULL;
}
/**
* @brief Function for deactivating all timers which are in the sorted list (active timers).
*/
static void sorted_list_stop_all(void)
{
app_timer_t * p_next;
do
{
p_next = sortlist_pop();
if (p_next)
{
p_next->active = false;
}
} while (p_next);
}
/**
* @brief Function for handling RTC counter overflow.
*
* Increment base counter used to calculate 64 bit timestamp.
*/
static void on_overflow_evt(void)
{
NRF_LOG_DEBUG("Overflow EVT");
m_base_counter += (DRV_RTC_MAX_CNT + 1);
}
/**
* #brief Function for handling RTC compare event - active timer expiration.
*/
static void on_compare_evt(drv_rtc_t const * const p_instance)
{
if (mp_active_timer)
{
/* If assert fails it suggests that safe window should be increased. */
ASSERT(app_timer_cnt_diff_compute(drv_rtc_counter_get(p_instance),
mp_active_timer->end_val & RTC_COUNTER_COUNTER_Msk) < APP_TIMER_SAFE_WINDOW);
NRF_LOG_INST_DEBUG(mp_active_timer->p_log, "Compare EVT");
UNUSED_RETURN_VALUE(timer_expire(mp_active_timer));
mp_active_timer = NULL;
}
else
{
NRF_LOG_WARNING("Compare event but no active timer (already stopped?)");
}
}
/**
* @brief Channel 1 is triggered in the middle of 24 bit period to updated control timestamp in
* place where there is no risk of overflow.
*/
static void on_compare1_evt(drv_rtc_t const * const p_instance)
{
m_stamp64 = get_now();
}
/**
* @brief Function updates RTC.
*
* Function is called at the end of RTC interrupt when all new user request and/or timer expiration
* occured. It configures RTC if there is any pending timer, reconfigures if the are timers with
* shorted timeout than active one or stops RTC if there is no active timers.
*/
static void rtc_update(drv_rtc_t const * const p_instance)
{
while(1)
{
app_timer_t * p_next = sortlist_peek();
bool rtc_reconf = false;
if (p_next) //Candidate for active timer
{
if (mp_active_timer == NULL)
{
//There is no active timer so candidate will become active timer.
rtc_reconf = true;
}
else if (p_next->end_val < mp_active_timer->end_val)
{
//Candidate has shorter timeout than current active timer. Candidate will replace active timer.
//Active timer is put back into sorted list.
rtc_reconf = true;
if (mp_active_timer->active)
{
NRF_LOG_INST_DEBUG(mp_active_timer->p_log, "Timer preempted.");
nrf_sortlist_add(&m_app_timer_sortlist, &mp_active_timer->list_item);
}
}
if (rtc_reconf)
{
bool rerun;
p_next = sortlist_pop();
NRF_LOG_INST_DEBUG(p_next->p_log, "Activating timer (CC:%d/%08x).", p_next->end_val, p_next->end_val);
if (rtc_schedule(p_next, &rerun))
{
if (!APP_TIMER_KEEPS_RTC_ACTIVE && (mp_active_timer == NULL))
{
drv_rtc_start(p_instance);
}
mp_active_timer = p_next;
if (rerun == false)
{
//RTC was successfully updated and sortlist was not updated. Function can be terminated.
break;
}
}
else
{
//If RTC driver indicated that timeout already occured a new candidate will be taken from sorted list.
NRF_LOG_INST_DEBUG(p_next->p_log,"Timer expired before scheduled to RTC.");
mp_active_timer = NULL;
}
}
else
{
//RTC will not be updated. Function can terminate.
break;
}
}
else //No candidate for active timer.
{
if (!APP_TIMER_KEEPS_RTC_ACTIVE && (mp_active_timer == NULL))
{
drv_rtc_stop(p_instance);
}
break;
}
}
}
/**
* @brief Function for processing user requests.
*
* Function is called only in the context of RTC interrupt.
*/
static void timer_req_process(drv_rtc_t const * const p_instance)
{
nrf_atfifo_item_get_t fifo_ctx;
timer_req_t * p_req = nrf_atfifo_item_get(m_req_fifo, &fifo_ctx);
while (p_req)
{
switch (p_req->type)
{
case TIMER_REQ_START:
if (!p_req->p_timer->active)
{
p_req->p_timer->active = true;
nrf_sortlist_add(&m_app_timer_sortlist, &(p_req->p_timer->list_item));
NRF_LOG_INST_DEBUG(p_req->p_timer->p_log,"Start request (expiring at %d/0x%08x).",
p_req->p_timer->end_val, p_req->p_timer->end_val);
}
break;
case TIMER_REQ_STOP:
if (p_req->p_timer == mp_active_timer)
{
mp_active_timer = NULL;
}
else
{
bool found = nrf_sortlist_remove(&m_app_timer_sortlist, &(p_req->p_timer->list_item));
if (!found)
{
NRF_LOG_INFO("Timer not found on sortlist (stopping expired timer).");
}
}
NRF_LOG_INST_DEBUG(p_req->p_timer->p_log,"Stop request.");
break;
case TIMER_REQ_STOP_ALL:
sorted_list_stop_all();
m_global_active = true;
NRF_LOG_INFO("Stop all request.");
break;
default:
break;
}
#if APP_TIMER_WITH_PROFILER
CRITICAL_REGION_ENTER();
#endif
UNUSED_RETURN_VALUE(nrf_atfifo_item_free(m_req_fifo, &fifo_ctx));
#if APP_TIMER_WITH_PROFILER
if (m_max_user_op_queue_utilization < m_current_user_op_queue_utilization)
{
m_max_user_op_queue_utilization = m_current_user_op_queue_utilization;
}
--m_current_user_op_queue_utilization;
CRITICAL_REGION_EXIT();
#endif /* APP_TIMER_WITH_PROFILER */
p_req = nrf_atfifo_item_get(m_req_fifo, &fifo_ctx);
}
}
static void rtc_irq(drv_rtc_t const * const p_instance)
{
if (drv_rtc_overflow_pending(p_instance))
{
on_overflow_evt();
}
if (drv_rtc_compare_pending(p_instance, 0))
{
on_compare_evt(p_instance);
}
if (drv_rtc_compare_pending(p_instance, 1))
{
on_compare1_evt(p_instance);
}
timer_req_process(p_instance);
rtc_update(p_instance);
}
/**
* @brief Function for triggering processing user requests.
*
* @note All user requests are processed in a single context - RTC interrupt.
*/
static inline void timer_request_proc_trigger(void)
{
drv_rtc_irq_trigger(&m_rtc_inst);
}
/**
* @brief Function for putting user request into the request queue
*/
static ret_code_t timer_req_schedule(app_timer_req_type_t type, app_timer_t * p_timer)
{
nrf_atfifo_item_put_t fifo_ctx;
timer_req_t * p_req;
#if APP_TIMER_WITH_PROFILER
CRITICAL_REGION_ENTER();
#endif
p_req = nrf_atfifo_item_alloc(m_req_fifo, &fifo_ctx);
#if APP_TIMER_WITH_PROFILER
if (p_req)
{
++m_current_user_op_queue_utilization;
}
CRITICAL_REGION_EXIT();
#endif /* APP_TIMER_WITH_PROFILER */
if (p_req)
{
p_req->type = type;
p_req->p_timer = p_timer;
if (nrf_atfifo_item_put(m_req_fifo, &fifo_ctx))
{
timer_request_proc_trigger();
}
else
{
NRF_LOG_WARNING("Scheduling interrupted another scheduling.");
}
return NRF_SUCCESS;
}
else
{
return NRF_ERROR_NO_MEM;
}
}
ret_code_t app_timer_init(void)
{
ret_code_t err_code;
drv_rtc_config_t config = {
.prescaler = APP_TIMER_CONFIG_RTC_FREQUENCY,
.interrupt_priority = APP_TIMER_CONFIG_IRQ_PRIORITY
};
err_code = NRF_ATFIFO_INIT(m_req_fifo);
if (err_code != NRFX_SUCCESS)
{
return err_code;
}
err_code = drv_rtc_init(&m_rtc_inst, &config, rtc_irq);
if (err_code != NRFX_SUCCESS)
{
return err_code;
}
drv_rtc_overflow_enable(&m_rtc_inst, true);
drv_rtc_compare_set(&m_rtc_inst, 1, DRV_RTC_MAX_CNT >> 1, true);
if (APP_TIMER_KEEPS_RTC_ACTIVE)
{
drv_rtc_start(&m_rtc_inst);
}
m_global_active = true;
return err_code;
}
ret_code_t app_timer_create(app_timer_id_t const * p_timer_id,
app_timer_mode_t mode,
app_timer_timeout_handler_t timeout_handler)
{
ASSERT(p_timer_id);
ASSERT(timeout_handler);
if (timeout_handler == NULL)
{
return NRF_ERROR_INVALID_PARAM;
}
app_timer_t * p_t = (app_timer_t *) *p_timer_id;
p_t->handler = timeout_handler;
p_t->repeat_period = (mode == APP_TIMER_MODE_REPEATED) ? 1 : 0;
return NRF_SUCCESS;
}
ret_code_t app_timer_start(app_timer_t * p_timer, uint32_t timeout_ticks, void * p_context)
{
ASSERT(p_timer);
app_timer_t * p_t = (app_timer_t *) p_timer;
if (p_t->active)
{
return NRF_SUCCESS;
}
p_t->p_context = p_context;
p_t->end_val = get_now() + timeout_ticks;
if (p_t->repeat_period)
{
p_t->repeat_period = timeout_ticks;
}
return timer_req_schedule(TIMER_REQ_START, p_t);
}
ret_code_t app_timer_stop(app_timer_t * p_timer)
{
ASSERT(p_timer);
app_timer_t * p_t = (app_timer_t *) p_timer;
p_t->active = false;
return timer_req_schedule(TIMER_REQ_STOP, p_t);
}
ret_code_t app_timer_stop_all(void)
{
//block timer globally
m_global_active = false;
return timer_req_schedule(TIMER_REQ_STOP_ALL, NULL);
}
#if APP_TIMER_WITH_PROFILER
uint8_t app_timer_op_queue_utilization_get(void)
{
return m_max_user_op_queue_utilization;
}
#endif /* APP_TIMER_WITH_PROFILER */
uint32_t app_timer_cnt_diff_compute(uint32_t ticks_to,
uint32_t ticks_from)
{
return ((ticks_to - ticks_from) & RTC_COUNTER_COUNTER_Msk);
}
uint32_t app_timer_cnt_get(void)
{
return drv_rtc_counter_get(&m_rtc_inst);
}
void app_timer_pause(void)
{
drv_rtc_stop(&m_rtc_inst);
}
void app_timer_resume(void)
{
drv_rtc_start(&m_rtc_inst);
}