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/us_ticker.c	Mon Dec 11 17:54:04 2017 +0000
@@ -0,0 +1,253 @@
+/* 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 usecond ticker is mapped to TIMER0.  A few issues must be dealt
+ * with in this driver:
+ *
+ * 1.  The us_ticker API must count upwards, not down.
+ * 2.  The expected range/resolution is 32 bits each of 1 usecond,
+ *     whereas TIMER0 runs at 48 MHz (not 1 MHz) and so actually
+ *     has a range/resolution of 26 bits at 0.02 useconds.  Software
+ *     has to compensate for this.
+ */
+
+#include "us_ticker_api.h"
+#include "mbed_critical.h"
+
+/* ----------------------------------------------------------------
+ * MACROS
+ * ----------------------------------------------------------------*/
+
+/* TIMER0 clock is 48 MHz */
+#define CLOCK_TICKS_PER_US 48
+
+/* The number of clock ticks in a full-run of
+ * TIMER0, scaled to represent useconds */
+#define USECONDS_PER_FULL_TIMER0_RUN 89478485
+
+/* ----------------------------------------------------------------
+ * TYPES
+ * ----------------------------------------------------------------*/
+
+/* ----------------------------------------------------------------
+ * GLOBAL VARIABLES
+ * ----------------------------------------------------------------*/
+
+/* Are we ready? */
+static bool g_initialised = false;
+
+/* Keep track of the number of useconds elapsed. */
+static uint32_t g_us_overflow = 0;
+
+/* The number of useconds to increment the by at each interrupt */
+static uint32_t g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
+
+/* Keep track of extra loops required to represent a particular time
+ * as the HW timer runs faster than 1 MHz */
+static uint32_t g_timer_extra_loops_required = 0;
+static uint32_t g_timer_extra_loops_done = 0;
+
+/* Keep track of any adjustment due to user interrupts . */
+static uint32_t g_user_interrupt_offset = 0;
+
+/* Flag that a user timer is running */
+static bool g_user_interrupt = false;
+
+/* ----------------------------------------------------------------
+ * FUNCTION PROTOTYPES
+ * ----------------------------------------------------------------*/
+
+static inline uint32_t divide_by_48(uint32_t x);
+
+/* ----------------------------------------------------------------
+ * NON-API FUNCTIONS
+ * ----------------------------------------------------------------*/
+
+/* Perform a divide-by-48 operation.
+ * This is done as a multiply-shift operation to take advantage of
+ * the ARM 32 bit single-cycle multiply and avoid using division;
+ * 1/48 is equivalent to 1365/2^16.  It is also done in two halves
+ * to make sure that the multiplies fit into 32 bits.
+ *
+ * The principle is:
+ *  - divide the top 16 bits by 48 using multiply-shift (=> x1),
+ *  - work out the remainder of that operation and divide that by 48 (=> x1r),
+ *  - divide the bottom 16 bits by 48 using multiply-shift (=> x2),
+ *  - add the lot together to get the result.
+ *
+ * The cost is 29 instructions.
+ */
+static inline uint32_t divide_by_48(uint32_t x)
+{
+    uint32_t x1 = ((x >> 16) * 1365) >> 16;
+    uint32_t x1r = ((x & 0xFFFF0000) - ((x1 * 48) << 16));
+             x1r = (x1r  * 1365) >> 16;
+    uint32_t x2 = ((x & 0xFFFF) * 1365) >> 16;
+
+    return (x1 << 16) + x1r + x2;
+}
+
+/* Timer0 handler */
+void IRQ1_TMR0_Handler(void)
+{
+    if (g_initialised) {
+        /* Increment the overflow count and set the increment
+         * value for next time */
+        g_us_overflow += g_us_overflow_increment;
+        g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
+
+        /* Now handle the user interrupt case */
+        if (g_user_interrupt) {
+            if (g_timer_extra_loops_done < g_timer_extra_loops_required) {
+                /* Let the timer go round again */
+                g_timer_extra_loops_done++;
+            } else {
+                /* We've done with looping around for a user interrupt */
+                g_user_interrupt = false;
+
+                /* Call the mbed API */
+                us_ticker_irq_handler();
+            }
+        }
+    }
+
+    NVIC_ClearPendingIRQ(Timer_IRQn);
+}
+
+/* ----------------------------------------------------------------
+ * MBED API CALLS
+ * ----------------------------------------------------------------*/
+
+void us_ticker_init(void)
+{
+    if (!g_initialised) {
+        /* Reset the globals */
+        g_timer_extra_loops_done = 0;
+        g_timer_extra_loops_required = 0;
+        g_us_overflow = 0;
+        g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
+        g_user_interrupt_offset = 0;
+        g_user_interrupt = false;
+
+        /* Get the timer running (starting at what is zero,
+         * once inverted), with repeat */
+        NVIC_ClearPendingIRQ(Timer_IRQn);
+        TIMER0_LOAD = 0xFFFFFFFF;
+        TIMER0_CTRL = 0x03;
+        NVIC_EnableIRQ(Timer_IRQn);
+
+        g_initialised = true;
+    }
+}
+
+uint32_t us_ticker_read()
+{
+    uint32_t timeValue;
+
+    /* This can be called before initialisation has been performed */
+    if (!g_initialised) {
+        us_ticker_init();
+    }
+
+    /* Disable interrupts to avoid collisions */
+    core_util_critical_section_enter();
+
+    /* Get the timer value, adding the offset in case we've been moved
+     * around by user activity, inverting it (as a count-up timer is
+     * expected), then scaling it to useconds and finally adding the
+     * usecond overflow value to make up the 32-bit usecond total */
+    timeValue = divide_by_48(~(TIMER0_TIME + g_user_interrupt_offset)) + g_us_overflow;
+
+    /* Put interrupts back */
+    core_util_critical_section_exit();
+
+    return timeValue;
+}
+
+/* NOTE: it seems to be an accepted fact that users
+ * will never ask for a timeout of more than 2^31 useconds
+ * and hence it's possible to do signed arithmetic
+ */
+void us_ticker_set_interrupt(timestamp_t timestamp)
+{
+    g_timer_extra_loops_required = 0;
+    g_timer_extra_loops_done = 0;
+    int32_t timeDelta;
+
+    /* Disable interrupts to avoid collisions */
+    core_util_critical_section_enter();
+
+    /* Establish how far we're being asked to move */
+    timeDelta = (int32_t) ((uint32_t) timestamp - us_ticker_read());
+
+    if (timeDelta <= 0) {
+        /* Make delta positive if it's not, it will expire pretty quickly */
+        /* Note: can't just call us_ticker_irq_handler() directly as we
+         * may already be in it and will overflow the stack */
+        timeDelta = 1;
+    }
+
+    /* The TIMER0 clock source is greater than 1 MHz, so
+     * work out how many times we have to go around
+     * and what the remainder is */
+    g_timer_extra_loops_required = (uint32_t) timeDelta / USECONDS_PER_FULL_TIMER0_RUN;
+    timeDelta -= g_timer_extra_loops_required * USECONDS_PER_FULL_TIMER0_RUN;
+
+    /* Next time we hit the interrupt the increment will be smaller */
+    g_us_overflow_increment = (uint32_t) timeDelta;
+
+    /* We're about to modify the timer value; work out the
+     * difference so that we can compensate for it when
+     * the time is read */
+    timeDelta = timeDelta * CLOCK_TICKS_PER_US;
+    g_user_interrupt_offset += TIMER0_TIME - timeDelta;
+
+    /* Run for the remainder first, then we can loop for the full
+     * USECONDS_PER_FULL_TIMER0_RUN afterwards */
+    TIMER0_LOAD = timeDelta;
+
+    /* A user interrupt is now running */
+    g_user_interrupt = true;
+
+    /* Put interrupts back */
+    core_util_critical_section_exit();
+}
+
+void us_ticker_fire_interrupt(void)
+{
+    g_user_interrupt = true;
+    NVIC_SetPendingIRQ(Timer_IRQn);
+}
+
+void us_ticker_disable_interrupt(void)
+{
+    /* Can't actually disable the interrupt here
+     * as we need it to manage the timer overflow,
+     * instead switch off the user interrupt part */
+    g_user_interrupt = false;
+    g_timer_extra_loops_required = 0;
+    g_us_overflow_increment = 0;
+}
+
+void us_ticker_clear_interrupt(void)
+{
+    /* As above, can't clear the interrupt as it
+     * may just be an overflow interrupt, instead
+     * clear the variables */
+    g_user_interrupt = false;
+    g_timer_extra_loops_required = 0;
+    g_us_overflow_increment = 0;
+}