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.