How to verify the 4Mbps transmission mode of the nRF54L15 series?

I noticed that the product information of the nRF54L15 series kit supports 4Mbps, but I'm unsure how to verify it. Are there any relevant examples available? I have currently tried the radio_test example, but this example doesn't show the actual data rate.

  • Hi,

    4 Mbit phy is for proprietary protocols only, not BLE (which goes up to 2 Mbit.)

    With the radio test sample in nRF Connect SDK v3.0.0, when building for the nRF54L15 DK, you should have the additional options to select data_rate of nrf_4Mbit_BT04 and/or nrf_4MbitBT06.

    You can also test it with Enhanced Shockburst (ESB) using the ESB samples, by changing the configuration structure in main.c used for esb_init() from

    config.bitrate = ESB_BITRATE_2MBPS

    to

    config.bitrate = ESB_BITRATE_4MBPS

    Regards,
    Terje

  • /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    #include <zephyr/drivers/clock_control.h>
    #include <zephyr/drivers/clock_control/nrf_clock_control.h>
    #if defined(NRF54L15_XXAA)
    #include <hal/nrf_clock.h>
    #endif /* defined(NRF54L15_XXAA) */
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/irq.h>
    #include <zephyr/logging/log.h>
    #include <nrf.h>
    #include <esb.h>
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/kernel.h>
    #include <zephyr/types.h>
    #include <dk_buttons_and_leds.h>
    #if defined(CONFIG_CLOCK_CONTROL_NRF2)
    #include <hal/nrf_lrcconf.h>
    #endif
    
    LOG_MODULE_REGISTER(esb_ptx, CONFIG_ESB_PTX_APP_LOG_LEVEL);
    static uint32_t total_bytes_sent = 0;
    static uint32_t last_report_time = 0;
    static bool ready = true;
    static struct esb_payload rx_payload;
    //static struct esb_payload tx_payload = ESB_CREATE_PAYLOAD(0,
    //	0x01, 0x00, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
    static struct esb_payload tx_payload = ESB_CREATE_PAYLOAD(0,
        0x01, 0x00, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
        0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
        0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20); 
    
    #define _RADIO_SHORTS_COMMON                                                   \
    	(RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk |         \
    	 RADIO_SHORTS_ADDRESS_RSSISTART_Msk |                                  \
    	 RADIO_SHORTS_DISABLED_RSSISTOP_Msk)
    
    #define PACKET_SIZE 252  // ESB最大有效载荷
    #define NO_ACK_MODE 1    // 禁用ACK提高吞吐量
    void event_handler(struct esb_evt const *event)
    {
        ready = true;
    
        static uint32_t last_rx_timestamp = 0;
        static uint32_t total_bytes_received = 0;
    
        switch (event->evt_id) {
        case ESB_EVENT_TX_SUCCESS:
            LOG_DBG("TX SUCCESS EVENT");
            break;
        case ESB_EVENT_TX_FAILED:
            LOG_DBG("TX FAILED EVENT");
            break;
        case ESB_EVENT_RX_RECEIVED:
            while (esb_read_rx_payload(&rx_payload) == 0) {
                uint32_t rx_timestamp = k_uptime_get();  // 当前接收时间戳(单位:毫秒)
                uint32_t tx_timestamp = 0;
    
                // 从数据包中提取发送时间戳
                tx_timestamp |= rx_payload.data[28] << 24;
                tx_timestamp |= rx_payload.data[29] << 16;
                tx_timestamp |= rx_payload.data[30] << 8;
                tx_timestamp |= rx_payload.data[31];
    
                // 计算接收到的数据包的传输时间(单位:秒)
                uint32_t time_diff = rx_timestamp - tx_timestamp;  // 时间差(毫秒)
                float time_diff_sec = time_diff / 1000.0f;  // 转换为秒
    
                // 计算吞吐量(单位:Mbps)
                float throughput = (rx_payload.length * 8) / time_diff_sec / 1000000.0f;  // 转换为 Mbps
                total_bytes_received += rx_payload.length;
    
                // 打印吞吐量和其他调试信息
                LOG_DBG("Packet received, len %d, time_diff: %d ms, throughput: %.2f Mbps : "
                        "0x%02x, 0x%02x, 0x%02x, 0x%02x, "
                        "0x%02x, 0x%02x, 0x%02x, 0x%02x",
                        rx_payload.length, time_diff, throughput,
                        rx_payload.data[0], rx_payload.data[1],
                        rx_payload.data[2], rx_payload.data[3],
                        rx_payload.data[4], rx_payload.data[5],
                        rx_payload.data[6], rx_payload.data[7]);
            }
            break;
        }
    }
    
    
    #if defined(CONFIG_CLOCK_CONTROL_NRF)
    int clocks_start(void)
    {
    	int err;
    	int res;
    	struct onoff_manager *clk_mgr;
    	struct onoff_client clk_cli;
    
    	clk_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
    	if (!clk_mgr) {
    		LOG_ERR("Unable to get the Clock manager");
    		return -ENXIO;
    	}
    
    	sys_notify_init_spinwait(&clk_cli.notify);
    
    	err = onoff_request(clk_mgr, &clk_cli);
    	if (err < 0) {
    		LOG_ERR("Clock request failed: %d", err);
    		return err;
    	}
    
    	do {
    		err = sys_notify_fetch_result(&clk_cli.notify, &res);
    		if (!err && res) {
    			LOG_ERR("Clock could not be started: %d", res);
    			return res;
    		}
    	} while (err);
    
    #if defined(NRF54L15_XXAA)
    	/* MLTPAN-20 */
    	nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_PLLSTART);
    #endif /* defined(NRF54L15_XXAA) */
    
    	LOG_DBG("HF clock started");
    	return 0;
    }
    
    #elif defined(CONFIG_CLOCK_CONTROL_NRF2)
    
    int clocks_start(void)
    {
    	int err;
    	int res;
    	const struct device *radio_clk_dev =
    		DEVICE_DT_GET_OR_NULL(DT_CLOCKS_CTLR(DT_NODELABEL(radio)));
    	struct onoff_client radio_cli;
    
    	/** Keep radio domain powered all the time to reduce latency. */
    	nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_1, true);
    
    	sys_notify_init_spinwait(&radio_cli.notify);
    
    	err = nrf_clock_control_request(radio_clk_dev, NULL, &radio_cli);
    
    	do {
    		err = sys_notify_fetch_result(&radio_cli.notify, &res);
    		if (!err && res) {
    			LOG_ERR("Clock could not be started: %d", res);
    			return res;
    		}
    	} while (err == -EAGAIN);
    
    #if defined(NRF54L15_XXAA)
    	/* MLTPAN-20 */
    	nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_PLLSTART);
    #endif /* defined(NRF54L15_XXAA) */
    
    	LOG_DBG("HF clock started");
    	return 0;
    }
    
    #else
    BUILD_ASSERT(false, "No Clock Control driver");
    #endif /* defined(CONFIG_CLOCK_CONTROL_NRF2) */
    
    int esb_initialize(void)
    {
    	int err;
    	/* These are arbitrary default addresses. In end user products
    	 * different addresses should be used for each set of devices.
    	 */
    	uint8_t base_addr_0[4] = {0xE7, 0xE7, 0xE7, 0xE7};
    	uint8_t base_addr_1[4] = {0xC2, 0xC2, 0xC2, 0xC2};
    	uint8_t addr_prefix[8] = {0xE7, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8};
    
    	struct esb_config config = ESB_DEFAULT_CONFIG;
    
    	config.protocol = ESB_PROTOCOL_ESB_DPL;
    	config.retransmit_delay = 600;
    	config.bitrate = ESB_BITRATE_4MBPS;
    	config.event_handler = event_handler;
    	config.mode = ESB_MODE_PTX;
    	config.selective_auto_ack = true;
    	if (IS_ENABLED(CONFIG_ESB_FAST_SWITCHING)) {
    		config.use_fast_ramp_up = true;
    	}
    
    	err = esb_init(&config);
    
    	if (err) {
    		return err;
    	}
    
    	err = esb_set_base_address_0(base_addr_0);
    	if (err) {
    		return err;
    	}
    
    	err = esb_set_base_address_1(base_addr_1);
    	if (err) {
    		return err;
    	}
    
    	err = esb_set_prefixes(addr_prefix, ARRAY_SIZE(addr_prefix));
    	if (err) {
    		return err;
    	}
    
    	return 0;
    }
    
    static void leds_update(uint8_t value)
    {
    	uint32_t leds_mask =
    		(!(value % 8 > 0 && value % 8 <= 4) ? DK_LED1_MSK : 0) |
    		(!(value % 8 > 1 && value % 8 <= 5) ? DK_LED2_MSK : 0) |
    		(!(value % 8 > 2 && value % 8 <= 6) ? DK_LED3_MSK : 0) |
    		(!(value % 8 > 3) ? DK_LED4_MSK : 0);
    
    	dk_set_leds(leds_mask);
    }
    
    int main(void)
    {
        int err;
    
        LOG_INF("Enhanced ShockBurst ptx sample");
    
        err = clocks_start();
        if (err) {
            return 0;
        }
    
        err = dk_leds_init();
        if (err) {
            LOG_ERR("LEDs initialization failed, err %d", err);
            return 0;
        }
    
        err = esb_initialize();
        if (err) {
            LOG_ERR("ESB initialization failed, err %d", err);
            return 0;
        }
    
        LOG_INF("Initialization complete");
        LOG_INF("Sending test packet");
    
        tx_payload.noack = NO_ACK_MODE;  // 禁用ACK
        
        uint32_t packet_counter = 0;
        uint32_t start_time = k_uptime_get();
        
        while (1) {
            if (ready) {
                ready = false;
                esb_flush_tx();
                leds_update(tx_payload.data[1]);
    
                // 获取当前发送时间戳,并存入数据包的最后 4 字节
                uint32_t timestamp = (uint32_t)k_uptime_get();
                tx_payload.data[28] = (timestamp >> 24) & 0xFF;
                tx_payload.data[29] = (timestamp >> 16) & 0xFF;
                tx_payload.data[30] = (timestamp >> 8) & 0xFF;
                tx_payload.data[31] = timestamp & 0xFF;
    
                err = esb_write_payload(&tx_payload);
                if (err) {
                    LOG_ERR("Payload write failed, err %d", err);
                }
    
                // 统计发送的字节数
                total_bytes_sent += tx_payload.length;  // 累加已发送的字节数
                uint32_t now = k_uptime_get();
                if (now - last_report_time >= 1000) {  // 每秒报告一次吞吐量
                    float time_elapsed = (now - last_report_time) / 1000.0f;
                    float throughput = (total_bytes_sent * 8) / time_elapsed / 1000000.0f;  // 计算吞吐量,单位为Mbps
    
                    LOG_INF("Throughput: %.2f Mbps (total bytes: %u)", throughput, total_bytes_sent);
    
                    total_bytes_sent = 0;  // 重置已发送字节数
                    last_report_time = now;  // 更新报告时间
                }
            }
    
            k_sleep(K_MSEC(1));  // 提高发送频率
        }
    }
    
    
    
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/clock_control.h>
    #include <zephyr/drivers/clock_control/nrf_clock_control.h>
    #if defined(NRF54L15_XXAA)
    #include <hal/nrf_clock.h>
    #endif /* defined(NRF54L15_XXAA) */
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/irq.h>
    #include <zephyr/logging/log.h>
    #include <nrf.h>
    #include <esb.h>
    #include <zephyr/kernel.h>
    #include <zephyr/types.h>
    #include <dk_buttons_and_leds.h>
    #if defined(CONFIG_CLOCK_CONTROL_NRF2)
    #include <hal/nrf_lrcconf.h>
    #endif
    #define PACKET_SIZE 252
    LOG_MODULE_REGISTER(esb_prx, CONFIG_ESB_PRX_APP_LOG_LEVEL);
    
    static struct esb_payload rx_payload;
    
    static void leds_update(uint8_t value)
    {
    	uint32_t leds_mask =
    		(!(value % 8 > 0 && value % 8 <= 4) ? DK_LED1_MSK : 0) |
    		(!(value % 8 > 1 && value % 8 <= 5) ? DK_LED2_MSK : 0) |
    		(!(value % 8 > 2 && value % 8 <= 6) ? DK_LED3_MSK : 0) |
    		(!(value % 8 > 3) ? DK_LED4_MSK : 0);
    
    	dk_set_leds(leds_mask);
    }
    static uint32_t total_bytes_received = 0;
    static uint32_t last_report_time = 0;
    void event_handler(struct esb_evt const *event)
    {
        static uint32_t last_rx_timestamp = 0;
    
        switch (event->evt_id) {
        case ESB_EVENT_RX_RECEIVED:
            while (esb_read_rx_payload(&rx_payload) == 0) {
                total_bytes_received += rx_payload.length;
            }
            
            uint32_t now = k_uptime_get();
            if (now - last_report_time >= 10000) {  // 每 10 秒报告一次吞吐量
                float time_elapsed = (now - last_report_time) / 1000.0f;  // 转换为秒
                float throughput = (total_bytes_received * 8) / time_elapsed / 1000000.0f;  // 计算吞吐量,单位为Mbps
                
                LOG_INF("Throughput: %.2f Mbps (total bytes: %u)", throughput, total_bytes_received);
                
                total_bytes_received = 0;  // 重置字节数
                last_report_time = now;   // 更新报告时间
            }
            break;
        }
    }
    
    
    #if defined(CONFIG_CLOCK_CONTROL_NRF)
    int clocks_start(void)
    {
    	int err;
    	int res;
    	struct onoff_manager *clk_mgr;
    	struct onoff_client clk_cli;
    
    	clk_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
    	if (!clk_mgr) {
    		LOG_ERR("Unable to get the Clock manager");
    		return -ENXIO;
    	}
    
    	sys_notify_init_spinwait(&clk_cli.notify);
    
    	err = onoff_request(clk_mgr, &clk_cli);
    	if (err < 0) {
    		LOG_ERR("Clock request failed: %d", err);
    		return err;
    	}
    
    	do {
    		err = sys_notify_fetch_result(&clk_cli.notify, &res);
    		if (!err && res) {
    			LOG_ERR("Clock could not be started: %d", res);
    			return res;
    		}
    	} while (err);
    
    #if defined(NRF54L15_XXAA)
    	/* MLTPAN-20 */
    	nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_PLLSTART);
    #endif /* defined(NRF54L15_XXAA) */
    
    	LOG_DBG("HF clock started");
    	return 0;
    }
    
    #elif defined(CONFIG_CLOCK_CONTROL_NRF2)
    
    int clocks_start(void)
    {
    	int err;
    	int res;
    	const struct device *radio_clk_dev =
    		DEVICE_DT_GET_OR_NULL(DT_CLOCKS_CTLR(DT_NODELABEL(radio)));
    	struct onoff_client radio_cli;
    
    	/** Keep radio domain powered all the time to reduce latency. */
    	nrf_lrcconf_poweron_force_set(NRF_LRCCONF010, NRF_LRCCONF_POWER_DOMAIN_1, true);
    
    	sys_notify_init_spinwait(&radio_cli.notify);
    
    	err = nrf_clock_control_request(radio_clk_dev, NULL, &radio_cli);
    
    	do {
    		err = sys_notify_fetch_result(&radio_cli.notify, &res);
    		if (!err && res) {
    			LOG_ERR("Clock could not be started: %d", res);
    			return res;
    		}
    	} while (err == -EAGAIN);
    
    #if defined(NRF54L15_XXAA)
    	/* MLTPAN-20 */
    	nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_PLLSTART);
    #endif /* defined(NRF54L15_XXAA) */
    
    	LOG_DBG("HF clock started");
    
    	return 0;
    }
    
    #else
    BUILD_ASSERT(false, "No Clock Control driver");
    #endif /* defined(CONFIG_CLOCK_CONTROL_NRF2) */
    
    int esb_initialize(void)
    {
    	int err;
    	/* These are arbitrary default addresses. In end user products
    	 * different addresses should be used for each set of devices.
    	 */
    	uint8_t base_addr_0[4] = {0xE7, 0xE7, 0xE7, 0xE7};
    	uint8_t base_addr_1[4] = {0xC2, 0xC2, 0xC2, 0xC2};
    	uint8_t addr_prefix[8] = {0xE7, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8};
    
    	struct esb_config config = ESB_DEFAULT_CONFIG;
    
    	config.protocol = ESB_PROTOCOL_ESB_DPL;
    	config.bitrate = ESB_BITRATE_4MBPS;
    	config.mode = ESB_MODE_PRX;
    	config.event_handler = event_handler;
    	config.selective_auto_ack = true;
    	if (IS_ENABLED(CONFIG_ESB_FAST_SWITCHING)) {
    		config.use_fast_ramp_up = true;
    	}
    
    	err = esb_init(&config);
    	if (err) {
    		return err;
    	}
    
    	err = esb_set_base_address_0(base_addr_0);
    	if (err) {
    		return err;
    	}
    
    	err = esb_set_base_address_1(base_addr_1);
    	if (err) {
    		return err;
    	}
    
    	err = esb_set_prefixes(addr_prefix, ARRAY_SIZE(addr_prefix));
    	if (err) {
    		return err;
    	}
    
    	return 0;
    }
    
    int main(void)
    {
        int err;
    
        LOG_INF("Enhanced ShockBurst prx sample");
    
        err = clocks_start();
        if (err) {
            return 0;
        }
    
        err = dk_leds_init();
        if (err) {
            LOG_ERR("LEDs initialization failed, err %d", err);
            return 0;
        }
    
        err = esb_initialize();
        if (err) {
            LOG_ERR("ESB initialization failed, err %d", err);
            return 0;
        }
    
        LOG_INF("Initialization complete");
    
        err = esb_start_rx();
        if (err) {
            LOG_ERR("RX setup failed, err %d", err);
            return 0;
        }
    
        /* return to idle thread */
        return 0;
    }
    
      

    Thank you for your reply. I used the ESB example for testing and only modified the main.c to add timestamps. However, the current measured transmission rate is only 0.12Mbps. How can I improve the transmission speed? In 4Mbps mode, I expected to achieve a transmission rate of 2-3Mbps.

    In addition to adding the timestamps, where else do I need to make adjustments? I increased the payload to 32 bytes. Is 32 bytes the maximum payload size for the NRF54L15?

Related