timer.c
4658 bytes
1#include <ch.h>
2
3#include "timer.h"
4
5static uint32_t ticks_offset = 0;
6static uint32_t last_ticks = 0;
7static uint32_t ms_offset = 0;
8static uint32_t saved_ms = 0;
9#if CH_CFG_ST_RESOLUTION < 32
10static uint32_t last_systime = 0;
11static uint32_t overflow = 0;
12#endif
13
14// Get the current system time in ticks as a 32-bit number.
15// This function must be called from within a system lock zone (so that it can safely use and update the static data).
16static inline uint32_t get_system_time_ticks(void) {
17 uint32_t systime = (uint32_t)chVTGetSystemTimeX();
18
19#if CH_CFG_ST_RESOLUTION < 32
20 // If the real system timer resolution is less than 32 bits, provide the missing bits by checking for the counter
21 // overflow. For this to work, this function must be called at least once for every overflow of the system timer.
22 // In the 16-bit case, the corresponding times are:
23 // - CH_CFG_ST_FREQUENCY = 100000, overflow will occur every ~0.65 seconds
24 // - CH_CFG_ST_FREQUENCY = 10000, overflow will occur every ~6.5 seconds
25 // - CH_CFG_ST_FREQUENCY = 1000, overflow will occur every ~65 seconds
26 if (systime < last_systime) {
27 overflow += ((uint32_t)1) << CH_CFG_ST_RESOLUTION;
28 }
29 last_systime = systime;
30 systime += overflow;
31#endif
32
33 return systime;
34}
35
36#if CH_CFG_ST_RESOLUTION < 32
37static virtual_timer_t update_timer;
38
39// Update the system tick counter every half of the timer overflow period; this should keep the tick counter correct
40// even if something blocks timer interrupts for 1/2 of the timer overflow period.
41# define UPDATE_INTERVAL (((sysinterval_t)1) << (CH_CFG_ST_RESOLUTION - 1))
42
43// VT callback function to keep the overflow bits of the system tick counter updated.
44static void update_fn(struct ch_virtual_timer *timer, void *arg) {
45 (void)arg;
46 chSysLockFromISR();
47 get_system_time_ticks();
48 chVTSetI(&update_timer, UPDATE_INTERVAL, update_fn, NULL);
49 chSysUnlockFromISR();
50}
51#endif
52
53// The highest multiple of CH_CFG_ST_FREQUENCY that fits into uint32_t. This number of ticks will necessarily
54// correspond to some integer number of seconds.
55#define OVERFLOW_ADJUST_TICKS ((uint32_t)((UINT32_MAX / CH_CFG_ST_FREQUENCY) * CH_CFG_ST_FREQUENCY))
56
57// The time in milliseconds which corresponds to OVERFLOW_ADJUST_TICKS ticks (this is a precise conversion, because
58// OVERFLOW_ADJUST_TICKS corresponds to an integer number of seconds).
59#define OVERFLOW_ADJUST_MS (TIME_I2MS(OVERFLOW_ADJUST_TICKS))
60
61void timer_init(void) {
62 timer_clear();
63#if CH_CFG_ST_RESOLUTION < 32
64 chVTObjectInit(&update_timer);
65 chVTSet(&update_timer, UPDATE_INTERVAL, update_fn, NULL);
66#endif
67}
68
69void timer_clear(void) {
70 chSysLock();
71 ticks_offset = get_system_time_ticks();
72 last_ticks = 0;
73 ms_offset = 0;
74 chSysUnlock();
75}
76
77__attribute__((weak)) void platform_timer_save_value(uint32_t value) {
78 saved_ms = value;
79}
80
81__attribute__((weak)) uint32_t platform_timer_restore_value(void) {
82 return saved_ms;
83}
84
85void timer_restore(void) {
86 chSysLock();
87 ticks_offset = get_system_time_ticks();
88 last_ticks = 0;
89 ms_offset = platform_timer_restore_value();
90 chSysUnlock();
91}
92
93void timer_save(void) {
94 platform_timer_save_value(timer_read32());
95}
96
97uint16_t timer_read(void) {
98 return (uint16_t)timer_read32();
99}
100
101uint32_t timer_read32(void) {
102 chSysLock();
103 uint32_t ticks = get_system_time_ticks() - ticks_offset;
104 if (ticks < last_ticks) {
105 // The 32-bit tick counter overflowed and wrapped around. We cannot just extend the counter to 64 bits here,
106 // because TIME_I2MS() may encounter overflows when handling a 64-bit argument; therefore the solution here is
107 // to subtract a reasonably large number of ticks from the tick counter to bring its value below the 32-bit
108 // limit again, and then add the equivalent number of milliseconds to the converted value. (Adjusting just the
109 // converted value to account for 2**32 ticks is not possible in general, because 2**32 ticks may not correspond
110 // to an integer number of milliseconds).
111 ticks -= OVERFLOW_ADJUST_TICKS;
112 ticks_offset += OVERFLOW_ADJUST_TICKS;
113 ms_offset += OVERFLOW_ADJUST_MS;
114 }
115 last_ticks = ticks;
116 uint32_t ms_offset_copy = ms_offset; // read while still holding the lock to ensure a consistent value
117 chSysUnlock();
118
119 return (uint32_t)TIME_I2MS(ticks) + ms_offset_copy;
120}
121
122uint16_t timer_elapsed(uint16_t last) {
123 return TIMER_DIFF_16(timer_read(), last);
124}
125
126uint32_t timer_elapsed32(uint32_t last) {
127 return TIMER_DIFF_32(timer_read32(), last);
128}