timer.c (4517B)
1 #include <ch.h> 2 3 #include "timer.h" 4 5 static uint32_t ticks_offset = 0; 6 static uint32_t last_ticks = 0; 7 static uint32_t ms_offset = 0; 8 static uint32_t saved_ms = 0; 9 #if CH_CFG_ST_RESOLUTION < 32 10 static uint32_t last_systime = 0; 11 static 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). 16 static 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 37 static 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. 44 static 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 61 void 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 69 void 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 85 void 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 93 void timer_save(void) { 94 platform_timer_save_value(timer_read32()); 95 } 96 97 uint16_t timer_read(void) { 98 return (uint16_t)timer_read32(); 99 } 100 101 uint32_t timer_read32(void) { 102 syssts_t sts = chSysGetStatusAndLockX(); 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 chSysRestoreStatusX(sts); 118 119 return (uint32_t)TIME_I2MS(ticks) + ms_offset_copy; 120 }