Measuring PSM idle current on the nRF91 DK

New updated guide can be found here:

Getting started with current measurements on the nRF9160

Or

Please follow the official documentation when using v1.0.0 of the DK:

- Prepare the board for measurements

- Measuring using PPK2  (similar connections used with third party power analyzer)
- or using other tools


Or

follow this updated YouTube tutorial:


NOTE: The information under is only related for the older versions of the nRF9160 DK.
In this blog I will go through how to measure current on the nRF91 DK (PCA10090 v0.8.5) and how you can achieve the lowest current consumption with PSM (Power Saving Mode) mode.

For earlier versions of the DK please have a look at Bjørn's answer in this post.

Test setup

How to prepare the DK

There are two ways to measure current on the DK, you can supply power directly to the nRF9160 chip with a power analyzer or similar, or you can measure current going through current measurement header P24 header with an ampere-meter or oscilloscope.

NOTE: Make sure that you are not accidentially shorting the GND pin with the other pins on the P24 header, this may break the board. So always make sure that the board is powered down before attaching any wires and that the connections are correct before turning the board back on.

Using a power analyzer

  • Cut SB43
  • Connect the power analyzer to P24 header as shown in the picture (yellow wire). Supply voltage in the range of 3.7V to 5V
  • Leave the USB cable connected to supply power to the rest of the board.

Using an ampere-meter

  • Cut SB43
  • Connect the ampere-meter to the P24 header as shown in the picture (green wire).
  • Leave the USB cable connected to supply power to the rest of the board, OR power the board from a separate power supply on the external supply header (yellow wire). Using an external supply is recommended in order to get rid of noise that would otherwise come from the USB bus, which might have an impact on the measurements.

For more information have a look at the DK user guide.

Firmware

The following example code shows how to set up a connection with a PSM interval of 10 minutes. Each time it wakes up from PSM it transmits a 1kB payload over UDP. It uses an application timer to initiate the UDP transfer.

It is based on the github project I linked to above with a few changes to make it use less current.

The project is configured to use NB-IoT.

In order to achieve the lowest possible current consumption, logging has to be turned off. This is done by,

  • Adding the CONFIG_SERIAL=n flag to the prj.conf file in the project folder.
  • Adding the CONFIG_SERIAL=n to the SPM prj.conf file. This is located in the SDK_ROOT/nrf/samples/nrf9160/spm folder.

If you want to turn logging back on for debugging, remove the flag from both prj.conf files.

Here is the main.c file used in the project:

/*
 * Copyright (c) 2018 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic
 */

#include <zephyr.h>
#include <net/socket.h>
#include <stdio.h>
#include <uart.h>
#include <string.h>
#include <lte_lc.h>

#define HOST "129.240.2.6"
#define PORT 123
#define RECV_BUF_SIZE 1024
#define SEND_BUF_SIZE 1024

u8_t recv_buf[RECV_BUF_SIZE];

/* As it is close to impossible to find
 * a proper UDP server, we just connect
 * to a ntp server and read the response
 */
struct ntp_format {
	u8_t flags;
	u8_t stratum; /* stratum */
	u8_t poll; /* poll interval */
	s8_t precision; /* precision */
	u32_t rootdelay; /* root delay */
	u32_t rootdisp; /* root dispersion */
	u32_t refid; /* reference ID */
	u32_t reftime_sec; /* reference time */
	u32_t reftime_frac; /* reference time */
	u32_t org_sec; /* origin timestamp */
	u32_t org_frac; /* origin timestamp */
	u32_t rec_sec; /* receive timestamp */
	u32_t rec_frac; /* receive timestamp */
	u32_t xmt_sec; /* transmit timestamp */
	u32_t xmt_frac; /* transmit timestamp */
};

int blocking_recv(int fd, u8_t *buf, u32_t size, u32_t flags)
{
	int err;

	do {
		err = recv(fd, buf, size, flags);
	} while (err < 0 && errno == EAGAIN);

	return err;
}

int blocking_recvfrom(int fd, void *buf, u32_t size, u32_t flags,
		      struct sockaddr *src_addr, socklen_t *addrlen)
{
	int err;

	do {
		err = recvfrom(fd, buf, size, flags, src_addr, addrlen);
	} while (err < 0 && errno == EAGAIN);

	return err;
}

int blocking_send(int fd, u8_t *buf, u32_t size, u32_t flags)
{
	int err;

	do {
		err = send(fd, buf, size, flags);
	} while (err < 0 && errno == EAGAIN);

	return err;
}

int blocking_connect(int fd, struct sockaddr *local_addr, socklen_t len)
{
	int err;

	do {
		err = connect(fd, local_addr, len);
	} while (err < 0 && errno == EAGAIN);

	return err;
}

void setup_psm(void)
{
	/*
	* GPRS Timer 3 value (octet 3)
	*
	* Bits 5 to 1 represent the binary coded timer value.
	*
	* Bits 6 to 8 defines the timer value unit for the GPRS timer as follows:
	* Bits 
	* 8 7 6
	* 0 0 0 value is incremented in multiples of 10 minutes 
	* 0 0 1 value is incremented in multiples of 1 hour 
	* 0 1 0 value is incremented in multiples of 10 hours
	* 0 1 1 value is incremented in multiples of 2 seconds
	* 1 0 0 value is incremented in multiples of 30 seconds
	* 1 0 1 value is incremented in multiples of 1 minute
	* 1 1 0 value is incremented in multiples of 320 hours (NOTE 1)
	* 1 1 1 value indicates that the timer is deactivated (NOTE 2).
	*/
	char psm_settings[] = CONFIG_LTE_PSM_REQ_RPTAU;
	printk("PSM bits: %c%c%c\n", psm_settings[0], psm_settings[1],
	       psm_settings[2]);
	printk("PSM Interval: %c%c%c%c%c\n", psm_settings[3], psm_settings[4],
	       psm_settings[5], psm_settings[6], psm_settings[7]);
	int err = lte_lc_psm_req(true);
	if (err < 0) {
		printk("Error setting PSM: %d Errno: %d\n", err, errno);
	}
}

void app_udp_start(void)
{
	struct addrinfo *res;
	socklen_t addrlen = sizeof(struct sockaddr_storage);

	int err = getaddrinfo(HOST, NULL, NULL, &res);
	if (err < 0) {
		printk("getaddrinfo err: %d\n\r", err);
		return;
	}
	((struct sockaddr_in *)res->ai_addr)->sin_port = htons(PORT);
	struct sockaddr_in local_addr;

	local_addr.sin_family = AF_INET;
	local_addr.sin_port = htons(0);
	local_addr.sin_addr.s_addr = 0;

	int client_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (client_fd < 0) {
		printk("client_fd: %d\n\r", client_fd);
		goto error;
	}

	err = bind(client_fd, (struct sockaddr *)&local_addr,
		   sizeof(local_addr));
	if (err < 0) {
		printk("bind err: %d errno: %d\n\r", err, errno);
		goto error;
	}

	err = connect(client_fd, (struct sockaddr *)res->ai_addr,
		      sizeof(struct sockaddr_in));
	if (err < 0) {
		printk("connect err: %d errno: %d\n\r", err, errno);
		goto error;
	}

	/* Just hard code the packet format */
	u8_t send_buf[sizeof(struct ntp_format)] = { 0xe3 };

	/*err = sendto(client_fd, send_buf, sizeof(send_buf), 0,
		     (struct sockaddr *)res->ai_addr, addrlen);*/
	err = send(client_fd, send_buf, sizeof(send_buf), 0);
	printk("sendto ret: %d\n\r", err);
	if (err < 0) {
		printk("sendto err: %d errno: %d\n\r", err, errno);
		goto error;
	}

	err = blocking_recvfrom(client_fd, recv_buf, sizeof(recv_buf), 0,
				(struct sockaddr *)res->ai_addr, &addrlen);
	if (err < 0) {
		printk("recvfrom err: %d errno: %d\n\r", err, errno);
		goto error;
	} else {
		printk("Got data back: ");
		for (int i = 0; i < err; i++) {
			printk("%x ", recv_buf[i]);
		}
		printk("\n");
	}

error:
	printk("Finished\n");
	(void)close(client_fd);
}

static volatile bool run_udp;

void app_timer_handler(struct k_timer *dummy)
{
	static u32_t minutes;

	minutes++;
	/* This shall match the PSM interval */
	if (minutes % 10 == 0) {
		run_udp = true;
	}
	printk("Elapsed time: %d\n", minutes);
}

K_TIMER_DEFINE(app_timer, app_timer_handler, NULL);

void timer_init(void)
{
	k_timer_start(&app_timer, K_MINUTES(1), K_MINUTES(1));
}

int main(void)
{
	printk("UDP test with PSM\n");
	app_udp_start();
	setup_psm();
	timer_init();
	while (1) {
		k_sleep(5000);
		if (run_udp == true) {
			printk("Run UDP\n");
			run_udp = false;
			app_udp_start();
		}
	}
	return 1;
}

And here is the prj.conf file:

CONFIG_NEWLIB_LIBC=n
CONFIG_BSD_LIBRARY=y
CONFIG_GPIO=n
CONFIG_SERIAL=n
CONFIG_STDOUT_CONSOLE=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NETWORKING=y
CONFIG_NET_BUF_USER_DATA_SIZE=1
CONFIG_NET_SOCKETS_OFFLOAD=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_NET_RAW_MODE=y
CONFIG_TRUSTED_EXECUTION_NONSECURE=y
CONFIG_LOG=n
CONFIG_LOG_DEFAULT_LEVEL=4
CONFIG_HEAP_MEM_POOL_SIZE=1024

# LTE link control
CONFIG_LTE_LINK_CONTROL=y
CONFIG_LTE_LEGACY_PCO_MODE=y
CONFIG_LTE_NETWORK_MODE_NBIOT=y

# Main thread
CONFIG_MAIN_THREAD_PRIORITY=7
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_LTE_PSM_REQ_RPTAU="10101010"
CONFIG_LTE_PSM_REQ_RAT="00100001"
CONFIG_AT_HOST_LIBRARY=n

Here is the whole project: 4721.udp_with_psm.zip

And here is a precompiled hex file you can use for testing: 7411.merged.hex

Measurements

Here's a measurement showing the lowest PSM idle current it is possible to achieve at the moment with the current modem FW (0.7.0-29.alpha), around 7 µA.

  • Have you managed to get it running with MCUBoot also?

  • According to 3GPP

    SIM may not be deactivated during the eDRX if SIM card is not allowing it.
    See

    3gpp 31.102 ch 4.2.18

    b4 is used to indicate whether the UICC polling interval to retrieve proactive commands can be modified (as described in TS 31.101 [11]) or weather the UICC interface can be deactivated (as described in clause 5.1.11) during extended DRX cycle.

    • b4=0: the ME is not authorized to modify the polling interval and/or disable the UICC interface during extended DRX cycle.
    • b4=1: the ME is authorized to modify the polling interval and/or disable the UICC interface during extended DRX cycle.

    For PSM these bytes will not effect.

  • I believe there is still small issue with the firmware and eDRX. Using the same SIM card with PSM and the above cycle (10m - 1m active), the device did stop the SIM card and the current dropped to ~8uA. However, with the same board and SIM card with an eDRX cycle of ~11m the current was about 30uA higher. Trying other SIM cards showed a range of readings some using ~50uA more than the PSM cycle.

  • Nice guide I tried this and it works perfectly with NB-IoT.  I also wanted to try this with LTE-M. I changed CONFIG_LTE_NETWORK_MODE_NBIOT=y to CONFIG_LTE_NETWORK_MODE_LTE_M=y. Now it doesn't seem to enter PSM but when enabling serial communication i can see that i send and receive data. Is there anything else i need to change to get PSM to work on LTE-M?

  • 35uA sounds like the SIM card. If the interval is too short the SIM card will not allow you to shut it down, because the lifetime will be greatly reduced. It depends on the SIM card really. Some SIM card will never shut down at all.