Optimize power consumption for maximum battery life in nrf52840 BLE Beacon.

I’m working on a BLE-based door sensor project and my focus is to optimize power consumption for maximum battery life. The code I’m using is designed for a beacon using sdk version v2.4.1 and I’m looking for guidance on reducing power usage. I’ve included my prj.conf and overlay files for reference. Any suggestions or insights on achieving the most efficient power management for this sensor would be greatly appreciated. I'm currently testing the current consumption of an nRF52840 using the Power Profiler Kit 2 as part of a BLE door sensor project. The current measured using ppk II is also included. It is approximately near 20mA. 

/* main.c - Application main entry point */

/*
 * Copyright (c) 2015-2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <stddef.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>

#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>

#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define SLEEP_TIME_MS   1000
//#define SW0_NODE DT_ALIAS(sw0) 

#define LED0_NODE DT_ALIAS(led4)
#define SW0_NODE DT_ALIAS(sw6)

uint8_t door_status = 0;
uint8_t d[3] = {0x11, 0x66, 0x00};


static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios);


/*
 * Set Advertisement data. Based on the Eddystone specification:
 * https://github.com/google/eddystone/blob/master/protocol-specification.md
 * https://github.com/google/eddystone/tree/master/eddystone-url
 */

static struct bt_data ad1[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x58, 0x36),
	BT_DATA_BYTES(BT_DATA_SVC_DATA16,0x11,0x66,0x00) 
	
};


static const struct bt_data sd[] = {
	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};


static void bt_ready(int err)
{
	char addr_s[BT_ADDR_LE_STR_LEN];
	bt_addr_le_t addr = {0};
	size_t count = 1;

	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	/* Start advertising */
	err = bt_le_adv_start(BT_LE_ADV_NCONN_IDENTITY, ad1, ARRAY_SIZE(ad1),
			      sd, ARRAY_SIZE(sd));
	if (err) {
		printk("Advertising failed to start (err %d)\n", err);
		return;
	}

	/* For connectable advertising you would use
	 * bt_le_oob_get_local().  For non-connectable non-identity
	 * advertising an non-resolvable private address is used;
	 * there is no API to retrieve that.
	 */

	bt_id_get(&addr, &count);
	bt_addr_le_to_str(&addr, addr_s, sizeof(addr_s));

	printk("Beacon started, advertising as %s\n", addr_s);
}

int main(void)
{
	int err,ret;
	if (!device_is_ready(led.port)) {
		return ;
	}
	if (!device_is_ready(button.port)) {
	 	 return ;
    }

	ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
    if (ret < 0) {
		return ;
    }

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return ;
	}

	printk("Starting Beacon Demo\n");

	 /* Initialize the Bluetooth Subsystem */
	err = bt_enable(bt_ready);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
	}
	while(1)
	{
	    bool val = gpio_pin_get_dt(&button);
		gpio_pin_set_dt(&led,val);
		printk("Button Value: %d\n", val);
		door_status = (val == 1) ? 1 : 0;
		printk("Door Status: %s\n", door_status ? "Open" : "Closed");
		d[2]=door_status;
		ad1[2].data = (const uint8_t *)(d);
		err = bt_le_adv_update_data(ad1, ARRAY_SIZE(ad1),sd, ARRAY_SIZE(sd));
		if (err) 
		{
		printk("Advertising update with (err %d)\n", err);
		
	    }
	}
	
	return 0;
}

/*
 * Copyright 2022 Dronetag
 *
 * SPDX-License-Identifier: Apache-2.0
 */
 /{
    coex_gpio: coex {
        compatible = "gpio-radio-coex";
        grant-gpios = <&gpio1 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>;
        grant-delay-us = <150>;
    };

};
&radio {
    coex = <&coex_gpio>;
};

/{
    leds{
        compatible = "gpio-leds";
        led4: led_4 {
            gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
            label = "Yellow LED 0";
            status="okay";
        };
    };
    
    buttons {
        compatible = "gpio-keys";
        button6: button_6 {
            //gpios = <&gpio0 15 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>;
            gpios = <&gpio0 15( GPIO_ACTIVE_LOW)>;
            label = "Push button switch 6";
        };
    };
    
        aliases {
            led4=&led4;
            sw6=&button6;
        };
    };

    
    &pwm_led0 {
	
        status = "disabled";	
    };
    &button1 {
        
        status = "disabled";	
    };
    &button2 {
        
        status = "disabled";	
    };
    &button3 {	
        status = "disabled";	
    };
    
    &led0 {
        status = "disabled";
    };
    &led1 {
        status = "disabled";
    };
    &led2 {
        status = "disabled";
    };
    &led3 {
        status = "disabled";
    };
    /* Disable unused peripherals to reduce power consumption */
    &uart0 {
        status = "disabled";
    };
    &adc {
        status = "disabled";
    };
    &uart1 {
        status = "disabled";
    };
    &i2c0 {
        status = "disabled";
    };
    &pwm0 {
        status = "disabled";
    };
    &spi1 {
        status = "disabled";
    };
    &spi0 {
        status = "disabled";
    };
    &usbd {
        status = "disabled";
    };
    
    
    &i2c1 {
        status = "disabled";
    };
    &spi2 {
        status = "disabled";
    };
    
    &qspi {
        status = "disabled";
    };
    
    &timer0 {
        status = "disabled";
    };
    
    &timer1 {
        status = "disabled";
    };
    
    &timer2 {
        status = "disabled";
    };
    
    &timer3 {
        status = "disabled";
    };
    
    &timer4 {
        status = "disabled";
    };
    &nvic {
        status = "disabled";
    };
    
    &gpiote {
        status = "disabled";
    };
    &temp {
        status = "disabled";
    };
    &rtc0 {
        status = "disabled";
    };
    
    &rtc1 {
        status = "disabled";
    };
    &ecb {
        status = "disabled";
    };
    
    &ccm {
        status = "disabled";
    };
    
    &egu0 {
        status = "disabled";
    };
    
    &egu1 {
        status = "disabled";
    };
    
    &egu2 {
        status = "disabled";
    };
    
    &egu3 {
        status = "disabled";
    };
    
    &egu4 {
        status = "disabled";
    };
    
    &egu5 {
        status = "disabled";
    };
    &pwm0 {
        status = "disabled";
    };
    
    &acl {
        status = "disabled";
    };
    
    &ppi {
        status = "disabled";
    };
    
    &mwu {
        status = "disabled";
    };
    
    &usbd {
        status = "disabled";
    };
    
    &spi3 {
        status = "disabled";
    };
    
    &wdt0 {
        status = "disabled";
    };
    
    &rng_hci {
        status = "disabled";
    };
    &ieee802154{
        status = "disabled";
    };
      

CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_DEVICE_NAME="Door Sensorrr"

###################################################
CONFIG_PM=y
CONFIG_LOG=n
CONFIG_SERIAL=n
CONFIG_PM_DEVICE=y
CONFIG_BT_CTLR_TX_PWR_ANTENNA=0

  • Hi,

    Serial and logging are disabled which is good.

    I would also check out the peripheral lbs example if you haven't yet. It's basically doing exactly what you want and more, so you could even strip stuff away.

    Also make sure that all un-needed peripherals are disabled in a board overlay file. You can check this quickly by either reading the zephyr.dts file in you build/sephyr folder to check for anything that has status="okay" that you arent using. Or you could use the dts visualizer tool and just click stuff to turn them off (which makes or edits the overlay file previously mentioned).

    If you dont have any peripherals that need GPIOTE make sure that is status="disabled". If you have to have gpiote for one of your peripherals and you have an interrupt to handle like your door signal you can use the sense method on it instead of gpiote which will save some power. You can use sense on many low frequency interrupts instead of the task-event method that gpiote uses. You can use the sense-edge-mask property to tell it which pins to use sense on. In the example below, 0x8800 = 0b1000 1000 0000 0000 so I'm telling it to use sense on pins 15 and 11.

    &gpio1 {
    	status = "okay";
    	sense-edge-mask = <0x8800>;
    };

    One main aspect of low power is letting the chip go into low power mode, which happens when there is no processing to do. In your case it seems like your main thread is constantly running with no delay. If you add a delay in there the chip can sleep during that time. Also instead of polling the button state you could try using an interrupt instead (again, take a look at the peripheral_lbs example). If you want to go ballistic on it, then you can find an example that shows how to put the chip in system on or even off mode and wake it up with the actual door signal.

  • Hi Neethu, 
    Have you tried to verify the power consumption using one of our DK ? 

    Please also try to measure the current consumption with the peripehral_lbs (use prj_minimal.conf configuration in stead of prj.conf) .
    You should be able to se the idle current of a few uA instead of more than 1mA in  you are measuring now. This will also very that the way you measuring the current consumption is correct. 

  • Hi,

    Thank you for your suggestion. I have tried and verified the power consumption using one of the DKs, and below are the

    current measurements I received. Also ,I require beacon type not the connected one . 

  • Hi Neethu, 


    Thanks for the trace. Green part look expected. The idle current is low (close to 0mA) But the red part is quite strange (idle current is about 4mA). For me it seems like the CPU didn't sleep the whole time. So you may want to check if there is something running in the background that keep the CPU awake all the time. 

  • I have implemented sleep mode in my BLE beacon project. However, I am still looking for ways
    to further reduce power consumption. I have configured the system to enter sleep mode, but I would like to know if there are additional strategies or configurations that can
    help minimize power usage even further.
    Any guidance or suggestions would be much appreciated!

    Thank you!

              

    /* main.c - Application main entry point */
    
    /*
     * Copyright (c) 2015-2016 Intel Corporation
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/types.h>
    #include <stddef.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/util.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/device.h>
    #include <zephyr/pm/pm.h>
    
    
    #define DEVICE_NAME CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    
    
    #define LED0_NODE DT_ALIAS(led4)
    #define SW0_NODE DT_ALIAS(sw6)
    void led_timer_expiry_function(struct  k_timer *timer_id);
    void  sleep_timer_expiry_function(struct  k_timer *timer_id);
    //int val;
    K_MSGQ_DEFINE(ble_status_queue, sizeof(int), 16, 4);
    K_TIMER_DEFINE(led_timer, led_timer_expiry_function, NULL);  // Define a timer for the LED 
    K_TIMER_DEFINE(sleep_timer, sleep_timer_expiry_function, NULL);  // Define a timer for the LED 
    
    uint8_t door_status = 0;
    uint8_t d[3] = {0x11, 0x66, 0x00};
    uint8_t current_state ,prev_state ;
    
    static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
    static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios);
    static struct gpio_callback button_cb_data;
    
    /*
     * Set Advertisement data. Based on the Eddystone specification:
     * https://github.com/google/eddystone/blob/master/protocol-specification.md
     * https://github.com/google/eddystone/tree/master/eddystone-url
     */
    
    static struct bt_data ad1[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
    	BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x58, 0x36),
    	BT_DATA_BYTES(BT_DATA_SVC_DATA16,0x11,0x66,0x00) 
    	
    };
    
    
    /* Set Scan Response data */
    static const struct bt_data sd[] = {
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static void bt_ready(int err)
    {
    	char addr_s[BT_ADDR_LE_STR_LEN];
    	bt_addr_le_t addr = {0};
    	size_t count = 1;
    
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return;
    	}
    
    	printk("Bluetooth initialized\n");
    
    	/* Start advertising */
    	err = bt_le_adv_start(BT_LE_ADV_NCONN_IDENTITY, ad1, ARRAY_SIZE(ad1),
    			      sd, ARRAY_SIZE(sd));
    	if (err) {
    		printk("Advertising failed to start (err %d)\n", err);
    		return;
    	}
    
    
    	/* For connectable advertising you would use
    	 * bt_le_oob_get_local().  For non-connectable non-identity
    	 * advertising an non-resolvable private address is used;
    	 * there is no API to retrieve that.
    	 */
    
    	bt_id_get(&addr, &count);
    	bt_addr_le_to_str(&addr, addr_s, sizeof(addr_s));
    
    	printk("Beacon started, advertising as %s\n", addr_s);
    }
    
    
    void led_timer_expiry_function(struct k_timer *timer_id)
    {
        gpio_pin_set_dt(&led, 0);  // Turn off the LED after 100ms
    }
    void  sleep_timer_expiry_function(struct  k_timer *timer_id)
    {
    	
    	pm_state_force(0, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0});
    }
    
     void button_pressed(const struct device *dev, struct gpio_callback *cb,
    		    uint32_t pins)
    {
    
    	//printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
    	int val= gpio_pin_get_dt(&button);
    	k_msgq_put(&ble_status_queue, &val, K_NO_WAIT);
    	
    }
    void update_advertisement_data(int rec_data)
    {
    	k_timer_stop(&sleep_timer);
    	printk("Door Status: %s\n", rec_data ? "Open" : "Closed");
    	gpio_pin_set_dt(&led, 1);
    	k_timer_start(&led_timer, K_MSEC(50), K_NO_WAIT);  // Start the timer for 100ms
    	d[2] = rec_data;
    	ad1[2].data =  (const uint8_t *)(d);
    			
    	int err = bt_le_adv_update_data(ad1, ARRAY_SIZE(ad1), sd, ARRAY_SIZE(sd));
    	if (err) {
    				printk("Advertising update failed with err %d\n", err);    
    	}
    	
    	k_timer_start(&sleep_timer, K_MSEC(5000), K_NO_WAIT);  // Start the timer for 5sec
    	
    	
    }
    
    
    		
    int main(void)
    {
    	int err,ret;
    
    	if (!device_is_ready(led.port)) {
    		return ;
    	}
    	if (!device_is_ready(button.port)) {
    	 	 return ;
        }
    
    	ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
        if (ret < 0) {
    		return ;
        }
    
    	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT);
    	if (ret < 0) {
    		return ;
    	}
    
    	ret = gpio_pin_interrupt_configure_dt(&button,GPIO_INT_EDGE_BOTH);
    	if (ret != 0) {
    		printk("Error %d: failed to configure interrupt on %s pin %d\n",
    			ret, button.port->name, button.pin);
    		return 0;
    	}
    
    	gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
    	gpio_add_callback(button.port, &button_cb_data);
    	printk("Starting Beacon Demo\n");
    
    	/* Initialize the Bluetooth Subsystem */
    	err = bt_enable(bt_ready);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    	}
    
    	//k_timer_init(&led_timer, NULL, led_timer_expiry_function);
        while(true)
    	{ 	int rec_data;
    		k_msgq_get(&ble_status_queue,&rec_data,K_FOREVER);
    		update_advertisement_data(rec_data);
    		
    	}
    	return 0;
    }
    

Related