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 an update already Slight smile

    The default power save timeout is 100ms, so the behavior you are seeing is expected. This power save timeout can be changed by sending a network management request.

    Here is a code snippet for setting the timeout. Change timeout_ms to the power save timeout you want.

    struct net_if *iface = net_if_get_first_wifi();
    struct wifi_ps_params params = { 0 };
    long timeout_ms = 100;
    
    params.timeout_ms = timeout_ms;
    params.type = WIFI_PS_PARAM_TIMEOUT;
    
    if(net_mgmt(NET_REQUEST_WIFI_PS, iface, &params, sizeof(params))) {
    	LOG_ERR("Setting PS timeout failed. Reason: %s", wifi_ps_get_config_err_code_str(params.fail_reason));
    }

    If you have shell enabled you can also configure this with the shell command wifi ps_timeout <timeout>.

    Best regards,
    Marte

  • Hello Marte,

    Thank you for your answer and sorry for the late response.

    I tried your fix and added the following lines:

    struct wifi_ps_params params = { 0 };
    struct net_if *iface = net_if_get_default();
    long timeout_ms = 0;
    params.timeout_ms = timeout_ms;
    params.type = WIFI_PS_PARAM_TIMEOUT;
    net_mgmt(NET_REQUEST_WIFI_PS, iface, &params, sizeof(params));


    and it seems to sometimes reduce the uptime - but not every time.
    I attached again a picture from the PPK2:


    We can see that for the marked UDP transmission, the up-time is reduced to around 20ms. However, in half of the cases, it seems that the module goes to sleep, and then wakes up (my guess would be to send the NULL-Function because there is a single transmission in the beginning) to then go to sleep again.
    So there might be still a problem somewhere.

    I would have still some open questions:

    1. Is there any way to prevent the module from sending these Null functions or to do it immediately 

    2. Even in the cases where it worked, the 20ms up-time is still very high if compared for example to the DTIM messages. It would be nice if the module only is in receiving when there is something planned to receive.

    3. In the UDP example for the nrf91 there is something called RAI to tell the module that there are no further messages - is there something similar for the nrf70

    4. I found another timeout parameter for the kconfig but I have no idea what it does:

    config NRF700X_RPU_PS_IDLE_TIMEOUT_MS
    	int "RPU power save idle timeout in milliseconds"
    	default 10

    Thank you again for your time and I hope we can find a solution for this. Really Low-power UDP transmissions would be a nice feature.

    br
    Julian

  • Hi Julian,

    Are you able to capture a sniffer log of this?

    JulianKaroliny said:
    1. Is there any way to prevent the module from sending these Null functions or to do it immediately 

    It should be disabled if you set the timeout to 0. I just tested with the Wi-Fi Shell sample using wifi ps_timeout 0 and net udp send. With timeout disabled I did not see any Null functions in the wireshark capture like I did when timeout was enabled.

    JulianKaroliny said:
    2. Even in the cases where it worked, the 20ms up-time is still very high if compared for example to the DTIM messages. It would be nice if the module only is in receiving when there is something planned to receive.

    The device receives MAC layer ACK after sending the UDP message. In addition to this, the device also sends a Power-Save poll to retrieve any buffered packets from the AP since it is awake. This is based on the 802.11 specification.

    JulianKaroliny said:
    3. In the UDP example for the nrf91 there is something called RAI to tell the module that there are no further messages - is there something similar for the nrf70

    RAI is only supported on the nRF91.

    JulianKaroliny said:
    4. I found another timeout parameter for the kconfig but I have no idea what it does:

    I will check this internally and get back to you.

    Best regards,
    Marte

  • Hey Marte,

    thanks again for your answer and for being patient with me :-D

    I set up the wifi shell and you are right, the nrf70 stops sending Null functions for wifi ps_timeout 0

    However, I am still confused by the results of my measurements:
    Setup: Wifi shell, network card in monitor mode, PPK2, wifi ps_timeout 0

    I checked what exactly happens for the UDP transmission so I did
    "net udp send 192.168.1.1 4242 hallo"

    On the network sniffer, I get exactly the one UDP transmission + Block ACK
    This can be seen also on the PPK measurements but after a short time in sleep, it wakes up again. For the second wakeup, I cannot sniff any corresponding packet sent to or from the nrf70.

    Here is the output of the PPK2:


    The marked one is the UDP transmission with the ACK. The one directly after I have no idea.

    (Mind I don't see this "2 Blocks" every time, but often enough)

     

    i don't know If I am doing something wrong :-D

    best
    Julian

  • Hi Julian,

    I will test this and get back to you tomorrow.

    Best regards,
    Marte

Related