Cannot scan my bare metal advertising on iOS or python

Hi Nordic DevZone !

I'm facing an issue that I cannot manage to fix by myself.

So here is the brief: I'm trying to create a BLE beacon that will send some analysis results in the manufacturer data. The beacon uses piezo electric energy harvesting so the current consumption is very important.

I based my code on this bare metal beacon example to comply with my energetic limitations: solar_sensor_beacon

My main issue is that the device is well showing up on NRF Connect App on Android but not on iOS version of the app. It is not showing up when I scan using a python BLE library.

The only way I managed to discover the device is either on Android NRF app or by creating a small C# app on windows.

So here is my ble source code:

#include "bluetooth_le.h"

#include "hal_radio.h"
#include "../timer/hal_timer.h"
#include "../clock/hal_clock.h"
#include "../adc/adc.h"
#include "nrf.h"
#include "string.h"


/* Advertising PDU configuration */
#define BD_ADDR_OFFS                (3)        /* BLE device address offset in the advertising PDU */
#define M_BD_ADDR_SIZE              (6)        /* BLE device address size */

/* Timing configuration */
#define HFCLK_STARTUP_TIME_US       (1600)     /* Time in microseconds to start up the HF clock */
#define INTERVAL_US                 (100000)  /* The time in microseconds between advertising events */
#define INITIAL_TIMEOUT             (INTERVAL_US)

/* Radio globals */
static bool volatile m_radio_isr_called;        /* Indicates that the radio ISR has executed */
static bool volatile m_rtc_isr_called;          /* Indicates that the RTC ISR has executed */
static uint32_t m_time_us;                      /* Keeps track of the latest scheduled point in time */
static uint8_t m_adv_pdu[40];                   /* The RAM representation of the advertising PDU */


// Helper function to convert a byte to hex string (without null terminator)
static void byte_to_hex(uint8_t byte, char* hex_str)
{
    const char hex_chars[] = "0123456789ABCDEF";
    hex_str[0] = hex_chars[(byte >> 4) & 0x0F];
    hex_str[1] = hex_chars[byte & 0x0F];
}

// Helper function to create device name with SOL + last 3 bytes of MAC
static uint8_t format_device_name(uint8_t* addr, char* name_str)
{
    // Start with "SOL"
    name_str[0] = 'S';
    name_str[1] = 'O';
    name_str[2] = 'L';
    
    // Add only 2 bytes of MAC address as hex (4 characters)
    byte_to_hex(addr[1], &name_str[3]);  // 2nd byte  
    byte_to_hex(addr[0], &name_str[5]);  // 1st byte (LSB)
    
    return 7; // Length: "SOL" + 4 hex characters = 7 total (reduced from 9)
}

/* Waits for the next NVIC event */
static void __INLINE cpu_wfe(void)
{
    __WFE();
    __SEV();
    __WFE();
}

/* RTC isr handler */
void RTC0_IRQHandler(void)
{
    NRF_RTC0->EVTENCLR = (RTC_EVTENCLR_COMPARE0_Enabled << RTC_EVTENCLR_COMPARE0_Pos);
    NRF_RTC0->INTENCLR = (RTC_INTENCLR_COMPARE0_Enabled << RTC_INTENCLR_COMPARE0_Pos);
    NRF_RTC0->EVENTS_COMPARE[0] = 0;
    
    m_rtc_isr_called = true;    
}

/* Radio interrupt handler */
void RADIO_IRQHandler(void)
{
    NRF_RADIO->EVENTS_DISABLED = 0;
    m_radio_isr_called = true;    
}

/* Initializes the beacon advertising PDU */
static void m_beacon_pdu_init(uint8_t * p_beacon_pdu)
{
    p_beacon_pdu[0] = 0x42;   // ADV_NONCONN_IND (0x02)
    p_beacon_pdu[1] = 0;      // Length placeholder
    //p_beacon_pdu[2] = 0;    // Reserved
}

/* Sets up the device address in the PDU */
static void m_beacon_pdu_bd_addr_default_set(uint8_t * p_beacon_pdu)
{
    // Use device address from FICR
    uint8_t addr[6];

    addr[0] = (NRF_FICR->DEVICEADDR[0]      ) & 0xFF;
    addr[1] = (NRF_FICR->DEVICEADDR[0] >>  8) & 0xFF;
    addr[2] = (NRF_FICR->DEVICEADDR[0] >> 16) & 0xFF;
    addr[3] = (NRF_FICR->DEVICEADDR[0] >> 24) & 0xFF;
    addr[4] = (NRF_FICR->DEVICEADDR[1]      ) & 0xFF;
    addr[5] = (NRF_FICR->DEVICEADDR[1] >>  8) & 0xFF;

    // Copy address to PDU
    memcpy(&p_beacon_pdu[BD_ADDR_OFFS], addr, 6);
    
    // Set TxAdd bit
    p_beacon_pdu[0] &= ~(1 << 5);  // Public address (TxAdd=0)
}


static void m_beacon_pdu_custom_data_set(uint8_t * p_beacon_pdu, adc_analysis_result_t* p_analysis_result)
{
    uint8_t adv_data_index = BD_ADDR_OFFS + M_BD_ADDR_SIZE;
    
    // Get the MAC address that was already set in the PDU
    uint8_t addr[6];
    memcpy(addr, &p_beacon_pdu[BD_ADDR_OFFS], 6);
    
    // Format device name as "SOL" + last 3 bytes of MAC
    char device_name[9];
    uint8_t name_len = format_device_name(addr, device_name);
    
    // Flags - mandatory for discovery
    p_beacon_pdu[adv_data_index++] = 0x02; // Length
    p_beacon_pdu[adv_data_index++] = 0x01; // Flags AD type
    p_beacon_pdu[adv_data_index++] = 0x06; // LE General Discoverable + BR/EDR Not Supported
    
    // Complete Local Name containing device name
    p_beacon_pdu[adv_data_index++] = name_len + 1; // Length (string length + AD type byte)
    p_beacon_pdu[adv_data_index++] = 0x09; // Complete Local Name AD type
    
    // Copy device name string
    for (uint8_t i = 0; i < name_len; i++) {
        p_beacon_pdu[adv_data_index++] = (uint8_t)device_name[i];
    }
    
    // Manufacturer Specific Data containing our ADC analysis
    p_beacon_pdu[adv_data_index++] = 0x09; // Length (2 bytes company ID + 1 type + 6 data bytes)
    p_beacon_pdu[adv_data_index++] = 0xFF; // Manufacturer Specific Data AD type
    
    // Company ID (using Nordic's ID as example, change as needed)
    p_beacon_pdu[adv_data_index++] = 0x59; // Nordic Semiconductor Company ID LSB
    p_beacon_pdu[adv_data_index++] = 0x00; // Nordic Semiconductor Company ID MSB
    
    // Data type identifier (custom)
    p_beacon_pdu[adv_data_index++] = 0xAD; // Custom type for ADC data
    
    // ADC Analysis Results - 6 bytes total
    // Peak values (3 bytes)
    p_beacon_pdu[adv_data_index++] = p_analysis_result->peak_ain0;
    p_beacon_pdu[adv_data_index++] = p_analysis_result->peak_ain5;
    p_beacon_pdu[adv_data_index++] = p_analysis_result->peak_ain7;
    
    // Timing values (3 bytes)
    p_beacon_pdu[adv_data_index++] = p_analysis_result->timing_ain0;
    p_beacon_pdu[adv_data_index++] = p_analysis_result->timing_ain5;
    p_beacon_pdu[adv_data_index++] = p_analysis_result->timing_ain7;
    
    // Update total PDU length
    p_beacon_pdu[1] = adv_data_index - BD_ADDR_OFFS; // Total length minus header (3 bytes)
}


void m_beacon_init(adc_analysis_result_t* p_analysis_result)
{
    /* Initialize the advertising PDU */
    m_beacon_pdu_init(&(m_adv_pdu[0]));
    m_beacon_pdu_bd_addr_default_set(&(m_adv_pdu[0]));
    m_beacon_pdu_custom_data_set(&(m_adv_pdu[0]), p_analysis_result);
}


/* Sends an advertising PDU on the given channel index */
static void send_one_packet(uint8_t channel_index)
{
    m_radio_isr_called = false;
    hal_radio_channel_index_set(channel_index);
    hal_radio_send(m_adv_pdu);
    while (!m_radio_isr_called)
    {
        cpu_wfe();
    }
    
    /* Small delay */
    for (uint8_t i = 0; i < 9; i++)
    {
        __NOP();
    }
}


/* Main beacon handling function */
void beacon_handler(void)
{
    hal_radio_reset();

    hal_timer_start();
    
    m_time_us = INITIAL_TIMEOUT - HFCLK_STARTUP_TIME_US; 

    while (1)
    {
        /* Wait until it's time to advertise */
        m_rtc_isr_called = false;
        hal_timer_timeout_set(m_time_us);
        while (!m_rtc_isr_called)
        {
            cpu_wfe();
        }
        
        /* Enable high-frequency clock */
        hal_clock_hfclk_enable();
        
        /* Wait for HF clock to stabilize */
        m_rtc_isr_called = false;
        m_time_us += HFCLK_STARTUP_TIME_US; 
        hal_timer_timeout_set(m_time_us);
        while (!m_rtc_isr_called)
        {
            cpu_wfe();
        }
        
        /* Send advertising packets on all three channels */
        send_one_packet(37);
        send_one_packet(38);
        send_one_packet(39);
        
        /* Disable high-frequency clock to save power */
        hal_clock_hfclk_disable();
        
        /* Schedule next advertising event */
        m_time_us = m_time_us + (INTERVAL_US - HFCLK_STARTUP_TIME_US); 
    }
}

And here is the hal_radio source code (same as the example):

#include "nrf.h"


uint8_t access_address[4] = {0xD6, 0xBE, 0x89, 0x8E};
uint8_t seed[3] = {0x55, 0x55, 0x55};


/**@brief The maximum possible length in device discovery mode. */
#define DD_MAX_PAYLOAD_LENGTH         (31 + 6)


/**@brief The default SHORTS configuration. */
#define DEFAULT_RADIO_SHORTS                                             \
(                                                                        \
    (RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos) | \
    (RADIO_SHORTS_END_DISABLE_Enabled << RADIO_SHORTS_END_DISABLE_Pos)   \
)


/**@brief The default CRC init polynominal. 

   @note Written in little endian but stored in big endian, because the BLE spec. prints
         is in little endian but the HW stores it in big endian. */
#define CRC_POLYNOMIAL_INIT_SETTINGS  ((0x5B << 0) | (0x06 << 8) | (0x00 << 16))


/**@brief This macro converts the given channel index to a freuency
          offset (i.e. offset from 2400 MHz).

  @param index - the channel index to be converted into frequency offset.
  
  @return The frequency offset for the given index. */
#define CHANNEL_IDX_TO_FREQ_OFFS(index) \
    (((index) == 37)?\
        (2)\
        :\
            (((index) == 38)?\
                (26)\
            :\
                (((index) == 39)?\
                    (80)\
                :\
                    ((/*((index) >= 0) &&*/ ((index) <= 10))?\
                        ((index)*2 + 4)\
                    :\
                        ((index)*2 + 6)))))
                        

void hal_radio_channel_index_set(uint8_t channel_index)
{
    NRF_RADIO->FREQUENCY = CHANNEL_IDX_TO_FREQ_OFFS(channel_index);
    NRF_RADIO->DATAWHITEIV = channel_index;
}


void hal_radio_reset(void)
{
    NVIC_DisableIRQ(RADIO_IRQn);
    
    // Disable and reenable radio power
    NRF_RADIO->POWER = RADIO_POWER_POWER_Disabled << RADIO_POWER_POWER_Pos;
    NRF_RADIO->POWER = RADIO_POWER_POWER_Enabled << RADIO_POWER_POWER_Pos;

    // Set to maximum power +4dBm
    //NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg40dBm << RADIO_TXPOWER_TXPOWER_Pos;
    NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos4dBm << RADIO_TXPOWER_TXPOWER_Pos;


    NRF_RADIO->SHORTS = DEFAULT_RADIO_SHORTS;
    
    NRF_RADIO->PCNF0 =   (((1UL) << RADIO_PCNF0_S0LEN_Pos) & RADIO_PCNF0_S0LEN_Msk)
                       | (((2UL) << RADIO_PCNF0_S1LEN_Pos) & RADIO_PCNF0_S1LEN_Msk)
                       | (((6UL) << RADIO_PCNF0_LFLEN_Pos) & RADIO_PCNF0_LFLEN_Msk);

    NRF_RADIO->PCNF1 =   (((RADIO_PCNF1_ENDIAN_Little)        << RADIO_PCNF1_ENDIAN_Pos) & RADIO_PCNF1_ENDIAN_Msk)
                       | (((3UL)                              << RADIO_PCNF1_BALEN_Pos)  & RADIO_PCNF1_BALEN_Msk)
                       | (((0UL)                              << RADIO_PCNF1_STATLEN_Pos)& RADIO_PCNF1_STATLEN_Msk)
                       | ((((uint32_t)DD_MAX_PAYLOAD_LENGTH)  << RADIO_PCNF1_MAXLEN_Pos) & RADIO_PCNF1_MAXLEN_Msk)
                       | ((RADIO_PCNF1_WHITEEN_Enabled << RADIO_PCNF1_WHITEEN_Pos) & RADIO_PCNF1_WHITEEN_Msk);
    
    /* The CRC polynomial is fixed, and is set here. */
    /* The CRC initial value may change, and is set by */
    /* higher level modules as needed. */
    NRF_RADIO->CRCPOLY = (uint32_t)CRC_POLYNOMIAL_INIT_SETTINGS;
    NRF_RADIO->CRCCNF = (((RADIO_CRCCNF_SKIPADDR_Skip) << RADIO_CRCCNF_SKIPADDR_Pos) & RADIO_CRCCNF_SKIPADDR_Msk)
                      | (((RADIO_CRCCNF_LEN_Three)      << RADIO_CRCCNF_LEN_Pos)       & RADIO_CRCCNF_LEN_Msk);

    NRF_RADIO->RXADDRESSES  = ( (RADIO_RXADDRESSES_ADDR0_Enabled) << RADIO_RXADDRESSES_ADDR0_Pos);

    NRF_RADIO->MODE    = ((RADIO_MODE_MODE_Ble_1Mbit) << RADIO_MODE_MODE_Pos) & RADIO_MODE_MODE_Msk;

    NRF_RADIO->TIFS = 150;

    NRF_RADIO->PREFIX0 = access_address[3];
    NRF_RADIO->BASE0   = ( (((uint32_t)access_address[2]) << 24) 
                         | (((uint32_t)access_address[1]) << 16)
                         | (((uint32_t)access_address[0]) << 8) );

    NRF_RADIO->CRCINIT = ((uint32_t)seed[0]) | ((uint32_t)seed[1])<<8 | ((uint32_t)seed[2])<<16;

    NRF_RADIO->INTENSET = (RADIO_INTENSET_DISABLED_Enabled << RADIO_INTENSET_DISABLED_Pos);
    NVIC_ClearPendingIRQ(RADIO_IRQn);
    NVIC_EnableIRQ(RADIO_IRQn);
}


void hal_radio_send(uint8_t *p_data)
{
    NRF_RADIO->PACKETPTR = (uint32_t)&(p_data[0]);
    NRF_RADIO->EVENTS_DISABLED = 0;
    NRF_RADIO->TASKS_TXEN = 1;
}

Interesting note, if I scan the device using the WireShark NRF BLE Sniffer plugin the packet is marked as "malformed"

See here the dump of the wireshark capture frame:

Frame 1060161: 55 bytes on wire (440 bits), 55 bytes captured (440 bits) on interface COM21-4.4, id 0
    Section number: 1
    Interface id: 0 (COM21-4.4)
        Interface name: COM21-4.4
        Interface description: nRF Sniffer for Bluetooth LE COM21
    Encapsulation type: nRF Sniffer for Bluetooth LE (186)
    Arrival Time: Jun  3, 2025 13:43:16.649269000 Paris, Madrid (heure d’été)
    UTC Arrival Time: Jun  3, 2025 11:43:16.649269000 UTC
    Epoch Arrival Time: 1748950996.649269000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 0.000476000 seconds]
    [Time delta from previous displayed frame: 0.000476000 seconds]
    [Time since reference or first frame: 5952.052808000 seconds]
    Frame Number: 1060161
    Frame Length: 55 bytes (440 bits)
    Capture Length: 55 bytes (440 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: nordic_ble:btle:btcommon]
nRF Sniffer for Bluetooth LE
    Board: 21
    Header Version: 3, Packet counter: 58932
        Length of payload: 48
        Protocol version: 3
        Packet counter: 58932
        Packet ID: 2
    Length of packet: 10
    Flags: 0x01
        .... ...1 = CRC: Ok
        .... ..0. = Reserved: 0
        .... .0.. = Reserved: 0
        .... 0... = Address Resolved: No
        .000 .... = PHY: LE 1M (0)
        0... .... = Reserved: 0
    Channel Index: 38
    RSSI: -53 dBm
    Event counter: 0
    Timestamp: 2689380822µs
    [Packet time (start to end): 312µs]
    [Delta time (end to start): 164µs]
    [Delta time (start to start): 476µs]
Bluetooth Low Energy Link Layer
    Access Address: 0x8e89bed6
    Packet Header: 0x1d42 (PDU Type: ADV_NONCONN_IND, TxAdd: Random)
        .... 0010 = PDU Type: 0x2 ADV_NONCONN_IND
        ...0 .... = Reserved: 0
        ..0. .... = Reserved: 0
        .1.. .... = Tx Address: Random
        0... .... = Reserved: 0
        Length: 29
    Advertising Address: d7:0b:55:ef:16:30 (d7:0b:55:ef:16:30)
    Advertising Data
        Flags
            Length: 2
            Type: Flags (0x01)
            000. .... = Reserved: 0x0
            ...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0)
            .... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0)
            .... .1.. = BR/EDR Not Supported: true (0x1)
            .... ..1. = LE General Discoverable Mode: true (0x1)
            .... ...0 = LE Limited Discoverable Mode: false (0x0)
        Device Name: SOL1630
            Length: 8
            Type: Device Name (0x09)
            Device Name: SOL1630
        Manufacturer Specific
            Length: 9
            Type: Manufacturer Specific (0xff)
            Company ID: Nordic Semiconductor ASA (0x0059)
            Data: adb491dc4080
                [Expert Info (Note/Undecoded): Undecoded]
                    [Undecoded]
                    [Severity level: Note]
                    [Group: Undecoded]
[Malformed Packet: BT Common]
    [Expert Info (Error/Malformed): Malformed Packet (Exception occurred)]
        [Malformed Packet (Exception occurred)]
        [Severity level: Error]
        [Group: Malformed]

I tried troubleshooting this several times without any success so I turn to you hoping to find a solution.

Thanks a lot in advance for any help you guys could give me.

Best regards,

Romain.

  • Hi,

     

    It seems that you have changed the pdu by one byte, but not altered the radio setup. Have you read this issue linked to the repo that you use?

    https://github.com/NordicPlayground/solar_sensor_beacon/issues/2

     

    Kind regards,

    Håkon

  • Hi Hakon,

    Thanks for your answer.

    Yes I already went trough the GitHub issue on the repo. So I played around testing different combinations of PDU and RADIO_PCNF0_SxLEN following the explanations of hanselfberg.

    So I don't know how tired I was yesterday but I was sure to have tried setting RADIO_PCNF0_S1LEN to 0 and RADIO_PCNF0_LFLEN to 8 in combination with having defined BD_ADDR_OFFS to 2. Without any success...

    However I did tried again right now and it finally works ! The advertising is now scanned as expected by my iPhone Slight smile

    I guess I forgot something yesterday.

    Anyway thanks a lot for putting me back on track !

    For anyone crossing this thread with similar issues, here is the correct combination that made it work:

    ble implementation:

    /* Advertising PDU configuration */
    #define BD_ADDR_OFFS                (2)        /* BLE device address offset in the advertising PDU */
    #define M_BD_ADDR_SIZE              (6)        /* BLE device address size */
    
    /* Timing configuration */
    #define HFCLK_STARTUP_TIME_US       (1600)     /* Time in microseconds to start up the HF clock */
    #define INTERVAL_US                 (100000)  /* The time in microseconds between advertising events */
    #define INITIAL_TIMEOUT             (INTERVAL_US)
    #define ADV_DURATION_US             (1000000)  /* Run for 1 second (1,000,000 microseconds) */
    
    /* Radio globals */
    static bool volatile m_radio_isr_called;        /* Indicates that the radio ISR has executed */
    static bool volatile m_rtc_isr_called;          /* Indicates that the RTC ISR has executed */
    static uint32_t m_time_us;                      /* Keeps track of the latest scheduled point in time */
    static uint8_t m_adv_pdu[40];                   /* The RAM representation of the advertising PDU */
    
    
    // Helper function to convert a byte to hex string (without null terminator)
    static void byte_to_hex(uint8_t byte, char* hex_str)
    {
        const char hex_chars[] = "0123456789ABCDEF";
        hex_str[0] = hex_chars[(byte >> 4) & 0x0F];
        hex_str[1] = hex_chars[byte & 0x0F];
    }
    
    // Helper function to create device name with SOL + last 3 bytes of MAC
    static uint8_t format_device_name(uint8_t* addr, char* name_str)
    {
        // Start with "SOL"
        name_str[0] = 'S';
        name_str[1] = 'O';
        name_str[2] = 'L';
        
        // Add only 2 bytes of MAC address as hex (4 characters)
        byte_to_hex(addr[1], &name_str[3]);  // 2nd byte  
        byte_to_hex(addr[0], &name_str[5]);  // 1st byte (LSB)
        
        return 7; // Length: "SOL" + 4 hex characters = 7 total (reduced from 9)
    }
    
    /* Waits for the next NVIC event */
    static void __INLINE cpu_wfe(void)
    {
        __WFE();
        __SEV();
        __WFE();
    }
    
    /* System reset function */
    static void system_reset(void)
    {
        // Disable interrupts before reset
        __disable_irq();
        
        // Use NVIC system reset
        NVIC_SystemReset();
        
        // Should never reach here, but just in case
        while(1);
    }
    
    /* RTC isr handler */
    void RTC0_IRQHandler(void)
    {
        NRF_RTC0->EVTENCLR = (RTC_EVTENCLR_COMPARE0_Enabled << RTC_EVTENCLR_COMPARE0_Pos);
        NRF_RTC0->INTENCLR = (RTC_INTENCLR_COMPARE0_Enabled << RTC_INTENCLR_COMPARE0_Pos);
        NRF_RTC0->EVENTS_COMPARE[0] = 0;
        
        m_rtc_isr_called = true;    
    }
    
    /* Radio interrupt handler */
    void RADIO_IRQHandler(void)
    {
        NRF_RADIO->EVENTS_DISABLED = 0;
        m_radio_isr_called = true;    
    }
    
    /* Initializes the beacon advertising PDU */
    static void m_beacon_pdu_init(uint8_t * p_beacon_pdu)
    {
        p_beacon_pdu[0] = 0x42;   // ADV_NONCONN_IND (0x02)
        p_beacon_pdu[1] = 0;      // Length placeholder
        //p_beacon_pdu[2] = 0;    // Reserved
    }
    
    /* Sets up the device address in the PDU */
    static void m_beacon_pdu_bd_addr_default_set(uint8_t * p_beacon_pdu)
    {
        // Use device address from FICR
        uint8_t addr[6];
    
        addr[0] = (NRF_FICR->DEVICEADDR[0]      ) & 0xFF;
        addr[1] = (NRF_FICR->DEVICEADDR[0] >>  8) & 0xFF;
        addr[2] = (NRF_FICR->DEVICEADDR[0] >> 16) & 0xFF;
        addr[3] = (NRF_FICR->DEVICEADDR[0] >> 24) & 0xFF;
        addr[4] = (NRF_FICR->DEVICEADDR[1]      ) & 0xFF;
        addr[5] = (NRF_FICR->DEVICEADDR[1] >>  8) & 0xFF;
    
        // Copy address to PDU
        memcpy(&p_beacon_pdu[BD_ADDR_OFFS], addr, 6);
        
        // Set TxAdd bit
        p_beacon_pdu[0] &= ~(1 << 5);  // Public address (TxAdd=0)
    }
    
    
    static void m_beacon_pdu_custom_data_set(uint8_t * p_beacon_pdu, adc_analysis_result_t* p_analysis_result)
    {
        uint8_t adv_data_index = BD_ADDR_OFFS + M_BD_ADDR_SIZE;
        
        // Get the MAC address that was already set in the PDU
        uint8_t addr[6];
        memcpy(addr, &p_beacon_pdu[BD_ADDR_OFFS], 6);
        
        // Format device name as "SOL" + last 3 bytes of MAC
        char device_name[9];
        uint8_t name_len = format_device_name(addr, device_name);
        
        // Flags - mandatory for discovery
        p_beacon_pdu[adv_data_index++] = 0x02; // Length
        p_beacon_pdu[adv_data_index++] = 0x01; // Flags AD type
        p_beacon_pdu[adv_data_index++] = 0x06; // LE General Discoverable + BR/EDR Not Supported
        
        // Complete Local Name containing device name
        p_beacon_pdu[adv_data_index++] = name_len + 1; // Length (string length + AD type byte)
        p_beacon_pdu[adv_data_index++] = 0x09; // Complete Local Name AD type
        
        // Copy device name string
        for (uint8_t i = 0; i < name_len; i++) {
            p_beacon_pdu[adv_data_index++] = (uint8_t)device_name[i];
        }
        
        // Manufacturer Specific Data containing our ADC analysis
        p_beacon_pdu[adv_data_index++] = 0x09; // Length (2 bytes company ID + 1 type + 6 data bytes)
        p_beacon_pdu[adv_data_index++] = 0xFF; // Manufacturer Specific Data AD type
        
        // Company ID (using Nordic's ID as example, change as needed)
        p_beacon_pdu[adv_data_index++] = 0x59; // Nordic Semiconductor Company ID LSB
        p_beacon_pdu[adv_data_index++] = 0x00; // Nordic Semiconductor Company ID MSB
        
        // Data type identifier (custom)
        p_beacon_pdu[adv_data_index++] = 0xAD; // Custom type for ADC data
        
        // ADC Analysis Results - 6 bytes total
        // Peak values (3 bytes)
        p_beacon_pdu[adv_data_index++] = p_analysis_result->peak_ain0;
        p_beacon_pdu[adv_data_index++] = p_analysis_result->peak_ain5;
        p_beacon_pdu[adv_data_index++] = p_analysis_result->peak_ain7;
        
        // Timing values (3 bytes)
        p_beacon_pdu[adv_data_index++] = p_analysis_result->timing_ain0;
        p_beacon_pdu[adv_data_index++] = p_analysis_result->timing_ain5;
        p_beacon_pdu[adv_data_index++] = p_analysis_result->timing_ain7;
        
        // Update total PDU length
        p_beacon_pdu[1] = adv_data_index - BD_ADDR_OFFS; // Total length minus header (3 bytes)
    }
    
    
    void m_beacon_init(adc_analysis_result_t* p_analysis_result)
    {
        /* Initialize the advertising PDU */
        m_beacon_pdu_init(&(m_adv_pdu[0]));
        m_beacon_pdu_bd_addr_default_set(&(m_adv_pdu[0]));
        m_beacon_pdu_custom_data_set(&(m_adv_pdu[0]), p_analysis_result);
    }
    
    
    /* Sends an advertising PDU on the given channel index */
    static void send_one_packet(uint8_t channel_index)
    {
        m_radio_isr_called = false;
        hal_radio_channel_index_set(channel_index);
        hal_radio_send(m_adv_pdu);
        while (!m_radio_isr_called)
        {
            cpu_wfe();
        }
        
        /* Small delay */
        for (uint8_t i = 0; i < 9; i++)
        {
            __NOP();
        }
    }
    
    
    /* Main beacon handling function */
    void beacon_handler(void)
    {
        hal_radio_reset();
        hal_timer_start();
        
        m_time_us = INITIAL_TIMEOUT - HFCLK_STARTUP_TIME_US;
        uint32_t start_time_us = m_time_us; // Record the start time
    
        while (1)
        {
            /* Check if 1 second has elapsed */
            if ((m_time_us - start_time_us) >= ADV_DURATION_US)
            {
                /* Clean up before reset */
                hal_clock_hfclk_disable();  // Ensure HF clock is disabled
                hal_radio_reset();          // Reset radio state
                
                /* Reset the chip */
                system_reset();
            }
            
            /* Wait until it's time to advertise */
            m_rtc_isr_called = false;
            hal_timer_timeout_set(m_time_us);
            while (!m_rtc_isr_called)
            {
                cpu_wfe();
            }
            
            /* Enable high-frequency clock */
            hal_clock_hfclk_enable();
            
            /* Wait for HF clock to stabilize */
            m_rtc_isr_called = false;
            m_time_us += HFCLK_STARTUP_TIME_US; 
            hal_timer_timeout_set(m_time_us);
            while (!m_rtc_isr_called)
            {
                cpu_wfe();
            }
            
            /* Send advertising packets on all three channels */
            send_one_packet(37);
            send_one_packet(38);
            send_one_packet(39);
            
            /* Disable high-frequency clock to save power */
            hal_clock_hfclk_disable();
            
            /* Schedule next advertising event */
            m_time_us = m_time_us + (INTERVAL_US - HFCLK_STARTUP_TIME_US); 
        }
    }

    radio implementation:

    #include "nrf.h"
    
    
    uint8_t access_address[4] = {0xD6, 0xBE, 0x89, 0x8E};
    uint8_t seed[3] = {0x55, 0x55, 0x55};
    
    
    /**@brief The maximum possible length in device discovery mode. */
    #define DD_MAX_PAYLOAD_LENGTH         (31 + 6)
    
    
    /**@brief The default SHORTS configuration. */
    #define DEFAULT_RADIO_SHORTS                                             \
    (                                                                        \
        (RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos) | \
        (RADIO_SHORTS_END_DISABLE_Enabled << RADIO_SHORTS_END_DISABLE_Pos)   \
    )
    
    
    /**@brief The default CRC init polynominal. 
    
       @note Written in little endian but stored in big endian, because the BLE spec. prints
             is in little endian but the HW stores it in big endian. */
    #define CRC_POLYNOMIAL_INIT_SETTINGS  ((0x5B << 0) | (0x06 << 8) | (0x00 << 16))
    
    
    /**@brief This macro converts the given channel index to a freuency
              offset (i.e. offset from 2400 MHz).
    
      @param index - the channel index to be converted into frequency offset.
      
      @return The frequency offset for the given index. */
    #define CHANNEL_IDX_TO_FREQ_OFFS(index) \
        (((index) == 37)?\
            (2)\
            :\
                (((index) == 38)?\
                    (26)\
                :\
                    (((index) == 39)?\
                        (80)\
                    :\
                        ((/*((index) >= 0) &&*/ ((index) <= 10))?\
                            ((index)*2 + 4)\
                        :\
                            ((index)*2 + 6)))))
                            
    
    void hal_radio_channel_index_set(uint8_t channel_index)
    {
        NRF_RADIO->FREQUENCY = CHANNEL_IDX_TO_FREQ_OFFS(channel_index);
        NRF_RADIO->DATAWHITEIV = channel_index;
    }
    
    
    void hal_radio_reset(void)
    {
        NVIC_DisableIRQ(RADIO_IRQn);
        
        // Disable and reenable radio power
        NRF_RADIO->POWER = RADIO_POWER_POWER_Disabled << RADIO_POWER_POWER_Pos;
        NRF_RADIO->POWER = RADIO_POWER_POWER_Enabled << RADIO_POWER_POWER_Pos;
    
        // Set to maximum power +4dBm
        //NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg40dBm << RADIO_TXPOWER_TXPOWER_Pos;
        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos4dBm << RADIO_TXPOWER_TXPOWER_Pos;
    
    
        NRF_RADIO->SHORTS = DEFAULT_RADIO_SHORTS;
        
        NRF_RADIO->PCNF0 =   (((1UL) << RADIO_PCNF0_S0LEN_Pos) & RADIO_PCNF0_S0LEN_Msk)
                           | (((0UL) << RADIO_PCNF0_S1LEN_Pos) & RADIO_PCNF0_S1LEN_Msk)
                           | (((8UL) << RADIO_PCNF0_LFLEN_Pos) & RADIO_PCNF0_LFLEN_Msk);
    
        NRF_RADIO->PCNF1 =   (((RADIO_PCNF1_ENDIAN_Little)        << RADIO_PCNF1_ENDIAN_Pos) & RADIO_PCNF1_ENDIAN_Msk)
                           | (((3UL)                              << RADIO_PCNF1_BALEN_Pos)  & RADIO_PCNF1_BALEN_Msk)
                           | (((0UL)                              << RADIO_PCNF1_STATLEN_Pos)& RADIO_PCNF1_STATLEN_Msk)
                           | ((((uint32_t)DD_MAX_PAYLOAD_LENGTH)  << RADIO_PCNF1_MAXLEN_Pos) & RADIO_PCNF1_MAXLEN_Msk)
                           | ((RADIO_PCNF1_WHITEEN_Enabled << RADIO_PCNF1_WHITEEN_Pos) & RADIO_PCNF1_WHITEEN_Msk);
        
        /* The CRC polynomial is fixed, and is set here. */
        /* The CRC initial value may change, and is set by */
        /* higher level modules as needed. */
        NRF_RADIO->CRCPOLY = (uint32_t)CRC_POLYNOMIAL_INIT_SETTINGS;
        NRF_RADIO->CRCCNF = (((RADIO_CRCCNF_SKIPADDR_Skip) << RADIO_CRCCNF_SKIPADDR_Pos) & RADIO_CRCCNF_SKIPADDR_Msk)
                          | (((RADIO_CRCCNF_LEN_Three)      << RADIO_CRCCNF_LEN_Pos)       & RADIO_CRCCNF_LEN_Msk);
    
        NRF_RADIO->RXADDRESSES  = ( (RADIO_RXADDRESSES_ADDR0_Enabled) << RADIO_RXADDRESSES_ADDR0_Pos);
    
        NRF_RADIO->MODE    = ((RADIO_MODE_MODE_Ble_1Mbit) << RADIO_MODE_MODE_Pos) & RADIO_MODE_MODE_Msk;
    
        NRF_RADIO->TIFS = 150;
    
        NRF_RADIO->PREFIX0 = access_address[3];
        NRF_RADIO->BASE0   = ( (((uint32_t)access_address[2]) << 24) 
                             | (((uint32_t)access_address[1]) << 16)
                             | (((uint32_t)access_address[0]) << 8) );
    
        NRF_RADIO->CRCINIT = ((uint32_t)seed[0]) | ((uint32_t)seed[1])<<8 | ((uint32_t)seed[2])<<16;
    
        NRF_RADIO->INTENSET = (RADIO_INTENSET_DISABLED_Enabled << RADIO_INTENSET_DISABLED_Pos);
        NVIC_ClearPendingIRQ(RADIO_IRQn);
        NVIC_EnableIRQ(RADIO_IRQn);
    }
    
    
    void hal_radio_send(uint8_t *p_data)
    {
        NRF_RADIO->PACKETPTR = (uint32_t)&(p_data[0]);
        NRF_RADIO->EVENTS_DISABLED = 0;
        NRF_RADIO->TASKS_TXEN = 1;
    }

    Thanks a lot and have a nice day !

Related