Resetting Speed

There's One Small Problem 

I'm not moving but the watch still thinks I am. There is a problem with the approach of calculating speed only on a trigger from the IR sensor. Sure, it's easy to do it this way, known distance, calculated time delta; however, the speed never resets if the next interrupt from the IR sensor doesn't happen. I thought this wouldn't be an issue at first. The watch would just display the wrong pace for a little bit and I'd eventually have the sensor "time out" at which point I'd set the pace to zero. This seemed like a good plan, after all, the distance field only gets updated at each interrupt so the watch would know to not add any more distance to the run.

Well, I was wrong. As it turns out, the watch is doing more with the instantaneous speed than you might think. Whereas the ANT+ SDM Profile states that
Instantaneous speed is intended to be appropriately filtered by the SDM, such that the receiving unit can directly display this value to the user.
The watch was using this value to determine distance as well, seemingly ignoring the actual distance I was reporting. This meant that for as long as the next white mark on the treadmill belt was not seen the watch would think that we were still moving. Even the initial plan to have a timeout of 15 seconds would cause an issue because we'd be adding 15 seconds of distance to the activity... not ideal.

A Fix

So to fix this issue, we're going to use a timer to fade the pace off until finally setting it to zero if enough time has gone by without getting an interrupt from the optical sensor.
APP_TIMER_DEF(m_pace_timer_id);

err_code = app_timer_create(
              &m_pace_timer_id, 
              APP_TIMER_MODE_REPEATED, 
              pace_recalc_timeout_handler);
APP_ERROR_CHECK(err_code);
In this case, we're going to have a repeating timer that calls the pace_recalc_timeout_handler function each time the timer expires.

When an interrupt from the optical sensor is handled, the code will disable the timer, and reset it with the next expected timeout time. In this code I used twice the time between the last two marks. At that point the "recalc" function decreases the speed by half and so on each time the timer triggers. This isn't exactly correct, but it's simple code-wise and really only trying to handle the case where the belt has likely stopped but the code can't tell because it never gets interrupted.
// This timer acts as a watchdog just in case this interrupt 
// doesn't trigger. At this point we know it has triggered
// so we'll stop the timer and reset it below.
err_code = app_timer_stop(m_pace_timer_id);
APP_ERROR_CHECK(err_code);
...
// Reset the timer to roughly twice the time of the next expected 
// interrupt
err_code = app_timer_start(
             m_pace_timer_id, 
             APP_TIMER_TICKS(timeInMillis * 2),
             NULL);
APP_ERROR_CHECK(err_code);

And finally, the timeout handler:
// Timeout handler for the repeated timer
static void pace_recalc_timeout_handler(void * p_context)
{
    // Fade the pace off... not exactly correct but it mostly works
    m_ant_sdm.SDM_PROFILE_speed = 
        ROUNDED_DIV(m_ant_sdm.SDM_PROFILE_speed, 2);
}

There's also some code in the timeout handler for that sets the speed to 0 when it gets slow enough instead of letting a very small fractional speed exist for a a while. I didn't show that code above. But now the treadmill can be stopped and within a few seconds the pace will fade from the last known pace to 0, indicating the belt stopped.

Next Up

We put it all together and bring this project to a close.

Comments