37
37
38
38
#include "lp_ticker_api.h"
39
39
#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"
43
49
#endif
44
50
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
+
45
58
LPTIM_HandleTypeDef LptimHandle ;
46
59
47
60
const ticker_info_t * lp_ticker_get_info ()
@@ -58,11 +71,14 @@ const ticker_info_t *lp_ticker_get_info()
58
71
}
59
72
60
73
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;
61
79
62
80
static int LPTICKER_inited = 0 ;
63
-
64
81
static void LPTIM1_IRQHandler (void );
65
- static void (* irq_handler )(void );
66
82
67
83
void lp_ticker_init (void )
68
84
{
@@ -168,42 +184,63 @@ void lp_ticker_init(void)
168
184
#endif
169
185
170
186
__HAL_LPTIM_ENABLE_IT (& LptimHandle , LPTIM_IT_CMPM );
187
+ __HAL_LPTIM_ENABLE_IT (& LptimHandle , LPTIM_IT_CMPOK );
171
188
HAL_LPTIM_Counter_Start (& LptimHandle , 0xFFFF );
172
189
173
190
/* Need to write a compare value in order to get LPTIM_FLAG_CMPOK in set_interrupt */
174
191
__HAL_LPTIM_CLEAR_FLAG (& LptimHandle , LPTIM_FLAG_CMPOK );
175
192
__HAL_LPTIM_COMPARE_SET (& LptimHandle , 0 );
176
193
while (__HAL_LPTIM_GET_FLAG (& LptimHandle , LPTIM_FLAG_CMPOK ) == RESET ) {
177
194
}
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;
178
200
}
179
201
180
202
static void LPTIM1_IRQHandler (void )
181
203
{
182
- LptimHandle . Instance = LPTIM1 ;
204
+ core_util_critical_section_enter () ;
183
205
184
206
if (lp_Fired ) {
185
207
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 ();
189
212
}
190
213
191
214
/* Compare match interrupt */
192
215
if (__HAL_LPTIM_GET_FLAG (& LptimHandle , LPTIM_FLAG_CMPM ) != RESET ) {
193
216
if (__HAL_LPTIM_GET_IT_SOURCE (& LptimHandle , LPTIM_IT_CMPM ) != RESET ) {
194
217
/* Clear Compare match flag */
195
218
__HAL_LPTIM_CLEAR_FLAG (& LptimHandle , LPTIM_FLAG_CMPM );
219
+ lp_ticker_irq_handler ();
220
+ }
221
+ }
196
222
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;
199
234
}
200
235
}
201
236
}
202
237
238
+
203
239
#if defined (__HAL_LPTIM_WAKEUPTIMER_EXTI_CLEAR_FLAG )
204
240
/* EXTI lines are not configured by default */
205
241
__HAL_LPTIM_WAKEUPTIMER_EXTI_CLEAR_FLAG ();
206
242
#endif
243
+ core_util_critical_section_exit ();
207
244
}
208
245
209
246
uint32_t lp_ticker_read (void )
@@ -217,42 +254,107 @@ uint32_t lp_ticker_read(void)
217
254
return lp_time ;
218
255
}
219
256
257
+ /* This function should always be called from critical section */
220
258
void lp_ticker_set_interrupt (timestamp_t timestamp )
221
259
{
222
- LptimHandle .Instance = LPTIM1 ;
223
- irq_handler = (void (* )(void ))lp_ticker_irq_handler ;
260
+ core_util_critical_section_enter ();
224
261
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 );
231
265
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
+ }
233
304
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 ();
235
316
}
236
317
237
318
void lp_ticker_fire_interrupt (void )
238
319
{
320
+ core_util_critical_section_enter ();
239
321
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;
241
324
NVIC_SetPendingIRQ (LPTIM1_IRQn );
242
325
NVIC_EnableIRQ (LPTIM1_IRQn );
326
+ core_util_critical_section_exit ();
243
327
}
244
328
245
329
void lp_ticker_disable_interrupt (void )
246
330
{
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 ;
247
346
NVIC_DisableIRQ (LPTIM1_IRQn );
248
- LptimHandle .Instance = LPTIM1 ;
347
+ NVIC_ClearPendingIRQ (LPTIM1_IRQn );
348
+
349
+ core_util_critical_section_exit ();
249
350
}
250
351
251
352
void lp_ticker_clear_interrupt (void )
252
353
{
253
- LptimHandle . Instance = LPTIM1 ;
354
+ core_util_critical_section_enter () ;
254
355
__HAL_LPTIM_CLEAR_FLAG (& LptimHandle , LPTIM_FLAG_CMPM );
255
356
NVIC_ClearPendingIRQ (LPTIM1_IRQn );
357
+ core_util_critical_section_exit ();
256
358
}
257
359
258
360
void lp_ticker_free (void )
0 commit comments