diff options
author | Fulup Ar Foll <fulup@iot.bzh> | 2017-05-26 18:45:56 +0200 |
---|---|---|
committer | Fulup Ar Foll <fulup@iot.bzh> | 2017-05-26 18:45:56 +0200 |
commit | d2e42029ec04c3f224580f8007cdfbbfe0fc47a6 (patch) | |
tree | ad2ccf167cf7997c84191d41e6ba55cb2efd6bed /ucs2-lib/src/ucs_timer.c | |
parent | 18e393e1443fd4c38b34979888fb55d30448cf31 (diff) |
Initial Commit
Diffstat (limited to 'ucs2-lib/src/ucs_timer.c')
-rw-r--r-- | ucs2-lib/src/ucs_timer.c | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/ucs2-lib/src/ucs_timer.c b/ucs2-lib/src/ucs_timer.c new file mode 100644 index 0000000..6563374 --- /dev/null +++ b/ucs2-lib/src/ucs_timer.c @@ -0,0 +1,456 @@ +/*------------------------------------------------------------------------------------------------*/ +/* UNICENS V2.1.0-3491 */ +/* Copyright (c) 2017 Microchip Technology Germany II GmbH & Co. KG. */ +/* */ +/* This program is free software: you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation, either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/* */ +/* You may also obtain this software under a propriety license from Microchip. */ +/* Please contact Microchip for further information. */ +/*------------------------------------------------------------------------------------------------*/ + +/*! + * \file + * \brief Implementation of the timer management module. + * + * \cond UCS_INTERNAL_DOC + * \addtogroup G_TIMER + * @{ + */ + +/*------------------------------------------------------------------------------------------------*/ +/* Includes */ +/*------------------------------------------------------------------------------------------------*/ +#include "ucs_timer.h" +#include "ucs_misc.h" +#include "ucs_trace.h" + +/*------------------------------------------------------------------------------------------------*/ +/* Service parameters */ +/*------------------------------------------------------------------------------------------------*/ +/*! Priority of the TM service used by scheduler */ +static const uint8_t TM_SRV_PRIO = 255U; /* parasoft-suppress MISRA2004-8_7 "Value shall be part of the module, not part of a function." */ +/*! Main event for the TM service */ +static const Srv_Event_t TM_EVENT_UPDATE_TIMERS = 1U; + +/*------------------------------------------------------------------------------------------------*/ +/* Internal prototypes */ +/*------------------------------------------------------------------------------------------------*/ +static void Tm_Service(void *self); +static void Tm_UpdateTimers(CTimerManagement *self); +static bool Tm_HandleElapsedTimer(CTimerManagement *self); +static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr); +static void Tm_SetTimerInternal(CTimerManagement *self, + CTimer *timer_ptr, + Tm_Handler_t handler_fptr, + void *args_ptr, + uint16_t elapse, + uint16_t period); + +/*------------------------------------------------------------------------------------------------*/ +/* Implementation of class CTimerManagement */ +/*------------------------------------------------------------------------------------------------*/ +/*! \brief Constructor of the timer management class. + * \param self Instance pointer + * \param scd Scheduler instance + * \param init_ptr Reference to the initialization data + * \param ucs_user_ptr User reference that needs to be passed in every callback function + */ +void Tm_Ctor(CTimerManagement *self, CScheduler *scd, const Tm_InitData_t *init_ptr, void * ucs_user_ptr) +{ + MISC_MEM_SET(self, 0, sizeof(*self)); + self->ucs_user_ptr = ucs_user_ptr; + /* Initialize subjects and add observers */ + Ssub_Ctor(&self->get_tick_count_subject, self->ucs_user_ptr); + (void)Ssub_AddObserver(&self->get_tick_count_subject, + init_ptr->get_tick_count_obs_ptr); + if(init_ptr->set_application_timer_obs_ptr != NULL) + { + self->delayed_tm_service_enabled = true; + Ssub_Ctor(&self->set_application_timer_subject, self->ucs_user_ptr); + (void)Ssub_AddObserver(&self->set_application_timer_subject, + init_ptr->set_application_timer_obs_ptr); + } + /* Initialize timer management service */ + Srv_Ctor(&self->tm_srv, TM_SRV_PRIO, self, &Tm_Service); + /* Add timer management service to scheduler */ + (void)Scd_AddService(scd, &self->tm_srv); +} + +/*! \brief Service function of the timer management. + * \param self Instance pointer + */ +static void Tm_Service(void *self) +{ + CTimerManagement *self_ = (CTimerManagement *)self; + Srv_Event_t event_mask; + + Srv_GetEvent(&self_->tm_srv, &event_mask); + + if(TM_EVENT_UPDATE_TIMERS == (event_mask & TM_EVENT_UPDATE_TIMERS)) /* Is event pending? */ + { + Srv_ClearEvent(&self_->tm_srv, TM_EVENT_UPDATE_TIMERS); + Tm_UpdateTimers(self_); + } +} + +/*! \brief If event TM_EVENT_UPDATE_TIMERS is set this function is called. Handles the update + * of the timer list. If a timer has expired the corresponding callback function is + * executed. If the expired timer is a periodic timer, the timer will be set again. + * \param self Instance pointer + */ +static void Tm_UpdateTimers(CTimerManagement *self) +{ + uint16_t current_tick_count; + Ssub_Notify(&self->get_tick_count_subject, ¤t_tick_count, false); + + if(self->timer_list.head != NULL) /* At least one timer is running? */ + { + bool continue_loop = true; + /* Calculate time difference between the current and the last TM service run */ + uint16_t tick_count_diff = (uint16_t)(current_tick_count - self->last_tick_count); + /* Save current tick count for next service run */ + self->last_tick_count = current_tick_count; + + /* Loop while timer list is not empty */ + while((self->timer_list.head != NULL) && (continue_loop!= false)) + { + /* Is not first timer in list elapsed yet? */ + if(tick_count_diff <= ((CTimer *)self->timer_list.head->data_ptr)->delta) + { + /* Update delta of first timer in list */ + ((CTimer *)self->timer_list.head->data_ptr)->delta -= tick_count_diff; + tick_count_diff = 0U; + } + else /* At least first timer in list elapsed */ + { + /* Update tick count difference for next timer in list */ + tick_count_diff -= ((CTimer *)self->timer_list.head->data_ptr)->delta; + /* First timer elapsed */ + ((CTimer *)self->timer_list.head->data_ptr)->delta = 0U; + } + + /* First timer in list elapsed? */ + if(0U == ((CTimer *)self->timer_list.head->data_ptr)->delta) + { + /* Handle elapsed timer */ + continue_loop = Tm_HandleElapsedTimer(self); + } + else /* No elapsed timer in list. */ + { + /* First timer in list updated! Set trigger to inform application (see + Tm_CheckForNextService()) and stop TM service. */ + self->set_service_timer = true; + continue_loop = false; + } + } + } +} + +/*! \brief This function is called if the first timer in list is elapsed. The timer handler + * callback function is invoked. If the timer is a periodic timer it is wound up again. + * \param self Instance pointer + * \return \c true if the next timer must be check. + * \return \c false if the wound up timer (periodic timer) is new head of timer list + */ +static bool Tm_HandleElapsedTimer(CTimerManagement *self) +{ + bool ret_val = true; + + CDlNode *node = self->timer_list.head; + /* Reset flag to be able to check if timer object has changed within handler + callback function */ + ((CTimer *)node->data_ptr)->changed = false; + /* Call timer handler callback function */ + ((CTimer *)node->data_ptr)->handler_fptr(((CTimer *)node->data_ptr)->args_ptr); + + /* Timer object hasn't changed within handler callback function? */ + if(false == ((CTimer *)node->data_ptr)->changed) + { + /* Remove current timer from list */ + (void)Dl_Remove(&self->timer_list, node); + /* Mark timer as unused */ + ((CTimer *)node->data_ptr)->in_use = false; + /* Is current timer a periodic timer? */ + if(((CTimer *)node->data_ptr)->period > 0U) + { + /* Reload current timer */ + Tm_SetTimerInternal(self, + ((CTimer *)node->data_ptr), + ((CTimer *)node->data_ptr)->handler_fptr, + ((CTimer *)node->data_ptr)->args_ptr, + ((CTimer *)node->data_ptr)->period, + ((CTimer *)node->data_ptr)->period); + + if(node == self->timer_list.head) /* Is current timer new head of list? */ + { + /* Set trigger to inform application (see Tm_CheckForNextService()) and + stop TM service. */ + self->set_service_timer = true; + ret_val = false; + } + } + } + + return ret_val; +} + +/*! \brief Calls an application callback function to inform the application that the UCS must be + * serviced not later than the passed time period. If the timer list is empty a possible + * running application timer will be stopped. This function is called at the end of + * Ucs_Service(). + * \param self Instance pointer + */ +void Tm_CheckForNextService(CTimerManagement *self) +{ + if(self->delayed_tm_service_enabled != false) + { + uint16_t current_tick_count; + Ssub_Notify(&self->get_tick_count_subject, ¤t_tick_count, false); + /* Has head of timer list changed? */ + if(self->set_service_timer != false) + { + uint16_t new_time; + uint16_t diff = current_tick_count - self->last_tick_count; + self->set_service_timer = false; + if (self->timer_list.head != NULL) + { + /* Timer expired since last TM service? */ + if(diff >= ((CTimer *)self->timer_list.head->data_ptr)->delta) + { + new_time = 1U; /* Return minimum value */ + } + else + { + /* Calculate new timeout */ + new_time = (uint16_t)(((CTimer *)self->timer_list.head->data_ptr)->delta - diff); + } + /* Inform the application that the UCS must be serviced not later than the passed + time period. */ + Ssub_Notify(&self->set_application_timer_subject, &new_time, false); + } + } + } + else + { + Tm_TriggerService(self); /* Application timer not implemented -> Retrigger TM */ + } +} + +/*! \brief Helper function to set the TM service event. + * \details This function is used by the application to trigger a service call of the Timer + * Management if the application timer has expired. + * \param self Instance pointer + */ +void Tm_TriggerService(CTimerManagement *self) +{ + if(self->timer_list.head != NULL) /* At least one timer is running? */ + { + Srv_SetEvent(&self->tm_srv, TM_EVENT_UPDATE_TIMERS); + } +} + +/*! \brief Helper function to stop the TM service. + * \param self Instance pointer + */ +void Tm_StopService(CTimerManagement *self) +{ + uint16_t new_time = 0U; + + /* Clear probable running application timer */ + Ssub_Notify(&self->set_application_timer_subject, &new_time, false); + + /* Reset the service timer. Not necessary ? */ + self->set_service_timer = false; + + /* Clear the timer head queue to prevent any event to be set */ + self->timer_list.head = NULL; +} + +/*! \brief Creates a new timer. The timer expires at the specified elapse time and then after + * every specified period. When the timer expires the specified callback function is + * called. + * \param self Instance pointer + * \param timer_ptr Reference to the timer object + * \param handler_fptr Callback function which is called when the timer expires + * \param args_ptr Reference to an optional parameter which is passed to the specified + * callback function + * \param elapse The elapse value before the timer expires for the first time, in + * milliseconds + * \param period The period of the timer, in milliseconds. If this parameter is zero, the + * timer is signaled once. If the parameter is greater than zero, the timer + * is periodic. + */ +void Tm_SetTimer(CTimerManagement *self, + CTimer *timer_ptr, + Tm_Handler_t handler_fptr, + void *args_ptr, + uint16_t elapse, + uint16_t period) +{ + (void)Tm_ClearTimer(self, timer_ptr); /* Clear timer if running */ + /* Call the internal method to set the new timer (-> does not trigger TM service!) */ + Tm_SetTimerInternal(self, timer_ptr, handler_fptr, args_ptr, elapse, period); + Tm_TriggerService(self); /* New timer added -> trigger timer list update */ +} + +/*! \brief This function contains the internal part when adding a new timer. The function is + * called within Tm_SetTimer() and within Tm_UpdateTimers(). + * \param self Instance pointer + * \param timer_ptr Reference to the timer object + * \param handler_fptr Callback function which is called when the timer expires + * \param args_ptr Reference to an optional parameter which is passed to the specified + * callback function + * \param elapse The elapse value before the timer expires for the first time, in + * milliseconds + * \param period The period of the timer, in milliseconds. If this parameter is zero, the + * timer is signaled once. If the parameter is greater than zero, the timer + * is periodic. + */ +static void Tm_SetTimerInternal(CTimerManagement *self, + CTimer *timer_ptr, + Tm_Handler_t handler_fptr, + void *args_ptr, + uint16_t elapse, + uint16_t period) +{ + uint16_t current_tick_count; + Ssub_Notify(&self->get_tick_count_subject, ¤t_tick_count, false); + + /* Save timer specific values */ + timer_ptr->changed = true; /* Flag is needed by Tm_UpdateTimers() */ + timer_ptr->in_use = true; + timer_ptr->handler_fptr = handler_fptr; + timer_ptr->args_ptr = args_ptr; + timer_ptr->elapse = elapse; + timer_ptr->period = period; + timer_ptr->delta = elapse; + + /* Create back link to be able to point from node to timer object */ + timer_ptr->node.data_ptr = (void *)timer_ptr; + + if(self->timer_list.head == NULL) /* Is timer list empty? */ + { + Dl_InsertHead(&self->timer_list, &timer_ptr->node); /* Add first timer to list */ + /* Save current tick count */ + Ssub_Notify(&self->get_tick_count_subject, &self->last_tick_count, false); + } + else /* Timer list is not empty */ + { + CDlNode *result_ptr = NULL; + + /* Set delta value in relation to last saved tick count (last TM service) */ + timer_ptr->delta += (uint16_t)(current_tick_count - self->last_tick_count); + + /* Search slot where new timer must be inserted. Update delta of new timer + and delta of the following timer in the list. */ + result_ptr = Dl_Foreach(&self->timer_list, &Tm_UpdateTimersAdd, (void *)timer_ptr); + + if(result_ptr != NULL) /* Slot found? */ + { + /* Insert new timer at found position */ + Dl_InsertBefore(&self->timer_list, result_ptr, &timer_ptr->node); + } + else /* No slot found -> Insert as last node */ + { + /* Add new timer to end of list */ + Dl_InsertTail(&self->timer_list, &timer_ptr->node); + } + } +} + +/*! \brief Removes the specified timer from the timer list. + * \param self Instance pointer + * \param timer_ptr Reference to the timer object + * \attention Make sure that for a timer object Tm_SetTimer() is called before Tm_ClearTimer() + * is called! + */ +void Tm_ClearTimer(CTimerManagement *self, CTimer *timer_ptr) +{ + if(timer_ptr->in_use != false) /* Is timer currently in use? */ + { + timer_ptr->changed = true; /* Flag is needed by Tm_UpdateTimers() */ + + if(timer_ptr->node.next != NULL) /* Has deleted timer a follower? */ + { + /* Adjust delta of following timer */ + ((CTimer *)timer_ptr->node.next->data_ptr)->delta += timer_ptr->delta; + } + + (void)Dl_Remove(&self->timer_list, &timer_ptr->node); + timer_ptr->in_use = false; + + Tm_TriggerService(self); /* Timer removed -> trigger timer list update */ + } +} + +/*! \brief Used by Tm_SetTimer() to find the slot where the new timer must be inserted. + * \param c_timer_ptr Reference to current timer processed by foreach loop + * \param n_timer_ptr Reference to new timer + * \return \c true: Slot found, stop foreach loop + * \return \c false: Slot not found, continue foreach loop + */ +static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr) +{ + CTimer *current_timer_ptr = (CTimer *)c_timer_ptr; + CTimer *new_timer_ptr = (CTimer *)n_timer_ptr; + bool ret_val; + + /* Is current timer lesser than new timer? */ + if(current_timer_ptr->delta <= new_timer_ptr->delta) + { + /* Update delta of new timer and continue foreach loop */ + new_timer_ptr->delta -= current_timer_ptr->delta; + ret_val = false; + } + else /* Slot found! */ + { + /* Correct delta of current timer and stop foreach loop */ + current_timer_ptr->delta -= new_timer_ptr->delta; + ret_val = true; + } + + return ret_val; +} + + +/*------------------------------------------------------------------------------------------------*/ +/* Implementation of class CTimer */ +/*------------------------------------------------------------------------------------------------*/ +/*! \brief Constructor of the Timer class. + * \param self Instance pointer + */ +void T_Ctor(CTimer *self) +{ + MISC_MEM_SET(self, 0, sizeof(*self)); +} + +/*! \brief Returns the status of the given timer. + * \param self Instance pointer + * \return \c true if the timer is currently in use + * \return \c false if the timer is not currently in use + */ +bool T_IsTimerInUse(CTimer *self) +{ + return self->in_use; +} + +/*! + * @} + * \endcond + */ + +/*------------------------------------------------------------------------------------------------*/ +/* End of file */ +/*------------------------------------------------------------------------------------------------*/ + |