Skip to content

Commit a84c239

Browse files
authored
Merge pull request #10701 from LMESTM/STM_lp_ticker_low_level_wrapper
STM: replace C++ low power ticker wrapper with a low level wrapper
2 parents 8b49ffc + 6331034 commit a84c239

File tree

2 files changed

+192
-61
lines changed

2 files changed

+192
-61
lines changed

targets/TARGET_STM/lp_ticker.c

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,24 @@
3737

3838
#include "lp_ticker_api.h"
3939
#include "mbed_error.h"
40-
41-
#if !defined(LPTICKER_DELAY_TICKS) || (LPTICKER_DELAY_TICKS < 3)
42-
#warning "lpticker_delay_ticks value should be set to 3"
40+
#include "mbed_power_mgmt.h"
41+
#include "platform/mbed_critical.h"
42+
#include <stdbool.h>
43+
44+
/* lpticker delay is for using C++ Low Power Ticker wrapper,
45+
* which introduces extra delays. We rather want to use the
46+
* low level implementation from this file */
47+
#if defined(LPTICKER_DELAY_TICKS) && (LPTICKER_DELAY_TICKS > 0)
48+
#warning "lpticker_delay_ticks usage not recommended"
4349
#endif
4450

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

4760
const ticker_info_t *lp_ticker_get_info()
@@ -58,11 +71,14 @@ const ticker_info_t *lp_ticker_get_info()
5871
}
5972

6073
volatile uint8_t lp_Fired = 0;
74+
/* Flag and stored counter to handle delayed programing at low level */
75+
volatile bool lp_delayed_prog = false;
76+
volatile bool lp_cmpok = false;
77+
volatile timestamp_t lp_delayed_counter = 0;
78+
volatile bool sleep_manager_locked = false;
6179

6280
static int LPTICKER_inited = 0;
63-
6481
static void LPTIM1_IRQHandler(void);
65-
static void (*irq_handler)(void);
6682

6783
void lp_ticker_init(void)
6884
{
@@ -168,42 +184,63 @@ void lp_ticker_init(void)
168184
#endif
169185

170186
__HAL_LPTIM_ENABLE_IT(&LptimHandle, LPTIM_IT_CMPM);
187+
__HAL_LPTIM_ENABLE_IT(&LptimHandle, LPTIM_IT_CMPOK);
171188
HAL_LPTIM_Counter_Start(&LptimHandle, 0xFFFF);
172189

173190
/* Need to write a compare value in order to get LPTIM_FLAG_CMPOK in set_interrupt */
174191
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
175192
__HAL_LPTIM_COMPARE_SET(&LptimHandle, 0);
176193
while (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) == RESET) {
177194
}
195+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
196+
197+
/* Init is called with Interrupts disabled, so the CMPOK interrupt
198+
* will not be handled. Let's mark it is now safe to write to LP counter */
199+
lp_cmpok = true;
178200
}
179201

180202
static void LPTIM1_IRQHandler(void)
181203
{
182-
LptimHandle.Instance = LPTIM1;
204+
core_util_critical_section_enter();
183205

184206
if (lp_Fired) {
185207
lp_Fired = 0;
186-
if (irq_handler) {
187-
irq_handler();
188-
}
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);
211+
lp_ticker_irq_handler();
189212
}
190213

191214
/* Compare match interrupt */
192215
if (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPM) != RESET) {
193216
if (__HAL_LPTIM_GET_IT_SOURCE(&LptimHandle, LPTIM_IT_CMPM) != RESET) {
194217
/* Clear Compare match flag */
195218
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPM);
219+
lp_ticker_irq_handler();
220+
}
221+
}
196222

197-
if (irq_handler) {
198-
irq_handler();
223+
if (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) != RESET) {
224+
if (__HAL_LPTIM_GET_IT_SOURCE(&LptimHandle, LPTIM_IT_CMPOK) != RESET) {
225+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
226+
lp_cmpok = true;
227+
if(sleep_manager_locked) {
228+
sleep_manager_unlock_deep_sleep();
229+
sleep_manager_locked = false;
230+
}
231+
if(lp_delayed_prog) {
232+
lp_ticker_set_interrupt(lp_delayed_counter);
233+
lp_delayed_prog = false;
199234
}
200235
}
201236
}
202237

238+
203239
#if defined (__HAL_LPTIM_WAKEUPTIMER_EXTI_CLEAR_FLAG)
204240
/* EXTI lines are not configured by default */
205241
__HAL_LPTIM_WAKEUPTIMER_EXTI_CLEAR_FLAG();
206242
#endif
243+
core_util_critical_section_exit();
207244
}
208245

209246
uint32_t lp_ticker_read(void)
@@ -217,42 +254,107 @@ uint32_t lp_ticker_read(void)
217254
return lp_time;
218255
}
219256

257+
/* This function should always be called from critical section */
220258
void lp_ticker_set_interrupt(timestamp_t timestamp)
221259
{
222-
LptimHandle.Instance = LPTIM1;
223-
irq_handler = (void (*)(void))lp_ticker_irq_handler;
260+
core_util_critical_section_enter();
224261

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);
229-
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
230-
__HAL_LPTIM_COMPARE_SET(&LptimHandle, timestamp);
262+
/* Always store the last requested timestamp */
263+
lp_delayed_counter = timestamp;
264+
NVIC_EnableIRQ(LPTIM1_IRQn);
231265

232-
lp_ticker_clear_interrupt();
266+
/* CMPOK is set by hardware to inform application that the APB bus write operation to the
267+
* LPTIM_CMP register has been successfully completed.
268+
* Any successive write before the CMPOK flag be set, will lead to unpredictable results
269+
* We need to prevent to set a new comparator value before CMPOK flag is set by HW */
270+
if (lp_cmpok == false) {
271+
/* if this is not safe to write, then delay the programing to the
272+
* time when CMPOK interrupt will trigger */
273+
lp_delayed_prog = true;
274+
} else {
275+
timestamp_t last_read_counter = lp_ticker_read();
276+
lp_ticker_clear_interrupt();
277+
278+
/* HW is not able to trig a very short term interrupt, that is
279+
* not less than few ticks away (LP_TIMER_SAFE_GUARD). So let's make sure it'
280+
* s at least current tick + LP_TIMER_SAFE_GUARD */
281+
for(uint8_t i = 0; i < LP_TIMER_SAFE_GUARD; i++) {
282+
if (LP_TIMER_WRAP(last_read_counter + i) == timestamp) {
283+
timestamp = LP_TIMER_WRAP(timestamp + LP_TIMER_SAFE_GUARD);
284+
}
285+
}
286+
/* Then check if this target timestamp is not in the past, or close to wrap-around
287+
* Let's assume last_read_counter = 0xFFFC, and we want to program timestamp = 0x100
288+
* The interrupt will not fire before the CMPOK flag is OK, so there are 2 cases:
289+
* in case CMPOK flag is set by HW after or at wrap-around, then this will fire only @0x100
290+
* in case CMPOK flag is set before, it will indeed fire early, as for the wrap-around case.
291+
* But that will take at least 3 cycles and the interrupt fires at the end of a cycle.
292+
* In our case 0xFFFC + 3 => at the transition between 0xFFFF and 0.
293+
* If last_read_counter was 0xFFFB, it should be at the transition between 0xFFFE and 0xFFFF.
294+
* There might be crossing cases where it would also fire @ 0xFFFE, but by the time we read the counter,
295+
* it may already have moved to the next one, so for now we've taken this as margin of error.
296+
*/
297+
if((timestamp < last_read_counter) && (last_read_counter <= (0xFFFF - LP_TIMER_SAFE_GUARD))) {
298+
/* Workaround, because limitation */
299+
__HAL_LPTIM_COMPARE_SET(&LptimHandle, ~0);
300+
} else {
301+
/* It is safe to write */
302+
__HAL_LPTIM_COMPARE_SET(&LptimHandle, timestamp);
303+
}
233304

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

237318
void lp_ticker_fire_interrupt(void)
238319
{
320+
core_util_critical_section_enter();
239321
lp_Fired = 1;
240-
irq_handler = (void (*)(void))lp_ticker_irq_handler;
322+
/* In case we fire interrupt now, then cancel pending programing */
323+
lp_delayed_prog = false;
241324
NVIC_SetPendingIRQ(LPTIM1_IRQn);
242325
NVIC_EnableIRQ(LPTIM1_IRQn);
326+
core_util_critical_section_exit();
243327
}
244328

245329
void lp_ticker_disable_interrupt(void)
246330
{
331+
core_util_critical_section_enter();
332+
333+
if(!lp_cmpok) {
334+
while (__HAL_LPTIM_GET_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK) == RESET) {
335+
}
336+
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPOK);
337+
lp_cmpok = true;
338+
}
339+
/* now that CMPOK is set, allow deep sleep again */
340+
if(sleep_manager_locked) {
341+
sleep_manager_unlock_deep_sleep();
342+
sleep_manager_locked = false;
343+
}
344+
lp_delayed_prog = false;
345+
lp_Fired = 0;
247346
NVIC_DisableIRQ(LPTIM1_IRQn);
248-
LptimHandle.Instance = LPTIM1;
347+
NVIC_ClearPendingIRQ(LPTIM1_IRQn);
348+
349+
core_util_critical_section_exit();
249350
}
250351

251352
void lp_ticker_clear_interrupt(void)
252353
{
253-
LptimHandle.Instance = LPTIM1;
354+
core_util_critical_section_enter();
254355
__HAL_LPTIM_CLEAR_FLAG(&LptimHandle, LPTIM_FLAG_CMPM);
255356
NVIC_ClearPendingIRQ(LPTIM1_IRQn);
357+
core_util_critical_section_exit();
256358
}
257359

258360
void lp_ticker_free(void)

0 commit comments

Comments
 (0)