This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Alternative way of using app timer as low power delay?

Hey all! I have been using your amazing app timer for a while now but I was wondering if there was a better way to create a low power delay than my current design which goes something like this:

timer2_handler()

{

    //execute code after 10ms has passed

}

timer1_handler()

{

    start_timer2(10ms in ticks);

}

This works nicely because the code returns to the power management state(wfe if i'm not mistaken) that is running in main(). However I was wondering if there is a way to perform the wait inside timer1_handler's context? This is mainly because if I require multiple delays in succession(such as waiting for sensors to wake up, etc.) then I would need to spawn more timers and as a result, more handlers, which can become very hard to follow the flow.

I've tried something like this(from this post: https://devzone.nordicsemi.com/f/nordic-q-a/26451/low-power-delay-implementation):

static void timer2_wait_handler(void *p_context)
{

NRF_LOG_INFO("in app timer");
NRF_LOG_FLUSH();
timerFlag = 1;

}


static void timer1_timer_handler(void *p_context)
{
timerFlag = 0;
NRF_LOG_INFO("before delay");
app_timer_start(timer2_wait_id,APP_TIMER_TICKS(1000),NULL);
while(!timerFlag)
{
sd_app_evt_wait();
}
NRF_LOG_INFO("after delay");
}

However the while loop is never 'escaped', and the timer 2  handler is never called(tried both logging and breakpoints). The timer2 handler is called if I comment out the while loop, but that would remove the in function delay aspect.

So is if there is a better way to have low power delay functions which don't rely on multiple handlers being executed in succession?

  • Hello,

    The reason the while loop in your last snippet is not working can be two reasons:

    1: The timerFlag is not a volatile variable. So because of optimization, timer1_timer_handler() may think that timerFlag is not changed outside this function. Try to declare timerFlag this way near the top of your main.c:

    volatile uint8_t timerFlag = 0;

    However, this will still not work, because of the next reason:

    2: because when you are waiting in an interrupt, you are blocking the interrupt from the timer2_wait_handler when you don't leave timer1_timer_handler(). They have the same priority (the priority of the app_timer). In general it is not recommended to wait in interrupts like this, exactly because you are blocking other interrupts with the same or lower priority.

    Actually, your first implementation looks quite good to me. It is better to leave the interrupts, and allow the main() loop to handle the sd_app_evt_wait() waiting.

    I understand that it may be complex, so you can look into other ways of handling the application logic. 

    One handy feature of the app_timer is that you can pass a pointer containing some information to your app_timer_start() call. The last (3rd) parameter is a pointer that you can fetch in the callback. You can e.g. do something like this (this is quite simplified, but perhaps you get some inspiration to how you can handle this):

    This should work in any of the examples that supports the app_timer. All of these are in main, but you can move the declaration of variables to another file if you'd like. I'll use comments to show where you can put these:

    /* Near the top of main.c, where your other variables are set: */
    APP_TIMER_DEF(m_test_context_timer_id);
    
    /* Enum to handle different timer "events"/"ID's": */
    enum CONTEXT_TYPE
    {
        EVENT_TYPE_0 = 0,
        EVENT_TYPE_1,
        EVENT_TYPE_2,
        EVENT_TYPE_3,
        EVENT_TYPE_4,
        EVENT_TYPE_5,
        EVENT_TYPE_6
    };
    
    /* Struct to pass information between app_timer_start() and timeout callback: */
    typedef struct
    {
        uint8_t context_header;           /**< Event header. */
        uint8_t some_other_variable;
    } m_context_t;
    
    /* Actual declaration of this app_timer struct: */
    m_context_t my_context;
    
    
    /* Forward declaration of timeout handler: */
    static void my_context_timer_timeout(void * p_context);
    
    /**@brief Function for initializing the timer module.
     */
    static void timers_init(void)
    {
        ret_code_t err_code = app_timer_init();
        APP_ERROR_CHECK(err_code);
        
        //create the timer (NB: Single shot mode)
        err_code = app_timer_create(&m_test_context_timer_id, APP_TIMER_MODE_SINGLE_SHOT, my_context_timer_timeout);
        APP_ERROR_CHECK(err_code);
        
        // Write some info and start the timer:
        my_context.context_header = EVENT_TYPE_0;
        my_context.some_other_variable = 4;
        err_code = app_timer_start(m_test_context_timer_id, APP_TIMER_TICKS(1000), &my_context);
        APP_ERROR_CHECK(err_code);
    }
    
    static void my_context_timer_timeout(void * p_context)
    {
        NRF_LOG_INFO("p_context: 0x%08x", p_context);
        ret_code_t err_code;
        m_context_t * local_pointer = (m_context_t*)p_context;
        
        switch(local_pointer->context_header)
        {
            case EVENT_TYPE_0:
                NRF_LOG_INFO("event type EVENT_TYPE_0");
                NRF_LOG_INFO("some additional information: %d", local_pointer->some_other_variable);
            
                // Start next timer:
                local_pointer->context_header = EVENT_TYPE_1;
                local_pointer->some_other_variable = 12;
                err_code = app_timer_start(m_test_context_timer_id, APP_TIMER_TICKS(1000), local_pointer);
                APP_ERROR_CHECK(err_code);
                break;
            
            case EVENT_TYPE_1:
                NRF_LOG_INFO("event type EVENT_TYPE_1");
                NRF_LOG_INFO("some additional information: %d", local_pointer->some_other_variable);
                // Start new timer if necessary
                break;
            
            default:
                NRF_LOG_INFO("undefined event type 0x%02x", local_pointer->context_header);
                break;
        }
    }
    
    int main(void)
    {
        // initialize stuff...
        ...
        timers_init();
        
        // main loop:
        for (;;)
        {
            sd_app_evt_wait();
        }
    }

    Let me know if you don't get it up and running, and I can send you the (a bit messy) project I used for testing this. 

    BR,

    Edvin

  • Hey, thanks for the clarification! I took inspiration from your state machine and decided to utilize the app scheduler to create a state machine which runs in the main context, which seems to work wonderfully. I'll try to post the code here later for any one who might wanna see how I implemented it. Thanks again for the help!

Related