nRF5340, enter low-power/sleep while timer enabled

I'm working on optimizing power consumption in my project (using nRF5340DK; as well as Power Profiler Kit 2 which is connected to DK's VDD measurement point as an ampere meter). After trial and error, I've narrowed down the timer (set up similarly to the timer example at NCS v2.5.0, modules/hal/nordic/nrfx/samples/src/nrfx_timer/timer/) as having the biggest impact towards power. When initializing the timer, I got an average current draw of almost 1mA; when it's not initialized, I see about 0.2mA.

Several questions:

  1. Is it expected to see that amount of additional current draw due to just simply setting up the timer?
  2. What are my options for bringing the current down as low as possible, while still being able to have the timer running?
    1. It appears possible when I was using k_sleep() in my main loop, as mentioned here. Though this seems to put the thread to sleep, but not the CPU.
    2. But I just recently discovered using sys_poweroff() as mentioned in this sample, which seems to put the entire CPU to sleep if I'm not mistaken. I'm investigating at the moment on my end too but I'm not sure if the timer can be used to wake the CPU back up.
  3. Are there any other methods of setting up timers that lend itself better to low power applications?

I'd appreciate any feedback for this. Thank you!

Parents
  • (OP replying) Somewhat related question, but is it possible to cancel the remainder of a k_sleep() call within an interrupt handler?

    void interrupt_handler(void)
    {
      restart_timer();  // for example, with RTC, this is counter_set_channel_alarm()
      
      cancel_ksleep();                  // DOES SOMETHING LIKE THIS EXIST...
    }
    
    int main()
    {
      init_timer();
    
      while (true) {
        execute_other_code();    // ... SO THAT THIS EXECUTES MORE THAN ONCE?
    
        k_sleep(K_FOREVER);
      }
    }

    I realize this may not be common to do; for example, I know that things happening periodically could just be put in the main loop with k_sleep(SOME_AMOUNT_OF_TIME). But it may be possible that my project would benefit from something like this what with the timers and other complex portions of our code.

  • Hi, 
    Yes you can use k_wakeup() to wake up a sleeping thread. I believe you can wake up the main thread. You may need to find how to get the main thread ID. But an easy workaround is to put the sleep forever into an independent thread. 

  • I used k_current_get() to get the main thread ID, like so. But it seems to wakeup much later (just before the interrupt handler gets called again). I tried having the interrupts occur every 3ms or every 3 seconds, and it gave the same behavior. I also tried putting the k_current_get() and infinite loop in another thread using K_THREAD_DEFINE() but it also gave the same behavior.

    k_tid_t main_thread_id;
    
    void wakeup_main(void)
    {
        k_wakeup(main_thread_id);
    }
    
    void interrupt_handler(void)
    {
      restart_timer();  // for example, with RTC, this is counter_set_channel_alarm()
      
      wakeup_main();                  // DOES WAKE UP MAIN...
    }
    
    int main()
    {
      main_thread_id = k_current_get();
    
      init_timer();
    
      while (true) {
        execute_other_code();    // ... BUT DOESN'T EXECUTE UNTIL BEFORE NEXT INTERRUPT
    
        k_sleep(K_FOREVER);
      }
    }

    I confirmed this using a logic analyzer (sampling rate of 25MHz). Red line toggles each time main() runs, and orange line toggles each time interrupt handler is just about to call k_wakeup(). I did confirm that if I do not call k_wakeup(), red line never toggles.

  • Hi, 
    Could you provide your toggling code so I can test here. 
    Please explain what you are trying to do ? If you plan to execute some code periodically, can't you just use the timer and leave the main loop clean? 

  • So I made a simpler project doing this, but it behaved as I expected. I looked into it more on my project (which gets a little more complicated: performs SPI reads in interrupt handler, which completes in another interrupt handler, etc.) and ultimately fixed that issue. Did not find the root cause, but generally having main woken up a little earlier (i.e., before the SPI read) seemed to help.

    Thank you for offering your help though!

  • I'm happy to help. But I would suggest to keep your task outside of the main loop. I would prefer to have different threads/timer for your tasks instead of putting them into a main loop .

  • I will consider that suggestion.

    In the meanwhile, I wanted to circle back to your suggestion to "put the sleep forever into an independent thread". I tried this but it just kept the main thread running all the time. If I misinterpreted what you meant, I'd appreciate a clarification. A quick example would also be greatly appreciated if possible.

    k_tid_t main_thread_id;
    
    void wakeup_main(void)
    {
        k_wakeup(main_thread_id);
    }
    
    void interrupt_handler(void)
    {
      restart_timer();  // for example, with RTC, this is counter_set_channel_alarm()
      wakeup_main();
    }
    
    int main()
    {
      main_thread_id = k_current_get();
      init_timer();
    
      while (true) {
        execute_other_code();    // ... BUT DOESN'T EXECUTE UNTIL BEFORE NEXT INTERRUPT
      }
    }
    
    #define STACKSIZE 1024
    static void sleep_thread(void *arg1, void *arg2, void *arg3)
    {
        int rc;
        ARG_UNUSED(arg1);
        ARG_UNUSED(arg2);
        ARG_UNUSED(arg3);
        
        k_sleep(K_FOREVER);
    }
    K_THREAD_DEFINE(sleep_thread_id, STACKSIZE, sleep_thread, NULL, NULL, NULL,
            K_LOWEST_THREAD_PRIO, 0, 100);

Reply
  • I will consider that suggestion.

    In the meanwhile, I wanted to circle back to your suggestion to "put the sleep forever into an independent thread". I tried this but it just kept the main thread running all the time. If I misinterpreted what you meant, I'd appreciate a clarification. A quick example would also be greatly appreciated if possible.

    k_tid_t main_thread_id;
    
    void wakeup_main(void)
    {
        k_wakeup(main_thread_id);
    }
    
    void interrupt_handler(void)
    {
      restart_timer();  // for example, with RTC, this is counter_set_channel_alarm()
      wakeup_main();
    }
    
    int main()
    {
      main_thread_id = k_current_get();
      init_timer();
    
      while (true) {
        execute_other_code();    // ... BUT DOESN'T EXECUTE UNTIL BEFORE NEXT INTERRUPT
      }
    }
    
    #define STACKSIZE 1024
    static void sleep_thread(void *arg1, void *arg2, void *arg3)
    {
        int rc;
        ARG_UNUSED(arg1);
        ARG_UNUSED(arg2);
        ARG_UNUSED(arg3);
        
        k_sleep(K_FOREVER);
    }
    K_THREAD_DEFINE(sleep_thread_id, STACKSIZE, sleep_thread, NULL, NULL, NULL,
            K_LOWEST_THREAD_PRIO, 0, 100);

Children
Related