Implementing Set Tx Power command in USB CDC

I am using the cdc_acm example and have added a command to set and read tx power. I am trying to use the following commands for this.

BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL
BT_HCI_OP_VS_READ_TX_POWER_LEVEL
When I try to read or set tx power the following error is seen.
As I understand this is due to a wrong implementation of the vendor specific HCI command but I am unsure how to resolve this issue, also in the above screenshot the error is not seen if the power is set to 0dBm which should be the default.
The implementation for this can be seen below as well as the project config file.
Parents Reply Children
  • Hello Einar,

    I have tried to copy and paste the implementation directly from the hci power control but still failing. I have the function outside of the shell command and then call it in the shell command with the value for tx power as the argument. I've attached my source code, the main.c has the implementation for the tx_power at the start of the code 

    /*
     * Copyright (c) 2019 Intel Corporation
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    /**
     * @file
     * @brief Sample echo app for CDC ACM class with BLE advertising and TX power control
     *
     * Sample app for USB CDC ACM class driver. The received data is echoed back
     * to the serial port. Additionally, it supports BLE advertising and TX power control 
     * via shell commands.
     */
    
    
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/uart.h>
    #include <zephyr/kernel.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/ring_buffer.h>
    #include <zephyr/usb/usb_device.h>
    #include <zephyr/usb/usbd.h>
    #include <zephyr/logging/log.h>
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/shell/shell.h>
    #include <C:\ncs\v2.6.1\modules\hal\nordic\nrfx\hal\nrf_radio.h>
    #include <zephyr/bluetooth/hci_vs.h>
    #include <zephyr/bluetooth/conn.h>
    
    LOG_MODULE_REGISTER(cdc_acm_echo, LOG_LEVEL_INF);
    
    #define RING_BUF_SIZE 1024
    uint8_t ring_buffer[RING_BUF_SIZE];
    
    struct ring_buf ringbuf;
    
    static bool rx_throttled;
    static struct bt_conn *default_conn;
    static uint16_t default_conn_handle;
    
    #if defined(CONFIG_USB_DEVICE_STACK_NEXT)
    USBD_CONFIGURATION_DEFINE(config_1,
    			  USB_SCD_SELF_POWERED,
    			  200);
    
    USBD_DESC_LANG_DEFINE(sample_lang);
    USBD_DESC_MANUFACTURER_DEFINE(sample_mfr, "ZEPHYR");
    USBD_DESC_PRODUCT_DEFINE(sample_product, "Zephyr USBD CDC ACM");
    USBD_DESC_SERIAL_NUMBER_DEFINE(sample_sn, "0123456789ABCDEF");
    
    USBD_DEVICE_DEFINE(sample_usbd,
    		   DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
    		   0x2fe3, 0x0001);
    
    static int enable_usb_device_next(void)
    {
    	int err;
    
    	err = usbd_add_descriptor(&sample_usbd, &sample_lang);
    	if (err) {
    		LOG_ERR("Failed to initialize language descriptor (%d)", err);
    		return err;
    	}
    
    	err = usbd_add_descriptor(&sample_usbd, &sample_mfr);
    	if (err) {
    		LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err);
    		return err;
    	}
    
    	err = usbd_add_descriptor(&sample_usbd, &sample_product);
    	if (err) {
    		LOG_ERR("Failed to initialize product descriptor (%d)", err);
    		return err;
    	}
    
    	err = usbd_add_descriptor(&sample_usbd, &sample_sn);
    	if (err) {
    		LOG_ERR("Failed to initialize SN descriptor (%d)", err);
    		return err;
    	}
    
    	err = usbd_add_configuration(&sample_usbd, &config_1);
    	if (err) {
    		LOG_ERR("Failed to add configuration (%d)", err);
    		return err;
    	}
    
    	err = usbd_register_class(&sample_usbd, "cdc_acm_0", 1);
    	if (err) {
    		LOG_ERR("Failed to register CDC ACM class (%d)", err);
    		return err;
    	}
    
    	err = usbd_init(&sample_usbd);
    	if (err) {
    		LOG_ERR("Failed to initialize device support");
    		return err;
    	}
    
    	err = usbd_enable(&sample_usbd);
    	if (err) {
    		LOG_ERR("Failed to enable device support");
    		return err;
    	}
    
    	LOG_DBG("USB device support enabled");
    
    	return 0;
    }
    #endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK_NEXT) */
    
    
    static int set_tx_power(uint8_t handle_type, uint16_t handle, int8_t tx_pwr_lvl)
    {
    	struct bt_hci_cp_vs_write_tx_power_level *cp;
    	struct bt_hci_rp_vs_write_tx_power_level *rp;
    	struct net_buf *buf, *rsp = NULL;
    	int err;
    
    	buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
    				sizeof(*cp));
    	if (!buf) {
    		printk("Unable to allocate command buffer\n");
    		return;
    	}
    
    	cp = net_buf_add(buf, sizeof(*cp));
    	cp->handle = sys_cpu_to_le16(handle);
    	cp->handle_type = handle_type;
    	cp->tx_power_level = tx_pwr_lvl;
    
    	err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
    				   buf, &rsp);
    	if (err) {
    		uint8_t reason = rsp ?
    			((struct bt_hci_rp_vs_write_tx_power_level *)
    			  rsp->data)->status : 0;
    		printk("Set Tx power err: %d reason 0x%02x\n", err, reason);
    		return;
    	}
    
    	rp = (void *)rsp->data;
    	printk("Actual Tx Power: %d\n", rp->selected_tx_power);
    
    	net_buf_unref(rsp);
    }
    
    static void get_tx_power(uint8_t handle_type, uint16_t handle, int8_t *tx_pwr_lvl)
    {
    	struct bt_hci_cp_vs_read_tx_power_level *cp;
    	struct bt_hci_rp_vs_read_tx_power_level *rp;
    	struct net_buf *buf, *rsp = NULL;
    	int err;
    
    	*tx_pwr_lvl = 0xFF;
    	buf = bt_hci_cmd_create(BT_HCI_OP_VS_READ_TX_POWER_LEVEL,
    				sizeof(*cp));
    	if (!buf) {
    		printk("Unable to allocate command buffer\n");
    		return;
    	}
    
    	cp = net_buf_add(buf, sizeof(*cp));
    	cp->handle = sys_cpu_to_le16(handle);
    	cp->handle_type = handle_type;
    
    	err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_TX_POWER_LEVEL,
    				   buf, &rsp);
    	if (err) {
    		uint8_t reason = rsp ?
    			((struct bt_hci_rp_vs_read_tx_power_level *)
    			  rsp->data)->status : 0;
    		printk("Read Tx power err: %d reason 0x%02x\n", err, reason);
    		return;
    	}
    
    	rp = (void *)rsp->data;
    	*tx_pwr_lvl = rp->tx_power_level;
    
    	net_buf_unref(rsp);
    }
    
    static int cmd_set_tx_pwr(const struct shell *shell, size_t argc, char **argv)
    {
        int8_t tx_power;
        struct net_buf *buf, *rsp;
        int err;
    
        if (argc < 2) {
            shell_error(shell, "Usage: tx_power <value>");
            return -EINVAL;
        }
    
        // Print the received argument
        shell_print(shell, "Received argument: %s", argv[1]);
    
        // Convert the argument to an integer
        tx_power = atoi(argv[1]);
    
        // Print the parsed value
        shell_print(shell, "Parsed TX power value: %d", tx_power);
    
        // Check if tx_power is within the acceptable range
        if (tx_power < -40 || tx_power > 8) { // Adjust range based on your chip
            shell_error(shell, "TX power must be between -40 and +8 dBm");
            return -EINVAL;
        }
    
        LOG_INF("Setting TX power to %d dBm", tx_power);
    
        // Send the command and wait for a response
        set_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
    				     default_conn_handle,
    				     tx_power);
        if (err) {
            shell_error(shell, "Failed to set TX power, error: %d", err);
            return -EIO;
        }
    
        shell_print(shell, "TX power set to %d dBm", tx_power);
        return 0;
    }
    
    
    
    static void nrf_radio_ook_configure(bool enable)
    {
        if (enable) {
            // Configure the radio to transmit continuously (OOK "on")
            NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_0dBm << RADIO_TXPOWER_TXPOWER_Pos;
            NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_1Mbit << RADIO_MODE_MODE_Pos;
    
            // Set packet configuration for continuous transmission
            NRF_RADIO->PCNF0 = (8 << RADIO_PCNF0_LFLEN_Pos);   // 8-bit length field
            NRF_RADIO->PCNF1 = (1 << RADIO_PCNF1_MAXLEN_Pos);   // Max length 1 byte
            NRF_RADIO->PACKETPTR = (uint32_t)&(uint8_t){0xFF};  // Send 0xFF for continuous "on"
    
            // Start transmitting
            NRF_RADIO->TASKS_TXEN = 1;
        } else {
            // Disable radio to stop transmission (OOK "off")
            NRF_RADIO->TASKS_DISABLE = 1;
        }
    }
    
    /* Shell command to toggle OOK (On-Off Keying) */
    static int cmd_toggle_ook(const struct shell *shell, size_t argc, char **argv)
    {
        static bool ook_enabled = false;
    
        // Parse the command argument
        if (argc > 1) {
            if (strcmp(argv[1], "on") == 0) {
                ook_enabled = true;
            } else if (strcmp(argv[1], "off") == 0) {
                ook_enabled = false;
            } else {
                shell_error(shell, "Invalid argument. Use 'on' or 'off'.");
                return -EINVAL;
            }
        } else {
            shell_error(shell, "Usage: ook <on|off>");
            return -EINVAL;
        }
    
        if (ook_enabled) {
            LOG_INF("Enabling OOK");
            nrf_radio_ook_configure(true);  // Enable OOK (start transmission)
        } else {
            LOG_INF("Disabling OOK");
            nrf_radio_ook_configure(false);  // Disable OOK (stop transmission)
        }
    
        shell_print(shell, "OOK %s", ook_enabled ? "enabled" : "disabled");
        return 0;
    }
    
    /* Shell command to start advertising */
    static int cmd_start_adv(const struct shell *shell, size_t argc, char **argv) {
        LOG_INF("Starting BLE advertising");
    
    	int err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return;
    	}
    
    	printk("Bluetooth initialized\n");
    
        struct bt_le_adv_param adv_param = {
            .options = BT_LE_ADV_OPT_CONNECTABLE,
            .interval_min = BT_GAP_ADV_SLOW_INT_MIN,
            .interval_max = BT_GAP_ADV_SLOW_INT_MAX,
            .peer = NULL
        };
    
        err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
        if (err) {
            shell_error(shell, "Failed to start advertising: %d", err);
            return err;
        }
    
        shell_print(shell, "Advertising started");
        return 0;
    }
    
    
    /* Shell command to stop advertising */
    static int cmd_stop_adv(const struct shell *shell, size_t argc, char **argv)
    {
        int err = bt_le_adv_stop();
        if (err) {
            shell_error(shell, "Failed to stop advertising: %d", err);
            return err;
        }
        shell_print(shell, "Advertising stopped");
        return 0;
    }
    
    /* Shell commands */
    // Define a static subcommand set for Bluetooth commands
    SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble);
    
    // Register the tx_power command
    SHELL_CMD_ARG_REGISTER(tx_power, NULL, "Set TX power <value>", cmd_set_tx_pwr, 2, 0);
    
    // Register the read_power command
    //SHELL_CMD_ARG_REGISTER(rd_power, NULL, "Read Current Tx power", cmd_get_tx_power, 1, 0);
    
    // Register the start_adv command
    SHELL_CMD_ARG_REGISTER(start_adv, NULL, "Start BLE advertising", cmd_start_adv, 1, 0);
    
    // Register the stop_adv command
    SHELL_CMD_ARG_REGISTER(stop_adv, NULL, "Stop BLE advertising", cmd_stop_adv, 1, 0);
    
    SHELL_CMD_ARG_REGISTER(ook, NULL, "Toggle OOK <on|off>", cmd_toggle_ook, 2, 0);
    
    // Register the main Bluetooth command with the subcommands
    SHELL_CMD_REGISTER(ble, &sub_ble, "Bluetooth commands", NULL);
    
    
    static void interrupt_handler(const struct device *dev, void *user_data)
    {
    	ARG_UNUSED(user_data);
    
    	while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
    		if (!rx_throttled && uart_irq_rx_ready(dev)) {
    			int recv_len, rb_len;
    			uint8_t buffer[64];
    			size_t len = MIN(ring_buf_space_get(&ringbuf),
    					 sizeof(buffer));
    
    			if (len == 0) {
    				/* Throttle because ring buffer is full */
    				uart_irq_rx_disable(dev);
    				rx_throttled = true;
    				continue;
    			}
    
    			recv_len = uart_fifo_read(dev, buffer, len);
    			if (recv_len < 0) {
    				LOG_ERR("Failed to read UART FIFO");
    				recv_len = 0;
    			};
    
    			rb_len = ring_buf_put(&ringbuf, buffer, recv_len);
    			if (rb_len < recv_len) {
    				LOG_ERR("Drop %u bytes", recv_len - rb_len);
    			}
    
    			LOG_DBG("tty fifo -> ringbuf %d bytes", rb_len);
    			if (rb_len) {
    				uart_irq_tx_enable(dev);
    			}
    		}
    
    		if (uart_irq_tx_ready(dev)) {
    			uint8_t buffer[64];
    			int rb_len, send_len;
    
    			rb_len = ring_buf_get(&ringbuf, buffer, sizeof(buffer));
    			if (!rb_len) {
    				LOG_DBG("Ring buffer empty, disable TX IRQ");
    				uart_irq_tx_disable(dev);
    				continue;
    			}
    
    			if (rx_throttled) {
    				uart_irq_rx_enable(dev);
    				rx_throttled = false;
    			}
    
    			send_len = uart_fifo_fill(dev, buffer, rb_len);
    			if (send_len < rb_len) {
    				LOG_ERR("Drop %d bytes", rb_len - send_len);
    			}
    
    			LOG_DBG("ringbuf -> tty fifo %d bytes", send_len);
    		}
    	}
    }
    
    void main(void)
    {
    	const struct device *dev;
    	uint32_t baudrate, dtr = 0U;
    	int ret;
    
    	dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
    	if (!device_is_ready(dev)) {
    		LOG_ERR("CDC ACM device not ready");
    		return;
    	}
    
    #if defined(CONFIG_USB_DEVICE_STACK_NEXT)
    		ret = enable_usb_device_next();
    #else
    		ret = usb_enable(NULL);
    #endif
    
    	if (ret != 0) {
    		LOG_ERR("Failed to enable USB");
    		return;
    	}
    
    	ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer);
    
    	LOG_INF("Wait for DTR");
    
    	while (true) {
    		uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
    		if (dtr) {
    			break;
    		} else {
    			/* Give CPU resources to low priority threads. */
    			k_sleep(K_MSEC(100));
    		}
    	}
    
    	LOG_INF("DTR set");
    
    	/* They are optional, we use them to test the interrupt endpoint */
    	ret = uart_line_ctrl_set(dev, UART_LINE_CTRL_DCD, 1);
    	if (ret) {
    		LOG_WRN("Failed to set DCD, ret code %d", ret);
    	}
    
    	ret = uart_line_ctrl_set(dev, UART_LINE_CTRL_DSR, 1);
    	if (ret) {
    		LOG_WRN("Failed to set DSR, ret code %d", ret);
    	}
    
    	/* Wait 100ms for the host to do all settings */
    	k_msleep(100);
    
    	ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
    	if (ret) {
    		LOG_WRN("Failed to get baudrate, ret code %d", ret);
    	} else {
    		LOG_INF("Baudrate detected: %d", baudrate);
    	}
    
    	/* Initialize Bluetooth */
    	if (bt_enable(NULL)) {
    		LOG_ERR("Failed to enable Bluetooth");
    		return;
    	}
    
    	uart_irq_callback_set(dev, interrupt_handler);
    
    	/* Enable rx interrupts */
    	uart_irq_rx_enable(dev);
    }
    

    # Enable USB device stack
    CONFIG_USB_DEVICE_STACK=y
    CONFIG_USB_DEVICE_PRODUCT="nRF52840 Dongle"
    
    # Enable USB CDC ACM (UART over USB)
    CONFIG_UART_LINE_CTRL=y
    CONFIG_UART_CONSOLE=y
    CONFIG_USB_CDC_ACM=y
    CONFIG_UART_INTERRUPT_DRIVEN=y
    
    # Enable Bluetooth functionality
    CONFIG_BT=y
    CONFIG_BT_PERIPHERAL=y
    CONFIG_BT_DEVICE_NAME="nRF52840 Dongle"
    CONFIG_BT_SHELL=y
    CONFIG_BT_HCI_VS_EXT=y
    
    # Enable TX power control
    CONFIG_BT_HCI=y
    CONFIG_BT_HCI_VS=y
    CONFIG_BT_CTLR=y
    CONFIG_BT_BROADCASTER=y
    CONFIG_BT_CENTRAL=y
    CONFIG_BT_LL_SOFTDEVICE=y
    CONFIG_BT_LL_SW_SPLIT=y
    
    CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
    CONFIG_BT_CTLR_ADVANCED_FEATURES=y
    CONFIG_BT_CTLR_CONN_RSSI=y
    # Shell configuration
    CONFIG_SHELL=y
    
    # Enable console
    CONFIG_CONSOLE=y
    
    # Power management settings
    CONFIG_PM_DEVICE=y
    
    #Enable logging
    CONFIG_LOG=y

  • Hi,

    There is one thing sticking out here. You are setting BT_HCI_VS_LL_HANDLE_TYPE_CONN and passing a handle that is never set. To set the power for a connection, you need to provide the handle to a active connection. Perhaps you should have set the advertising Tx power? That is done by BT_HCI_VS_LL_HANDLE_TYPE_ADV (and any subsequent resulting connection will inherit that Tx power). This is also demonstrated in the hci_pwer_ctrl sample.

Related