1 /*
2 * Copyright (c) 2006-2018, RT-Thread Development Team
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Change Logs:
7 * Date Author Notes
8 * 2006-03-12 Bernard first version
9 * 2006-04-29 Bernard implement thread timer
10 * 2006-06-04 Bernard implement rt_timer_control
11 * 2006-08-10 Bernard fix the periodic timer bug
12 * 2006-09-03 Bernard implement rt_timer_detach
13 * 2009-11-11 LiJin add soft timer
14 * 2010-05-12 Bernard fix the timer check bug.
15 * 2010-11-02 Charlie re-implement tick overflow issue
16 * 2012-12-15 Bernard fix the next timeout issue in soft timer
17 * 2014-07-12 Bernard does not lock scheduler when invoking soft-timer
18 * timeout function.
19 */
20
21 #include <rtthread.h>
22 #include <rthw.h>
23
24 /* hard timer list */
25 static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL];
26
27 #ifdef RT_USING_TIMER_SOFT
28 #ifndef RT_TIMER_THREAD_STACK_SIZE
29 #define RT_TIMER_THREAD_STACK_SIZE 512
30 #endif
31
32 #ifndef RT_TIMER_THREAD_PRIO
33 #define RT_TIMER_THREAD_PRIO 0
34 #endif
35
36 /* soft timer list */
37 static rt_list_t rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL];
38 static struct rt_thread timer_thread;
39 ALIGN(RT_ALIGN_SIZE)
40 static rt_uint8_t timer_thread_stack[RT_TIMER_THREAD_STACK_SIZE];
41 #endif
42
43 #ifdef RT_USING_HOOK
44 extern void (*rt_object_take_hook)(struct rt_object *object);
45 extern void (*rt_object_put_hook)(struct rt_object *object);
46 static void (*rt_timer_enter_hook)(struct rt_timer *timer);
47 static void (*rt_timer_exit_hook)(struct rt_timer *timer);
48
49 /**
50 * @addtogroup Hook
51 */
52
53 /**@{*/
54
55 /**
56 * This function will set a hook function, which will be invoked when enter
57 * timer timeout callback function.
58 *
59 * @param hook the hook function
60 */
rt_timer_enter_sethook(void (* hook)(struct rt_timer * timer))61 void rt_timer_enter_sethook(void (*hook)(struct rt_timer *timer))
62 {
63 rt_timer_enter_hook = hook;
64 }
65
66 /**
67 * This function will set a hook function, which will be invoked when exit
68 * timer timeout callback function.
69 *
70 * @param hook the hook function
71 */
rt_timer_exit_sethook(void (* hook)(struct rt_timer * timer))72 void rt_timer_exit_sethook(void (*hook)(struct rt_timer *timer))
73 {
74 rt_timer_exit_hook = hook;
75 }
76
77 /**@}*/
78 #endif
79
_rt_timer_init(rt_timer_t timer,void (* timeout)(void * parameter),void * parameter,rt_tick_t time,rt_uint8_t flag)80 static void _rt_timer_init(rt_timer_t timer,
81 void (*timeout)(void *parameter),
82 void *parameter,
83 rt_tick_t time,
84 rt_uint8_t flag)
85 {
86 int i;
87
88 /* set flag */
89 timer->parent.flag = flag;
90
91 /* set deactivated */
92 timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
93
94 timer->timeout_func = timeout;
95 timer->parameter = parameter;
96
97 timer->timeout_tick = 0;
98 timer->init_tick = time;
99
100 /* initialize timer list */
101 for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
102 {
103 rt_list_init(&(timer->row[i]));
104 }
105 }
106
107 /* the fist timer always in the last row */
rt_timer_list_next_timeout(rt_list_t timer_list[])108 static rt_tick_t rt_timer_list_next_timeout(rt_list_t timer_list[])
109 {
110 struct rt_timer *timer;
111
112 if (rt_list_isempty(&timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
113 return RT_TICK_MAX;
114
115 timer = rt_list_entry(timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
116 struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
117
118 return timer->timeout_tick;
119 }
120
_rt_timer_remove(rt_timer_t timer)121 rt_inline void _rt_timer_remove(rt_timer_t timer)
122 {
123 int i;
124
125 for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
126 {
127 rt_list_remove(&timer->row[i]);
128 }
129 }
130
131 #if RT_DEBUG_TIMER
rt_timer_count_height(struct rt_timer * timer)132 static int rt_timer_count_height(struct rt_timer *timer)
133 {
134 int i, cnt = 0;
135
136 for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
137 {
138 if (!rt_list_isempty(&timer->row[i]))
139 cnt++;
140 }
141 return cnt;
142 }
143
rt_timer_dump(rt_list_t timer_heads[])144 void rt_timer_dump(rt_list_t timer_heads[])
145 {
146 rt_list_t *list;
147
148 for (list = timer_heads[RT_TIMER_SKIP_LIST_LEVEL - 1].next;
149 list != &timer_heads[RT_TIMER_SKIP_LIST_LEVEL - 1];
150 list = list->next)
151 {
152 struct rt_timer *timer = rt_list_entry(list,
153 struct rt_timer,
154 row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
155 rt_kprintf("%d", rt_timer_count_height(timer));
156 }
157 rt_kprintf("\n");
158 }
159 #endif
160
161 /**
162 * @addtogroup Clock
163 */
164
165 /**@{*/
166
167 /**
168 * This function will initialize a timer, normally this function is used to
169 * initialize a static timer object.
170 *
171 * @param timer the static timer object
172 * @param name the name of timer
173 * @param timeout the timeout function
174 * @param parameter the parameter of timeout function
175 * @param time the tick of timer
176 * @param flag the flag of timer
177 */
rt_timer_init(rt_timer_t timer,const char * name,void (* timeout)(void * parameter),void * parameter,rt_tick_t time,rt_uint8_t flag)178 void rt_timer_init(rt_timer_t timer,
179 const char *name,
180 void (*timeout)(void *parameter),
181 void *parameter,
182 rt_tick_t time,
183 rt_uint8_t flag)
184 {
185 /* timer check */
186 RT_ASSERT(timer != RT_NULL);
187
188 /* timer object initialization */
189 rt_object_init((rt_object_t)timer, RT_Object_Class_Timer, name);
190
191 _rt_timer_init(timer, timeout, parameter, time, flag);
192 }
193 RTM_EXPORT(rt_timer_init);
194
195 /**
196 * This function will detach a timer from timer management.
197 *
198 * @param timer the static timer object
199 *
200 * @return the operation status, RT_EOK on OK; RT_ERROR on error
201 */
rt_timer_detach(rt_timer_t timer)202 rt_err_t rt_timer_detach(rt_timer_t timer)
203 {
204 register rt_base_t level;
205
206 /* timer check */
207 RT_ASSERT(timer != RT_NULL);
208 RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
209 RT_ASSERT(rt_object_is_systemobject(&timer->parent));
210
211 /* disable interrupt */
212 level = rt_hw_interrupt_disable();
213
214 _rt_timer_remove(timer);
215
216 /* enable interrupt */
217 rt_hw_interrupt_enable(level);
218
219 rt_object_detach((rt_object_t)timer);
220
221 return RT_EOK;
222 }
223 RTM_EXPORT(rt_timer_detach);
224
225 #ifdef RT_USING_HEAP
226 /**
227 * This function will create a timer
228 *
229 * @param name the name of timer
230 * @param timeout the timeout function
231 * @param parameter the parameter of timeout function
232 * @param time the tick of timer
233 * @param flag the flag of timer
234 *
235 * @return the created timer object
236 */
rt_timer_create(const char * name,void (* timeout)(void * parameter),void * parameter,rt_tick_t time,rt_uint8_t flag)237 rt_timer_t rt_timer_create(const char *name,
238 void (*timeout)(void *parameter),
239 void *parameter,
240 rt_tick_t time,
241 rt_uint8_t flag)
242 {
243 struct rt_timer *timer;
244
245 /* allocate a object */
246 timer = (struct rt_timer *)rt_object_allocate(RT_Object_Class_Timer, name);
247 if (timer == RT_NULL)
248 {
249 return RT_NULL;
250 }
251
252 _rt_timer_init(timer, timeout, parameter, time, flag);
253
254 return timer;
255 }
256 RTM_EXPORT(rt_timer_create);
257
258 /**
259 * This function will delete a timer and release timer memory
260 *
261 * @param timer the timer to be deleted
262 *
263 * @return the operation status, RT_EOK on OK; RT_ERROR on error
264 */
rt_timer_delete(rt_timer_t timer)265 rt_err_t rt_timer_delete(rt_timer_t timer)
266 {
267 register rt_base_t level;
268
269 /* timer check */
270 RT_ASSERT(timer != RT_NULL);
271 RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
272 RT_ASSERT(rt_object_is_systemobject(&timer->parent) == RT_FALSE);
273
274 /* disable interrupt */
275 level = rt_hw_interrupt_disable();
276
277 _rt_timer_remove(timer);
278
279 /* enable interrupt */
280 rt_hw_interrupt_enable(level);
281
282 rt_object_delete((rt_object_t)timer);
283
284 return RT_EOK;
285 }
286 RTM_EXPORT(rt_timer_delete);
287 #endif
288
289 /**
290 * This function will start the timer
291 *
292 * @param timer the timer to be started
293 *
294 * @return the operation status, RT_EOK on OK, -RT_ERROR on error
295 */
rt_timer_start(rt_timer_t timer)296 rt_err_t rt_timer_start(rt_timer_t timer)
297 {
298 unsigned int row_lvl;
299 rt_list_t *timer_list;
300 register rt_base_t level;
301 rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
302 unsigned int tst_nr;
303 static unsigned int random_nr;
304
305 /* timer check */
306 RT_ASSERT(timer != RT_NULL);
307 RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
308
309 /* stop timer firstly */
310 level = rt_hw_interrupt_disable();
311 /* remove timer from list */
312 _rt_timer_remove(timer);
313 /* change status of timer */
314 timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
315 rt_hw_interrupt_enable(level);
316
317 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));
318
319 /*
320 * get timeout tick,
321 * the max timeout tick shall not great than RT_TICK_MAX/2
322 */
323 RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
324 timer->timeout_tick = rt_tick_get() + timer->init_tick;
325
326 /* disable interrupt */
327 level = rt_hw_interrupt_disable();
328
329 #ifdef RT_USING_TIMER_SOFT
330 if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
331 {
332 /* insert timer to soft timer list */
333 timer_list = rt_soft_timer_list;
334 }
335 else
336 #endif
337 {
338 /* insert timer to system timer list */
339 timer_list = rt_timer_list;
340 }
341
342 row_head[0] = &timer_list[0];
343 for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
344 {
345 for (; row_head[row_lvl] != timer_list[row_lvl].prev;
346 row_head[row_lvl] = row_head[row_lvl]->next)
347 {
348 struct rt_timer *t;
349 rt_list_t *p = row_head[row_lvl]->next;
350
351 /* fix up the entry pointer */
352 t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
353
354 /* If we have two timers that timeout at the same time, it's
355 * preferred that the timer inserted early get called early.
356 * So insert the new timer to the end the the some-timeout timer
357 * list.
358 */
359 if ((t->timeout_tick - timer->timeout_tick) == 0)
360 {
361 continue;
362 }
363 else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
364 {
365 break;
366 }
367 }
368 if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
369 row_head[row_lvl + 1] = row_head[row_lvl] + 1;
370 }
371
372 /* Interestingly, this super simple timer insert counter works very very
373 * well on distributing the list height uniformly. By means of "very very
374 * well", I mean it beats the randomness of timer->timeout_tick very easily
375 * (actually, the timeout_tick is not random and easy to be attacked). */
376 random_nr++;
377 tst_nr = random_nr;
378
379 rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
380 &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
381 for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
382 {
383 if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
384 rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],
385 &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));
386 else
387 break;
388 /* Shift over the bits we have tested. Works well with 1 bit and 2
389 * bits. */
390 tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
391 }
392
393 timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
394
395 /* enable interrupt */
396 rt_hw_interrupt_enable(level);
397
398 #ifdef RT_USING_TIMER_SOFT
399 if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
400 {
401 /* check whether timer thread is ready */
402 if ((timer_thread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND)
403 {
404 /* resume timer thread to check soft timer */
405 rt_thread_resume(&timer_thread);
406 rt_schedule();
407 }
408 }
409 #endif
410
411 return RT_EOK;
412 }
413 RTM_EXPORT(rt_timer_start);
414
415 /**
416 * This function will stop the timer
417 *
418 * @param timer the timer to be stopped
419 *
420 * @return the operation status, RT_EOK on OK, -RT_ERROR on error
421 */
rt_timer_stop(rt_timer_t timer)422 rt_err_t rt_timer_stop(rt_timer_t timer)
423 {
424 register rt_base_t level;
425
426 /* timer check */
427 RT_ASSERT(timer != RT_NULL);
428 RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
429
430 if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
431 return -RT_ERROR;
432
433 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(timer->parent)));
434
435 /* disable interrupt */
436 level = rt_hw_interrupt_disable();
437
438 _rt_timer_remove(timer);
439
440 /* enable interrupt */
441 rt_hw_interrupt_enable(level);
442
443 /* change stat */
444 timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
445
446 return RT_EOK;
447 }
448 RTM_EXPORT(rt_timer_stop);
449
450 /**
451 * This function will get or set some options of the timer
452 *
453 * @param timer the timer to be get or set
454 * @param cmd the control command
455 * @param arg the argument
456 *
457 * @return RT_EOK
458 */
rt_timer_control(rt_timer_t timer,int cmd,void * arg)459 rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
460 {
461 /* timer check */
462 RT_ASSERT(timer != RT_NULL);
463 RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
464
465 switch (cmd)
466 {
467 case RT_TIMER_CTRL_GET_TIME:
468 *(rt_tick_t *)arg = timer->init_tick;
469 break;
470
471 case RT_TIMER_CTRL_SET_TIME:
472 timer->init_tick = *(rt_tick_t *)arg;
473 break;
474
475 case RT_TIMER_CTRL_SET_ONESHOT:
476 timer->parent.flag &= ~RT_TIMER_FLAG_PERIODIC;
477 break;
478
479 case RT_TIMER_CTRL_SET_PERIODIC:
480 timer->parent.flag |= RT_TIMER_FLAG_PERIODIC;
481 break;
482 }
483
484 return RT_EOK;
485 }
486 RTM_EXPORT(rt_timer_control);
487
488 /**
489 * This function will check timer list, if a timeout event happens, the
490 * corresponding timeout function will be invoked.
491 *
492 * @note this function shall be invoked in operating system timer interrupt.
493 */
rt_timer_check(void)494 void rt_timer_check(void)
495 {
496 struct rt_timer *t;
497 rt_tick_t current_tick;
498 register rt_base_t level;
499
500 RT_DEBUG_LOG(RT_DEBUG_TIMER, ("timer check enter\n"));
501
502 current_tick = rt_tick_get();
503
504 /* disable interrupt */
505 level = rt_hw_interrupt_disable();
506
507 while (!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
508 {
509 t = rt_list_entry(rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
510 struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
511
512 /*
513 * It supposes that the new tick shall less than the half duration of
514 * tick max.
515 */
516 if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
517 {
518 RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));
519
520 /* remove timer from timer list firstly */
521 _rt_timer_remove(t);
522
523 /* call timeout function */
524 t->timeout_func(t->parameter);
525
526 /* re-get tick */
527 current_tick = rt_tick_get();
528
529 RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));
530 RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));
531
532 if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
533 (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
534 {
535 /* start it */
536 t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
537 rt_timer_start(t);
538 }
539 else
540 {
541 /* stop timer */
542 t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
543 }
544 }
545 else
546 break;
547 }
548
549 /* enable interrupt */
550 rt_hw_interrupt_enable(level);
551
552 RT_DEBUG_LOG(RT_DEBUG_TIMER, ("timer check leave\n"));
553 }
554
555 /**
556 * This function will return the next timeout tick in the system.
557 *
558 * @return the next timeout tick in the system
559 */
rt_timer_next_timeout_tick(void)560 rt_tick_t rt_timer_next_timeout_tick(void)
561 {
562 return rt_timer_list_next_timeout(rt_timer_list);
563 }
564
565 #ifdef RT_USING_TIMER_SOFT
566 /**
567 * This function will check timer list, if a timeout event happens, the
568 * corresponding timeout function will be invoked.
569 */
rt_soft_timer_check(void)570 void rt_soft_timer_check(void)
571 {
572 rt_tick_t current_tick;
573 rt_list_t *n;
574 struct rt_timer *t;
575
576 RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check enter\n"));
577
578 current_tick = rt_tick_get();
579
580 /* lock scheduler */
581 rt_enter_critical();
582
583 for (n = rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next;
584 n != &(rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]);)
585 {
586 t = rt_list_entry(n, struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
587
588 /*
589 * It supposes that the new tick shall less than the half duration of
590 * tick max.
591 */
592 if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
593 {
594 RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));
595
596 /* move node to the next */
597 n = n->next;
598
599 /* remove timer from timer list firstly */
600 _rt_timer_remove(t);
601
602 /* not lock scheduler when performing timeout function */
603 rt_exit_critical();
604 /* call timeout function */
605 t->timeout_func(t->parameter);
606
607 /* re-get tick */
608 current_tick = rt_tick_get();
609
610 RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));
611 RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));
612
613 /* lock scheduler */
614 rt_enter_critical();
615
616 if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
617 (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
618 {
619 /* start it */
620 t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
621 rt_timer_start(t);
622 }
623 else
624 {
625 /* stop timer */
626 t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
627 }
628 }
629 else break; /* not check anymore */
630 }
631
632 /* unlock scheduler */
633 rt_exit_critical();
634
635 RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check leave\n"));
636 }
637
638 /* system timer thread entry */
rt_thread_timer_entry(void * parameter)639 static void rt_thread_timer_entry(void *parameter)
640 {
641 rt_tick_t next_timeout;
642
643 while (1)
644 {
645 /* get the next timeout tick */
646 next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list);
647 if (next_timeout == RT_TICK_MAX)
648 {
649 /* no software timer exist, suspend self. */
650 rt_thread_suspend(rt_thread_self());
651 rt_schedule();
652 }
653 else
654 {
655 rt_tick_t current_tick;
656
657 /* get current tick */
658 current_tick = rt_tick_get();
659
660 if ((next_timeout - current_tick) < RT_TICK_MAX / 2)
661 {
662 /* get the delta timeout tick */
663 next_timeout = next_timeout - current_tick;
664 rt_thread_delay(next_timeout);
665 }
666 }
667
668 /* check software timer */
669 rt_soft_timer_check();
670 }
671 }
672 #endif
673
674 /**
675 * @ingroup SystemInit
676 *
677 * This function will initialize system timer
678 */
rt_system_timer_init(void)679 void rt_system_timer_init(void)
680 {
681 int i;
682
683 for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
684 {
685 rt_list_init(rt_timer_list + i);
686 }
687 }
688
689 /**
690 * @ingroup SystemInit
691 *
692 * This function will initialize system timer thread
693 */
rt_system_timer_thread_init(void)694 void rt_system_timer_thread_init(void)
695 {
696 #ifdef RT_USING_TIMER_SOFT
697 int i;
698
699 for (i = 0;
700 i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]);
701 i++)
702 {
703 rt_list_init(rt_soft_timer_list + i);
704 }
705
706 /* start software timer thread */
707 rt_thread_init(&timer_thread,
708 "timer",
709 rt_thread_timer_entry,
710 RT_NULL,
711 &timer_thread_stack[0],
712 sizeof(timer_thread_stack),
713 RT_TIMER_THREAD_PRIO,
714 10);
715
716 /* startup */
717 rt_thread_startup(&timer_thread);
718 #endif
719 }
720
721 /**@}*/
722