Parent directory

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}