mbed-os

Dependents:   cobaLCDJoyMotor_Thread odometry_omni_3roda_v3 odometry_omni_3roda_v1 odometry_omni_3roda_v2 ... more

Revision:
0:b74591d5ab33
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/TARGET_ublox/TARGET_HI2110/lp_ticker.c	Mon Dec 11 17:54:04 2017 +0000
@@ -0,0 +1,348 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2016 u-blox
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* The LP Ticker performs two functions for mbed:
+ *
+ * 1.  Allows tracking of the passage of time.
+ * 2.  Allows the system to enter the lowest power
+ *     state for a given time.
+ *
+ * For this to work the single RTC interrupt needs
+ * to perform two functions.  It needs to increment
+ * an overflow counter at every 32-bit overflow without
+ * otherwise affecting the system state (i.e. not waking it
+ * up and not putting it to sleep) and, when requested,
+ * it *also* needs to wake the system up from sleep
+ * at a specific time.  Note also that the units of time
+ * from an mbed perspective are useconds, whereas the RTC
+ * is clocked at 32 kHz, hence there is conversion to be done.
+ *
+ * Since it is not possible to reset the RTC, we maintain
+ * a 32-bit window on it, starting at g_last_32bit_overflow_value
+ * and ending at g_next_32bit_overflow_value.  All values
+ * fed back up to mbed are relative to g_last_32bit_overflow_value.
+ */
+
+#include "lp_ticker_api.h"
+#include "sleep_api.h"
+#include "mbed_critical.h"
+
+/* ----------------------------------------------------------------
+ * MACROS
+ * ----------------------------------------------------------------*/
+
+/* The maximum value of the RTC (48 bits) */
+#define RTC_MAX 0x0000FFFFFFFFFFFFULL
+
+/* RTC modulo */
+#define RTC_MODULO (RTC_MAX + 1)
+
+/* The 32-bit overflow value */
+#define MODULO_32BIT 0x100000000ULL
+
+/* Macro to increment a 64-bit RTC value x by y, with wrap */
+#define INCREMENT_MOD(x, y)  (x = ((uint64_t) x + (uint64_t) y) % RTC_MODULO)
+
+/* Macro to get MSBs from a 64-bit integer */
+#define MSBS(x) ((uint32_t) ((uint64_t) (x) >> 32))
+
+/* Macro to get LSBs from a 64-bit integer */
+#define LSBS(x) ((uint32_t) (x))
+
+/* ----------------------------------------------------------------
+ * TYPES
+ * ----------------------------------------------------------------*/
+
+/* ----------------------------------------------------------------
+ * GLOBAL VARIABLES
+ * ----------------------------------------------------------------*/
+
+/* Incremented each time the RTC goes over 32 bits */
+static uint32_t g_overflow_count = 0;
+
+/* Set when a user interrupt has been requested but an overflow
+ * interrupt needs to happen first */
+static bool g_user_interrupt_pending = false;
+
+/* Set when a user interrupt is the next interrupt to happen */
+static bool g_user_interrupt_set = false;
+
+/* Initialised flag, used to protect against interrupts going
+ * off before we're initialised */
+static bool g_initialised = false;
+
+/* The next overflow value to be used */
+static uint64_t g_next_32bit_overflow_value;
+
+/* The next match-compare value to be used */
+static uint64_t g_next_compare_value;
+
+/* Keep track of the previous 32-bit overflow
+ * value so that we can report 32-bit time
+ * correctly */
+static uint64_t g_last_32bit_overflow_value;
+
+/* ----------------------------------------------------------------
+ * FUNCTION PROTOTYPES
+ * ----------------------------------------------------------------*/
+
+static void set_interrupt_to_32bit_overflow(void);
+static void set_interrupt_to_user_value(void);
+
+/* ----------------------------------------------------------------
+ * STATIC FUNCTIONS
+ * ----------------------------------------------------------------*/
+
+/* Convert a tick value (32,768 Hz) into a microsecond value */
+static inline uint32_t ticksToUSeconds(uint32_t x)
+{
+    /* TODO: find a way to avoid the multiply by 1000000
+     * Shift by 20 would introduce a 5% error, which is
+     * probably too much */
+    uint64_t result = ((((uint64_t) x) * 1000000) >> 15);
+
+    if (result > 0xFFFFFFFF) {
+        result = 0xFFFFFFFF;
+    }
+
+    return (uint32_t) result;
+}
+
+/* Convert a microsecond value into a tick value (32,768 Hz) */
+static inline uint32_t uSecondsToTicks(uint32_t x)
+{
+    /* TODO: find a way to avoid the divide by 1000000
+     * Shift by 20 would introduce a 5% error, which is
+     * probably too much */
+    return (uint32_t) ((((uint64_t) x) << 15) / 1000000);
+}
+
+/* Take g_next_32bit_overflow_value and apply it to g_next_compare_value and
+ * then the chip registers
+ * NOTE: the RTC interrupt should be disabled when calling this function */
+static inline void set_interrupt_to_32bit_overflow()
+{
+    /* Load up the values */
+    g_next_compare_value = g_next_32bit_overflow_value;
+
+    /* Set up the match register values */
+    RTC_IRQ_TIME_MSBS = MSBS(g_next_compare_value);
+    RTC_IRQ_TIME_LSBS = LSBS(g_next_compare_value);
+}
+
+/* Take g_next_compare_value and apply it to the chip registers
+ * NOTE: the RTC interrupt should be disabled when calling this function */
+static inline void set_interrupt_to_user_value()
+{
+    g_user_interrupt_set = true;
+
+    /* Write MSBS first, then the value is latched on LSBS write */
+    RTC_IRQ_TIME_MSBS = MSBS(g_next_compare_value);
+    RTC_IRQ_TIME_LSBS = LSBS(g_next_compare_value);
+}
+
+/* Get the RTC value
+ * NOTE: the RTC interrupt should be disabled when calling this function */
+static inline uint64_t get_rtc_value()
+{
+    uint64_t rtc_value;
+
+    rtc_value = ((uint64_t) RTC_TIME_MSBS) << 32;
+    rtc_value |= RTC_TIME_LSBS;
+
+    return rtc_value;
+}
+
+/* ----------------------------------------------------------------
+ * NON-API FUNCTIONS
+ * ----------------------------------------------------------------*/
+
+/* RTC handler */
+void IRQ0_RTC_Handler(void)
+{
+    /* Have seen this interrupt occurring before initialisation, so guard
+     * against that */
+    if (g_initialised) {
+        if (g_user_interrupt_pending) {
+            /* If there was a user interrupt pending, set it now */
+            set_interrupt_to_user_value();
+
+            /* Reset the pending flag */
+            g_user_interrupt_pending = false;
+
+            /* This must have been a 32-bit overflow interrupt so
+             * increment the count */
+            g_overflow_count++;
+            g_last_32bit_overflow_value = g_next_32bit_overflow_value;
+            INCREMENT_MOD(g_next_32bit_overflow_value, MODULO_32BIT);
+        } else {
+            if (g_user_interrupt_set) {
+                /* It's a user interrupt, so wake from sleep but don't
+                 * increment the overflow count as this is not an
+                 * overflow interrupt */
+
+                /* Reset the user interrupt flag and call mbed */
+                g_user_interrupt_set = false;
+                lp_ticker_irq_handler();
+            } else {
+                /* Increment the count as this was a 32-bit overflow
+                 * interrupt rather than a user interrupt */
+                g_overflow_count++;
+                g_last_32bit_overflow_value = g_next_32bit_overflow_value;
+                INCREMENT_MOD(g_next_32bit_overflow_value, MODULO_32BIT);
+            }
+
+            /* Set the next interrupt to be at the 32-bit overflow */
+            set_interrupt_to_32bit_overflow();
+        }
+    }
+
+    /* Clear the interrupt */
+    RTC_IRQ_CLR = 0xFFFFFFFF;
+}
+
+/* ----------------------------------------------------------------
+ * MBED API CALLS
+ * ----------------------------------------------------------------*/
+
+/* This will be called once at start of day to get the RTC running */
+void lp_ticker_init(void)
+{
+    if (!g_initialised) {
+        /* Reset the overflow count and the flags */
+        g_overflow_count = 0;
+        g_user_interrupt_pending = false;
+        g_user_interrupt_set = false;
+
+        /* Setup the next natural 32-bit overflow value */
+        g_next_32bit_overflow_value = get_rtc_value();
+        g_last_32bit_overflow_value = g_next_32bit_overflow_value;
+        INCREMENT_MOD(g_next_32bit_overflow_value, MODULO_32BIT);
+
+        /* Clear the interrupt */
+        RTC_IRQ_CLR = 0xFFFFFFFF;
+
+        /* Interrupt at 32-bit overflow */
+        set_interrupt_to_32bit_overflow();
+
+        /* Enable the interrupt */
+        g_initialised = true;
+        NVIC_EnableIRQ(RTC_IRQn);
+    }
+}
+
+uint32_t lp_ticker_read(void)
+{
+    uint64_t rtcNow;
+
+    /* Disable interrupts to avoid collisions */
+    core_util_critical_section_enter();
+
+    /* Just in case this is called before initialisation has been performed */
+    if (!g_initialised) {
+        lp_ticker_init();
+    }
+
+    /* What mbed expects here is a 32 bit timer value.  There is no
+     * way to reset the RTC so, to pretend it is 32 bits, we have to
+     * maintain a 32-bit window on it using the remembered overflow
+     * value */
+    rtcNow = get_rtc_value();
+
+    /* Put interrupts back */
+    core_util_critical_section_exit();
+
+    return ticksToUSeconds(rtcNow - g_last_32bit_overflow_value);
+}
+
+void lp_ticker_set_interrupt(timestamp_t time)
+{
+    uint32_t timeNow = get_rtc_value() - g_last_32bit_overflow_value;
+    uint32_t timeOffset = uSecondsToTicks(time) - timeNow;
+
+    /* Disable interrupts to avoid collisions */
+    core_util_critical_section_enter();
+
+    g_user_interrupt_pending = false;
+    g_user_interrupt_set = false;
+
+    /* Handle time slipping into the past */
+    if (timeOffset > 0xEFFFFFFF) {
+        timeOffset = 100;
+    }
+
+    /* Read the current time */
+    g_next_compare_value = get_rtc_value();
+
+    /* Add the offset */
+    INCREMENT_MOD(g_next_compare_value, timeOffset);
+
+    /* We must let the normal overflow interrupt occur as
+     * well as setting this interrupt so, if the value
+     * of 'time' would occur after the overflow point,
+     * put the change of compare-value off until afterwards. */
+    /* TODO: this needs proper testing. */
+    if (g_next_32bit_overflow_value > g_next_compare_value) {
+        /* The easy case, no overlap */
+    } else {
+        /* Could be because g_next_compare_value has wrapped (around the
+         * 48-bit limit of the RTC) */
+        if (g_next_32bit_overflow_value - g_next_compare_value >= MODULO_32BIT) {
+            /* The wrap case, we're OK */
+        } else {
+            /* There is an overlap, apply the value later */
+            g_user_interrupt_pending = true;
+
+            if (g_next_32bit_overflow_value == g_next_compare_value) {
+                /* If they are on top of each other, bump this
+                 * one forward to avoid losing the interrupt */
+                INCREMENT_MOD(g_next_compare_value, 2);
+            }
+        }
+    }
+
+    if (!g_user_interrupt_pending) {
+        /* Make the change immediately */
+        set_interrupt_to_user_value();
+    }
+
+    /* Put interrupts back */
+    core_util_critical_section_exit();
+}
+
+void lp_ticker_fire_interrupt(void)
+{
+    // user interrupt only set, this will invoke from ISR routine directly lp handler
+    g_user_interrupt_pending = false;
+    g_user_interrupt_set = true;
+    NVIC_SetPendingIRQ(RTC_IRQn);
+}
+
+void lp_ticker_disable_interrupt(void)
+{
+    /* Can't disable interrupts as we need them to manage
+     * overflow.  Instead, switch off the user part. */
+    g_user_interrupt_pending = false;
+    g_user_interrupt_set = false;
+}
+
+void lp_ticker_clear_interrupt(void)
+{
+    /* Can't disable interrupts as we need them to manage
+     * overflow.  Instead, switch off the user part. */
+    g_user_interrupt_pending = false;
+    g_user_interrupt_set = false;
+}