Description
I know that I am not the first person to bring up NRF51822 ticker issues here but I just thought I would open this issue to document what I discovered when investigating this issue report on the mbed site.
With this particular repro case, I was able to track down an issue caused by this line in the NRF51822 us_ticker_set_interrupt() routine.
if (us_ticker_appTimerRunning) {
return;
}
The failing scenario was:
- The ticker fired and the first thing that Ticker::handler() does is attempt to insert the next tick into the queue here.
- This ends up calling the NRF51822 version of us_ticker_set_interrupt(). By the time this code completes its call to Nordic's app_timer_start() API, the desired time has already passed.
- You might think this is where the ticker gets orphaned but there is actually more to the story.
- When us_ticker_set_interrupt() started this expired timer, it set the us_ticker_appTimerRunning global to true.
- After the application's ticker handler completes, it ends up passing control back to us_ticker_irq_handler(). This function is actually smart enough to check for such expired events and execute them immediately so that they don't get lost.
- At this point no expired events have been lost and everything is as it should be.
- us_ticker_irq_handler() then finds the next future event and queues it up for execution.
- It is that call to the NRF51822's version of us_ticker_set_interrupt() which leads to the problem. This call will just ignore the request to queue up this future ticker event because us_ticker_appTimerRunning is set to true.
I tried calling Nordic's app_timer_stop() API when us_ticker_appTimerRunning is already set to true. This is my updated version of the function with a bunch of instrumentation added for debugging purposes:
uint32_t g_tickerLastSetTime;
uint32_t g_tickerLastSetReturn;
uint32_t g_tickerLastStopTime;
uint32_t g_tickerLastStopReturn;
uint32_t g_tickerLastFailCreateTime;
uint32_t g_tickerLastFailSetTime;
uint32_t g_tickerLastSuccess;
void us_ticker_set_interrupt(timestamp_t timestamp)
{
if (!us_ticker_inited) {
us_ticker_init();
}
if (us_ticker_appTimerID == TIMER_NULL) {
if (app_timer_create(&us_ticker_appTimerID, APP_TIMER_MODE_SINGLE_SHOT, us_ticker_app_timer_callback) != NRF_SUCCESS) {
/* placeholder to do something to recover from error */
g_tickerLastFailCreateTime = us_ticker_read();
return;
}
}
if (us_ticker_appTimerRunning) {
g_tickerLastStopReturn = app_timer_stop(us_ticker_appTimerID);
us_ticker_appTimerRunning = false;
g_tickerLastStopTime = us_ticker_read();
return;
}
timestamp_t currentCounter64;
app_timer_cnt_get(¤tCounter64);
uint32_t currentCounter = currentCounter64 & MAX_RTC_COUNTER_VAL;
uint32_t targetCounter = ((uint32_t)((timestamp * (uint64_t)APP_TIMER_CLOCK_FREQ) / 1000000) + 1) & MAX_RTC_COUNTER_VAL;
uint32_t ticksToCount = (targetCounter >= currentCounter) ?
(targetCounter - currentCounter) : (MAX_RTC_COUNTER_VAL + 1) - (currentCounter - targetCounter);
if (ticksToCount < APP_TIMER_MIN_TIMEOUT_TICKS) { /* Honour the minimum value of the timeout_ticks parameter of app_timer_start() */
ticksToCount = APP_TIMER_MIN_TIMEOUT_TICKS;
}
uint32_t rc;
rc = app_timer_start(us_ticker_appTimerID, ticksToCount, NULL /*p_context*/);
if (rc != NRF_SUCCESS) {
/* placeholder to do something to recover from error */
g_tickerLastFailSetTime = us_ticker_read();
g_tickerLastSetReturn = rc;
return;
}
us_ticker_appTimerRunning = true;
g_tickerLastSetTime = (uint32_t)timestamp;
g_tickerLastSuccess = us_ticker_read();
}
When I run this code under GDB, I find that the events only get orphaned when the Nordic app_timer_stop() and app_timer_start() APIs both fail with a NRF_ERROR_NO_MEM when attempting to clear out an older event to replace with the future event that occurs the soonest.
Here is the dump of the state of the above code once the ticker stops running:
(gdb) p *'us_ticker_api.c'::head
$1 = {
timestamp = 3430256,
id = 536879592,
next = 0x0 <__fp_lock>
}
(gdb) p g_tickerLastSetTime
$2 = 3429256
(gdb) p g_tickerLastSetReturn
$3 = 4
(gdb) p g_tickerLastSuccess
$4 = 3429382
(gdb) p g_tickerLastFailSetTime
$5 = 3429504
(gdb) p g_tickerLastStopTime
$6 = 3429412
(gdb) p g_tickerLastStopReturn
$7 = 4
# Both the stop and the start fail with the same error of 4.
# This is NRF_ERROR_NO_MEM
Since I don't have access to the Nordic code, this is as far as I can take this investigation. Hopefully someone else will be able to carry on where I have left off.