Skip to content

Commit 17892cb

Browse files
committed
Add handling for synchronized low power tickers
Some low power tickers take multiple cycles of the low power clock to set a compare value. Because of this if the compare value is set twice back-to-back these implementations will block until that time has passed. This can cause system stability issues since interrupts are disabling for this time. To gracefully support this kind of hardware this patch adds code to prevent back-to-back writes to the hardware. It does this by recording the low power clock cycle of the initial write. If any writes come in too soon after this initial write the microsecond ticker is used to schedule the new write in the future when the hardware is ready to accept a new value. To enable this feature on a target the macro LOWPOWERTIMER_DELAY_TICKS must be set to the number of low power clock cycles that must elapse between writes to the low power timer.
1 parent 4a5ac14 commit 17892cb

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

hal/mbed_lp_ticker_api.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
#if DEVICE_LOWPOWERTIMER
1919

20+
void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp);
21+
2022
static ticker_event_queue_t events = { 0 };
2123

2224
static ticker_irq_handler_type irq_handler = ticker_irq_handler;
@@ -26,7 +28,11 @@ static const ticker_interface_t lp_interface = {
2628
.read = lp_ticker_read,
2729
.disable_interrupt = lp_ticker_disable_interrupt,
2830
.clear_interrupt = lp_ticker_clear_interrupt,
31+
#if LOWPOWERTIMER_DELAY_TICKS > 0
32+
.set_interrupt = lp_ticker_set_interrupt_wrapper,
33+
#else
2934
.set_interrupt = lp_ticker_set_interrupt,
35+
#endif
3036
.fire_interrupt = lp_ticker_fire_interrupt,
3137
.get_info = lp_ticker_get_info,
3238
};

hal/mbed_lp_ticker_wrapper.cpp

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/* mbed Microcontroller Library
2+
* Copyright (c) 2018 ARM Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
#include "hal/lp_ticker_api.h"
17+
18+
#if DEVICE_LOWPOWERTIMER && (LOWPOWERTIMER_DELAY_TICKS > 0)
19+
20+
#include "Timeout.h"
21+
#include "mbed_critical.h"
22+
23+
static const timestamp_t min_delta = LOWPOWERTIMER_DELAY_TICKS;
24+
25+
static bool init = false;
26+
static bool pending = false;
27+
static bool timeout_pending = false;
28+
static timestamp_t last_set_interrupt = 0;
29+
static timestamp_t last_request = 0;
30+
static timestamp_t next = 0;
31+
32+
static timestamp_t mask;
33+
static timestamp_t reschedule_us;
34+
35+
// Do not use SingletonPtr since this must be initialized in a critical section
36+
static mbed::Timeout *timeout;
37+
static uint64_t timeout_data[sizeof(mbed::Timeout) / 8];
38+
39+
/**
40+
* Initialize variables
41+
*/
42+
static void init_local()
43+
{
44+
MBED_ASSERT(core_util_in_critical_section());
45+
46+
const ticker_info_t* info = lp_ticker_get_info();
47+
if (info->bits >= 32) {
48+
mask = 0xffffffff;
49+
} else {
50+
mask = ((uint64_t)1 << info->bits) - 1;
51+
}
52+
53+
// Round us_per_tick up
54+
timestamp_t us_per_tick = (1000000 + info->frequency - 1) / info->frequency;
55+
56+
// Add 1 tick to the min delta for the case where the clock transitions after you read it
57+
// Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period)
58+
reschedule_us = (min_delta + 1) * us_per_tick + 4;
59+
60+
timeout = new (timeout_data) mbed::Timeout();
61+
}
62+
63+
/**
64+
* Call lp_ticker_set_interrupt with a value that is guaranteed to fire
65+
*
66+
* Assumptions
67+
* -Only one low power clock tick can pass from the last read (last_read)
68+
* -The closest an interrupt can fire is max_delta + 1
69+
*
70+
* @param last_read The last value read from lp_ticker_read
71+
* @param timestamp The timestamp to trigger the interrupt at
72+
*/
73+
static void set_interrupt_safe(timestamp_t last_read, timestamp_t timestamp)
74+
{
75+
MBED_ASSERT(core_util_in_critical_section());
76+
uint32_t delta = (timestamp - last_read) & mask;
77+
if (delta < min_delta + 2) {
78+
timestamp = (last_read + min_delta + 2) & mask;
79+
}
80+
lp_ticker_set_interrupt(timestamp);
81+
}
82+
83+
/**
84+
* Set the low power ticker match time when hardware is ready
85+
*
86+
* This event is scheduled to set the lp timer after the previous write
87+
* has taken effect and it is safe to write a new value without blocking.
88+
* If the time has already passed then this function fires and interrupt
89+
* immediately.
90+
*/
91+
static void set_interrupt_later()
92+
{
93+
core_util_critical_section_enter();
94+
95+
timestamp_t current = lp_ticker_read();
96+
if (_ticker_match_interval_passed(last_request, current, next)) {
97+
lp_ticker_fire_interrupt();
98+
} else {
99+
set_interrupt_safe(current, next);
100+
last_set_interrupt = lp_ticker_read();
101+
}
102+
timeout_pending = false;
103+
104+
core_util_critical_section_exit();
105+
}
106+
107+
/**
108+
* Wrapper around lp_ticker_set_interrupt to prevent blocking
109+
*
110+
* Problems this function is solving:
111+
* 1. Interrupt may not fire if set earlier than LOWPOWERTIMER_DELAY_TICKS low power clock cycles
112+
* 2. Setting the interrupt back-to-back will block
113+
*
114+
* This wrapper function prevents lp_ticker_set_interrupt from being called
115+
* back-to-back and blocking while the first write is in progress. This function
116+
* avoids that problem by scheduling a timeout event if the lp ticker is in the
117+
* middle of a write operation.
118+
*
119+
* @param timestamp Time to call ticker irq
120+
* @note this is a utility function and it's not required part of HAL implementation
121+
*/
122+
extern "C" void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp)
123+
{
124+
core_util_critical_section_enter();
125+
126+
if (!init) {
127+
init_local();
128+
init = true;
129+
}
130+
131+
timestamp_t current = lp_ticker_read();
132+
if (pending) {
133+
// Check if pending should be cleared
134+
if (((current - last_set_interrupt) & mask) >= min_delta) {
135+
pending = false;
136+
}
137+
}
138+
139+
if (pending || timeout_pending) {
140+
next = timestamp;
141+
last_request = current;
142+
if (!timeout_pending) {
143+
timeout->attach_us(set_interrupt_later, reschedule_us);
144+
timeout_pending = true;
145+
}
146+
} else {
147+
// Schedule immediately if nothing is pending
148+
set_interrupt_safe(current, timestamp);
149+
last_set_interrupt = lp_ticker_read();
150+
pending = true;
151+
}
152+
153+
core_util_critical_section_exit();
154+
}
155+
156+
#endif

0 commit comments

Comments
 (0)