ON Semiconductor / mbed-os

Dependents:   mbed-TFT-example-NCS36510 mbed-Accelerometer-example-NCS36510 mbed-Accelerometer-example-NCS36510

Committer:
group-onsemi
Date:
Wed Jan 25 20:34:15 2017 +0000
Revision:
0:098463de4c5d
Initial commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
group-onsemi 0:098463de4c5d 1 /* mbed Microcontroller Library
group-onsemi 0:098463de4c5d 2 * Copyright (c) 2016 u-blox
group-onsemi 0:098463de4c5d 3 *
group-onsemi 0:098463de4c5d 4 * Licensed under the Apache License, Version 2.0 (the "License");
group-onsemi 0:098463de4c5d 5 * you may not use this file except in compliance with the License.
group-onsemi 0:098463de4c5d 6 * You may obtain a copy of the License at
group-onsemi 0:098463de4c5d 7 *
group-onsemi 0:098463de4c5d 8 * http://www.apache.org/licenses/LICENSE-2.0
group-onsemi 0:098463de4c5d 9 *
group-onsemi 0:098463de4c5d 10 * Unless required by applicable law or agreed to in writing, software
group-onsemi 0:098463de4c5d 11 * distributed under the License is distributed on an "AS IS" BASIS,
group-onsemi 0:098463de4c5d 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
group-onsemi 0:098463de4c5d 13 * See the License for the specific language governing permissions and
group-onsemi 0:098463de4c5d 14 * limitations under the License.
group-onsemi 0:098463de4c5d 15 */
group-onsemi 0:098463de4c5d 16
group-onsemi 0:098463de4c5d 17 /* The usecond ticker is mapped to TIMER0. A few issues must be dealt
group-onsemi 0:098463de4c5d 18 * with in this driver:
group-onsemi 0:098463de4c5d 19 *
group-onsemi 0:098463de4c5d 20 * 1. The us_ticker API must count upwards, not down.
group-onsemi 0:098463de4c5d 21 * 2. The expected range/resolution is 32 bits each of 1 usecond,
group-onsemi 0:098463de4c5d 22 * whereas TIMER0 runs at 48 MHz (not 1 MHz) and so actually
group-onsemi 0:098463de4c5d 23 * has a range/resolution of 26 bits at 0.02 useconds. Software
group-onsemi 0:098463de4c5d 24 * has to compensate for this.
group-onsemi 0:098463de4c5d 25 */
group-onsemi 0:098463de4c5d 26
group-onsemi 0:098463de4c5d 27 #include "us_ticker_api.h"
group-onsemi 0:098463de4c5d 28 #include "critical.h"
group-onsemi 0:098463de4c5d 29
group-onsemi 0:098463de4c5d 30 /* ----------------------------------------------------------------
group-onsemi 0:098463de4c5d 31 * MACROS
group-onsemi 0:098463de4c5d 32 * ----------------------------------------------------------------*/
group-onsemi 0:098463de4c5d 33
group-onsemi 0:098463de4c5d 34 /* TIMER0 clock is 48 MHz */
group-onsemi 0:098463de4c5d 35 #define CLOCK_TICKS_PER_US 48
group-onsemi 0:098463de4c5d 36
group-onsemi 0:098463de4c5d 37 /* The number of clock ticks in a full-run of
group-onsemi 0:098463de4c5d 38 * TIMER0, scaled to represent useconds */
group-onsemi 0:098463de4c5d 39 #define USECONDS_PER_FULL_TIMER0_RUN 89478485
group-onsemi 0:098463de4c5d 40
group-onsemi 0:098463de4c5d 41 /* ----------------------------------------------------------------
group-onsemi 0:098463de4c5d 42 * TYPES
group-onsemi 0:098463de4c5d 43 * ----------------------------------------------------------------*/
group-onsemi 0:098463de4c5d 44
group-onsemi 0:098463de4c5d 45 /* ----------------------------------------------------------------
group-onsemi 0:098463de4c5d 46 * GLOBAL VARIABLES
group-onsemi 0:098463de4c5d 47 * ----------------------------------------------------------------*/
group-onsemi 0:098463de4c5d 48
group-onsemi 0:098463de4c5d 49 /* Are we ready? */
group-onsemi 0:098463de4c5d 50 static bool g_initialised = false;
group-onsemi 0:098463de4c5d 51
group-onsemi 0:098463de4c5d 52 /* Keep track of the number of useconds elapsed. */
group-onsemi 0:098463de4c5d 53 static uint32_t g_us_overflow = 0;
group-onsemi 0:098463de4c5d 54
group-onsemi 0:098463de4c5d 55 /* The number of useconds to increment the by at each interrupt */
group-onsemi 0:098463de4c5d 56 static uint32_t g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
group-onsemi 0:098463de4c5d 57
group-onsemi 0:098463de4c5d 58 /* Keep track of extra loops required to represent a particular time
group-onsemi 0:098463de4c5d 59 * as the HW timer runs faster than 1 MHz */
group-onsemi 0:098463de4c5d 60 static uint32_t g_timer_extra_loops_required = 0;
group-onsemi 0:098463de4c5d 61 static uint32_t g_timer_extra_loops_done = 0;
group-onsemi 0:098463de4c5d 62
group-onsemi 0:098463de4c5d 63 /* Keep track of any adjustment due to user interrupts . */
group-onsemi 0:098463de4c5d 64 static uint32_t g_user_interrupt_offset = 0;
group-onsemi 0:098463de4c5d 65
group-onsemi 0:098463de4c5d 66 /* Flag that a user timer is running */
group-onsemi 0:098463de4c5d 67 static bool g_user_interrupt = false;
group-onsemi 0:098463de4c5d 68
group-onsemi 0:098463de4c5d 69 /* ----------------------------------------------------------------
group-onsemi 0:098463de4c5d 70 * FUNCTION PROTOTYPES
group-onsemi 0:098463de4c5d 71 * ----------------------------------------------------------------*/
group-onsemi 0:098463de4c5d 72
group-onsemi 0:098463de4c5d 73 static inline uint32_t divide_by_48(uint32_t x);
group-onsemi 0:098463de4c5d 74
group-onsemi 0:098463de4c5d 75 /* ----------------------------------------------------------------
group-onsemi 0:098463de4c5d 76 * NON-API FUNCTIONS
group-onsemi 0:098463de4c5d 77 * ----------------------------------------------------------------*/
group-onsemi 0:098463de4c5d 78
group-onsemi 0:098463de4c5d 79 /* Perform a divide-by-48 operation.
group-onsemi 0:098463de4c5d 80 * This is done as a multiply-shift operation to take advantage of
group-onsemi 0:098463de4c5d 81 * the ARM 32 bit single-cycle multiply and avoid using division;
group-onsemi 0:098463de4c5d 82 * 1/48 is equivalent to 1365/2^16. It is also done in two halves
group-onsemi 0:098463de4c5d 83 * to make sure that the multiplies fit into 32 bits.
group-onsemi 0:098463de4c5d 84 *
group-onsemi 0:098463de4c5d 85 * The principle is:
group-onsemi 0:098463de4c5d 86 * - divide the top 16 bits by 48 using multiply-shift (=> x1),
group-onsemi 0:098463de4c5d 87 * - work out the remainder of that operation and divide that by 48 (=> x1r),
group-onsemi 0:098463de4c5d 88 * - divide the bottom 16 bits by 48 using multiply-shift (=> x2),
group-onsemi 0:098463de4c5d 89 * - add the lot together to get the result.
group-onsemi 0:098463de4c5d 90 *
group-onsemi 0:098463de4c5d 91 * The cost is 29 instructions.
group-onsemi 0:098463de4c5d 92 */
group-onsemi 0:098463de4c5d 93 static inline uint32_t divide_by_48(uint32_t x)
group-onsemi 0:098463de4c5d 94 {
group-onsemi 0:098463de4c5d 95 uint32_t x1 = ((x >> 16) * 1365) >> 16;
group-onsemi 0:098463de4c5d 96 uint32_t x1r = ((x & 0xFFFF0000) - ((x1 * 48) << 16));
group-onsemi 0:098463de4c5d 97 x1r = (x1r * 1365) >> 16;
group-onsemi 0:098463de4c5d 98 uint32_t x2 = ((x & 0xFFFF) * 1365) >> 16;
group-onsemi 0:098463de4c5d 99
group-onsemi 0:098463de4c5d 100 return (x1 << 16) + x1r + x2;
group-onsemi 0:098463de4c5d 101 }
group-onsemi 0:098463de4c5d 102
group-onsemi 0:098463de4c5d 103 /* Timer0 handler */
group-onsemi 0:098463de4c5d 104 void IRQ1_TMR0_Handler(void)
group-onsemi 0:098463de4c5d 105 {
group-onsemi 0:098463de4c5d 106 if (g_initialised) {
group-onsemi 0:098463de4c5d 107 /* Increment the overflow count and set the increment
group-onsemi 0:098463de4c5d 108 * value for next time */
group-onsemi 0:098463de4c5d 109 g_us_overflow += g_us_overflow_increment;
group-onsemi 0:098463de4c5d 110 g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
group-onsemi 0:098463de4c5d 111
group-onsemi 0:098463de4c5d 112 /* Now handle the user interrupt case */
group-onsemi 0:098463de4c5d 113 if (g_user_interrupt) {
group-onsemi 0:098463de4c5d 114 if (g_timer_extra_loops_done < g_timer_extra_loops_required) {
group-onsemi 0:098463de4c5d 115 /* Let the timer go round again */
group-onsemi 0:098463de4c5d 116 g_timer_extra_loops_done++;
group-onsemi 0:098463de4c5d 117 } else {
group-onsemi 0:098463de4c5d 118 /* We've done with looping around for a user interrupt */
group-onsemi 0:098463de4c5d 119 g_user_interrupt = false;
group-onsemi 0:098463de4c5d 120
group-onsemi 0:098463de4c5d 121 /* Call the mbed API */
group-onsemi 0:098463de4c5d 122 us_ticker_irq_handler();
group-onsemi 0:098463de4c5d 123 }
group-onsemi 0:098463de4c5d 124 }
group-onsemi 0:098463de4c5d 125 }
group-onsemi 0:098463de4c5d 126
group-onsemi 0:098463de4c5d 127 NVIC_ClearPendingIRQ(Timer_IRQn);
group-onsemi 0:098463de4c5d 128 }
group-onsemi 0:098463de4c5d 129
group-onsemi 0:098463de4c5d 130 /* ----------------------------------------------------------------
group-onsemi 0:098463de4c5d 131 * MBED API CALLS
group-onsemi 0:098463de4c5d 132 * ----------------------------------------------------------------*/
group-onsemi 0:098463de4c5d 133
group-onsemi 0:098463de4c5d 134 void us_ticker_init(void)
group-onsemi 0:098463de4c5d 135 {
group-onsemi 0:098463de4c5d 136 if (!g_initialised) {
group-onsemi 0:098463de4c5d 137 /* Reset the globals */
group-onsemi 0:098463de4c5d 138 g_timer_extra_loops_done = 0;
group-onsemi 0:098463de4c5d 139 g_timer_extra_loops_required = 0;
group-onsemi 0:098463de4c5d 140 g_us_overflow = 0;
group-onsemi 0:098463de4c5d 141 g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
group-onsemi 0:098463de4c5d 142 g_user_interrupt_offset = 0;
group-onsemi 0:098463de4c5d 143 g_user_interrupt = false;
group-onsemi 0:098463de4c5d 144
group-onsemi 0:098463de4c5d 145 /* Get the timer running (starting at what is zero,
group-onsemi 0:098463de4c5d 146 * once inverted), with repeat */
group-onsemi 0:098463de4c5d 147 NVIC_ClearPendingIRQ(Timer_IRQn);
group-onsemi 0:098463de4c5d 148 TIMER0_LOAD = 0xFFFFFFFF;
group-onsemi 0:098463de4c5d 149 TIMER0_CTRL = 0x03;
group-onsemi 0:098463de4c5d 150 NVIC_EnableIRQ(Timer_IRQn);
group-onsemi 0:098463de4c5d 151
group-onsemi 0:098463de4c5d 152 g_initialised = true;
group-onsemi 0:098463de4c5d 153 }
group-onsemi 0:098463de4c5d 154 }
group-onsemi 0:098463de4c5d 155
group-onsemi 0:098463de4c5d 156 uint32_t us_ticker_read()
group-onsemi 0:098463de4c5d 157 {
group-onsemi 0:098463de4c5d 158 uint32_t timeValue;
group-onsemi 0:098463de4c5d 159
group-onsemi 0:098463de4c5d 160 /* This can be called before initialisation has been performed */
group-onsemi 0:098463de4c5d 161 if (!g_initialised) {
group-onsemi 0:098463de4c5d 162 us_ticker_init();
group-onsemi 0:098463de4c5d 163 }
group-onsemi 0:098463de4c5d 164
group-onsemi 0:098463de4c5d 165 /* Disable interrupts to avoid collisions */
group-onsemi 0:098463de4c5d 166 core_util_critical_section_enter();
group-onsemi 0:098463de4c5d 167
group-onsemi 0:098463de4c5d 168 /* Get the timer value, adding the offset in case we've been moved
group-onsemi 0:098463de4c5d 169 * around by user activity, inverting it (as a count-up timer is
group-onsemi 0:098463de4c5d 170 * expected), then scaling it to useconds and finally adding the
group-onsemi 0:098463de4c5d 171 * usecond overflow value to make up the 32-bit usecond total */
group-onsemi 0:098463de4c5d 172 timeValue = divide_by_48(~(TIMER0_TIME + g_user_interrupt_offset)) + g_us_overflow;
group-onsemi 0:098463de4c5d 173
group-onsemi 0:098463de4c5d 174 /* Put interrupts back */
group-onsemi 0:098463de4c5d 175 core_util_critical_section_exit();
group-onsemi 0:098463de4c5d 176
group-onsemi 0:098463de4c5d 177 return timeValue;
group-onsemi 0:098463de4c5d 178 }
group-onsemi 0:098463de4c5d 179
group-onsemi 0:098463de4c5d 180 /* NOTE: it seems to be an accepted fact that users
group-onsemi 0:098463de4c5d 181 * will never ask for a timeout of more than 2^31 useconds
group-onsemi 0:098463de4c5d 182 * and hence it's possible to do signed arithmetic
group-onsemi 0:098463de4c5d 183 */
group-onsemi 0:098463de4c5d 184 void us_ticker_set_interrupt(timestamp_t timestamp)
group-onsemi 0:098463de4c5d 185 {
group-onsemi 0:098463de4c5d 186 g_timer_extra_loops_required = 0;
group-onsemi 0:098463de4c5d 187 g_timer_extra_loops_done = 0;
group-onsemi 0:098463de4c5d 188 int32_t timeDelta;
group-onsemi 0:098463de4c5d 189
group-onsemi 0:098463de4c5d 190 /* Disable interrupts to avoid collisions */
group-onsemi 0:098463de4c5d 191 core_util_critical_section_enter();
group-onsemi 0:098463de4c5d 192
group-onsemi 0:098463de4c5d 193 /* Establish how far we're being asked to move */
group-onsemi 0:098463de4c5d 194 timeDelta = (int32_t) ((uint32_t) timestamp - us_ticker_read());
group-onsemi 0:098463de4c5d 195
group-onsemi 0:098463de4c5d 196 if (timeDelta <= 0) {
group-onsemi 0:098463de4c5d 197 /* Make delta positive if it's not, it will expire pretty quickly */
group-onsemi 0:098463de4c5d 198 /* Note: can't just call us_ticker_irq_handler() directly as we
group-onsemi 0:098463de4c5d 199 * may already be in it and will overflow the stack */
group-onsemi 0:098463de4c5d 200 timeDelta = 1;
group-onsemi 0:098463de4c5d 201 }
group-onsemi 0:098463de4c5d 202
group-onsemi 0:098463de4c5d 203 /* The TIMER0 clock source is greater than 1 MHz, so
group-onsemi 0:098463de4c5d 204 * work out how many times we have to go around
group-onsemi 0:098463de4c5d 205 * and what the remainder is */
group-onsemi 0:098463de4c5d 206 g_timer_extra_loops_required = (uint32_t) timeDelta / USECONDS_PER_FULL_TIMER0_RUN;
group-onsemi 0:098463de4c5d 207 timeDelta -= g_timer_extra_loops_required * USECONDS_PER_FULL_TIMER0_RUN;
group-onsemi 0:098463de4c5d 208
group-onsemi 0:098463de4c5d 209 /* Next time we hit the interrupt the increment will be smaller */
group-onsemi 0:098463de4c5d 210 g_us_overflow_increment = (uint32_t) timeDelta;
group-onsemi 0:098463de4c5d 211
group-onsemi 0:098463de4c5d 212 /* We're about to modify the timer value; work out the
group-onsemi 0:098463de4c5d 213 * difference so that we can compensate for it when
group-onsemi 0:098463de4c5d 214 * the time is read */
group-onsemi 0:098463de4c5d 215 timeDelta = timeDelta * CLOCK_TICKS_PER_US;
group-onsemi 0:098463de4c5d 216 g_user_interrupt_offset += TIMER0_TIME - timeDelta;
group-onsemi 0:098463de4c5d 217
group-onsemi 0:098463de4c5d 218 /* Run for the remainder first, then we can loop for the full
group-onsemi 0:098463de4c5d 219 * USECONDS_PER_FULL_TIMER0_RUN afterwards */
group-onsemi 0:098463de4c5d 220 TIMER0_LOAD = timeDelta;
group-onsemi 0:098463de4c5d 221
group-onsemi 0:098463de4c5d 222 /* A user interrupt is now running */
group-onsemi 0:098463de4c5d 223 g_user_interrupt = true;
group-onsemi 0:098463de4c5d 224
group-onsemi 0:098463de4c5d 225 /* Put interrupts back */
group-onsemi 0:098463de4c5d 226 core_util_critical_section_exit();
group-onsemi 0:098463de4c5d 227 }
group-onsemi 0:098463de4c5d 228
group-onsemi 0:098463de4c5d 229 void us_ticker_disable_interrupt(void)
group-onsemi 0:098463de4c5d 230 {
group-onsemi 0:098463de4c5d 231 /* Can't actually disable the interrupt here
group-onsemi 0:098463de4c5d 232 * as we need it to manage the timer overflow,
group-onsemi 0:098463de4c5d 233 * instead switch off the user interrupt part */
group-onsemi 0:098463de4c5d 234 g_user_interrupt = false;
group-onsemi 0:098463de4c5d 235 g_timer_extra_loops_required = 0;
group-onsemi 0:098463de4c5d 236 g_us_overflow_increment = 0;
group-onsemi 0:098463de4c5d 237 }
group-onsemi 0:098463de4c5d 238
group-onsemi 0:098463de4c5d 239 void us_ticker_clear_interrupt(void)
group-onsemi 0:098463de4c5d 240 {
group-onsemi 0:098463de4c5d 241 /* As above, can't clear the interrupt as it
group-onsemi 0:098463de4c5d 242 * may just be an overflow interrupt, instead
group-onsemi 0:098463de4c5d 243 * clear the variables */
group-onsemi 0:098463de4c5d 244 g_user_interrupt = false;
group-onsemi 0:098463de4c5d 245 g_timer_extra_loops_required = 0;
group-onsemi 0:098463de4c5d 246 g_us_overflow_increment = 0;
group-onsemi 0:098463de4c5d 247 }