nRF7002dk UDP Client Power Consumption

Dear Devzone-Team,

I am working on an nRF7002 based IoT Sensor using the nRF7002DK. I managed to set up the WiFi in STA mode and connect to Raspberry Pi working as AP. The nRF7002 should act as a UDP Client and send data to the AP.  I managed to set up the socket and the communication to the Raspberry is working. 

Now the problem:
When I look at the measurements with the Power Profiler Kit II there is too much power consumption in my opinion. Without UDP transmissions I have roughly the power consumption mentioned in Operating in power save modes and Target Wake Time on the nRF7002 DK for the normal DTIM Mode.
However, it seems that for the UDP transmissions it takes a long time until the sleep mode is activated again (~60mA for around 150ms). Please look at the attached figure from the PPK2, where you can see the UDP transmissions every 1s. The narrow spikes are the DTIM and the broader ones (~150ms) are when the UDP transmission is started.

Maybe you can help me to reduce the power of the UDP transmissions to the absolute minimum. (Maybe the nRF7002 waits for an response by the AP?)

Here is the main file:

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

/** @file
 * @brief WiFi station sample
 */

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sta, CONFIG_LOG_DEFAULT_LEVEL);


#include <nrfx_clock.h>
#include <zephyr/kernel.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/printk.h>
#include <zephyr/init.h>

#include <zephyr/net/net_if.h>
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/net/net_event.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/net/wifi.h>

#include <qspi_if.h>

#include <zephyr/net/socket.h>
#include <unistd.h> 

#include "net_private.h"

#define WIFI_SHELL_MODULE "wifi"

#define WIFI_SHELL_MGMT_EVENTS (NET_EVENT_WIFI_CONNECT_RESULT |		\
				NET_EVENT_WIFI_DISCONNECT_RESULT)

#define MAX_SSID_LEN        32
#define DHCP_TIMEOUT        70
#define CONNECTION_TIMEOUT  100
#define STATUS_POLLING_MS   300

/* 1000 msec = 1 sec */
#define LED_SLEEP_TIME_MS   100

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)

#define UDP_CLIENT_PORT 4242


// Tasks:

//! Wifi thread priority level
#define WIFI_STACK_SIZE 4096
//! Wifi thread priority level
#define WIFI_PRIORITY 4

//! WiFi stack definition
K_THREAD_STACK_DEFINE(WIFI_STACK, WIFI_STACK_SIZE);
//! Variable to identify the Wifi thread
static struct k_thread wifiThread;



#define SENSOR_DATA_LENGTH 1000

struct sensorData
{
	uint16_t frame_number;
	uint8_t data[SENSOR_DATA_LENGTH];
};

struct sensorData transmit_data;


void fillArrayWithRandom(uint8_t *array, uint16_t size) {
    for (uint16_t i = 0; i < size; i++) {
        array[i] = (uint8_t)rand(); // Fill each element with a random value
    }
}


/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

static struct net_mgmt_event_callback wifi_shell_mgmt_cb;
static struct net_mgmt_event_callback net_shell_mgmt_cb;

static struct {
	const struct shell *sh;
	union {
		struct {
			uint8_t connected	: 1;
			uint8_t connect_result	: 1;
			uint8_t disconnect_requested	: 1;
			uint8_t _unused		: 5;
		};
		uint8_t all;
	};
} context;

void toggle_led(void)
{
	int ret;

	if (!device_is_ready(led.port)) {
		LOG_ERR("LED device is not ready");
		return;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		LOG_ERR("Error %d: failed to configure LED pin", ret);
		return;
	}

	while (1) {
		if (context.connected) {
			gpio_pin_toggle_dt(&led);
			k_msleep(LED_SLEEP_TIME_MS);
		} else {
			gpio_pin_set_dt(&led, 0);
			k_msleep(LED_SLEEP_TIME_MS);
		}
	}
}

K_THREAD_DEFINE(led_thread_id, 1024, toggle_led, NULL, NULL, NULL,
		7, 0, 0);

static int cmd_wifi_status(void)
{
	struct net_if *iface = net_if_get_default();
	struct wifi_iface_status status = { 0 };

	if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, iface, &status,
				sizeof(struct wifi_iface_status))) {
		LOG_INF("Status request failed");

		return -ENOEXEC;
	}

	LOG_INF("==================");
	LOG_INF("State: %s", wifi_state_txt(status.state));

	if (status.state >= WIFI_STATE_ASSOCIATED) {
		uint8_t mac_string_buf[sizeof("xx:xx:xx:xx:xx:xx")];

		LOG_INF("Interface Mode: %s",
		       wifi_mode_txt(status.iface_mode));
		LOG_INF("Link Mode: %s",
		       wifi_link_mode_txt(status.link_mode));
		LOG_INF("SSID: %-32s", status.ssid);
		LOG_INF("BSSID: %s",
		       net_sprint_ll_addr_buf(
				status.bssid, WIFI_MAC_ADDR_LEN,
				mac_string_buf, sizeof(mac_string_buf)));
		LOG_INF("Band: %s", wifi_band_txt(status.band));
		LOG_INF("Channel: %d", status.channel);
		LOG_INF("Security: %s", wifi_security_txt(status.security));
		LOG_INF("MFP: %s", wifi_mfp_txt(status.mfp));
		LOG_INF("RSSI: %d", status.rssi);
	}
	return 0;
}

static void handle_wifi_connect_result(struct net_mgmt_event_callback *cb)
{
	const struct wifi_status *status =
		(const struct wifi_status *) cb->info;

	if (context.connected) {
		return;
	}

	if (status->status) {
		LOG_ERR("Connection failed (%d)", status->status);
	} else {
		LOG_INF("Connected");
		context.connected = true;
	}

	context.connect_result = true;
}

static void handle_wifi_disconnect_result(struct net_mgmt_event_callback *cb)
{
	const struct wifi_status *status =
		(const struct wifi_status *) cb->info;

	if (!context.connected) {
		return;
	}

	if (context.disconnect_requested) {
		LOG_INF("Disconnection request %s (%d)",
			 status->status ? "failed" : "done",
					status->status);
		context.disconnect_requested = false;
	} else {
		LOG_INF("Received Disconnected");
		context.connected = false;
	}

	cmd_wifi_status();
}

static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb,
				     uint32_t mgmt_event, struct net_if *iface)
{
	switch (mgmt_event) {
	case NET_EVENT_WIFI_CONNECT_RESULT:
		handle_wifi_connect_result(cb);
		break;
	case NET_EVENT_WIFI_DISCONNECT_RESULT:
		handle_wifi_disconnect_result(cb);
		break;
	default:
		break;
	}
}

static void print_dhcp_ip(struct net_mgmt_event_callback *cb)
{
	/* Get DHCP info from struct net_if_dhcpv4 and print */
	const struct net_if_dhcpv4 *dhcpv4 = cb->info;
	const struct in_addr *addr = &dhcpv4->requested_ip;
	char dhcp_info[128];

	net_addr_ntop(AF_INET, addr, dhcp_info, sizeof(dhcp_info));

	LOG_INF("DHCP IP address: %s", dhcp_info);
}
static void net_mgmt_event_handler(struct net_mgmt_event_callback *cb,
				    uint32_t mgmt_event, struct net_if *iface)
{
	switch (mgmt_event) {
	case NET_EVENT_IPV4_DHCP_BOUND:
		print_dhcp_ip(cb);
		break;
	default:
		break;
	}
}

static int __wifi_args_to_params(struct wifi_connect_req_params *params)
{
	params->timeout = SYS_FOREVER_MS;

	/* SSID */
	params->ssid = CONFIG_STA_SAMPLE_SSID;
	params->ssid_length = strlen(params->ssid);

#if defined(CONFIG_STA_KEY_MGMT_WPA2)
	params->security = 1;
#elif defined(CONFIG_STA_KEY_MGMT_WPA2_256)
	params->security = 2;
#elif defined(CONFIG_STA_KEY_MGMT_WPA3)
	params->security = 3;
#else
	params->security = 0;
#endif

#if !defined(CONFIG_STA_KEY_MGMT_NONE)
	params->psk = CONFIG_STA_SAMPLE_PASSWORD;
	params->psk_length = strlen(params->psk);
#endif
	params->channel = WIFI_CHANNEL_ANY;

	/* MFP (optional) */
	params->mfp = WIFI_MFP_OPTIONAL;

	return 0;
}

static int wifi_connect(void)
{
	struct net_if *iface = net_if_get_default();
	static struct wifi_connect_req_params cnx_params;

	context.connected = false;
	context.connect_result = false;
	__wifi_args_to_params(&cnx_params);


	if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
		     &cnx_params, sizeof(struct wifi_connect_req_params))) {
		LOG_ERR("Connection request failed");

		return -ENOEXEC;
	}

	LOG_INF("Connection requested");

	return 0;
}

int bytes_from_str(const char *str, uint8_t *bytes, size_t bytes_len)
{
	size_t i;
	char byte_str[3];

	if (strlen(str) != bytes_len * 2) {
		LOG_ERR("Invalid string length: %zu (expected: %d)\n",
			strlen(str), bytes_len * 2);
		return -EINVAL;
	}

	for (i = 0; i < bytes_len; i++) {
		memcpy(byte_str, str + i * 2, 2);
		byte_str[2] = '\0';
		bytes[i] = strtol(byte_str, NULL, 16);
	}

	return 0;
}



int Wifi_Stationing(void)
{
	int i;
	memset(&context, 0, sizeof(context));

	net_mgmt_init_event_callback(&wifi_shell_mgmt_cb, wifi_mgmt_event_handler,WIFI_SHELL_MGMT_EVENTS);

	net_mgmt_add_event_callback(&wifi_shell_mgmt_cb);


	net_mgmt_init_event_callback(&net_shell_mgmt_cb,net_mgmt_event_handler,NET_EVENT_IPV4_DHCP_BOUND);

	net_mgmt_add_event_callback(&net_shell_mgmt_cb);

	LOG_INF("Starting %s with CPU frequency: %d MHz", CONFIG_BOARD, SystemCoreClock/MHZ(1));
	k_sleep(K_SECONDS(1));


	LOG_INF("Static IP address (overridable): %s/%s -> %s",
		CONFIG_NET_CONFIG_MY_IPV4_ADDR,
		CONFIG_NET_CONFIG_MY_IPV4_NETMASK,
		CONFIG_NET_CONFIG_MY_IPV4_GW);

	while (1) {
		wifi_connect();

		for (i = 0; i < CONNECTION_TIMEOUT; i++) {
			k_sleep(K_MSEC(STATUS_POLLING_MS));
			cmd_wifi_status();
			if (context.connect_result) {
				break;
			}
		}
		if (context.connected) {
			k_sleep(K_FOREVER);
		} else if (!context.connect_result) {
			LOG_ERR("Connection Timed Out");
		}
	}

	return 0;
}

int udpClientSocket;
struct sockaddr_in serverAddress;
void UDP_Client(uint8_t* connected_flag) {

	
	int connectionResult;
	int sentBytes = 0;

	
	// Starve the thread until a DHCP IP is assigned to the board 
    while( !context.connected ){
		k_msleep( 100 );
	}
	// Server IPV4 address configuration 
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons( UDP_CLIENT_PORT );
    zsock_inet_pton( 																\
			AF_INET, 														\
			"192.168.1.1", 								\
			&serverAddress.sin_addr );

	// Client socket creation 
	udpClientSocket = zsock_socket( 												\
							serverAddress.sin_family, 						\
							SOCK_DGRAM, 									\
							IPPROTO_UDP );									\

	if ( udpClientSocket < 0 ) {
		LOG_ERR( "UDP Client error: socket: %d\n", errno );
		k_sleep( K_FOREVER );
	}




	// Connection to the server. 
	connectionResult = connect( 											\
							udpClientSocket, 								\
							( struct sockaddr * )&serverAddress, 			\
							sizeof( serverAddress ));

	if ( connectionResult < 0 ) {
		LOG_ERR( "UDP Client error: connect: %d\n", errno );
		k_sleep( K_FOREVER );
	}
	LOG_INF( "UDP Client connected correctly" );

	*connected_flag = 1;	

}


int main(void)
{

	// START THE WIFI CONNECTION THREAD
	k_thread_create	(											\
		&wifiThread,										    \
		WIFI_STACK,										        \
		WIFI_STACK_SIZE,									    \
		(k_thread_entry_t)Wifi_Stationing,						\
		NULL,													\
		NULL,													\
		NULL,													\
		WIFI_PRIORITY,									        \
		0,														\
		K_NO_WAIT);	

	 k_thread_name_set(&wifiThread, "wifiStationing");
	 k_thread_start(&wifiThread);

	while( !context.connected ){
		k_msleep( 1000 );
	}
	LOG_INF("Connection Worked");

	uint8_t client_connected = 0;
	UDP_Client(&client_connected);

	LOG_INF("wait until client is connected");
	while(client_connected == 0){
		k_msleep( 500 );
	}
	LOG_INF("Client is connected -> start transmitting data");

	// struct wifi_ps_params params = { 0 };
	// params.wakeup_mode = WIFI_PS_WAKEUP_MODE_LISTEN_INTERVAL;
	// params.type = WIFI_PS_PARAM_WAKEUP_MODE;
	// struct net_if *iface = net_if_get_default();
	// net_mgmt(NET_REQUEST_WIFI_PS, iface, &params, sizeof(params));

	uint32_t counter = 0;
	while(1){
		transmit_data.frame_number = counter;
		fillArrayWithRandom(transmit_data.data, SENSOR_DATA_LENGTH); 

		send(udpClientSocket, (uint8_t* ) &transmit_data, sizeof(transmit_data), 0);
		//sendto(udpClientSocket, (uint8_t* ) &transmit_data, sizeof(transmit_data), 0,( struct sockaddr * )&serverAddress, sizeof( serverAddress ));
		counter++;
		k_msleep( 1000 );
	}

}

and the prj.conf

#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_WIFI=y
CONFIG_WIFI_NRF700X=y

# WPA supplicant
CONFIG_WPA_SUPP=y

# Below configs need to be modified based on security
# CONFIG_STA_KEY_MGMT_NONE=y
CONFIG_STA_KEY_MGMT_WPA2=y
# CONFIG_STA_KEY_MGMT_WPA2_256=y
# CONFIG_STA_KEY_MGMT_WPA3=y
CONFIG_STA_SAMPLE_SSID="RSPILAN"
CONFIG_STA_SAMPLE_PASSWORD="silicon123"



# System settings
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_NANO=n

# Networking
CONFIG_NETWORKING=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_LOG=y
CONFIG_NET_IPV4=y
CONFIG_NET_UDP=y
CONFIG_NET_TCP=y
CONFIG_NET_DHCPV4=y

CONFIG_NET_PKT_RX_COUNT=8
CONFIG_NET_PKT_TX_COUNT=8

# Below section is the primary contributor to SRAM and is currently
# tuned for performance, but this will be revisited in the future.
CONFIG_NET_BUF_RX_COUNT=16
CONFIG_NET_BUF_TX_COUNT=16
CONFIG_NET_BUF_DATA_SIZE=128
CONFIG_HEAP_MEM_POOL_SIZE=153600
CONFIG_NET_TC_TX_COUNT=0

CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1
CONFIG_NET_MAX_CONTEXTS=5
CONFIG_NET_CONTEXT_SYNC_RECV=y

CONFIG_INIT_STACKS=y

CONFIG_NET_L2_ETHERNET=y

CONFIG_NET_CONFIG_SETTINGS=y

CONFIG_NET_SOCKETS_POLL_MAX=4

# Memories
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_NET_TX_STACK_SIZE=4096
CONFIG_NET_RX_STACK_SIZE=4096

# Debugging
CONFIG_STACK_SENTINEL=y
CONFIG_DEBUG_COREDUMP=y
CONFIG_DEBUG_COREDUMP_BACKEND_LOGGING=y
CONFIG_DEBUG_COREDUMP_MEMORY_DUMP_MIN=y
CONFIG_SHELL_CMDS_RESIZE=n


# Kernel options
CONFIG_ENTROPY_GENERATOR=y

# Logging
CONFIG_LOG=y
CONFIG_LOG_BUFFER_SIZE=2048
CONFIG_POSIX_CLOCK=y

CONFIG_PM=y

CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.99"
CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0"
CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1"

# printing of scan results puts pressure on queues in new locking
# design in net_mgmt. So, use a higher timeout for a crowded
# environment.
CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT=5000

CONFIG_NET_SOCKETS_POSIX_NAMES=y


CONFIG_POSIX_MAX_FDS=9 

I am using nrf connect sdk v2.4.1 currently.

Thanks in advance

Julian

  • Hi Julian,

    I have started looking into this and will get back to you later today.

    Best regards,
    Marte

  • Hi Julian,

    I have not been able to find a way to reduce the awake time for the UDP transmission, but I have asked internally.

    Another thing you do to reduce the power consumption is to use listen interval instead of DTIM as wakeup mode. You can set listen interval with the following:

    struct net_if *iface = net_if_get_default();
    
    struct wifi_ps_params params = {0};
    params.wakeup_mode = WIFI_PS_WAKEUP_MODE_LISTEN_INTERVAL;
    params.type = WIFI_PS_PARAM_WAKEUP_MODE;
    params.listen_interval = // your interval
    
    if (net_mgmt(NET_REQUEST_WIFI_PS, iface, &params, sizeof(params))) {
    	LOG_ERR("Setting wakeup mode failed. Reason %s",
    		wifi_ps_get_config_err_code_str(params.fail_reason));
    	return -1;
    }

    Best regards,
    Marte

  • Hey Marte,
    Thank you for looking into the topic.

    Also, thanks for the code to define the wakeup mode, however, this I already tried. Similar lines can also be found in the main() of the code I provided you (although they are commented out).
    Sadly as you might guess the DTIM is not the problem here, especially since I want to increase the UDP transmission rate.

    For me, it looks like the nrf7001 goes into receive mode after the UDP transmission, but I cannot verify this. Maybe this can be deactivated somehow?
    The ~150ms after the UDP transmissions are simply too long - the actual communication should be way quicker. I also reduced the amount of data but this does not affect this time interval.

    This might be also interesting: If you look at the figure I provided, the "active time" is not the same every time. It varies sometimes strongly between the UDP transmissions.

    I hope we can find a solution to this problem to make the nRF70 a low-power beast.

    best
    Julian

  • One small but maybe important addition

    I now used an external WiFi card in monitor mode to see the actual communication. Here is one snippet of wireshark:


    - The duration of the UDP packets is around 260us (NRF MAC: F4:CE....) which is in no relation to the 150ms of high power consumption. It seems that the amount of data needs 3 UDP packets for one send() command 
    - The AP (MAC: E4:5F....) directly block acknowledges in nearly no time 

    But now comes the interesting part:
    around 100ms after the last UDP the nRF70  always sends a NULL Function packet (see image).
    Google says: Null Function (Null data)- 
    Notifies the AP to change the power save status by updating the Power Management bit

    It seems like the nRF70 is waiting some time until it decides to sleep and sends this NULL Function message to the AP. It also send a Null Function packet to the AP after waking up. Maybe this helps you find a solution.

    br 
    Julian

  • Hi Julian,

    I agree, it seems like the reason for the long awake time is that the nRF70 waits some time before sending the NULL function to the AP. I have asked the development team if there is a way to decrease the time before the nRF70 sends this packet or if there are any other ways to decrease the total time. I will let you know when I have an update.

    Best regards,
    Marte

Related