1 /*------------------------------------------------------------------------------------------------*/
2 /* UNICENS V2.1.0-3491 */
3 /* Copyright (c) 2017 Microchip Technology Germany II GmbH & Co. KG. */
5 /* This program is free software: you can redistribute it and/or modify */
6 /* it under the terms of the GNU General Public License as published by */
7 /* the Free Software Foundation, either version 2 of the License, or */
8 /* (at your option) any later version. */
10 /* This program is distributed in the hope that it will be useful, */
11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
13 /* GNU General Public License for more details. */
15 /* You should have received a copy of the GNU General Public License */
16 /* along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 /* You may also obtain this software under a propriety license from Microchip. */
19 /* Please contact Microchip for further information. */
20 /*------------------------------------------------------------------------------------------------*/
24 * \brief Implementation of the timer management module.
26 * \cond UCS_INTERNAL_DOC
31 /*------------------------------------------------------------------------------------------------*/
33 /*------------------------------------------------------------------------------------------------*/
34 #include "ucs_timer.h"
36 #include "ucs_trace.h"
38 /*------------------------------------------------------------------------------------------------*/
39 /* Service parameters */
40 /*------------------------------------------------------------------------------------------------*/
41 /*! Priority of the TM service used by scheduler */
42 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." */
43 /*! Main event for the TM service */
44 static const Srv_Event_t TM_EVENT_UPDATE_TIMERS = 1U;
46 /*------------------------------------------------------------------------------------------------*/
47 /* Internal prototypes */
48 /*------------------------------------------------------------------------------------------------*/
49 static void Tm_Service(void *self);
50 static void Tm_UpdateTimers(CTimerManagement *self);
51 static bool Tm_HandleElapsedTimer(CTimerManagement *self);
52 static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr);
53 static void Tm_SetTimerInternal(CTimerManagement *self,
55 Tm_Handler_t handler_fptr,
60 /*------------------------------------------------------------------------------------------------*/
61 /* Implementation of class CTimerManagement */
62 /*------------------------------------------------------------------------------------------------*/
63 /*! \brief Constructor of the timer management class.
64 * \param self Instance pointer
65 * \param scd Scheduler instance
66 * \param init_ptr Reference to the initialization data
67 * \param ucs_user_ptr User reference that needs to be passed in every callback function
69 void Tm_Ctor(CTimerManagement *self, CScheduler *scd, const Tm_InitData_t *init_ptr, void * ucs_user_ptr)
71 MISC_MEM_SET(self, 0, sizeof(*self));
72 self->ucs_user_ptr = ucs_user_ptr;
73 /* Initialize subjects and add observers */
74 Ssub_Ctor(&self->get_tick_count_subject, self->ucs_user_ptr);
75 (void)Ssub_AddObserver(&self->get_tick_count_subject,
76 init_ptr->get_tick_count_obs_ptr);
77 if(init_ptr->set_application_timer_obs_ptr != NULL)
79 self->delayed_tm_service_enabled = true;
80 Ssub_Ctor(&self->set_application_timer_subject, self->ucs_user_ptr);
81 (void)Ssub_AddObserver(&self->set_application_timer_subject,
82 init_ptr->set_application_timer_obs_ptr);
84 /* Initialize timer management service */
85 Srv_Ctor(&self->tm_srv, TM_SRV_PRIO, self, &Tm_Service);
86 /* Add timer management service to scheduler */
87 (void)Scd_AddService(scd, &self->tm_srv);
90 /*! \brief Service function of the timer management.
91 * \param self Instance pointer
93 static void Tm_Service(void *self)
95 CTimerManagement *self_ = (CTimerManagement *)self;
96 Srv_Event_t event_mask;
98 Srv_GetEvent(&self_->tm_srv, &event_mask);
100 if(TM_EVENT_UPDATE_TIMERS == (event_mask & TM_EVENT_UPDATE_TIMERS)) /* Is event pending? */
102 Srv_ClearEvent(&self_->tm_srv, TM_EVENT_UPDATE_TIMERS);
103 Tm_UpdateTimers(self_);
107 /*! \brief If event TM_EVENT_UPDATE_TIMERS is set this function is called. Handles the update
108 * of the timer list. If a timer has expired the corresponding callback function is
109 * executed. If the expired timer is a periodic timer, the timer will be set again.
110 * \param self Instance pointer
112 static void Tm_UpdateTimers(CTimerManagement *self)
114 uint16_t current_tick_count;
115 Ssub_Notify(&self->get_tick_count_subject, ¤t_tick_count, false);
117 if(self->timer_list.head != NULL) /* At least one timer is running? */
119 bool continue_loop = true;
120 /* Calculate time difference between the current and the last TM service run */
121 uint16_t tick_count_diff = (uint16_t)(current_tick_count - self->last_tick_count);
122 /* Save current tick count for next service run */
123 self->last_tick_count = current_tick_count;
125 /* Loop while timer list is not empty */
126 while((self->timer_list.head != NULL) && (continue_loop!= false))
128 /* Is not first timer in list elapsed yet? */
129 if(tick_count_diff <= ((CTimer *)self->timer_list.head->data_ptr)->delta)
131 /* Update delta of first timer in list */
132 ((CTimer *)self->timer_list.head->data_ptr)->delta -= tick_count_diff;
133 tick_count_diff = 0U;
135 else /* At least first timer in list elapsed */
137 /* Update tick count difference for next timer in list */
138 tick_count_diff -= ((CTimer *)self->timer_list.head->data_ptr)->delta;
139 /* First timer elapsed */
140 ((CTimer *)self->timer_list.head->data_ptr)->delta = 0U;
143 /* First timer in list elapsed? */
144 if(0U == ((CTimer *)self->timer_list.head->data_ptr)->delta)
146 /* Handle elapsed timer */
147 continue_loop = Tm_HandleElapsedTimer(self);
149 else /* No elapsed timer in list. */
151 /* First timer in list updated! Set trigger to inform application (see
152 Tm_CheckForNextService()) and stop TM service. */
153 self->set_service_timer = true;
154 continue_loop = false;
160 /*! \brief This function is called if the first timer in list is elapsed. The timer handler
161 * callback function is invoked. If the timer is a periodic timer it is wound up again.
162 * \param self Instance pointer
163 * \return \c true if the next timer must be check.
164 * \return \c false if the wound up timer (periodic timer) is new head of timer list
166 static bool Tm_HandleElapsedTimer(CTimerManagement *self)
170 CDlNode *node = self->timer_list.head;
171 /* Reset flag to be able to check if timer object has changed within handler
173 ((CTimer *)node->data_ptr)->changed = false;
174 /* Call timer handler callback function */
175 ((CTimer *)node->data_ptr)->handler_fptr(((CTimer *)node->data_ptr)->args_ptr);
177 /* Timer object hasn't changed within handler callback function? */
178 if(false == ((CTimer *)node->data_ptr)->changed)
180 /* Remove current timer from list */
181 (void)Dl_Remove(&self->timer_list, node);
182 /* Mark timer as unused */
183 ((CTimer *)node->data_ptr)->in_use = false;
184 /* Is current timer a periodic timer? */
185 if(((CTimer *)node->data_ptr)->period > 0U)
187 /* Reload current timer */
188 Tm_SetTimerInternal(self,
189 ((CTimer *)node->data_ptr),
190 ((CTimer *)node->data_ptr)->handler_fptr,
191 ((CTimer *)node->data_ptr)->args_ptr,
192 ((CTimer *)node->data_ptr)->period,
193 ((CTimer *)node->data_ptr)->period);
195 if(node == self->timer_list.head) /* Is current timer new head of list? */
197 /* Set trigger to inform application (see Tm_CheckForNextService()) and
199 self->set_service_timer = true;
208 /*! \brief Calls an application callback function to inform the application that the UCS must be
209 * serviced not later than the passed time period. If the timer list is empty a possible
210 * running application timer will be stopped. This function is called at the end of
212 * \param self Instance pointer
214 void Tm_CheckForNextService(CTimerManagement *self)
216 if(self->delayed_tm_service_enabled != false)
218 uint16_t current_tick_count;
219 Ssub_Notify(&self->get_tick_count_subject, ¤t_tick_count, false);
220 /* Has head of timer list changed? */
221 if(self->set_service_timer != false)
224 uint16_t diff = current_tick_count - self->last_tick_count;
225 self->set_service_timer = false;
226 if (self->timer_list.head != NULL)
228 /* Timer expired since last TM service? */
229 if(diff >= ((CTimer *)self->timer_list.head->data_ptr)->delta)
231 new_time = 1U; /* Return minimum value */
235 /* Calculate new timeout */
236 new_time = (uint16_t)(((CTimer *)self->timer_list.head->data_ptr)->delta - diff);
238 /* Inform the application that the UCS must be serviced not later than the passed
240 Ssub_Notify(&self->set_application_timer_subject, &new_time, false);
246 Tm_TriggerService(self); /* Application timer not implemented -> Retrigger TM */
250 /*! \brief Helper function to set the TM service event.
251 * \details This function is used by the application to trigger a service call of the Timer
252 * Management if the application timer has expired.
253 * \param self Instance pointer
255 void Tm_TriggerService(CTimerManagement *self)
257 if(self->timer_list.head != NULL) /* At least one timer is running? */
259 Srv_SetEvent(&self->tm_srv, TM_EVENT_UPDATE_TIMERS);
263 /*! \brief Helper function to stop the TM service.
264 * \param self Instance pointer
266 void Tm_StopService(CTimerManagement *self)
268 uint16_t new_time = 0U;
270 /* Clear probable running application timer */
271 Ssub_Notify(&self->set_application_timer_subject, &new_time, false);
273 /* Reset the service timer. Not necessary ? */
274 self->set_service_timer = false;
276 /* Clear the timer head queue to prevent any event to be set */
277 self->timer_list.head = NULL;
280 /*! \brief Creates a new timer. The timer expires at the specified elapse time and then after
281 * every specified period. When the timer expires the specified callback function is
283 * \param self Instance pointer
284 * \param timer_ptr Reference to the timer object
285 * \param handler_fptr Callback function which is called when the timer expires
286 * \param args_ptr Reference to an optional parameter which is passed to the specified
288 * \param elapse The elapse value before the timer expires for the first time, in
290 * \param period The period of the timer, in milliseconds. If this parameter is zero, the
291 * timer is signaled once. If the parameter is greater than zero, the timer
294 void Tm_SetTimer(CTimerManagement *self,
296 Tm_Handler_t handler_fptr,
301 (void)Tm_ClearTimer(self, timer_ptr); /* Clear timer if running */
302 /* Call the internal method to set the new timer (-> does not trigger TM service!) */
303 Tm_SetTimerInternal(self, timer_ptr, handler_fptr, args_ptr, elapse, period);
304 Tm_TriggerService(self); /* New timer added -> trigger timer list update */
307 /*! \brief This function contains the internal part when adding a new timer. The function is
308 * called within Tm_SetTimer() and within Tm_UpdateTimers().
309 * \param self Instance pointer
310 * \param timer_ptr Reference to the timer object
311 * \param handler_fptr Callback function which is called when the timer expires
312 * \param args_ptr Reference to an optional parameter which is passed to the specified
314 * \param elapse The elapse value before the timer expires for the first time, in
316 * \param period The period of the timer, in milliseconds. If this parameter is zero, the
317 * timer is signaled once. If the parameter is greater than zero, the timer
320 static void Tm_SetTimerInternal(CTimerManagement *self,
322 Tm_Handler_t handler_fptr,
327 uint16_t current_tick_count;
328 Ssub_Notify(&self->get_tick_count_subject, ¤t_tick_count, false);
330 /* Save timer specific values */
331 timer_ptr->changed = true; /* Flag is needed by Tm_UpdateTimers() */
332 timer_ptr->in_use = true;
333 timer_ptr->handler_fptr = handler_fptr;
334 timer_ptr->args_ptr = args_ptr;
335 timer_ptr->elapse = elapse;
336 timer_ptr->period = period;
337 timer_ptr->delta = elapse;
339 /* Create back link to be able to point from node to timer object */
340 timer_ptr->node.data_ptr = (void *)timer_ptr;
342 if(self->timer_list.head == NULL) /* Is timer list empty? */
344 Dl_InsertHead(&self->timer_list, &timer_ptr->node); /* Add first timer to list */
345 /* Save current tick count */
346 Ssub_Notify(&self->get_tick_count_subject, &self->last_tick_count, false);
348 else /* Timer list is not empty */
350 CDlNode *result_ptr = NULL;
352 /* Set delta value in relation to last saved tick count (last TM service) */
353 timer_ptr->delta += (uint16_t)(current_tick_count - self->last_tick_count);
355 /* Search slot where new timer must be inserted. Update delta of new timer
356 and delta of the following timer in the list. */
357 result_ptr = Dl_Foreach(&self->timer_list, &Tm_UpdateTimersAdd, (void *)timer_ptr);
359 if(result_ptr != NULL) /* Slot found? */
361 /* Insert new timer at found position */
362 Dl_InsertBefore(&self->timer_list, result_ptr, &timer_ptr->node);
364 else /* No slot found -> Insert as last node */
366 /* Add new timer to end of list */
367 Dl_InsertTail(&self->timer_list, &timer_ptr->node);
372 /*! \brief Removes the specified timer from the timer list.
373 * \param self Instance pointer
374 * \param timer_ptr Reference to the timer object
375 * \attention Make sure that for a timer object Tm_SetTimer() is called before Tm_ClearTimer()
378 void Tm_ClearTimer(CTimerManagement *self, CTimer *timer_ptr)
380 if(timer_ptr->in_use != false) /* Is timer currently in use? */
382 timer_ptr->changed = true; /* Flag is needed by Tm_UpdateTimers() */
384 if(timer_ptr->node.next != NULL) /* Has deleted timer a follower? */
386 /* Adjust delta of following timer */
387 ((CTimer *)timer_ptr->node.next->data_ptr)->delta += timer_ptr->delta;
390 (void)Dl_Remove(&self->timer_list, &timer_ptr->node);
391 timer_ptr->in_use = false;
393 Tm_TriggerService(self); /* Timer removed -> trigger timer list update */
397 /*! \brief Used by Tm_SetTimer() to find the slot where the new timer must be inserted.
398 * \param c_timer_ptr Reference to current timer processed by foreach loop
399 * \param n_timer_ptr Reference to new timer
400 * \return \c true: Slot found, stop foreach loop
401 * \return \c false: Slot not found, continue foreach loop
403 static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr)
405 CTimer *current_timer_ptr = (CTimer *)c_timer_ptr;
406 CTimer *new_timer_ptr = (CTimer *)n_timer_ptr;
409 /* Is current timer lesser than new timer? */
410 if(current_timer_ptr->delta <= new_timer_ptr->delta)
412 /* Update delta of new timer and continue foreach loop */
413 new_timer_ptr->delta -= current_timer_ptr->delta;
416 else /* Slot found! */
418 /* Correct delta of current timer and stop foreach loop */
419 current_timer_ptr->delta -= new_timer_ptr->delta;
427 /*------------------------------------------------------------------------------------------------*/
428 /* Implementation of class CTimer */
429 /*------------------------------------------------------------------------------------------------*/
430 /*! \brief Constructor of the Timer class.
431 * \param self Instance pointer
433 void T_Ctor(CTimer *self)
435 MISC_MEM_SET(self, 0, sizeof(*self));
438 /*! \brief Returns the status of the given timer.
439 * \param self Instance pointer
440 * \return \c true if the timer is currently in use
441 * \return \c false if the timer is not currently in use
443 bool T_IsTimerInUse(CTimer *self)
453 /*------------------------------------------------------------------------------------------------*/
455 /*------------------------------------------------------------------------------------------------*/