Dynamic BLE TX Power Config Fails (nRF5340-DK)

Hello everyone,

I’m working on a project using two nRF5340-DK boards to measure throughput and RSSI while dynamically changing the PHY and TX power. I started from the official throughput example and added:

  • RSSI measurement (I think this works)

  • A shell command config pwr <value in dBm> to adjust the transmit power, implemented similarly to the PHY-change command

The problem is that power adjustment only works at the default 0 dBm. If I try any other value, the command runs but returns an error, so it looks like my implementation isn’t actually applying the new power setting.

When I set the transmit power to anything other than 0 dBm, I get this output:

Here’s what I’ve changed (all code is available on GitHub):

  1. cmds.c

    • Added a default TX power of 0 dBm

    • Registered the config pwr subcommand in SHELL_STATIC_SUBCMD_SET_CREATE

    • Implemented the pwr_cmd function to parse and store the dBm value

  2. main.h / main.c

    • Declared extern int8_t g_tx_power; in main.h and defined it in main.c

    • Modified connection_configuration_set() to apply g_tx_power to the BLE connection

You can find the full code here:
https://github.com/diegonovo02/prueba_ble

Does anyone know why only 0 dBm works, and how I can get other power levels to apply correctly? Any pointers or fixes would be hugely appreciated!

Thanks in advance,
Diego

Parents
  • Hi!

    Try this main.c

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/console/console.h>
    #include <zephyr/sys/printk.h>
    #include <string.h>
    #include <stdlib.h>
    #include <zephyr/types.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/crypto.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <bluetooth/services/throughput.h>
    #include <bluetooth/scan.h>
    #include <bluetooth/gatt_dm.h>
    
    #include <zephyr/shell/shell_uart.h>
    
    #include <dk_buttons_and_leds.h>
    
    #include <zephyr/net/buf.h>
    #include <zephyr/sys/byteorder.h>
    #include <zephyr/bluetooth/hci_vs.h>
    
    #include "main.h"
    
    int8_t g_tx_power = 0;
    
    #define DEVICE_NAME	CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    #define INTERVAL_MIN	0x6	/* 6 units, 7.5 ms, only used to setup connection */
    #define INTERVAL_MAX	0x6	/* 6 units, 7.5 ms, only used to setup connection */
    
    #define THROUGHPUT_CONFIG_TIMEOUT K_SECONDS(20)
    
    static K_SEM_DEFINE(throughput_sem, 0, 1);
    
    static volatile bool data_length_req;
    static volatile bool test_ready;
    static struct bt_conn *default_conn;
    static uint16_t default_conn_handle;
    static struct bt_throughput throughput;
    static const struct bt_uuid *uuid128 = BT_UUID_THROUGHPUT;
    static struct bt_gatt_exchange_params exchange_params;
    static struct bt_le_conn_param *conn_param =
    	BT_LE_CONN_PARAM(INTERVAL_MIN, INTERVAL_MAX, 0, 400);
    
    static const struct bt_data ad[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    	BT_DATA_BYTES(BT_DATA_UUID128_ALL,
    		0xBB, 0x4A, 0xFF, 0x4F, 0xAD, 0x03, 0x41, 0x5D,
    		0xA9, 0x6C, 0x9D, 0x6C, 0xDD, 0xDA, 0x83, 0x04),
    };
    
    static const struct bt_data sd[] = {
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static void button_handler_cb(uint32_t button_state, uint32_t has_changed);
    
    static const char *phy2str(uint8_t phy)
    {
    	switch (phy) {
    	case 0: return "No packets";
    	case BT_GAP_LE_PHY_1M: return "LE 1M";
    	case BT_GAP_LE_PHY_2M: return "LE 2M";
    	case BT_GAP_LE_PHY_CODED: return "LE Coded";
    	default: return "Unknown";
    	}
    }
    
    static void instruction_print(void)
    {
    	printk("\nType 'config' to change the configuration parameters.\n");
    	printk("You can use the Tab key to autocomplete your input.\n");
    	printk("Type 'run' when you are ready to run the test.\n");
    }
    
    static int read_conn_rssi_val(uint16_t handle, int8_t *rssi_out)
    {
        struct net_buf *cmd, *rsp;
        struct bt_hci_cp_read_rssi *cp;
        struct bt_hci_rp_read_rssi *rp;
        int err;
    
        cmd = bt_hci_cmd_create(BT_HCI_OP_READ_RSSI, sizeof(*cp));
        if (!cmd) {
            printk("ERR: HCI cmd buffer\n");
            return -ENOMEM;
        }
    
        cp = net_buf_add(cmd, sizeof(*cp));
        cp->handle = sys_cpu_to_le16(handle);
    
        err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, cmd, &rsp);
        if (err) {
            printk("ERR: HCI_READ_RSSI %d\n", err);
            return err;
        }
    
        rp = (struct bt_hci_rp_read_rssi *)rsp->data;
        *rssi_out = rp->rssi;
        net_buf_unref(rsp);
    
        return 0;
    }
    
    void scan_filter_match(struct bt_scan_device_info *device_info,
    		       struct bt_scan_filter_match *filter_match,
    		       bool connectable)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
    
    	printk("Filters matched. Address: %s connectable: %d\n",
    		addr, connectable);
    }
    
    void scan_filter_no_match(struct bt_scan_device_info *device_info,
    			  bool connectable)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
    
    	printk("Filter not match. Address: %s connectable: %d\n",
    				addr, connectable);
    }
    
    void scan_connecting_error(struct bt_scan_device_info *device_info)
    {
    	printk("Connecting failed\n");
    }
    
    BT_SCAN_CB_INIT(scan_cb, scan_filter_match, scan_filter_no_match,
    		scan_connecting_error, NULL);
    
    static void exchange_func(struct bt_conn *conn, uint8_t att_err,
    			  struct bt_gatt_exchange_params *params)
    {
    	struct bt_conn_info info = {0};
    	int err;
    
    	printk("MTU exchange %s\n", att_err == 0 ? "successful" : "failed");
    
    	err = bt_conn_get_info(conn, &info);
    	if (err) {
    		printk("Failed to get connection info %d\n", err);
    		return;
    	}
    
    	if (info.role == BT_CONN_ROLE_CENTRAL) {
    		instruction_print();
    		test_ready = true;
    	}
    }
    
    static void discovery_complete(struct bt_gatt_dm *dm,
    			       void *context)
    {
    	int err;
    	struct bt_throughput *throughput = context;
    
    	printk("Service discovery completed\n");
    
    	bt_gatt_dm_data_print(dm);
    	bt_throughput_handles_assign(dm, throughput);
    	bt_gatt_dm_data_release(dm);
    
    	exchange_params.func = exchange_func;
    
    	err = bt_gatt_exchange_mtu(default_conn, &exchange_params);
    	if (err) {
    		printk("MTU exchange failed (err %d)\n", err);
    	} else {
    		printk("MTU exchange pending\n");
    	}
    }
    
    static void discovery_service_not_found(struct bt_conn *conn,
    					void *context)
    {
    	printk("Service not found\n");
    }
    
    static void discovery_error(struct bt_conn *conn,
    			    int err,
    			    void *context)
    {
    	printk("Error while discovering GATT database: (%d)\n", err);
    }
    
    struct bt_gatt_dm_cb discovery_cb = {
    	.completed         = discovery_complete,
    	.service_not_found = discovery_service_not_found,
    	.error_found       = discovery_error,
    };
    
    static void connected(struct bt_conn *conn, uint8_t hci_err)
    {
    	struct bt_conn_info info = {0};
    	int err;
    
    	if (hci_err) {
    		if (hci_err == BT_HCI_ERR_UNKNOWN_CONN_ID) {
    			/* Canceled creating connection */
    			return;
    		}
    
    		printk("Connection failed, err 0x%02x %s\n", hci_err, bt_hci_err_to_str(hci_err));
    		return;
    	}
    
    	if (default_conn) {
    		printk("Connection exists, disconnect second connection\n");
    		bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
    		return;
    	}
    
    	default_conn = bt_conn_ref(conn);
    
    	err = bt_conn_get_info(default_conn, &info);
    	if (err) {
    		printk("Failed to get connection info %d\n", err);
    		return;
    	}
    
    	printk("Connected as %s\n",
    	       info.role == BT_CONN_ROLE_CENTRAL ? "central" : "peripheral");
    	printk("Conn. interval is %u units\n", info.le.interval);
    
    	if (info.role == BT_CONN_ROLE_PERIPHERAL) {
    		err = bt_conn_set_security(conn, BT_SECURITY_L2);
    		if (err) {
    			printk("Failed to set security: %d\n", err);
    		}
    	}
    }
    
    void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err security_err)
    {
    	printk("Security changed: level %i, err: %i %s\n", level, security_err,
    	       bt_security_err_to_str(security_err));
    
    	if (security_err != 0) {
    		printk("Failed to encrypt link\n");
    		bt_conn_disconnect(conn, BT_HCI_ERR_PAIRING_NOT_SUPPORTED);
    		return;
    	}
    
    	struct bt_conn_info info = {0};
    	int err;
    
    	err = bt_conn_get_info(default_conn, &info);
    	if (info.role == BT_CONN_ROLE_CENTRAL) {
    		err = bt_gatt_dm_start(default_conn,
    				       BT_UUID_THROUGHPUT,
    				       &discovery_cb,
    				       &throughput);
    		if (err) {
    			printk("Discover failed (err %d)\n", err);
    		}
    	}
    }
    
    static void scan_init(void)
    {
    	int err;
    	struct bt_le_scan_param scan_param = {
    		.type = BT_LE_SCAN_TYPE_PASSIVE,
    		.options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
    		.interval = 0x0010,
    		.window = 0x0010,
    	};
    
    	struct bt_scan_init_param scan_init = {
    		.connect_if_match = 1,
    		.scan_param = &scan_param,
    		.conn_param = conn_param
    	};
    
    	bt_scan_init(&scan_init);
    	bt_scan_cb_register(&scan_cb);
    
    	err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, uuid128);
    	if (err) {
    		printk("Scanning filters cannot be set\n");
    
    		return;
    	}
    
    	err = bt_scan_filter_enable(BT_SCAN_UUID_FILTER, false);
    	if (err) {
    		printk("Filters cannot be turned on\n");
    	}
    }
    
    static void scan_start(void)
    {
    	int err;
    
    	err = bt_scan_start(BT_SCAN_TYPE_SCAN_PASSIVE);
    	if (err) {
    		printk("Starting scanning failed (err %d)\n", err);
    		return;
    	}
    }
    
    static void adv_start(void)
    {
    	const struct bt_le_adv_param *adv_param =
    		BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE |
    				BT_LE_ADV_OPT_ONE_TIME,
    				BT_GAP_ADV_FAST_INT_MIN_2,
    				BT_GAP_ADV_FAST_INT_MAX_2,
    				NULL);
    	int err;
    
    	err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd,
    			      ARRAY_SIZE(sd));
    	if (err) {
    		printk("Failed to start advertiser (%d)\n", err);
    		return;
    	}
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	struct bt_conn_info info = {0};
    	int err;
    
    	printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
    
    	test_ready = false;
    	if (default_conn) {
    		bt_conn_unref(default_conn);
    		default_conn = NULL;
    	}
    
    	err = bt_conn_get_info(conn, &info);
    	if (err) {
    		printk("Failed to get connection info (%d)\n", err);
    		return;
    	}
    
    	/* Re-connect using same roles */
    	if (info.role == BT_CONN_ROLE_CENTRAL) {
    		scan_start();
    	} else {
    		adv_start();
    	}
    }
    
    static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
    {
    	printk("Connection parameters update request received.\n");
    	printk("Minimum interval: %d, Maximum interval: %d\n",
    	       param->interval_min, param->interval_max);
    	printk("Latency: %d, Timeout: %d\n", param->latency, param->timeout);
    
    	return true;
    }
    
    static void le_param_updated(struct bt_conn *conn, uint16_t interval,
    			     uint16_t latency, uint16_t timeout)
    {
    	printk("Connection parameters updated.\n"
    	       " interval: %d, latency: %d, timeout: %d\n",
    	       interval, latency, timeout);
    
    	k_sem_give(&throughput_sem);
    }
    
    static void le_phy_updated(struct bt_conn *conn,
    			   struct bt_conn_le_phy_info *param)
    {
    	printk("LE PHY updated: TX PHY %s, RX PHY %s\n",
    	       phy2str(param->tx_phy), phy2str(param->rx_phy));
    
    	k_sem_give(&throughput_sem);
    }
    
    static void le_data_length_updated(struct bt_conn *conn,
    				   struct bt_conn_le_data_len_info *info)
    {
    	if (!data_length_req) {
    		return;
    	}
    
    	printk("LE data len updated: TX (len: %d time: %d)"
    	       " RX (len: %d time: %d)\n", info->tx_max_len,
    	       info->tx_max_time, info->rx_max_len, info->rx_max_time);
    
    	data_length_req = false;
    	k_sem_give(&throughput_sem);
    }
    
    static uint8_t throughput_read(const struct bt_throughput_metrics *met)
    {
    	printk("[peer] received %u bytes (%u KB)"
    	       " in %u GATT writes at %u bps\n",
    	       met->write_len, met->write_len / 1024, met->write_count,
    	       met->write_rate);
    
    	k_sem_give(&throughput_sem);
    
    	return BT_GATT_ITER_STOP;
    }
    
    static void throughput_received(const struct bt_throughput_metrics *met)
    {
    	static uint32_t kb;
    
    	if (met->write_len == 0) {
    		kb = 0;
    		printk("\n");
    
    		return;
    	}
    
    	if ((met->write_len / 1024) != kb) {
    		kb = (met->write_len / 1024);
    		printk("=");
    	}
    }
    
    static void throughput_send(const struct bt_throughput_metrics *met)
    {
    	printk("\n[local] received %u bytes (%u KB)"
    		" in %u GATT writes at %u bps\n",
    		met->write_len, met->write_len / 1024,
    		met->write_count, met->write_rate);
    }
    
    static const struct bt_throughput_cb throughput_cb = {
    	.data_read = throughput_read,
    	.data_received = throughput_received,
    	.data_send = throughput_send
    };
    
    static struct button_handler button = {
    	.cb = button_handler_cb,
    };
    
    void select_role(bool is_central)
    {
    	int err;
    	static bool role_selected;
    
    	if (role_selected) {
    		printk("\nCannot change role after it was selected once.\n");
    		return;
    	} else if (is_central) {
    		printk("\nCentral. Starting scanning\n");
    		scan_start();
    	} else {
    		printk("\nPeripheral. Starting advertising\n");
    		adv_start();
    	}
    
    	role_selected = true;
    
    	/* The role has been selected, button are not needed any more. */
    	err = dk_button_handler_remove(&button);
    	if (err) {
    		printk("Button disable error: %d\n", err);
    	}
    }
    
    static void button_handler_cb(uint32_t button_state, uint32_t has_changed)
    {
    	ARG_UNUSED(has_changed);
    
    	if (button_state & DK_BTN1_MSK) {
    		select_role(true);
    	} else if (button_state & DK_BTN2_MSK) {
    		select_role(false);
    	}
    }
    
    static void buttons_init(void)
    {
    	int err;
    
    	err = dk_buttons_init(NULL);
    	if (err) {
    		printk("Buttons initialization failed.\n");
    		return;
    	}
    
    	/* Add dynamic buttons handler. Buttons should be activated only when
    	 * during the board role choosing.
    	 */
    	dk_button_handler_add(&button);
    }
    
    
    
    static int connection_configuration_set(const struct shell *shell,
    			const struct bt_le_conn_param *conn_param,
    			const struct bt_conn_le_phy_param *phy,
    			const struct bt_conn_le_data_len_param *data_len)
    {
    	int err;
    	struct bt_conn_info info = {0};
    
    	err = bt_conn_get_info(default_conn, &info);
    	if (err) {
    		shell_error(shell, "Failed to get connection info %d", err);
    		return err;
    	}
    
    	if (info.role != BT_CONN_ROLE_CENTRAL) {
    		shell_error(shell,
    		"'run' command shall be executed only on the central board");
    	}
    
    	err = bt_conn_le_phy_update(default_conn, phy);
    	if (err) {
    		shell_error(shell, "PHY update failed: %d\n", err);
    		return err;
    	}
    
    	shell_print(shell, "PHY update pending");
    	err = k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    	if (err) {
    		shell_error(shell, "PHY update timeout");
    		return err;
    	}
    
    	if (info.le.interval != conn_param->interval_max) {
    		err = bt_conn_le_param_update(default_conn, conn_param);
    		if (err) {
    			shell_error(shell,
    				    "Connection parameters update failed: %d",
    				    err);
    			return err;
    		}
    
    		shell_print(shell, "Connection parameters update pending");
    		err = k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    		if (err) {
    			shell_error(shell,
    				    "Connection parameters update timeout");
    			return err;
    		}
    	}
    
    	if (info.le.data_len->tx_max_len != data_len->tx_max_len) {
    		data_length_req = true;
    
    		err = bt_conn_le_data_len_update(default_conn, data_len);
    		if (err) {
    			shell_error(shell, "LE data length update failed: %d",
    				    err);
    			return err;
    		}
    
    		shell_print(shell, "LE Data length update pending");
    		err = k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    		if (err) {
    			shell_error(shell, "LE Data Length update timeout");
    			return err;
    		}
    	}
    
    	if (g_tx_power != 0) {
    
    
    		    shell_print(shell, "Setting TX power to : %d dBm",
                    g_tx_power);
    
        struct net_buf *buf, *rsp = NULL;
        struct bt_hci_cp_vs_write_tx_power_level *cp;
        struct bt_hci_rp_vs_write_tx_power_level *rp;
        int err;
    
        buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
                                sizeof(*cp));
        if (!buf) {
            shell_error(shell, "No HCI buffer");
            return -ENOMEM;
        }
    
    	err = bt_hci_get_conn_handle(default_conn,
    						&default_conn_handle);
    	if (err) {
    		shell_print(shell,"No connection handle (err %d)", err);
    		return err;
    	} 
    
        cp = net_buf_add(buf, sizeof(*cp));
        cp->handle_type    = BT_HCI_VS_LL_HANDLE_TYPE_CONN;
        cp->handle         = sys_cpu_to_le16(default_conn_handle);
        cp->tx_power_level = g_tx_power;
    
        err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
                                   buf, &rsp);
        if (err) {
            shell_error(shell, "Error setting TX power: %d", err);
            return err;
        }
    
        rp = (void *)rsp->data;
        shell_print(shell, "Actual TX power: %d dBm",
                    rp->selected_tx_power);
    
        net_buf_unref(rsp);
    }
    
    	return 0;
    }
    
    int test_run(const struct shell *shell,
    	     const struct bt_le_conn_param *conn_param,
    	     const struct bt_conn_le_phy_param *phy,
    	     const struct bt_conn_le_data_len_param *data_len)
    {
    	int err;
    	uint64_t stamp;
    	int64_t delta;
    	uint32_t data = 0;
    	int64_t  rssi_sum   = 0;
        int      rssi_count = 0;
    
    	/* a dummy data buffer */
    	static char dummy[495];
    
    	if (!default_conn) {
    		shell_error(shell, "Device is disconnected %s",
    			    "Connect to the peer device before running test");
    		return -EFAULT;
    	}
    
    	if (!test_ready) {
    		shell_error(shell, "Device is not ready."
    			"Please wait for the service discovery and MTU exchange end");
    		return 0;
    	}
    
    	shell_print(shell, "\n==== Starting throughput test ====");
    
    	err = connection_configuration_set(shell, conn_param, phy, data_len);
    	if (err) {
    		return err;
    	}
    
    	shell_print(shell, "The test is in progress and will require around %d seconds "
    		"to complete.", CONFIG_BT_THROUGHPUT_DURATION / 1000);
    
    	/* Make sure that all BLE procedures are finished. */
    	k_sleep(K_MSEC(500));
    
    	/* reset peer metrics */
    	err = bt_throughput_write(&throughput, dummy, 1);
    	if (err) {
    		shell_error(shell, "Reset peer metrics failed.");
    		return err;
    	}
    
    	/* get cycle stamp */
    	stamp = k_uptime_get_32();
    
    	while (true) {
    		err = bt_throughput_write(&throughput, dummy, 495);
    		if (err) {
    			shell_error(shell, "GATT write failed (err %d)", err);
    			break;
    		}
    
    		{
                int8_t rssi;
                if (read_conn_rssi_val(bt_conn_index(default_conn), &rssi) == 0) {
                    rssi_sum   += rssi;
                    rssi_count += 1;
                }
            }
    
    		data += 495;
    		if (k_uptime_get_32() - stamp > CONFIG_BT_THROUGHPUT_DURATION) {
    			break;
    		}
    	}
    
    	delta = k_uptime_delta(&stamp);
    
    	printk("\nDone\n");
    	printk("[local] sent %u bytes (%u KB) in %lld ms at %llu kbps\n",
    	       data, data / 1024, delta, ((uint64_t)data * 8 / delta));
    
    	if (rssi_count > 0) {
            int8_t avg_rssi = rssi_sum / rssi_count;
            printk("Average RSSI: %d dBm over %d samples\n",
                   avg_rssi, rssi_count);
        } else {
            printk("No RSSI samples collected\n");
        }
    
    	/* read back char from peer */
    	err = bt_throughput_read(&throughput);
    	if (err) {
    		shell_error(shell, "GATT read failed (err %d)", err);
    		return err;
    	}
    
    	k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    
    	instruction_print();
    
    	return 0;
    }
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    	.le_param_req = le_param_req,
    	.le_param_updated = le_param_updated,
    	.le_phy_updated = le_phy_updated,
    	.le_data_len_updated = le_data_length_updated,
    	.security_changed = security_changed
    };
    
    int main(void)
    {
    	int err;
    
    	printk("Starting Bluetooth Throughput example\n");
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return 0;
    	}
    
    	printk("Bluetooth initialized\n");
    
    	scan_init();
    
    	err = bt_throughput_init(&throughput, &throughput_cb);
    	if (err) {
    		printk("Throughput service initialization failed.\n");
    		return 0;
    	}
    
    	printk("\n");
    
    	if (IS_ENABLED(CONFIG_SOC_SERIES_NRF54HX) || IS_ENABLED(CONFIG_SOC_SERIES_NRF54LX)) {
    		printk("Press button 0 or type \"central\" on the central board.\n");
    		printk("Press button 1 or type \"peripheral\" on the peripheral board.\n");
    	} else {
    		printk("Press button 1 or type \"central\" on the central board.\n");
    		printk("Press button 2 or type \"peripheral\" on the peripheral board.\n");
    	}
    
    	buttons_init();
    
    	return 0;
    }
    

    PS: Max tx-power on nRF5340 is 3 dbm.

Reply
  • Hi!

    Try this main.c

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/console/console.h>
    #include <zephyr/sys/printk.h>
    #include <string.h>
    #include <stdlib.h>
    #include <zephyr/types.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/crypto.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <bluetooth/services/throughput.h>
    #include <bluetooth/scan.h>
    #include <bluetooth/gatt_dm.h>
    
    #include <zephyr/shell/shell_uart.h>
    
    #include <dk_buttons_and_leds.h>
    
    #include <zephyr/net/buf.h>
    #include <zephyr/sys/byteorder.h>
    #include <zephyr/bluetooth/hci_vs.h>
    
    #include "main.h"
    
    int8_t g_tx_power = 0;
    
    #define DEVICE_NAME	CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    #define INTERVAL_MIN	0x6	/* 6 units, 7.5 ms, only used to setup connection */
    #define INTERVAL_MAX	0x6	/* 6 units, 7.5 ms, only used to setup connection */
    
    #define THROUGHPUT_CONFIG_TIMEOUT K_SECONDS(20)
    
    static K_SEM_DEFINE(throughput_sem, 0, 1);
    
    static volatile bool data_length_req;
    static volatile bool test_ready;
    static struct bt_conn *default_conn;
    static uint16_t default_conn_handle;
    static struct bt_throughput throughput;
    static const struct bt_uuid *uuid128 = BT_UUID_THROUGHPUT;
    static struct bt_gatt_exchange_params exchange_params;
    static struct bt_le_conn_param *conn_param =
    	BT_LE_CONN_PARAM(INTERVAL_MIN, INTERVAL_MAX, 0, 400);
    
    static const struct bt_data ad[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    	BT_DATA_BYTES(BT_DATA_UUID128_ALL,
    		0xBB, 0x4A, 0xFF, 0x4F, 0xAD, 0x03, 0x41, 0x5D,
    		0xA9, 0x6C, 0x9D, 0x6C, 0xDD, 0xDA, 0x83, 0x04),
    };
    
    static const struct bt_data sd[] = {
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static void button_handler_cb(uint32_t button_state, uint32_t has_changed);
    
    static const char *phy2str(uint8_t phy)
    {
    	switch (phy) {
    	case 0: return "No packets";
    	case BT_GAP_LE_PHY_1M: return "LE 1M";
    	case BT_GAP_LE_PHY_2M: return "LE 2M";
    	case BT_GAP_LE_PHY_CODED: return "LE Coded";
    	default: return "Unknown";
    	}
    }
    
    static void instruction_print(void)
    {
    	printk("\nType 'config' to change the configuration parameters.\n");
    	printk("You can use the Tab key to autocomplete your input.\n");
    	printk("Type 'run' when you are ready to run the test.\n");
    }
    
    static int read_conn_rssi_val(uint16_t handle, int8_t *rssi_out)
    {
        struct net_buf *cmd, *rsp;
        struct bt_hci_cp_read_rssi *cp;
        struct bt_hci_rp_read_rssi *rp;
        int err;
    
        cmd = bt_hci_cmd_create(BT_HCI_OP_READ_RSSI, sizeof(*cp));
        if (!cmd) {
            printk("ERR: HCI cmd buffer\n");
            return -ENOMEM;
        }
    
        cp = net_buf_add(cmd, sizeof(*cp));
        cp->handle = sys_cpu_to_le16(handle);
    
        err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, cmd, &rsp);
        if (err) {
            printk("ERR: HCI_READ_RSSI %d\n", err);
            return err;
        }
    
        rp = (struct bt_hci_rp_read_rssi *)rsp->data;
        *rssi_out = rp->rssi;
        net_buf_unref(rsp);
    
        return 0;
    }
    
    void scan_filter_match(struct bt_scan_device_info *device_info,
    		       struct bt_scan_filter_match *filter_match,
    		       bool connectable)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
    
    	printk("Filters matched. Address: %s connectable: %d\n",
    		addr, connectable);
    }
    
    void scan_filter_no_match(struct bt_scan_device_info *device_info,
    			  bool connectable)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
    
    	printk("Filter not match. Address: %s connectable: %d\n",
    				addr, connectable);
    }
    
    void scan_connecting_error(struct bt_scan_device_info *device_info)
    {
    	printk("Connecting failed\n");
    }
    
    BT_SCAN_CB_INIT(scan_cb, scan_filter_match, scan_filter_no_match,
    		scan_connecting_error, NULL);
    
    static void exchange_func(struct bt_conn *conn, uint8_t att_err,
    			  struct bt_gatt_exchange_params *params)
    {
    	struct bt_conn_info info = {0};
    	int err;
    
    	printk("MTU exchange %s\n", att_err == 0 ? "successful" : "failed");
    
    	err = bt_conn_get_info(conn, &info);
    	if (err) {
    		printk("Failed to get connection info %d\n", err);
    		return;
    	}
    
    	if (info.role == BT_CONN_ROLE_CENTRAL) {
    		instruction_print();
    		test_ready = true;
    	}
    }
    
    static void discovery_complete(struct bt_gatt_dm *dm,
    			       void *context)
    {
    	int err;
    	struct bt_throughput *throughput = context;
    
    	printk("Service discovery completed\n");
    
    	bt_gatt_dm_data_print(dm);
    	bt_throughput_handles_assign(dm, throughput);
    	bt_gatt_dm_data_release(dm);
    
    	exchange_params.func = exchange_func;
    
    	err = bt_gatt_exchange_mtu(default_conn, &exchange_params);
    	if (err) {
    		printk("MTU exchange failed (err %d)\n", err);
    	} else {
    		printk("MTU exchange pending\n");
    	}
    }
    
    static void discovery_service_not_found(struct bt_conn *conn,
    					void *context)
    {
    	printk("Service not found\n");
    }
    
    static void discovery_error(struct bt_conn *conn,
    			    int err,
    			    void *context)
    {
    	printk("Error while discovering GATT database: (%d)\n", err);
    }
    
    struct bt_gatt_dm_cb discovery_cb = {
    	.completed         = discovery_complete,
    	.service_not_found = discovery_service_not_found,
    	.error_found       = discovery_error,
    };
    
    static void connected(struct bt_conn *conn, uint8_t hci_err)
    {
    	struct bt_conn_info info = {0};
    	int err;
    
    	if (hci_err) {
    		if (hci_err == BT_HCI_ERR_UNKNOWN_CONN_ID) {
    			/* Canceled creating connection */
    			return;
    		}
    
    		printk("Connection failed, err 0x%02x %s\n", hci_err, bt_hci_err_to_str(hci_err));
    		return;
    	}
    
    	if (default_conn) {
    		printk("Connection exists, disconnect second connection\n");
    		bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
    		return;
    	}
    
    	default_conn = bt_conn_ref(conn);
    
    	err = bt_conn_get_info(default_conn, &info);
    	if (err) {
    		printk("Failed to get connection info %d\n", err);
    		return;
    	}
    
    	printk("Connected as %s\n",
    	       info.role == BT_CONN_ROLE_CENTRAL ? "central" : "peripheral");
    	printk("Conn. interval is %u units\n", info.le.interval);
    
    	if (info.role == BT_CONN_ROLE_PERIPHERAL) {
    		err = bt_conn_set_security(conn, BT_SECURITY_L2);
    		if (err) {
    			printk("Failed to set security: %d\n", err);
    		}
    	}
    }
    
    void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err security_err)
    {
    	printk("Security changed: level %i, err: %i %s\n", level, security_err,
    	       bt_security_err_to_str(security_err));
    
    	if (security_err != 0) {
    		printk("Failed to encrypt link\n");
    		bt_conn_disconnect(conn, BT_HCI_ERR_PAIRING_NOT_SUPPORTED);
    		return;
    	}
    
    	struct bt_conn_info info = {0};
    	int err;
    
    	err = bt_conn_get_info(default_conn, &info);
    	if (info.role == BT_CONN_ROLE_CENTRAL) {
    		err = bt_gatt_dm_start(default_conn,
    				       BT_UUID_THROUGHPUT,
    				       &discovery_cb,
    				       &throughput);
    		if (err) {
    			printk("Discover failed (err %d)\n", err);
    		}
    	}
    }
    
    static void scan_init(void)
    {
    	int err;
    	struct bt_le_scan_param scan_param = {
    		.type = BT_LE_SCAN_TYPE_PASSIVE,
    		.options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
    		.interval = 0x0010,
    		.window = 0x0010,
    	};
    
    	struct bt_scan_init_param scan_init = {
    		.connect_if_match = 1,
    		.scan_param = &scan_param,
    		.conn_param = conn_param
    	};
    
    	bt_scan_init(&scan_init);
    	bt_scan_cb_register(&scan_cb);
    
    	err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, uuid128);
    	if (err) {
    		printk("Scanning filters cannot be set\n");
    
    		return;
    	}
    
    	err = bt_scan_filter_enable(BT_SCAN_UUID_FILTER, false);
    	if (err) {
    		printk("Filters cannot be turned on\n");
    	}
    }
    
    static void scan_start(void)
    {
    	int err;
    
    	err = bt_scan_start(BT_SCAN_TYPE_SCAN_PASSIVE);
    	if (err) {
    		printk("Starting scanning failed (err %d)\n", err);
    		return;
    	}
    }
    
    static void adv_start(void)
    {
    	const struct bt_le_adv_param *adv_param =
    		BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE |
    				BT_LE_ADV_OPT_ONE_TIME,
    				BT_GAP_ADV_FAST_INT_MIN_2,
    				BT_GAP_ADV_FAST_INT_MAX_2,
    				NULL);
    	int err;
    
    	err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd,
    			      ARRAY_SIZE(sd));
    	if (err) {
    		printk("Failed to start advertiser (%d)\n", err);
    		return;
    	}
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	struct bt_conn_info info = {0};
    	int err;
    
    	printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
    
    	test_ready = false;
    	if (default_conn) {
    		bt_conn_unref(default_conn);
    		default_conn = NULL;
    	}
    
    	err = bt_conn_get_info(conn, &info);
    	if (err) {
    		printk("Failed to get connection info (%d)\n", err);
    		return;
    	}
    
    	/* Re-connect using same roles */
    	if (info.role == BT_CONN_ROLE_CENTRAL) {
    		scan_start();
    	} else {
    		adv_start();
    	}
    }
    
    static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
    {
    	printk("Connection parameters update request received.\n");
    	printk("Minimum interval: %d, Maximum interval: %d\n",
    	       param->interval_min, param->interval_max);
    	printk("Latency: %d, Timeout: %d\n", param->latency, param->timeout);
    
    	return true;
    }
    
    static void le_param_updated(struct bt_conn *conn, uint16_t interval,
    			     uint16_t latency, uint16_t timeout)
    {
    	printk("Connection parameters updated.\n"
    	       " interval: %d, latency: %d, timeout: %d\n",
    	       interval, latency, timeout);
    
    	k_sem_give(&throughput_sem);
    }
    
    static void le_phy_updated(struct bt_conn *conn,
    			   struct bt_conn_le_phy_info *param)
    {
    	printk("LE PHY updated: TX PHY %s, RX PHY %s\n",
    	       phy2str(param->tx_phy), phy2str(param->rx_phy));
    
    	k_sem_give(&throughput_sem);
    }
    
    static void le_data_length_updated(struct bt_conn *conn,
    				   struct bt_conn_le_data_len_info *info)
    {
    	if (!data_length_req) {
    		return;
    	}
    
    	printk("LE data len updated: TX (len: %d time: %d)"
    	       " RX (len: %d time: %d)\n", info->tx_max_len,
    	       info->tx_max_time, info->rx_max_len, info->rx_max_time);
    
    	data_length_req = false;
    	k_sem_give(&throughput_sem);
    }
    
    static uint8_t throughput_read(const struct bt_throughput_metrics *met)
    {
    	printk("[peer] received %u bytes (%u KB)"
    	       " in %u GATT writes at %u bps\n",
    	       met->write_len, met->write_len / 1024, met->write_count,
    	       met->write_rate);
    
    	k_sem_give(&throughput_sem);
    
    	return BT_GATT_ITER_STOP;
    }
    
    static void throughput_received(const struct bt_throughput_metrics *met)
    {
    	static uint32_t kb;
    
    	if (met->write_len == 0) {
    		kb = 0;
    		printk("\n");
    
    		return;
    	}
    
    	if ((met->write_len / 1024) != kb) {
    		kb = (met->write_len / 1024);
    		printk("=");
    	}
    }
    
    static void throughput_send(const struct bt_throughput_metrics *met)
    {
    	printk("\n[local] received %u bytes (%u KB)"
    		" in %u GATT writes at %u bps\n",
    		met->write_len, met->write_len / 1024,
    		met->write_count, met->write_rate);
    }
    
    static const struct bt_throughput_cb throughput_cb = {
    	.data_read = throughput_read,
    	.data_received = throughput_received,
    	.data_send = throughput_send
    };
    
    static struct button_handler button = {
    	.cb = button_handler_cb,
    };
    
    void select_role(bool is_central)
    {
    	int err;
    	static bool role_selected;
    
    	if (role_selected) {
    		printk("\nCannot change role after it was selected once.\n");
    		return;
    	} else if (is_central) {
    		printk("\nCentral. Starting scanning\n");
    		scan_start();
    	} else {
    		printk("\nPeripheral. Starting advertising\n");
    		adv_start();
    	}
    
    	role_selected = true;
    
    	/* The role has been selected, button are not needed any more. */
    	err = dk_button_handler_remove(&button);
    	if (err) {
    		printk("Button disable error: %d\n", err);
    	}
    }
    
    static void button_handler_cb(uint32_t button_state, uint32_t has_changed)
    {
    	ARG_UNUSED(has_changed);
    
    	if (button_state & DK_BTN1_MSK) {
    		select_role(true);
    	} else if (button_state & DK_BTN2_MSK) {
    		select_role(false);
    	}
    }
    
    static void buttons_init(void)
    {
    	int err;
    
    	err = dk_buttons_init(NULL);
    	if (err) {
    		printk("Buttons initialization failed.\n");
    		return;
    	}
    
    	/* Add dynamic buttons handler. Buttons should be activated only when
    	 * during the board role choosing.
    	 */
    	dk_button_handler_add(&button);
    }
    
    
    
    static int connection_configuration_set(const struct shell *shell,
    			const struct bt_le_conn_param *conn_param,
    			const struct bt_conn_le_phy_param *phy,
    			const struct bt_conn_le_data_len_param *data_len)
    {
    	int err;
    	struct bt_conn_info info = {0};
    
    	err = bt_conn_get_info(default_conn, &info);
    	if (err) {
    		shell_error(shell, "Failed to get connection info %d", err);
    		return err;
    	}
    
    	if (info.role != BT_CONN_ROLE_CENTRAL) {
    		shell_error(shell,
    		"'run' command shall be executed only on the central board");
    	}
    
    	err = bt_conn_le_phy_update(default_conn, phy);
    	if (err) {
    		shell_error(shell, "PHY update failed: %d\n", err);
    		return err;
    	}
    
    	shell_print(shell, "PHY update pending");
    	err = k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    	if (err) {
    		shell_error(shell, "PHY update timeout");
    		return err;
    	}
    
    	if (info.le.interval != conn_param->interval_max) {
    		err = bt_conn_le_param_update(default_conn, conn_param);
    		if (err) {
    			shell_error(shell,
    				    "Connection parameters update failed: %d",
    				    err);
    			return err;
    		}
    
    		shell_print(shell, "Connection parameters update pending");
    		err = k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    		if (err) {
    			shell_error(shell,
    				    "Connection parameters update timeout");
    			return err;
    		}
    	}
    
    	if (info.le.data_len->tx_max_len != data_len->tx_max_len) {
    		data_length_req = true;
    
    		err = bt_conn_le_data_len_update(default_conn, data_len);
    		if (err) {
    			shell_error(shell, "LE data length update failed: %d",
    				    err);
    			return err;
    		}
    
    		shell_print(shell, "LE Data length update pending");
    		err = k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    		if (err) {
    			shell_error(shell, "LE Data Length update timeout");
    			return err;
    		}
    	}
    
    	if (g_tx_power != 0) {
    
    
    		    shell_print(shell, "Setting TX power to : %d dBm",
                    g_tx_power);
    
        struct net_buf *buf, *rsp = NULL;
        struct bt_hci_cp_vs_write_tx_power_level *cp;
        struct bt_hci_rp_vs_write_tx_power_level *rp;
        int err;
    
        buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
                                sizeof(*cp));
        if (!buf) {
            shell_error(shell, "No HCI buffer");
            return -ENOMEM;
        }
    
    	err = bt_hci_get_conn_handle(default_conn,
    						&default_conn_handle);
    	if (err) {
    		shell_print(shell,"No connection handle (err %d)", err);
    		return err;
    	} 
    
        cp = net_buf_add(buf, sizeof(*cp));
        cp->handle_type    = BT_HCI_VS_LL_HANDLE_TYPE_CONN;
        cp->handle         = sys_cpu_to_le16(default_conn_handle);
        cp->tx_power_level = g_tx_power;
    
        err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
                                   buf, &rsp);
        if (err) {
            shell_error(shell, "Error setting TX power: %d", err);
            return err;
        }
    
        rp = (void *)rsp->data;
        shell_print(shell, "Actual TX power: %d dBm",
                    rp->selected_tx_power);
    
        net_buf_unref(rsp);
    }
    
    	return 0;
    }
    
    int test_run(const struct shell *shell,
    	     const struct bt_le_conn_param *conn_param,
    	     const struct bt_conn_le_phy_param *phy,
    	     const struct bt_conn_le_data_len_param *data_len)
    {
    	int err;
    	uint64_t stamp;
    	int64_t delta;
    	uint32_t data = 0;
    	int64_t  rssi_sum   = 0;
        int      rssi_count = 0;
    
    	/* a dummy data buffer */
    	static char dummy[495];
    
    	if (!default_conn) {
    		shell_error(shell, "Device is disconnected %s",
    			    "Connect to the peer device before running test");
    		return -EFAULT;
    	}
    
    	if (!test_ready) {
    		shell_error(shell, "Device is not ready."
    			"Please wait for the service discovery and MTU exchange end");
    		return 0;
    	}
    
    	shell_print(shell, "\n==== Starting throughput test ====");
    
    	err = connection_configuration_set(shell, conn_param, phy, data_len);
    	if (err) {
    		return err;
    	}
    
    	shell_print(shell, "The test is in progress and will require around %d seconds "
    		"to complete.", CONFIG_BT_THROUGHPUT_DURATION / 1000);
    
    	/* Make sure that all BLE procedures are finished. */
    	k_sleep(K_MSEC(500));
    
    	/* reset peer metrics */
    	err = bt_throughput_write(&throughput, dummy, 1);
    	if (err) {
    		shell_error(shell, "Reset peer metrics failed.");
    		return err;
    	}
    
    	/* get cycle stamp */
    	stamp = k_uptime_get_32();
    
    	while (true) {
    		err = bt_throughput_write(&throughput, dummy, 495);
    		if (err) {
    			shell_error(shell, "GATT write failed (err %d)", err);
    			break;
    		}
    
    		{
                int8_t rssi;
                if (read_conn_rssi_val(bt_conn_index(default_conn), &rssi) == 0) {
                    rssi_sum   += rssi;
                    rssi_count += 1;
                }
            }
    
    		data += 495;
    		if (k_uptime_get_32() - stamp > CONFIG_BT_THROUGHPUT_DURATION) {
    			break;
    		}
    	}
    
    	delta = k_uptime_delta(&stamp);
    
    	printk("\nDone\n");
    	printk("[local] sent %u bytes (%u KB) in %lld ms at %llu kbps\n",
    	       data, data / 1024, delta, ((uint64_t)data * 8 / delta));
    
    	if (rssi_count > 0) {
            int8_t avg_rssi = rssi_sum / rssi_count;
            printk("Average RSSI: %d dBm over %d samples\n",
                   avg_rssi, rssi_count);
        } else {
            printk("No RSSI samples collected\n");
        }
    
    	/* read back char from peer */
    	err = bt_throughput_read(&throughput);
    	if (err) {
    		shell_error(shell, "GATT read failed (err %d)", err);
    		return err;
    	}
    
    	k_sem_take(&throughput_sem, THROUGHPUT_CONFIG_TIMEOUT);
    
    	instruction_print();
    
    	return 0;
    }
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    	.le_param_req = le_param_req,
    	.le_param_updated = le_param_updated,
    	.le_phy_updated = le_phy_updated,
    	.le_data_len_updated = le_data_length_updated,
    	.security_changed = security_changed
    };
    
    int main(void)
    {
    	int err;
    
    	printk("Starting Bluetooth Throughput example\n");
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return 0;
    	}
    
    	printk("Bluetooth initialized\n");
    
    	scan_init();
    
    	err = bt_throughput_init(&throughput, &throughput_cb);
    	if (err) {
    		printk("Throughput service initialization failed.\n");
    		return 0;
    	}
    
    	printk("\n");
    
    	if (IS_ENABLED(CONFIG_SOC_SERIES_NRF54HX) || IS_ENABLED(CONFIG_SOC_SERIES_NRF54LX)) {
    		printk("Press button 0 or type \"central\" on the central board.\n");
    		printk("Press button 1 or type \"peripheral\" on the peripheral board.\n");
    	} else {
    		printk("Press button 1 or type \"central\" on the central board.\n");
    		printk("Press button 2 or type \"peripheral\" on the peripheral board.\n");
    	}
    
    	buttons_init();
    
    	return 0;
    }
    

    PS: Max tx-power on nRF5340 is 3 dbm.

Children
No Data
Related