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.