Knight KE / Mbed OS Game_Master
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers mbed_lp_ticker_wrapper.cpp Source File

mbed_lp_ticker_wrapper.cpp

00001 /* mbed Microcontroller Library
00002  * Copyright (c) 2018 ARM Limited
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 #include "hal/lp_ticker_api.h"
00017 
00018 #if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
00019 
00020 #include "Timeout.h"
00021 #include "mbed_critical.h"
00022 
00023 static const timestamp_t min_delta = LPTICKER_DELAY_TICKS;
00024 
00025 static bool init = false;
00026 static bool pending = false;
00027 static bool timeout_pending = false;
00028 static timestamp_t last_set_interrupt = 0;
00029 static timestamp_t last_request = 0;
00030 static timestamp_t next = 0;
00031 
00032 static timestamp_t mask;
00033 static timestamp_t reschedule_us;
00034 
00035 // Do not use SingletonPtr since this must be initialized in a critical section
00036 static mbed::Timeout *timeout;
00037 static uint64_t timeout_data[sizeof(mbed::Timeout) / 8];
00038 
00039 /**
00040  * Initialize variables
00041  */
00042 static void init_local()
00043 {
00044     MBED_ASSERT(core_util_in_critical_section());
00045 
00046     const ticker_info_t* info = lp_ticker_get_info();
00047     if (info->bits >= 32) {
00048         mask = 0xffffffff;
00049     } else {
00050         mask = ((uint64_t)1 << info->bits) - 1;
00051     }
00052 
00053     // Round us_per_tick up
00054     timestamp_t us_per_tick = (1000000 + info->frequency - 1) / info->frequency;
00055 
00056     // Add 1 tick to the min delta for the case where the clock transitions after you read it
00057     // Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period)
00058     reschedule_us = (min_delta + 1) * us_per_tick + 4;
00059 
00060     timeout = new (timeout_data) mbed::Timeout();
00061 }
00062 
00063 /**
00064  * Call lp_ticker_set_interrupt with a value that is guaranteed to fire
00065  *
00066  * Assumptions
00067  * -Only one low power clock tick can pass from the last read (last_read)
00068  * -The closest an interrupt can fire is max_delta + 1
00069  *
00070  * @param last_read The last value read from lp_ticker_read
00071  * @param timestamp The timestamp to trigger the interrupt at
00072  */
00073 static void set_interrupt_safe(timestamp_t last_read, timestamp_t timestamp)
00074 {
00075     MBED_ASSERT(core_util_in_critical_section());
00076     uint32_t delta = (timestamp - last_read) & mask;
00077     if (delta < min_delta + 2) {
00078         timestamp = (last_read + min_delta + 2) & mask;
00079     }
00080     lp_ticker_set_interrupt(timestamp);
00081 }
00082 
00083 /**
00084  * Set the low power ticker match time when hardware is ready
00085  *
00086  * This event is scheduled to set the lp timer after the previous write
00087  * has taken effect and it is safe to write a new value without blocking.
00088  * If the time has already passed then this function fires and interrupt
00089  * immediately.
00090  */
00091 static void set_interrupt_later()
00092 {
00093     core_util_critical_section_enter();
00094 
00095     timestamp_t current = lp_ticker_read();
00096     if (_ticker_match_interval_passed(last_request, current, next)) {
00097         lp_ticker_fire_interrupt();
00098     } else {
00099         set_interrupt_safe(current, next);
00100         last_set_interrupt = lp_ticker_read();
00101     }
00102     timeout_pending = false;
00103 
00104     core_util_critical_section_exit();
00105 }
00106 
00107 /**
00108  * Wrapper around lp_ticker_set_interrupt to prevent blocking
00109  *
00110  * Problems this function is solving:
00111  * 1. Interrupt may not fire if set earlier than LPTICKER_DELAY_TICKS low power clock cycles
00112  * 2. Setting the interrupt back-to-back will block
00113  *
00114  * This wrapper function prevents lp_ticker_set_interrupt from being called
00115  * back-to-back and blocking while the first write is in progress. This function
00116  * avoids that problem by scheduling a timeout event if the lp ticker is in the
00117  * middle of a write operation.
00118  *
00119  * @param timestamp Time to call ticker irq
00120  * @note this is a utility function and it's not required part of HAL implementation
00121  */
00122 extern "C" void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp)
00123 {
00124     core_util_critical_section_enter();
00125 
00126     if (!init) {
00127         init_local();
00128         init = true;
00129     }
00130 
00131     timestamp_t current = lp_ticker_read();
00132     if (pending) {
00133         // Check if pending should be cleared
00134         if (((current - last_set_interrupt) & mask) >= min_delta) {
00135             pending = false;
00136         }
00137     }
00138 
00139     if (pending || timeout_pending) {
00140         next = timestamp;
00141         last_request = current;
00142         if (!timeout_pending) {
00143             timeout->attach_us(set_interrupt_later, reschedule_us);
00144             timeout_pending = true;
00145         }
00146     } else {
00147         // Schedule immediately if nothing is pending
00148         set_interrupt_safe(current, timestamp);
00149         last_set_interrupt = lp_ticker_read();
00150         pending = true;
00151     }
00152 
00153     core_util_critical_section_exit();
00154 }
00155 
00156 #endif