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+
4558LPTIM_HandleTypeDef LptimHandle ;
4659
4760const ticker_info_t * lp_ticker_get_info ()
@@ -58,11 +71,14 @@ const ticker_info_t *lp_ticker_get_info()
5871}
5972
6073volatile 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
6280static int LPTICKER_inited = 0 ;
63-
6481static void LPTIM1_IRQHandler (void );
65- static void (* irq_handler )(void );
6682
6783void 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
180202static 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
209246uint32_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 */
220258void 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
237318void 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
245329void 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
251352void 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
258360void lp_ticker_free (void )
0 commit comments