Problem getting power consumption below 2.2mA even in PM_STATE_SOFT_OFF

I am using a custom board with a nRF52832 (inside a BMD-300 from ublox) that I can't seem to get to a lower power state. The lowest I've been able to get is 2.2mA even using bare-bones firmware (see below for code). Can anyone shine some light on why this is? From what I can tell, I've turned all of the important settings off in my prj.config and my code is doing barely anything.

My custom board is connected to the "Debug Out" connector on the nRF52 DK. I'm using this to power the custom board with the DK 5V out. My multimeter is connected between the 5V out and the input pin on my custom board.

I have tried power cycling the device in case it's stuck in debug mode.

In addition to the code, I've attached both my prj.conf and my .dts file.

I am using NCS 2.1.2 and Zephyr 3.1.99.

Please let me know if I can provide more information that may be helpful in tracking this down.

#include <zephyr.h>
#include <logging/log.h>
#include <stdio.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/policy.h>
#include <zephyr/pm/state.h>
#include <zephyr/pm/device.h>
#include <device.h>
#include <drivers/gpio.h>
#include <hal/nrf_gpio.h>
#include <hal/nrf_clock.h>
#include <hal/nrf_power.h>

#define LOG_MODULE_NAME stylus_main

LOG_MODULE_REGISTER(LOG_MODULE_NAME);

// Is it necessary to disable this in order to manually go to sleep? Examples vary.
static int disable_ds_1(const struct device *dev)
{
	ARG_UNUSED(dev);

	pm_policy_state_lock_get(PM_STATE_SOFT_OFF, PM_ALL_SUBSTATES);
	return 0;
}

SYS_INIT(disable_ds_1, PRE_KERNEL_2, 0);

void main(void)
{
	k_usleep(1);
	k_sleep(K_MSEC(10));

	nrf_clock_lf_src_set(NRF_CLOCK, NRF_CLOCK_LFCLK_RC);
	nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART);

	// This seems to have the same effect as pm_state_force below on the current.
	// nrf_power_system_off(NRF_POWER);

	bool success = pm_state_force(0, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0});
	k_sleep(K_SECONDS(2U));
	if (success)
	{
		// We successfully went to sleep
		// This is never called because we're asleep - that's expected.
		// blink_with_colors(1000, 0, 0, 0, 250, 5);
	}
	else
	{
		printf("Failed to go to sleep.");
	}

	// Should never be reached
	while (1)
	{
	}
}

Here is my prj.conf:

# Bluetooth
CONFIG_BT=n
# CONFIG_BT_PERIPHERAL=n
# CONFIG_BT_DEVICE_NAME="Stylus"
# CONFIG_BT_DEVICE_APPEARANCE=0
# CONFIG_BT_MAX_CONN=1
# CONFIG_BT_LL_SOFTDEVICE=n

CONFIG_GPIO=n
# CONFIG_DK_LIBRARY=n
CONFIG_ASSERT=n

# DFU
CONFIG_MCUMGR=n
# CONFIG_MCUMGR_CMD_IMG_MGMT=n
# CONFIG_MCUMGR_CMD_OS_MGMT=n
# CONFIG_BT_L2CAP_TX_MTU=253
# CONFIG_BT_BUF_ACL_RX_SIZE=256
# CONFIG_MCUMGR_SMP_BT=n
# CONFIG_MCUMGR_SMP_BT_AUTHEN=n

CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1024
CONFIG_INIT_STACKS=n
CONFIG_THREAD_STACK_INFO=n
CONFIG_MAIN_STACK_SIZE=1024
# CONFIG_BT_RX_STACK_SIZE=2048

CONFIG_ADC=n
# CONFIG_ADC_NRFX_SAADC=n

# CONFIG_BT_BUF_ACL_TX_COUNT=74
# CONFIG_BT_PERIPHERAL_PREF_MIN_INT=6
# CONFIG_BT_PERIPHERAL_PREF_MAX_INT=20

# CONFIG_STDOUT_CONSOLE=n
CONFIG_I2C=n
CONFIG_SENSOR=n
CONFIG_BMI270=n
CONFIG_BMM150=n
CONFIG_BMM150_PRESET_LOW_POWER=y
# CONFIG_BMM150_PRESET_HIGH_ACCURACY=n

# Storage
CONFIG_FLASH=n
CONFIG_FLASH_PAGE_LAYOUT=n
CONFIG_NVS=n
# CONFIG_NVS_LOG_LEVEL_DBG=n
CONFIG_REBOOT=n
CONFIG_MPU_ALLOW_FLASH_WRITE=n

# Comment this out if you need to print floats
# CONFIG_NEWLIB_LIBC=n
CONFIG_CBPRINTF_FP_SUPPORT=n
CONFIG_PRINTK=n

CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y
CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=n

# MCUBoot
CONFIG_BOOTLOADER_MCUBOOT=n

# Debugging
CONFIG_UART_CONSOLE=n
# CONFIG_RTT_CONSOLE=n
CONFIG_USE_SEGGER_RTT=n
CONFIG_LOG=n
CONFIG_LOG_DEFAULT_LEVEL=0
# CONFIG_DEBUG_OPTIMIZATIONS=n
# CONFIG_DEBUG_THREAD_INFO=n
CONFIG_SENSOR_LOG_LEVEL_DBG=n

CONFIG_SERIAL=n
CONFIG_RTT_CONSOLE=n

# Power Management
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y
CONFIG_PM_DEVICE_POWER_DOMAIN=y

# Test
CONFIG_CONSOLE=n
CONFIG_SPI_NOR_IDLE_IN_DPD=y
CONFIG_SPI=n
CONFIG_TICKLESS_KERNEL=y
CONFIG_ADC_NRFX_SAADC=n

# Minimize boot time
CONFIG_BOOT_DELAY=0

# Disable unused hardware
CONFIG_NFCT_PINS_AS_GPIOS=y

CONFIG_PWM=n

And here is my .dts file:

/dts-v1/;
#include <nordic/nrf52832_qfaa.dtsi>
#include "mydevice-pinctrl.dtsi"

/ {
	model = "MyDevice";
	compatible = "nordic,mydevice";

	chosen {
		zephyr,console = &uart0;
		zephyr,shell-uart = &uart0;
		zephyr,uart-mcumgr = &uart0;
		zephyr,bt-mon-uart = &uart0;
		zephyr,bt-c2h-uart = &uart0;
		zephyr,sram = &sram0;
		zephyr,flash = &flash0;
		zephyr,code-partition = &slot0_partition;
	};

	buttons {
		compatible = "gpio-keys";
		
		capbutton: cap_button {
			//gpios = <&gpio0 12 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; // Incorrect, but used for testing charger
			gpios = <&gpio0 24 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; // Correct
			label = "Button on the top of the stylus cap";
		};

		battcharge: batt_charge {
			//gpios = <&gpio0 24 (GPIO_ACTIVE_LOW | GPIO_PULL_DOWN)>; // Incorrect, but used for testing charger
			gpios = <&gpio0 12 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; // Correct
			label = "Charge pin connected to the battery";
		};

		battfault: batt_fault {
			gpios = <&gpio0 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
			label = "Fault pin connected to the battery";
		};

		dockstatus: dock_status {
			gpios = <&gpio0 30 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>;
			label = "Hall effect sensor output that determines if the device is docked on the base station";
		};
	};

	// These pins are not used but can't be left in a floating electrical state.
	// If they are, the device will leak current.
	extras {
		compatible = "gpio-keys";

		// BMI270
		imu_int1: imu_int1 {
			gpios = <&gpio0 3 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "IMU Interrupt 1";
		};
		imu_int2: imu_int2 {
			gpios = <&gpio0 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "IMU Interrupt 2";
		};
		// End BMI 270

		// BMM150
		mag_drdy: mag_drdy {
			gpios = <&gpio0 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Magnetometer Data Ready";
		};
		mag_int: mag_int {
			gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Magnetometer Interrupt";
		};
	};

	aliases {
		capbutton = &capbutton;
		battcharge = &battcharge;
		battfault = &battfault;
		dockstatus = &dockstatus;
		backupstorage = &backupstorage;
		imu-int1 = &imu_int1;
		imu-int2 = &imu_int2;
		mag-drdy = &mag_drdy;
		mag-int = &mag_int;
	};

};

&adc {
	status = "disabled";

	#address-cells = <1>;
   	#size-cells = <0>;

	// Force Sensor
	// NOTE: If any issues are encountered with the force sensor, note this changed from AIN5 (P0.29) to AIN0 (P0.02)
	channel@0 {
		reg = <0>;
		zephyr,gain = "ADC_GAIN_1_5";
		zephyr,reference = "ADC_REF_INTERNAL";
		zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
		zephyr,input-positive = <NRF_SAADC_AIN0>;
		status = "disabled";
	};
 
	// Battery VBatt Divided
	channel@1 {
		reg = <1>;
		zephyr,gain = "ADC_GAIN_1_5";
		zephyr,reference = "ADC_REF_INTERNAL";
		zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
		zephyr,input-positive = <NRF_SAADC_AIN2>;
		status = "disabled";
	};
};

&gpiote {
	status = "disabled";
};

&gpio0 {
	status = "disabled";
};

&uart0 {
	status = "disabled";
	compatible = "nordic,nrf-uarte";
	current-speed = <115200>;
	pinctrl-0 = <&uart0_default>;
	pinctrl-1 = <&uart0_sleep>;
	pinctrl-names = "default", "sleep";
};

&i2c0 {
	compatible = "nordic,nrf-twi";
	status = "disabled";

	pinctrl-0 = <&i2c0_default>;
	pinctrl-1 = <&i2c0_sleep>;
	pinctrl-names = "default", "sleep";
	clock-frequency = <400000>;

	bmi270: bmi270@68 {
		compatible = "bosch,bmi270";
		reg = <0x68>;
		label = "BMI270";
		status = "disabled";
	};

	bmm150: bmm150@10 {
		compatible = "bosch,bmm150";
		reg = <0x10>;
		label = "BMM150";
		status = "disabled";
	};

	ledctrl: ledctrl@58 {
		compatible = "i2c-device";
		reg = <0x58>;
		label = "ledctrl";
		status = "disabled";
	};
};

&spi1 {
	status = "disabled";
	compatible = "nordic,nrf-spi";
	cs-gpios = <&gpio0 13 (GPIO_ACTIVE_LOW | GPIO_PULL_DOWN)>;
	pinctrl-0 = <&spi1_default>;
	pinctrl-1 = <&spi1_sleep>;
	pinctrl-names = "default", "sleep";

	backupstorage: backupstorage@0 {
		compatible = "issi,is66wvs4m8bll";
		reg = <0>;
		spi-max-frequency = <1600000>;
		label = "backupstorage";
	};
};

&flash0 {

	partitions {
		compatible = "fixed-partitions";
		#address-cells = <1>;
		#size-cells = <1>;

		boot_partition: partition@0 {
			label = "mcuboot";
			reg = <0x00000000 0xc000>;
		};
		slot0_partition: partition@c000 {
			label = "image-0";
			reg = <0x0000C000 0x32000>;
		};
		slot1_partition: partition@3e000 {
			label = "image-1";
			reg = <0x0003E000 0x32000>;
		};
		scratch_partition: partition@70000 {
			label = "image-scratch";
			reg = <0x00070000 0xa000>;
		};
		// 0x0007a000 0x00006000 = 475136
		// 475136 = 464KB
		storage_partition: partition@7a000 {
			label = "storage";
			reg = <0x0007a000 0x00006000>;
		};
	};
};


Parents Reply Children
  • Thank you for replying. I am using the code from that example, but with Zephyr version 3.1.99, which uses a different function:
    https://github.com/nrfconnect/sdk-zephyr/blob/v3.1.99-ncs1/samples/boards/nrf/system_off/src/main.c

    pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0});

    The code you linked is from Zephyr version 3.6.99, which uses sys_poweroff(). That function is not available in 3.1.99.

    I believe the device is going to sleep properly because no code is run after it goes to sleep. I can blink the LED's before the pm_state_force() call, but they don't blink after the call. And, if I configure a wakeup source (my button), it correctly reboots the device.

    But, even when my device is asleep, I've found that it draws ~2.2 mA, which is way higher than what I'd expect.

    Does that point to a floating pin? A peripheral that needs to be shut down but isn't? I'm not sure how to debug this.

  • Maybe clarify "custom board is connected to the "Debug Out" connector on the nRF52 DK. I'm using this to power the custom board with the DK 5V out". There is no supply of 5v on that port, and if using 5v from elsewhere on the nRF52DK there must be a regulator between the 5V and the BMD350 otherwise excessive current will flow in unexpected ways and the internal nRF52832 die will eventually die. If there is a regulator feeding the BMD350 then just ignore this post.

  • Sorry, I should have clarified. The 5V output from the nRF52 DK is fed into a voltage regular on the custom board (a TLV71330PDQNT). Thanks for pointing this out. All clues are welcome at this point!

  • Maybe share the schematic, or at least the part where io pins connect to external devices. Often phantom power disappears down internal schottky protection diodes in peripheral chips which have had the power turned off, or external FTDI tools, or J-ink debuggers ,,. I take it removing the debug connection from nRF52 DK and using a 3V or 5V supply from elsewhere doesn't help (ie nothing connected to the board except a battery)?

Related