Sleep current during k_sleep()

In a nrf52840 project using Zephyr 3.0 following Zephyr drivers are used:

  • the modbus serial driver(RTU mode) which uses uart1 in interrupt mode
  • the lora-mac driver which uses spi1
  • serial logging on uart0

The application works fine but during inactivity periods the standby current is sometimes much too high: 520uA instead of ~10uA

I know that the uarts don't suspend automatically so immediately before k_sleep() I execute:

pm_device_action_run(uart0, PM_DEVICE_ACTION_SUSPEND);
pm_device_action_run(uart1, PM_DEVICE_ACTION_SUSPEND);

Without success I also tried:

  • executing nrf_uart_errorsrc_get_and_clear() for both uarts before k_sleep().
  • disable logging completely but as well without success.
  • "disable" all unneeded periphery in the device tree.
  • checked thread activity via the Zephyr Thread Analyzer but I could not find excessive activity.
  • executing pm_device_action_run()  for spi1, gpio0, gpio1 , ...
  • modbus_disable() during sleep
  •  ...

Do you have any hints how I could enclose or solve the problem?

Thx  Matthias

Parents
  • Hi, sorry for the late response

    Transitions on RX could be causing interrupts, which would be an issue.
    Could you keep the pin stable during sleep to test whether that reduces the current?
    It could also be a good idea to avoid going to sleep while the uart is sending or receiving.

  • Unfortunately I can't prevent transitions on RX without additional logic.

    But I had some progress ...

    By defining 'CONFIG_LOG_MODE_IMMEDIATE=y' and additionally waiting about 50ms (by a additional k_sleep() possibly  to write out the interrupt driven buffer) and thereafter put the uarts into suspend mode as follows:

    while (log_process(false));   // unneeded when CONFIG_LOG_MODE_IMMEDIATE=y

    pm_device_action_run(uart0, PM_DEVICE_ACTION_SUSPEND);
    pm_device_action_run(uart1, PM_DEVICE_ACTION_SUSPEND);
    nrf_uart_errorsrc_get_and_clear((NRF_UART_Type *)0x40002000);
    nrf_uart_errorsrc_get_and_clear((NRF_UART_Type *)0x40002800);
    k_sleep(several minutes)

    ... the standby current will go down to 12uA .

    But unfortunately when enabling the BLE stack by use of 'CONFIG_LOG_MODE_IMMEDIATE' results in following compiler error:
    error: static assertion failed: "Immediate logging on selected backend(s) not supported with the software Link Layer"

    So I had to be disabled logging completely by 'CONFIG_LOG=n' .

    And regarding your suggestion it seems the problem is not caused by the transitions on uart1 (which is used for RS485 Modbus) but by the console logging task.

    I can live with this solution but the 12uA are still a bit high according the nrf52840 product datasheet.

    Shouldn't it about 3uA in SYSTEM-ON mode when using the LF clock (RC or XTAL)?

    What could be the reason for the additional 9uA?

  • Take a look at this guide for optimizing power on nRF52: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs

    If you are using a custom board, are there any other chips on the board which may be contributing to this current draw?

    How are you measuring the current? If you are using a PPK could you upload the power profiler trace?

Reply Children
  • Thx for suggestion but I already checked the power optimization guide you referred and took all into account. (Unfortunately there is no nrf52 specific guide for zephyr.)

    I ordered a PPK2 but will have to wait about a month - I will come back if I get it.

  • Is there a easy way to check the power state of ALL periphery of the SoC (nrf52840) under Zephyr?

    (possibly by using pm_device_state_get() but then I would have to define all 10th of device handles manually. Does it exist a easier solution?)

  • Finally I came to following solution to solve my standby current problem:

    1.)

    Running ths function with argument true/false before/after k_sleep() :

    void set_pm_mode_suspend(bool suspend)
    {
      const struct device *uart0 = device_get_binding(DT_LABEL(DT_NODELABEL(uart0)));
      const struct device *uart1 = device_get_binding(DT_LABEL(DT_NODELABEL(uart1)));
      const struct device *spi1 = device_get_binding(DT_LABEL(DT_NODELABEL(spi1)));

      enum pm_device_action action =
           suspend ? PM_DEVICE_ACTION_SUSPEND : PM_DEVICE_ACTION_RESUME;


    #ifdef CONFIG_LOG
      if (suspend)
        while (log_process(false)) ; // flush log buffer, otherwise it is lost
    #endif

      pm_device_action_run(uart1, action); // -1mA !!
      pm_device_action_run(uart0, action); // -3mA !!
      nrf_uart_errorsrc_get_and_clear((NRF_UART_Type *) DT_REG_ADDR(DT_NODELABEL(uart0)));
      nrf_uart_errorsrc_get_and_clear((NRF_UART_Type *) DT_REG_ADDR(DT_NODELABEL(uart1)));
      uart_irq_rx_disable(uart0);    // no input (eg. shell) used on console port
      if (suspend) uart_irq_rx_disable(uart1);
      pm_device_action_run(spi1, action);
    }

    Remark: with solution above, I could used normal console logging enabled using the default log buffering but mention that log messages from other tasks during set_pm_mode_suspend(true) will be lost.

    2.)

    Enabling a edge triggered GPIO button interrupt will caused additional 15uA current. Because of this level triggered interrupt is used by:

      gpio_pin_interrupt_configure_dt(&button, GPIO_INT_LEVEL_ACTIVE);

    Then disabling the interrupt in the interrupt handler by:

      gpio_pin_interrupt_configure_dt(&button, GPIO_INT_DISABLE);

    And reenable it later eg. by using a rtc timer or some application logic.

    3.)

    Before doing any current measurement, a power cycle is needed after flashing to disable the SWD interface!

    Forther the connection to SWD and the console RxD/TxD has to be disconnected if the power supply is not sourced from the programmers VCC supply (eg. when using the PPK2 power supply).

Related