Skip to content

Add handling for synchronized low power tickers #6536

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions hal/mbed_lp_ticker_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

#if DEVICE_LOWPOWERTIMER

void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp);

static ticker_event_queue_t events = { 0 };

static ticker_irq_handler_type irq_handler = ticker_irq_handler;
Expand All @@ -26,7 +28,11 @@ static const ticker_interface_t lp_interface = {
.read = lp_ticker_read,
.disable_interrupt = lp_ticker_disable_interrupt,
.clear_interrupt = lp_ticker_clear_interrupt,
#if LOWPOWERTIMER_DELAY_TICKS > 0
.set_interrupt = lp_ticker_set_interrupt_wrapper,
#else
.set_interrupt = lp_ticker_set_interrupt,
#endif
.fire_interrupt = lp_ticker_fire_interrupt,
.get_info = lp_ticker_get_info,
};
Expand Down
156 changes: 156 additions & 0 deletions hal/mbed_lp_ticker_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* mbed Microcontroller Library
* Copyright (c) 2018 ARM Limited
*
* 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.
*/
#include "hal/lp_ticker_api.h"

#if DEVICE_LOWPOWERTIMER && (LOWPOWERTIMER_DELAY_TICKS > 0)

#include "Timeout.h"
#include "mbed_critical.h"

static const timestamp_t min_delta = LOWPOWERTIMER_DELAY_TICKS;

static bool init = false;
static bool pending = false;
static bool timeout_pending = false;
static timestamp_t last_set_interrupt = 0;
static timestamp_t last_request = 0;
static timestamp_t next = 0;

static timestamp_t mask;
static timestamp_t reschedule_us;

// Do not use SingletonPtr since this must be initialized in a critical section
static mbed::Timeout *timeout;
static uint64_t timeout_data[sizeof(mbed::Timeout) / 8];

/**
* Initialize variables
*/
static void init_local()
{
MBED_ASSERT(core_util_in_critical_section());

const ticker_info_t* info = lp_ticker_get_info();
if (info->bits >= 32) {
mask = 0xffffffff;
} else {
mask = ((uint64_t)1 << info->bits) - 1;
}

// Round us_per_tick up
timestamp_t us_per_tick = (1000000 + info->frequency - 1) / info->frequency;

// Add 1 tick to the min delta for the case where the clock transitions after you read it
// Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period)
reschedule_us = (min_delta + 1) * us_per_tick + 4;

timeout = new (timeout_data) mbed::Timeout();
}

/**
* Call lp_ticker_set_interrupt with a value that is guaranteed to fire
*
* Assumptions
* -Only one low power clock tick can pass from the last read (last_read)
* -The closest an interrupt can fire is max_delta + 1
*
* @param last_read The last value read from lp_ticker_read
* @param timestamp The timestamp to trigger the interrupt at
*/
static void set_interrupt_safe(timestamp_t last_read, timestamp_t timestamp)
{
MBED_ASSERT(core_util_in_critical_section());
uint32_t delta = (timestamp - last_read) & mask;
if (delta < min_delta + 2) {
timestamp = (last_read + min_delta + 2) & mask;
}
lp_ticker_set_interrupt(timestamp);
}

/**
* Set the low power ticker match time when hardware is ready
*
* This event is scheduled to set the lp timer after the previous write
* has taken effect and it is safe to write a new value without blocking.
* If the time has already passed then this function fires and interrupt
* immediately.
*/
static void set_interrupt_later()
{
core_util_critical_section_enter();

timestamp_t current = lp_ticker_read();
if (_ticker_match_interval_passed(last_request, current, next)) {
lp_ticker_fire_interrupt();
} else {
set_interrupt_safe(current, next);
last_set_interrupt = lp_ticker_read();
}
timeout_pending = false;

core_util_critical_section_exit();
}

/**
* Wrapper around lp_ticker_set_interrupt to prevent blocking
*
* Problems this function is solving:
* 1. Interrupt may not fire if set earlier than LOWPOWERTIMER_DELAY_TICKS low power clock cycles
* 2. Setting the interrupt back-to-back will block
*
* This wrapper function prevents lp_ticker_set_interrupt from being called
* back-to-back and blocking while the first write is in progress. This function
* avoids that problem by scheduling a timeout event if the lp ticker is in the
* middle of a write operation.
*
* @param timestamp Time to call ticker irq
* @note this is a utility function and it's not required part of HAL implementation
*/
extern "C" void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp)
{
core_util_critical_section_enter();

if (!init) {
init_local();
init = true;
}

timestamp_t current = lp_ticker_read();
if (pending) {
// Check if pending should be cleared
if (((current - last_set_interrupt) & mask) >= min_delta) {
pending = false;
}
}

if (pending || timeout_pending) {
next = timestamp;
last_request = current;
if (!timeout_pending) {
timeout->attach_us(set_interrupt_later, reschedule_us);
timeout_pending = true;
}
} else {
// Schedule immediately if nothing is pending
set_interrupt_safe(current, timestamp);
last_set_interrupt = lp_ticker_read();
pending = true;
}

core_util_critical_section_exit();
}

#endif