Zephyr - 2 NRF52840 Low Power Mode Test

Hardware

Electrical specification

image-20230811123132294

Hardware connection

image-20230811172915082

image-20230811122949817

Signal Chip Module (All GPIOs are floating)

Condition:

  • supply voltage: 3V

  • temperature: 25

System Off Mode: 0.7uA

Typical: 0.4uA

image-20230811122817189

System on Mode: 3.5uA (Full RAM with RTC wakeup)

Typical: 3.16uA

image-20230811161202996

NRF52840DK-NRF52840

Cut

  • SB16
  • SB10 - SB12
  • SB13 - SB15
  • SB40 (VDD_nRF)

Remove

  • R16 - R19
  • U5 - U8
  • SW9

Fly wire

  • Debug in pin4 -> P26 pin4 (SWDCLK)
  • Debug in pin2 -> P26 pin5 (SWDIO)

System-off Mode: 0.82uA

image-20230811162251698

System on idle Mode: 3.86uA

image-20230811162451545

Cactes BLB200

System Off Mode with LoRa in Sleep mode 1.15uA

image-20230811171841645

System on Mode with LoRa in sleep mode 3.71uA

image-20230811172412347

System On Mode with LoRa in standby mode (716uA)

image-20230811164751999

System Off Mode with LoRa in standby mode (739uA)

image-20230811164556041

Source Code for BLB200 module

prj.conf

# Power Control
CONFIG_PM_DEVICE=y
CONFIG_POWEROFF=y

# GPIO
CONFIG_GPIO=y

# UART
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

# SPI
CONFIG_SPI=y

# LoRa
CONFIG_LORA=y

CONFIG_USE_SEGGER_RTT=n

main.c

/*
 * Copyright (c) 2023 Cactes Technology
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/init.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/policy.h>
#include <zephyr/sys/poweroff.h>

#include <soc.h>
#include <hal/nrf_gpio.h>
#include <zephyr/dt-bindings/gpio/nordic-nrf-gpio.h>

const struct device *gpio0 = DEVICE_DT_GET(DT_NODELABEL(gpio0));
const struct device *gpio1 = DEVICE_DT_GET(DT_NODELABEL(gpio1));

void gpio_disconnect(const struct device *gpio_dev, int pin)
{
    int ret;
    if (!device_is_ready(gpio_dev)) {
        printk("%s: device not ready.\n", gpio_dev->name);
        return;
    } else {
        printk("%s: device ready.\n", gpio_dev->name);
    }

    ret = gpio_pin_configure(gpio_dev, pin, GPIO_DISCONNECTED);
    if (!ret) {
        printk("set gpio ok %d\n", ret);
    }
}

void gpio_low(const struct device *gpio_dev, int pin)
{
    int ret;
    if (!device_is_ready(gpio_dev)) {
        printk("%s: device not ready.\n", gpio_dev->name);
        return;
    } else {
        printk("%s: device ready.\n", gpio_dev->name);
    }

    ret = gpio_pin_configure(gpio_dev, pin, GPIO_OUTPUT_LOW);
    if (!ret) {
        printk("set gpio ok %d\n", ret);
    }
}

void gpio_high(const struct device *gpio_dev, int pin)
{
    int ret;
    if (!device_is_ready(gpio_dev)) {
        printk("%s: device not ready.\n", gpio_dev->name);
        return;
    } else {
        printk("%s: device ready.\n", gpio_dev->name);
    }

    ret = gpio_pin_configure(gpio_dev, pin, GPIO_OUTPUT_HIGH);
    if (!ret) {
        printk("set gpio ok %d\n", ret);
    }
}

struct gpios_map {
    const struct device *gpio;
    int pin;
    int action;
};

/* -2 skip, -1 disconnect, 0 low, 1 high */
struct gpios_map gpio_map_disconnect [] = {
#ifdef CONFIG_APP_SYSTEMOFF
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 0, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 1, .action = -1 },
#else
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 0, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 1, .action = -2 },
#endif
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 2, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 3, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 4, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 5, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 6, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 7, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 8, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 9, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 10, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 11, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 12, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 13, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 14, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 15, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 16, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 17, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 18, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 19, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 20, .action = -2 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 21, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 22, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 23, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 24, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 25, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 26, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 27, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 28, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 29, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 30, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 31, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 0, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 1, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 2, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 3, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 4, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 5, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 6, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 7, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 8, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 9, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 10, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 11, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 12, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 13, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 14, .action = -1 },
    { .gpio = DEVICE_DT_GET(DT_NODELABEL(gpio1)), .pin = 15, .action = -1 },
};

void deinit(void)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(gpio_map_disconnect); i++) {
        switch (gpio_map_disconnect[i].action) {
        case -2:
            break;
        case -1:
            gpio_disconnect(gpio_map_disconnect[i].gpio, gpio_map_disconnect[i].pin);
            break;
        case 0:
            gpio_low(gpio_map_disconnect[i].gpio, gpio_map_disconnect[i].pin);
            break;
        case 1:
            gpio_high(gpio_map_disconnect[i].gpio, gpio_map_disconnect[i].pin);
            break;
        }

    }
}

#if DT_NODE_HAS_STATUS(DT_ALIAS(lora0), okay)
#include <zephyr/drivers/lora.h>
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
static const struct device *const lora_spi = DEVICE_DT_GET(DT_PARENT(DT_ALIAS(lora0)));
#endif

#ifdef CONFIG_SERIAL
static const struct device *const cons = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
#endif

#define BUSY_WAIT_S 2U
#define SLEEP_S 2U

void main(void)
{
    int rc;

    /* lora init and set to sleep */
#if DT_NODE_HAS_STATUS(DT_ALIAS(lora0), okay)
    struct lora_modem_config config;
	if (!device_is_ready(lora_dev)) {
		printk("%s Device not ready\n", lora_dev->name);
	}

    config.frequency = 865100000;
	config.bandwidth = BW_125_KHZ;
	config.datarate = SF_10;
	config.preamble_len = 8;
	config.coding_rate = CR_4_5;
	config.iq_inverted = false;
	config.public_network = false;
	config.tx_power = 4;
	config.tx = true;

	rc = lora_config(lora_dev, &config);
	if (rc < 0) {
		printk("LoRa config failed\n");
	}

    rc = pm_device_action_run(lora_spi, PM_DEVICE_ACTION_SUSPEND);
#endif

    if (!device_is_ready(gpio0)) {
        printk("%s: device not ready.\n", gpio0->name);
    }
    if (!device_is_ready(gpio1)) {
        printk("%s: device not ready.\n", gpio1->name);
    }

    /* uart deinit */
#ifdef CONFIG_SERIAL
    rc = pm_device_action_run(cons, PM_DEVICE_ACTION_SUSPEND);
#endif

    /* gpio deinit */
    deinit();

    while (true) {
        /* simulate system is busy */
        k_busy_wait(BUSY_WAIT_S * USEC_PER_SEC);

        /* enter sleep mode */
#ifdef CONFIG_APP_STANDBY
        k_sleep(K_SECONDS(SLEEP_S));
#elif defined CONFIG_APP_SYSTEMOFF
        sys_poweroff();
        k_sleep(K_SECONDS(SLEEP_S));
#endif
    }
}
  • auto deinit serial and spi
  • auto deinit non-lora connected gpios

Reference

  • A user confirmed about ~4uA idle sleep mode
  • https://devzone.nordicsemi.com/f/nordic-q-a/102588/nrf52840-zephyr-blinky-sleep-power-consumption
  • Can only achieve 0.7uA with another one's firmware
  • Worth to read,
    • https://devzone.nordicsemi.com/f/nordic-q-a/53117/high-current-consumption-4ua-even-without-peripherals-enabled-in-idle/214583