Standalone ESB without MPSL isn't working

For reference I am using NCS v2.0 and VSCode NCS plugin programming in C++.

I am trying to get the example ESB TX example working from the nordic sample (link).

(Notably I replaced the logging with my own logging code and stripped out the LED indications).

However, when I run the code, it crashes repeatedly, giving the following output:

Enhanced ShockBurst ptx sample
HF clock started
Initialization complete
Sending test packet
MPSL ASSERT: 112, 2189

I configured an MPSL assert handler which is how I determined the MPSL was crashing out.

I have my full code and prj.conf file below.

Can you give any guidance on what I'm doing wrong?  I have a lot of features enabled in my prj.conf because I'm separately working on many other aspects of a large application which I want to incorporate ESB into.  That includes Bluetooth, which perhaps is activating MPSL and a source of contention here?

Please let me know what I would need to change in order for this to work.

Code:

#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#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 "Log.h"


static bool ready = true;
static struct esb_payload rx_payload;
inline static struct esb_payload tx_payload = {
     .length = 8,
     .pipe = 0,
     .data = { 0x01, 0x00, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }
};

#define _RADIO_SHORTS_COMMON                                                   \
	(RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk |         \
	 RADIO_SHORTS_ADDRESS_RSSISTART_Msk |                                  \
	 RADIO_SHORTS_DISABLED_RSSISTOP_Msk)

void event_handler(struct esb_evt const *event)
{
	ready = true;

	switch (event->evt_id) {
	case ESB_EVENT_TX_SUCCESS:
		Log("TX SUCCESS EVENT");
		break;
	case ESB_EVENT_TX_FAILED:
		Log("TX FAILED EVENT");
		break;
	case ESB_EVENT_RX_RECEIVED:
		while (esb_read_rx_payload(&rx_payload) == 0) {
			Log("Packet received, len %d : "
				"0x%02x, 0x%02x, 0x%02x, 0x%02x, "
				"0x%02x, 0x%02x, 0x%02x, 0x%02x",
				rx_payload.length, 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;
	}
}

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("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("Clock request failed: %d", err);
		return err;
	}

	do {
		err = sys_notify_fetch_result(&clk_cli.notify, &res);
		if (!err && res) {
			Log("Clock could not be started: %d", res);
			return res;
		}
	} while (err);

	Log("HF clock started");
	return 0;
}

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_2MBPS;
	config.event_handler = event_handler;
	config.mode = ESB_MODE_PTX;
	config.selective_auto_ack = 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;
}


void main(void)
{
	int err;

	Log("Enhanced ShockBurst ptx sample");

	err = clocks_start();
	if (err) {
		return;
	}

	err = esb_initialize();
	if (err) {
		Log("ESB initialization failed, err %d", err);
		return;
	}

	Log("Initialization complete");
	Log("Sending test packet");

	tx_payload.noack = false;
	while (1) {
		if (ready) {
			ready = false;
			esb_flush_tx();

			err = esb_write_payload(&tx_payload);
			if (err) {
				Log("Payload write failed, err %d", err);
			}
			tx_payload.data[1]++;
		}
		k_sleep(K_MSEC(100));
	}
}

prj.conf:

#####################################################################
# set up project to use c++17 and useful libs/stl
#####################################################################
CONFIG_CPLUSPLUS=y
CONFIG_LIB_CPLUSPLUS=y
CONFIG_NEWLIB_LIBC=y
CONFIG_STD_CPP20=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_CBPRINTF_FP_SUPPORT=y


#####################################################################
# maximum debugging - my code, kernel, everything
#####################################################################
CONFIG_DEBUG_OPTIMIZATIONS=y
CONFIG_DEBUG=y
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_EXCEPTION_STACK_TRACE=y
CONFIG_DEBUG_INFO=y

#####################################################################
# system features
#####################################################################

# Enable rebooting by code
CONFIG_REBOOT=y

# use internal RC oscillator instead of external 32KHz crystal
# speeds up boot time
# in my case, going from infinity down to instant
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y

# Disable NFC so I can use the GPIO pins
CONFIG_NFCT_PINS_AS_GPIOS=y

# monitor high-water mark on stack by placing known-values in stack
# buffer before running
CONFIG_INIT_STACKS=y
CONFIG_MPU_STACK_GUARD=y


#####################################################################
# Set up interfacing
#####################################################################
# I want serial enabled in general
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_ASYNC_API=y

# I want logging to function
CONFIG_LOG=y

# I want to use the serial interface for logging
# but when I enable shell, this makes printf'd material come out twice,
# once normally, and another time with the shell prefix.
# so I'm disabling it for now.
# CONFIG_LOG_BACKEND_UART=y

# I want synchronous logging (no queue and print later)
CONFIG_LOG_MODE_IMMEDIATE=y

# I want to be able to influnce what gets logged at runtime (via shell)
CONFIG_LOG_RUNTIME_FILTERING=y

# I want the default to be that every module prints all levels
# 0 = off
# 1 = error
# 2 = error and warning
# 3 = error and warning and info
# 4 = error and warning and info and debug
CONFIG_LOG_DEFAULT_LEVEL=4

# show function name in output
CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y
CONFIG_LOG_FUNC_NAME_PREFIX_WRN=y
CONFIG_LOG_FUNC_NAME_PREFIX_INF=y
CONFIG_LOG_FUNC_NAME_PREFIX_DBG=y

# color it
CONFIG_LOG_BACKEND_SWO=y
CONFIG_LOG_BACKEND_SHOW_COLOR=y

# redirect printk to the log
# I am disabling this, because when shell enabled, prink output was
# prefixed with shell.  now I see it on serial with no prefix.
#CONFIG_LOG_PRINTK=y




# ##### Shell #####

# # I want the shell interface to operate
CONFIG_SHELL=y




#####################################################################
# set up stats
#####################################################################
CONFIG_THREAD_RUNTIME_STATS=y




#####################################################################
# set up pwm
#####################################################################
CONFIG_PWM=y


#####################################################################
# set up usb
#####################################################################
# CONFIG_USB_DEVICE_STACK=y
# CONFIG_USB_DEVICE_PRODUCT="Glow Bike"
# CONFIG_USB_MASS_STORAGE=y


#####################################################################
# set up disk
#####################################################################

# CONFIG_DISK_ACCESS=y
# CONFIG_DISK_DRIVERS=y


# CONFIG_FLASH=y
# CONFIG_DISK_DRIVER_FLASH=y


# CONFIG_FLASH_MAP=y
# CONFIG_FLASH_PAGE_LAYOUT=y
# CONFIG_MPU_ALLOW_FLASH_WRITE=y


# CONFIG_FILE_SYSTEM=y
# CONFIG_FAT_FILESYSTEM_ELM=y
# CONFIG_FS_FATFS_READ_ONLY=n
# CONFIG_FS_FATFS_MOUNT_MKFS=y




#####################################################################
# set multi-protocol service layer (multiple radio protocol support)
#####################################################################
# enable multi-protocol service layer (doesn't work with CONFIG_BT_LL_SW_SPLIT seemingly)
CONFIG_MPSL=y
# limit to 1 for now, that's all I'm using atm
CONFIG_MPSL_TIMESLOT_SESSION_COUNT=2
# let's see during dev
CONFIG_MPSL_LOG_LEVEL_DBG=y
# allow myself to catch assert errors in my own code (and still die)
CONFIG_MPSL_ASSERT_HANDLER=y

#####################################################################
# set up ble
#####################################################################
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_BT_RX_STACK_SIZE=4096
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_BT=y
CONFIG_BT_HCI=y
CONFIG_BT_CTLR=y
# Use the zephyr BLE implementation instead of the SoftDevice from Nordic
# CONFIG_BT_LL_SW_SPLIT=y



CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_BROADCASTER=y
CONFIG_BT_OBSERVER=y

# Enable Security Manager Protocol (SMP), making it possible to pair devices over LE
CONFIG_BT_SMP=y

# allow me to set the code to show to users on their device via bt_passkey_set()
CONFIG_BT_FIXED_PASSKEY=y

# let me see more details about the other side of the connection
CONFIG_BT_REMOTE_INFO=y

# let me change and see changes to the PHY layer
CONFIG_BT_USER_PHY_UPDATE=y

# let me change and see changes to the data length (MTU?)
CONFIG_BT_USER_DATA_LEN_UPDATE=y

# let me express preferences for phy modulation
CONFIG_BT_CTLR_PHY_CODED=y
# CONFIG_BT_HCI_VS_EXT=y
# CONFIG_BT_PHY_UPDATE=y

# let me change (my) and measure (their) transmit power levels
CONFIG_BT_CTLR_CONN_RSSI=y
# CONFIG_BT_CTLR_ADVANCED_FEATURES=y    ; # triggers warning, needed?
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
# ?
# CONFIG_BT_TPS=y



#####################################################################
# set up ESB (extended shock burst)
#####################################################################
CONFIG_ESB=y
CONFIG_NCS_SAMPLES_DEFAULTS=y




# fat frames
# https://devzone.nordicsemi.com/f/nordic-q-a/84226/increasing-ble-mtu-data-length-for-notifications-on-ncs-v1-8
# https://github.com/zephyrproject-rtos/zephyr/issues/25648
# Uncoded LE Data Packet Format:
#
# | Preamble | Access  |                           PDU (2 to 257 bytes)                         |   CRC   |
# |          | Address | LL Header |       Link Layer PDU (0 to 251 bytes)           |    MIC   |         |
# |          |         |           | L2CAP Header |      L2CAP PDU (0-247 bytes)     | Optional |         |
# |          |         |           |              |    ATT Header    |  ATT Payload  |          |         |
# |          |         |           |              | OpCode | Handle  |               |          |         |
# |  1 byte  | 4 bytes |  2 bytes  |   4 bytes    | 1 byte | 2 bytes | 0 - 244 bytes | 4 bytes  | 3 bytes |
#
# L2CAP can be larger than 247 bytes, but will be split across multiple LL packets as a result.
# ATT payloads can be larger than 244 bytes, but will be split across multiple LL packets as a result.
# 
#
# Kconfig Symbols:
#     CONFIG_BT_CTLR_DATA_LENGTH_MAX : Maximum payload size of the Link Layer PDU
#     CONFIG_BT_BUF_ACL_TX_SIZE      : Maximum L2CAP PDU is limited to this value - 4
#     CONFIG_BT_BUF_ACL_RX_SIZE      : Maximum L2CAP PDU is limited to this value - 4
#     CONFIG_BT_L2CAP_TX_MTU         : Maximum L2CAP PDU size for transmission
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
# CONFIG_BT_GATT_AUTO_UPDATE_MTU=


CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC=y
CONFIG_BT_DEVICE_NAME_DYNAMIC=y


# unsure these
#CONFIG_BT_EXT_ADV=y

CONFIG_PM=n


# settings subsystem required as well for bluetooth libs (for bonding)
CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y

# major memory hog, ~2k per connection (wtf)
CONFIG_BT_MAX_CONN=20




#####################################################################
# allow access to flash for read/write (non-volatile memory store)
#####################################################################
# CONFIG_MAIN_STACK_SIZE=2048

# CONFIG_MPU_ALLOW_FLASH_WRITE=y

# CONFIG_FLASH=y
# CONFIG_FLASH_MAP=y
# CONFIG_FLASH_PAGE_LAYOUT=y

# CONFIG_FILE_SYSTEM=y
# CONFIG_FILE_SYSTEM_LITTLEFS=y


#####################################################################
# attempt to get RTT working, but no, still not working
#####################################################################
# CONFIG_USE_SEGGER_RTT=y

#####################################################################
# disable startup banner printed to serial by zephyr
#####################################################################
CONFIG_BOOT_BANNER=n




Thanks.

Related