Skip to content

Commit 7ce1cfe

Browse files
committed
Manage lp_ticker delay at low level
LP TICKER mbed-os wrapper needs to be disabled as it introduces too much latencies. LP TICKER wrapper has been disabled and we need to managed the HW constraints at low level: - main HW constraint is that once the comparator has been programmed once, driver cannot program it again before CMPOK HW flag is set, which takes about 3 30us cycles. To make it even more complex, the driver also needs to cope with "LP ticker workaround" See commit: LP ticker workaround There is an errata in LPTIM specification that explains that CMP Flag condition is not an exact match (COUNTER = MATCH) but rather a comparison (COUNTER >= MATCH). Also the disable interrupt is more complete now: - always check sleep manager status and restore it - remove irq_handler as comparator is always programed and might get called eventually when LP TICK is restarted - reset delayed_prog Also in set_interrupt, make sure interrupt does not fire early. If needed, we decide to slightly delay the tick to cope with the HW limitation to make sure it will fire as soon as HW is capable. Functions are called under critical section as they may be called from the IRQ handler now, not only from driver layer.
1 parent afa6ed3 commit 7ce1cfe

File tree

1 file changed

+110
-16
lines changed

1 file changed

+110
-16
lines changed

targets/TARGET_STM/lp_ticker.c

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,21 @@
3737

3838
#include "lp_ticker_api.h"
3939
#include "mbed_error.h"
40+
#include "mbed_power_mgmt.h"
41+
#include "platform/mbed_critical.h"
42+
#include <stdbool.h>
4043

4144
#if !defined(LPTICKER_DELAY_TICKS) || (LPTICKER_DELAY_TICKS < 3)
4245
#warning "lpticker_delay_ticks value should be set to 3"
4346
#endif
4447

48+
#define LP_TIMER_WRAP(val) (val & 0xFFFF)
49+
/* Safe guard is the number of ticks between the current tick and the next
50+
* tick we want to program an interrupt for. Programing an interrupt in
51+
* between is unreliable */
52+
#define LP_TIMER_SAFE_GUARD 5
53+
54+
4555
LPTIM_HandleTypeDef LptimHandle;
4656

4757
const ticker_info_t *lp_ticker_get_info()
@@ -58,9 +68,13 @@ const ticker_info_t *lp_ticker_get_info()
5868
}
5969

6070
volatile uint8_t lp_Fired = 0;
71+
/* Flag and stored counter to handle delayed programing at low level */
72+
volatile bool lp_delayed_prog = false;
73+
volatile bool lp_cmpok = false;
74+
volatile timestamp_t lp_delayed_counter = 0;
75+
volatile bool sleep_manager_locked = false;
6176

6277
static int LPTICKER_inited = 0;
63-
6478
static void LPTIM1_IRQHandler(void);
6579
static void (*irq_handler)(void);
6680

@@ -168,21 +182,32 @@ void lp_ticker_init(void)
168182
#endif
169183

170184
__HAL_LPTIM_ENABLE_IT(&LptimHandle, LPTIM_IT_CMPM);
185+
__HAL_LPTIM_ENABLE_IT(&LptimHandle, LPTIM_IT_CMPOK);
171186
HAL_LPTIM_Counter_Start(&LptimHandle, 0xFFFF);
172187

173188
/* Need to write a compare value in order to get LPTIM_FLAG_CMPOK in set_interrupt */
174189
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
175190
__HAL_LPTIM_COMPARE_SET(&LptimHandle, 0);
176191
while (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) == RESET) {
177192
}
193+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
194+
195+
/* Init is called with Interrupts disabled, so the CMPOK interrupt
196+
* will not be handler. Let's mark it is now safe to write to LP counter */
197+
lp_cmpok = true;
178198
}
179199

180200
static void LPTIM1_IRQHandler(void)
181201
{
202+
core_util_critical_section_enter();
203+
182204
LptimHandle.Instance = LPTIM1;
183205

184206
if (lp_Fired) {
185207
lp_Fired = 0;
208+
/* We're already in handler and interrupt might be pending,
209+
* so clear the flag, to avoid calling irq_handler twice */
210+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPM);
186211
if (irq_handler) {
187212
irq_handler();
188213
}
@@ -193,17 +218,33 @@ static void LPTIM1_IRQHandler(void)
193218
if (__HAL_LPTIM_GET_IT_SOURCE(&LptimHandle, LPTIM_IT_CMPM) != RESET) {
194219
/* Clear Compare match flag */
195220
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPM);
196-
197221
if (irq_handler) {
198222
irq_handler();
199223
}
200224
}
201225
}
202226

227+
if (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) != RESET) {
228+
if (__HAL_LPTIM_GET_IT_SOURCE(&LptimHandle, LPTIM_IT_CMPOK) != RESET) {
229+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
230+
lp_cmpok = true;
231+
if(sleep_manager_locked) {
232+
sleep_manager_unlock_deep_sleep();
233+
sleep_manager_locked = false;
234+
}
235+
if(lp_delayed_prog) {
236+
lp_ticker_set_interrupt(lp_delayed_counter);
237+
lp_delayed_prog = false;
238+
}
239+
}
240+
}
241+
242+
203243
#if defined (__HAL_LPTIM_WAKEUPTIMER_EXTI_CLEAR_FLAG)
204244
/* EXTI lines are not configured by default */
205245
__HAL_LPTIM_WAKEUPTIMER_EXTI_CLEAR_FLAG();
206246
#endif
247+
core_util_critical_section_exit();
207248
}
208249

209250
uint32_t lp_ticker_read(void)
@@ -217,49 +258,102 @@ uint32_t lp_ticker_read(void)
217258
return lp_time;
218259
}
219260

261+
/* This function should always be called from critical section */
220262
void lp_ticker_set_interrupt(timestamp_t timestamp)
221263
{
222264
LptimHandle.Instance = LPTIM1;
223265
irq_handler = (void (*)(void))lp_ticker_irq_handler;
266+
core_util_critical_section_enter();
224267

225-
/* CMPOK is set by hardware to inform application that the APB bus write operation to the LPTIM_CMP register has been successfully completed */
226-
/* Any successive write before the CMPOK flag be set, will lead to unpredictable results */
227-
/* LPTICKER_DELAY_TICKS value prevents OS to call this set interrupt function before CMPOK */
228-
MBED_ASSERT(__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) == SET);
268+
/* Always store the last requested timestamp */
269+
lp_delayed_counter = timestamp;
270+
NVIC_EnableIRQ(LPTIM1_IRQn);
229271

230-
if(timestamp < lp_ticker_read()) {
231-
/* Workaround, because limitation */
232-
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
233-
__HAL_LPTIM_COMPARE_SET(&LptimHandle, ~0);
272+
/* CMPOK is set by hardware to inform application that the APB bus write operation to the
273+
* LPTIM_CMP register has been successfully completed.
274+
* Any successive write before the CMPOK flag be set, will lead to unpredictable results
275+
* We need to prevent to set a new comparator value before CMPOK flag is set by HW */
276+
if (lp_cmpok == false) {
277+
/* if this is not safe to write, then delay the programing to the
278+
* time when CMPOK interrupt will trigger */
279+
lp_delayed_prog = true;
234280
} else {
235-
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
236-
__HAL_LPTIM_COMPARE_SET(&LptimHandle, timestamp);
237-
}
238-
239-
lp_ticker_clear_interrupt();
281+
timestamp_t last_read_counter = lp_ticker_read();
282+
lp_ticker_clear_interrupt();
283+
284+
/* HW is not able to trig a very short term interrupt, that is
285+
* not less than few ticks away (LP_TIMER_SAFE_GUARD). So let's make sure it'
286+
* s at least current tick + LP_TIMER_SAFE_GUARD */
287+
for(uint8_t i = 0; i < LP_TIMER_SAFE_GUARD; i++) {
288+
if (LP_TIMER_WRAP(last_read_counter + i) == timestamp) {
289+
timestamp = LP_TIMER_WRAP(timestamp + LP_TIMER_SAFE_GUARD);
290+
}
291+
}
292+
/* Then check if this target timestamp is not in the past, or close to wrap-around */
293+
if((timestamp < last_read_counter) && (last_read_counter <= (0xFFFF - LP_TIMER_SAFE_GUARD))) {
294+
/* Workaround, because limitation */
295+
__HAL_LPTIM_COMPARE_SET(&LptimHandle, ~0);
296+
} else {
297+
/* It is safe to write */
298+
__HAL_LPTIM_COMPARE_SET(&LptimHandle, timestamp);
299+
}
240300

241-
NVIC_EnableIRQ(LPTIM1_IRQn);
301+
/* We just programed the CMP so we'll need to wait for cmpok before
302+
* next programing */
303+
lp_cmpok = false;
304+
/* Prevent from sleeping after compare register was set as we need CMPOK
305+
* interrupt to fire (in ~3x30us cycles) before we can safely enter deep sleep mode */
306+
if(!sleep_manager_locked) {
307+
sleep_manager_lock_deep_sleep();
308+
sleep_manager_locked = true;
309+
}
310+
}
311+
core_util_critical_section_exit();
242312
}
243313

244314
void lp_ticker_fire_interrupt(void)
245315
{
316+
core_util_critical_section_enter();
246317
lp_Fired = 1;
318+
/* In case we fire interrupt now, then cancel pending programing */
319+
lp_delayed_prog = false;
247320
irq_handler = (void (*)(void))lp_ticker_irq_handler;
248321
NVIC_SetPendingIRQ(LPTIM1_IRQn);
249322
NVIC_EnableIRQ(LPTIM1_IRQn);
323+
core_util_critical_section_exit();
250324
}
251325

252326
void lp_ticker_disable_interrupt(void)
253327
{
328+
core_util_critical_section_enter();
329+
330+
if(!lp_cmpok) {
331+
while (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) == RESET) {
332+
}
333+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
334+
lp_cmpok = true;
335+
}
336+
/* now that CMPOK is set, allow deep sleep again */
337+
if(sleep_manager_locked) {
338+
sleep_manager_unlock_deep_sleep();
339+
sleep_manager_locked = false;
340+
}
341+
irq_handler = NULL;
342+
lp_delayed_prog = false;
254343
NVIC_DisableIRQ(LPTIM1_IRQn);
344+
NVIC_ClearPendingIRQ(LPTIM1_IRQn);
255345
LptimHandle.Instance = LPTIM1;
346+
347+
core_util_critical_section_exit();
256348
}
257349

258350
void lp_ticker_clear_interrupt(void)
259351
{
352+
core_util_critical_section_enter();
260353
LptimHandle.Instance = LPTIM1;
261354
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPM);
262355
NVIC_ClearPendingIRQ(LPTIM1_IRQn);
356+
core_util_critical_section_exit();
263357
}
264358

265359
void lp_ticker_free(void)

0 commit comments

Comments
 (0)