Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of mbed-dev by
Diff: targets/TARGET_ublox/TARGET_HI2110/us_ticker.c
- Revision:
- 150:02e0a0aed4ec
- Child:
- 160:d5399cc887bb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/TARGET_ublox/TARGET_HI2110/us_ticker.c Tue Nov 08 17:45:16 2016 +0000 @@ -0,0 +1,247 @@ +/* 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 "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_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; +}