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!

  • Yes, CONFIG_SERIAL=n seemed to do it. The LBS project seems to have its child image project files over in the sample HCI_RPMSG project (my local NCS installation path, zephyr\samples\bluetooth\hci_rpmsg). I edited the prj.conf there w/ CONFIG_SERIAL=n, rebuilt the project, flashed the build, and confirmed w/ PPK2 that when idle it's drawing roughly 3.5uA. I've attached the .ppk file here (sorry about that; I got confused between the Power Profiler's log and the session data).

    ppk2-session-data-nrf5340dk_hciRpmsgConfigSerialNo.ppk

    Current measurement setup should be correct. When first making this post, I have been using an nRF5340DK, and did already cut SB40. I put PPK2's GND to DK's P21 negative pin, VIN to P22 'bottom pin' (close to SW9), and VOUT to P22 'top pin' (close to P23).

    But thanks! I will take a look at the config from the LBS project, and see how I can apply it to my own project. Still need to check out the timer utilizing RTC; will reply again when I have an update.

  • (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? 

Related