·15 min read·BASHCAT 技術團隊·embedded

Nordic nRF54L15 Bluetooth 6.0 Development Guide: From Beginner to Expert

An in-depth exploration of Bluetooth 6.0 development with the Nordic nRF54L15, covering Channel Sounding indoor positioning, Matter protocol integration, and low-power design practices.

#Nordic#nRF54L15#Bluetooth 6.0#Embedded Development#Channel Sounding#IoT

Nordic nRF54L15 Bluetooth 6.0 Development Guide: From Beginner to Expert

Nordic Semiconductor's latest nRF54L15 is the first SoC to support the Bluetooth 6.0 specification, bringing revolutionary advancements to IoT applications. This article provides an in-depth look at developing innovative Bluetooth applications with the nRF54L15, along with practical hands-on experience.

Why Choose the nRF54L15?

Core Advantages

Bluetooth 6.0 Support:

  • Channel Sounding (CS) - Centimeter-level indoor positioning accuracy
  • Enhanced Security - Stronger security capabilities
  • Lower Power Consumption - 50% extended battery life

Powerful Processing Capability:

CPU: 128MHz ARM Cortex-M33
RAM: 256KB
Flash: 1536KB
RF Performance: +8dBm output power
Sensitivity: -98dBm @ 1Mbps

Multi-Protocol Support:

  • Bluetooth LE (5.4 + 6.0)
  • Thread
  • Zigbee
  • Matter
  • 802.15.4

Development Environment Setup

Required Tool Installation

nRF Connect SDK Installation:

# Install the West tool
pip3 install west

# Initialize nRF Connect SDK
west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.6.0
cd nrf
west update

# Install dependencies
pip3 install -r zephyr/scripts/requirements.txt
pip3 install -r nrf/scripts/requirements.txt
pip3 install -r bootloader/mcuboot/scripts/requirements.txt

Development Board Setup:

# Connect the nRF54L15 DK
# Install nRF Command Line Tools
brew install --cask nrf-command-line-tools

# Verify development board connection
nrfjprog --version
nrfjprog -f NRF54L --ids

Channel Sounding Indoor Positioning Implementation

Basic Architecture

Channel Sounding is the killer feature of Bluetooth 6.0, enabling centimeter-level indoor positioning accuracy.

System Architecture:

┌─────────────┐     Channel      ┌─────────────┐
│  Initiator  │ ←─── Sounding ──→│  Reflector  │
│  (nRF54L15) │                   │  (nRF54L15) │
└─────────────┘                   └─────────────┘
       ↓                                 ↓
   [Measure Phase]                  [Measure Phase]
       ↓                                 ↓
    ┌──────────────────────────────────┐
    │    Calculate Distance (ToF + PDoA)│
    └──────────────────────────────────┘

Core Implementation Code:

// channel_sounding.c
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/cs.h>

#define CS_PROCEDURE_INTERVAL_MS 100
#define CS_SUBEVENT_COUNT 8
#define CS_MODE_3_STEP_COUNT 10

static struct bt_le_cs_config cs_config = {
    .mode = BT_LE_CS_MODE_3,
    .phy = BT_LE_CS_PHY_1M,
    .rtt_type = BT_LE_CS_RTT_AA_ONLY,
    .role = BT_LE_CS_ROLE_INITIATOR,
    .main_mode_steps = CS_MODE_3_STEP_COUNT,
};

void cs_results_callback(struct bt_conn *conn,
                        struct bt_le_cs_subevent_result *result)
{
    // Calculate Time of Flight (ToF)
    int32_t tof_ns = result->procedure_done_status.tof;

    // Convert to distance (speed of light ≈ 30 cm/ns)
    float distance_cm = (tof_ns * 0.3f) / 2.0f;

    printk("CS Result: Distance = %.2f cm\n", distance_cm);
    printk("RSSI = %d dBm\n", result->rssi);

    // Calculate Phase-based Direction (PDoA)
    if (result->procedure_done_status.pdoa_valid) {
        float angle = result->procedure_done_status.pdoa * 0.01f;
        printk("Direction: %.2f degrees\n", angle);
    }
}

int init_channel_sounding(struct bt_conn *conn)
{
    int err;

    // Set CS configuration
    err = bt_le_cs_set_procedure_parameters(conn, &cs_config);
    if (err) {
        printk("Failed to set CS parameters: %d\n", err);
        return err;
    }

    // Register result callback
    static struct bt_le_cs_cb cs_callbacks = {
        .result = cs_results_callback,
    };
    bt_le_cs_register_cb(&cs_callbacks);

    // Start CS procedure
    err = bt_le_cs_start_procedure(conn);
    if (err) {
        printk("Failed to start CS: %d\n", err);
        return err;
    }

    return 0;
}

Indoor Positioning Use Case

Smart Warehouse Asset Tracking:

// asset_tracking.c
#include "channel_sounding.h"

#define MAX_ANCHORS 4
#define TRILATERATION_MIN_ANCHORS 3

struct anchor_node {
    bt_addr_le_t addr;
    float x, y, z;  // Anchor coordinates
    float distance; // Measured distance
};

struct anchor_node anchors[MAX_ANCHORS];
int active_anchor_count = 0;

typedef struct {
    float x;
    float y;
    float z;
} position_t;

position_t calculate_position(struct anchor_node *anchors, int count)
{
    position_t pos = {0};

    if (count < TRILATERATION_MIN_ANCHORS) {
        printk("Insufficient anchors for positioning\n");
        return pos;
    }

    // Trilateration algorithm implementation
    // Using least squares method to solve
    float A[3][3] = {0};
    float b[3] = {0};

    for (int i = 0; i < count - 1; i++) {
        A[i][0] = 2 * (anchors[i+1].x - anchors[0].x);
        A[i][1] = 2 * (anchors[i+1].y - anchors[0].y);
        A[i][2] = 2 * (anchors[i+1].z - anchors[0].z);

        b[i] = (pow(anchors[i+1].distance, 2) - pow(anchors[0].distance, 2))
             - (pow(anchors[i+1].x, 2) - pow(anchors[0].x, 2))
             - (pow(anchors[i+1].y, 2) - pow(anchors[0].y, 2))
             - (pow(anchors[i+1].z, 2) - pow(anchors[0].z, 2));
    }

    // Solve linear system of equations (simplified version)
    // In production, more robust numerical methods should be used

    return pos;
}

void asset_tracking_task(void)
{
    while (1) {
        // Scan all anchor nodes
        for (int i = 0; i < active_anchor_count; i++) {
            // Perform Channel Sounding
            // Update distance measurements
        }

        // Calculate position
        position_t position = calculate_position(anchors, active_anchor_count);

        printk("Asset Position: (%.2f, %.2f, %.2f)\n",
               position.x, position.y, position.z);

        k_sleep(K_MSEC(CS_PROCEDURE_INTERVAL_MS));
    }
}

Matter Protocol Integration

Matter over Thread Implementation

Matter is the next-generation smart home standard, natively supported by the nRF54L15.

Basic Setup:

// matter_device.c
#include <app/server/Server.h>
#include <platform/CHIPDeviceLayer.h>

using namespace chip;
using namespace chip::app;

class LightBulbDevice {
public:
    void Init() {
        // Initialize Matter stack
        chip::DeviceLayer::PlatformMgr().InitChipStack();

        // Set device information
        chip::DeviceLayer::ConfigurationMgr().StoreVendorId(0xFFF1);
        chip::DeviceLayer::ConfigurationMgr().StoreProductId(0x8001);

        // Start Matter server
        chip::Server::GetInstance().Init();
    }

    void SetOnOff(bool on) {
        m_isOn = on;
        // Update Matter attribute
        UpdateMatterAttribute();
    }

private:
    bool m_isOn = false;

    void UpdateMatterAttribute() {
        // Update OnOff Cluster
        chip::app::Clusters::OnOff::Attributes::OnOff::Set(
            1, /* endpoint */
            m_isOn
        );
    }
};

Commissioning Flow:

void matter_commissioning_window(void)
{
    printk("Opening commissioning window...\n");

    // Open commissioning window (3 minutes)
    chip::Server::GetInstance().GetCommissioningWindowManager()
        .OpenBasicCommissioningWindow(chip::System::Clock::Seconds16(180));

    // Generate QR Code pairing code
    char qr_code[256];
    chip::ManualSetupPayloadGenerator generator;
    generator.payloadString(qr_code, sizeof(qr_code));

    printk("QR Code: %s\n", qr_code);
}

Low-Power Design Practices

Power Management Strategy

Dynamic Power Management:

// power_management.c
#include <zephyr/pm/pm.h>
#include <zephyr/pm/policy.h>

// Define power states
enum power_mode {
    POWER_MODE_ACTIVE,      // Full-speed operation
    POWER_MODE_LOW_POWER,   // Low-power mode
    POWER_MODE_DEEP_SLEEP,  // Deep sleep
};

static enum power_mode current_mode = POWER_MODE_ACTIVE;

void set_power_mode(enum power_mode mode)
{
    switch (mode) {
    case POWER_MODE_ACTIVE:
        // Full speed 128MHz
        pm_constraint_set(PM_STATE_ACTIVE);
        break;

    case POWER_MODE_LOW_POWER:
        // Reduce frequency to 16MHz, peripheral clock gating
        pm_constraint_release(PM_STATE_ACTIVE);
        pm_constraint_set(PM_STATE_RUNTIME_IDLE);
        break;

    case POWER_MODE_DEEP_SLEEP:
        // Deep sleep, retain only RTC
        pm_constraint_release(PM_STATE_RUNTIME_IDLE);
        break;
    }

    current_mode = mode;
    printk("Power mode changed to: %d\n", mode);
}

// Adaptive power management
void adaptive_power_management(void)
{
    static uint32_t idle_counter = 0;

    // Monitor system activity
    if (is_ble_active() || is_data_processing()) {
        idle_counter = 0;
        set_power_mode(POWER_MODE_ACTIVE);
    } else {
        idle_counter++;

        if (idle_counter > 100) {
            // Idle for more than 10 seconds, enter deep sleep
            set_power_mode(POWER_MODE_DEEP_SLEEP);
        } else if (idle_counter > 10) {
            // Idle for more than 1 second, enter low-power mode
            set_power_mode(POWER_MODE_LOW_POWER);
        }
    }
}

Advertising Optimization:

// Reduce advertising frequency to conserve power
static const struct bt_le_adv_param adv_param = {
    .id = BT_ID_DEFAULT,
    .options = BT_LE_ADV_OPT_CONNECTABLE,
    .interval_min = BT_GAP_ADV_SLOW_INT_MIN,  // 1 second
    .interval_max = BT_GAP_ADV_SLOW_INT_MAX,  // 1.28 seconds
    .peer = NULL,
};

// Connection parameter optimization
static const struct bt_le_conn_param conn_param = {
    .interval_min = 400,  // 500ms
    .interval_max = 400,  // 500ms
    .latency = 4,         // Allow skipping 4 connection events
    .timeout = 400,       // 4-second supervision timeout
};

Measured Current Consumption

Power Consumption Across Different Modes:

Operating Mode           Current Draw    Use Case
─────────────────────────────────────────────
TX @ +8dBm             7.5 mA       Maximum power transmission
RX                     5.2 mA       Data reception
CPU Active @ 128MHz    1.8 mA       Computation processing
CPU Idle               0.9 mA       Waiting for events
System ON (IDLE)       2.5 μA       Maintaining connection
Deep Sleep            0.3 μA        Deep sleep

Battery Life Calculation:

// battery_calculator.c

#define BATTERY_CAPACITY_MAH 220  // CR2032 battery capacity

float calculate_battery_life_days(float avg_current_ua)
{
    float battery_capacity_uah = BATTERY_CAPACITY_MAH * 1000.0f;
    float life_hours = battery_capacity_uah / avg_current_ua;
    float life_days = life_hours / 24.0f;

    return life_days;
}

void estimate_device_lifetime(void)
{
    // Typical usage scenario
    // - Advertise once per second (1ms)
    // - Transmit data once per minute (100ms)
    // - Deep sleep for the remaining time

    float tx_current = 7500;  // μA
    float rx_current = 5200;  // μA
    float sleep_current = 0.3; // μA

    float tx_duty = 0.001;    // 0.1% (1ms/1s)
    float rx_duty = 0.0017;   // 0.17% (100ms/60s)
    float sleep_duty = 0.997; // 99.7%

    float avg_current = (tx_current * tx_duty) +
                       (rx_current * rx_duty) +
                       (sleep_current * sleep_duty);

    float battery_life = calculate_battery_life_days(avg_current);

    printk("Average current: %.2f μA\n", avg_current);
    printk("Estimated battery life: %.1f days (%.1f months)\n",
           battery_life, battery_life / 30.0f);
}

Security Implementation

Bluetooth LE Secure Connections

// security.c
#include <zephyr/bluetooth/conn.h>

static void security_changed(struct bt_conn *conn, bt_security_t level,
                            enum bt_security_err err)
{
    char addr[BT_ADDR_LE_STR_LEN];

    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

    if (!err) {
        printk("Security changed: %s level %u\n", addr, level);
    } else {
        printk("Security failed: %s level %u err %d\n", addr, level, err);
    }
}

static struct bt_conn_auth_cb auth_callbacks = {
    .passkey_display = auth_passkey_display,
    .passkey_entry = auth_passkey_entry,
    .cancel = auth_cancel,
};

void init_security(void)
{
    // Register security callbacks
    bt_conn_auth_cb_register(&auth_callbacks);

    // Set default security level
    bt_conn_set_security(conn, BT_SECURITY_L4);
}

Debugging Tips

RTT Log Output

// Enable Segger RTT real-time logging
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=n

// Use RTT Viewer to monitor logs
$ JLinkRTTViewer

Power Analysis

# Use Power Profiler Kit II
$ nrfutil device profile --device-family NRF54L

# Or use an oscilloscope to measure current
# Insert a 10Ω resistor in series with the VDD pin to measure voltage drop

Real-World Application Examples

1. Smart Door Lock

Key Features:

  • Channel Sounding distance verification
  • ECDH key exchange
  • Matter standard integration
  • Low-power design (battery life >2 years)

2. Industrial Asset Tracking

Key Features:

  • 10cm positioning accuracy
  • Mesh network topology
  • LoRa long-range backhaul
  • IP67 protection rating

3. Health Wearable Device

Key Features:

  • Heart rate / SpO2 monitoring
  • Activity tracking
  • Bluetooth 6.0 Find My functionality
  • 14-day battery life

Conclusion

The Nordic nRF54L15 represents a new milestone in Bluetooth technology, with its Channel Sounding feature delivering breakthrough advances in indoor positioning. Key advantages include:

  1. Centimeter-Level Positioning Accuracy - Revolutionizing indoor positioning applications
  2. Ultra-Low Power Consumption - 50% improvement in battery life
  3. Multi-Protocol Support - A single SoC supporting BLE/Thread/Zigbee/Matter
  4. Robust Security - Bluetooth 6.0 enhanced security features
  5. Rich Ecosystem - Comprehensive Nordic SDK and community support

At BASHCAT, we possess extensive experience in Nordic Bluetooth development and can help you rapidly transform innovative ideas into real products. Feel free to contact us to discuss your Bluetooth project requirements.

Further Reading

$ tail -n 1 /var/log/bashcat/posts

More from the workshop.