Electronics Design AU
nRFSolved

nRF52840 BLE advertising not starting — device not showing up in scanner after bt_enable completes

5 min read3 replies
Original Question

Asked by stale_biscuit_03 ·

Trying my first Zephyr/NCS project on an nRF52840 DK after mostly doing ESP32 stuff. Following the BLE GATT peripheral guide — got the custom GATT service defined, code compiles without errors, flashes fine (the LED blink in main() is running), but the device never shows up when I scan in nRF Connect for Mobile. Not even as an unconnectable advertisement.

My main looks like this:

int main(void)
{
    int err = bt_enable(NULL);
    if (err) {
        printk("bt_enable failed: %d\n", err);
        return err;
    }
    start_advertising();

    while (1) {
        k_sleep(K_SECONDS(1));
    }
}

bt_enable() isn't returning an error (I checked). start_advertising() is being called. But I can scan for 5 minutes and never see the device. Running NCS 2.6.1.

What am I missing? Feels like something obvious but I can't find it.

From the knowledge baseHow Do You Implement a BLE Peripheral with Custom GATT Services on nRF52 Using Zephyr?

3 Replies

zephyr_devotee
Accepted Answer

Classic first-project symptom, and there's a short list of causes. Work through these in order.

1. Check prj.conf first

The most common reason a nRF52840 project compiles, runs, and produces no BLE advertisements is missing Kconfig symbols. You need at minimum:

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="My Device"
CONFIG_BT_MAX_CONN=1

CONFIG_BT_PERIPHERAL=y is the critical one. Without it, the Zephyr BT stack compiles in but the peripheral role is not built — bt_le_adv_start() with BT_LE_ADV_CONN will return a non-zero error code because connectable advertising requires peripheral role support. The device can't advertise as connectable if Kconfig doesn't know it's a peripheral.

2. Check the return value of bt_le_adv_start()

This is the call that actually starts advertising, and it's easy to write without error handling:

static void start_advertising(void)
{
    int err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
    if (err) {
        printk("bt_le_adv_start failed: %d\n", err);
        return;
    }
    printk("Advertising started OK\n");
}

If you haven't added that check, you won't know it's failing silently. Add it now — the error code will tell you exactly what's wrong. -22 (-EINVAL) usually means role or parameter mismatch; -12 (-ENOMEM) means advertising set allocation failed.

3. Make sure you can see printk output

On the nRF52840 DK with NCS, printk() goes to the UART console exposed via the J-Link USB VirtualCOM port. Open a serial terminal (115200 baud, 8N1) on the port that appears when you plug in the DK USB cable. If you haven't opened that terminal, you won't see any of the error messages above.

You can also enable the Bluetooth debug log to get more detail from the stack itself:

CONFIG_BT_DEBUG_LOG=y

That will print controller init progress and any internal advertising errors to the same console.

4. Board target

Confirm you're building with the right target. For NCS v2.5+:

west build -b nrf52840dk/nrf52840 .

Note the slash separator — older NCS versions used an underscore. The NCS setup guide covers build targets in detail. An incorrect board target can cause the radio hardware to not initialise cleanly, which silently prevents advertising even if the software path looks correct.

If you've already built with a wrong config and want a clean state:

west build --pristine -b nrf52840dk/nrf52840 .

Start with prj.conf and the bt_le_adv_start return value. One of those two resolves this for the vast majority of people seeing exactly what you're describing.

nrf_nordic_nerd

One more angle on the Kconfig issue: if CONFIG_BT is not set (not just =n, but absent entirely), the build may still succeed if the BT headers are pulled in transitively. You end up with bt_enable() resolving to a stub that returns 0, which is why err looks fine, but the actual BLE controller is never started.

Double-check that CONFIG_BT=y is explicitly in your prj.conf, not just assumed from a dependency. Run west build -- -DCONFIG_BT=y once to confirm the symbol is being picked up, then put it in prj.conf properly.

Also worth doing after any Kconfig change: west build --pristine. CMake caches configuration state between builds and can carry a stale Kconfig resolution into the next build if you only add a line to prj.conf without clearing. I've seen this catch people on Nordic DevZone more times than it should.

The nRF Connect for Mobile app is also worth cross-checking: make sure "Show only connectable devices" isn't filtered on (top right of the scanner view). Some people rule out their device because they're filtering to connectable-only before the peripheral role issue is fixed.

ble_mesh_maven

The Kconfig items are almost certainly the problem here, but since the question page notes that bt_enable(NULL) is synchronous — worth clarifying one edge case so it doesn't bite later.

The NULL form blocks in main() once the Zephyr scheduler is running, which covers the typical application case. Where it gets subtle: if you ever call bt_enable() from a SYS_INIT() callback or a high-priority initialisation thread that runs before the system workqueue is fully online, the blocking semantics aren't guaranteed and you can end up calling bt_le_adv_start() before the controller is ready. The callback form is explicit about this:

static void bt_ready(int err)
{
    if (err) {
        printk("bt_enable failed: %d\n", err);
        return;
    }
    start_advertising();
}

int main(void)
{
    bt_enable(bt_ready);
    /* application work continues here while BT initialises */
}

With the callback, start_advertising() is guaranteed to run only after the controller confirms readiness — no timing assumptions. For a simple main() loop the NULL form is fine and the guide is correct. But if you're building something where BT initialisation competes with other early-startup work, switch to the callback and remove the ambiguity.

Not the cause of your issue — fix prj.conf first — but good to know before the project gets more complex.

Related Discussions