Advertising using raw radio registers with nRF52832 (without SoftDevice)

Hi,

I'm trying to write a small sample code that performs BLE advertising without using SoftDevice but instead using the raw radio registers.

I've looked at the solar_sensor_beacon sample on the Nordic GitHub repo and my code looks very similar.

Basically I have one app_timer that runs in repeated mode and once per second it should send a fixed test advertisement packet on channels 37, 38, 39.

I attach the code below as it is very short.

The behaviour that I'm facing is that when the device tries to send the advertisemtent the code stops working, the timer never fires up again and no radio event is triggered.

I cannot understand which could be the cause, the lfclk is started at the beginning and the hfclk is started before configuring the RADIO registers... but no way...

#include <stdbool.h>
#include <stdint.h>
#include "nordic_common.h"
#include "ble.h"
#include "nrf_delay.h"
#include "nrf_drv_clock.h"
#include "app_timer.h"
#include "nrf_pwr_mgmt.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define DEAD_BEEF                       0xDEADBEEF


APP_TIMER_DEF(m_timer);

typedef enum
{
	CHANNEL_37 = 0,
	CHANNEL_38 = 1,
	CHANNEL_39 = 2,
} channel_t;

typedef struct
{
	uint8_t channel;
	uint8_t frequency;
} channel_frequency_pair_t;

static const channel_frequency_pair_t m_channel_frequency_map[] = 
{
	{ .channel = 37, .frequency = 2  },		// channel 37, frequency 2402
	{ .channel = 38, .frequency = 26 },		// channel 38, frequency 2426
	{ .channel = 39, .frequency = 80 }		// channel 39, frequency 2480
};
static uint8_t m_adv_pdu[40];
static uint8_t m_adv_ch = CHANNEL_37;

static bool m_send_adv = false;
static bool m_adv_sent = false;

void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
    app_error_handler(DEAD_BEEF, line_num, p_file_name);
}

void nrf_radio_advertise(void)
{	
	m_adv_pdu[0]  = 0x40 | 0x02;	// First header byte: TxAdd|RxAdd (0x40) + PDU Type
	m_adv_pdu[1]  = 0;              // Second header byte: LENGTH (will be updated at the end)
    m_adv_pdu[2]  = 0;
    
    m_adv_pdu[3]  = (NRF_FICR->DEVICEADDR[0]      ) & 0xFF;
    m_adv_pdu[4]  = (NRF_FICR->DEVICEADDR[0] >>  8) & 0xFF;
    m_adv_pdu[5]  = (NRF_FICR->DEVICEADDR[0] >> 16) & 0xFF;
    m_adv_pdu[6]  = (NRF_FICR->DEVICEADDR[0] >> 24)       ;
    m_adv_pdu[7]  = (NRF_FICR->DEVICEADDR[1]      ) & 0xFF;
    m_adv_pdu[8]  = (NRF_FICR->DEVICEADDR[1] >>  8) & 0xFF;
    
    m_adv_pdu[9]  = 0x02;
    m_adv_pdu[10] = 0x01;
    m_adv_pdu[11] = 0x06;
    
    m_adv_pdu[12] = 0x05;
    m_adv_pdu[13] = 0x09;
    m_adv_pdu[14] = 'T';
    m_adv_pdu[15] = 'E';
    m_adv_pdu[16] = 'S';
    m_adv_pdu[17] = 'T';
    
    // update data length 
	m_adv_pdu[1] = BLE_GAP_ADDR_LEN + 9;
	
	// init first advertising channel
	m_adv_ch = CHANNEL_37;
		
	/* Reset all states in the radio peripheral */
	NVIC_ClearPendingIRQ(RADIO_IRQn);
	NVIC_DisableIRQ(RADIO_IRQn);
    
    NRF_RADIO->POWER = ((RADIO_POWER_POWER_Disabled << RADIO_POWER_POWER_Pos) & RADIO_POWER_POWER_Msk);
	NRF_RADIO->POWER = ((RADIO_POWER_POWER_Enabled << RADIO_POWER_POWER_Pos) & RADIO_POWER_POWER_Msk);

	/* Do BLE specific radio setup */
	NRF_RADIO->EVENTS_DISABLED = 0;

	/* Set radio configuration parameters */
	NRF_RADIO->TXPOWER = ((RADIO_TXPOWER_TXPOWER_Pos4dBm << RADIO_TXPOWER_TXPOWER_Pos) & RADIO_TXPOWER_TXPOWER_Msk);
	NRF_RADIO->MODE    = ((RADIO_MODE_MODE_Ble_1Mbit << RADIO_MODE_MODE_Pos) & RADIO_MODE_MODE_Msk);


	NRF_RADIO->FREQUENCY   = m_channel_frequency_map[m_adv_ch].frequency;
	NRF_RADIO->DATAWHITEIV = m_channel_frequency_map[m_adv_ch].channel;

	/* Configure Access Address to be the BLE standard */
	NRF_RADIO->PREFIX0	   = 0x8e;
	NRF_RADIO->BASE0       = 0x89bed600; 
	NRF_RADIO->TXADDRESS   = 0x00;				// Use logical address 0 (prefix0 + base0) = 0x8E89BED6 when transmitting
	NRF_RADIO->RXADDRESSES = 0x01;				// Enable reception on logical address 0 (PREFIX0 + BASE0)

	/* PCNF-> Packet Configuration. Now we need to configure the sizes S0, S1 and length field to match the datapacket format of the advertisement packets. */
	NRF_RADIO->PCNF0 =  (
			              (((1UL) << RADIO_PCNF0_S0LEN_Pos) & RADIO_PCNF0_S0LEN_Msk)    // length of S0 field in bytes 0-1.
			            | (((2UL) << RADIO_PCNF0_S1LEN_Pos) & RADIO_PCNF0_S1LEN_Msk)    // length of S1 field in bits 0-8.
			            | (((6UL) << RADIO_PCNF0_LFLEN_Pos) & RADIO_PCNF0_LFLEN_Msk)    // length of length field in bits 0-8.
			          );

	/* Packet configuration */
	NRF_RADIO->PCNF1 =  (
			              (((37UL)                      << RADIO_PCNF1_MAXLEN_Pos)  & RADIO_PCNF1_MAXLEN_Msk)   // maximum length of payload in bytes [0-255]
			            | (((0UL)                       << RADIO_PCNF1_STATLEN_Pos) & RADIO_PCNF1_STATLEN_Msk)	// expand the payload with N bytes in addition to LENGTH [0-255]
			            | (((3UL)                       << RADIO_PCNF1_BALEN_Pos)   & RADIO_PCNF1_BALEN_Msk)    // base address length in number of bytes.
			            | (((RADIO_PCNF1_ENDIAN_Little) << RADIO_PCNF1_ENDIAN_Pos)  & RADIO_PCNF1_ENDIAN_Msk)   // endianess of the S0, LENGTH, S1 and PAYLOAD fields.
			            | (((1UL)                       << RADIO_PCNF1_WHITEEN_Pos) & RADIO_PCNF1_WHITEEN_Msk)	// enable packet whitening
			          );

	/* CRC config */
	NRF_RADIO->CRCCNF = (RADIO_CRCCNF_LEN_Three << RADIO_CRCCNF_LEN_Pos) | 
			            (RADIO_CRCCNF_SKIPADDR_Skip << RADIO_CRCCNF_SKIPADDR_Pos); // Skip Address when computing crc     
	NRF_RADIO->CRCINIT = 0x555555;    // Initial value of CRC
	NRF_RADIO->CRCPOLY = 0x00065B;    // CRC polynomial function

	/* Lock interframe spacing, so that the radio won't send too soon / start RX too early */
	NRF_RADIO->TIFS = 145;
	
	/* Enable radio interrupt propagation */
	NVIC_EnableIRQ(RADIO_IRQn);
	
	/* trigger task early, the rest of the setup can be done in RXRU */
	NRF_RADIO->TASKS_TXEN = 1;
	
	NRF_RADIO->SHORTS = (
						  ((RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos) & RADIO_SHORTS_READY_START_Msk) 
						| ((RADIO_SHORTS_END_DISABLE_Enabled << RADIO_SHORTS_END_DISABLE_Pos) & RADIO_SHORTS_END_DISABLE_Msk)
					  );

	NRF_RADIO->INTENSET = ((RADIO_INTENSET_DISABLED_Set << RADIO_INTENSET_DISABLED_Pos) & RADIO_INTENSET_DISABLED_Msk);

	/* Recover packet pointer */
	NRF_RADIO->PACKETPTR = (uint32_t) &m_adv_pdu[0];
	
	NRF_LOG_INFO("TXEN");
}

void nrf_radio_event(void)
{	
	NRF_LOG_INFO("nrf_radio_event");
		
	if (NRF_RADIO->EVENTS_DISABLED) 
	{
		m_adv_ch++;
		
		if (m_adv_ch <= CHANNEL_39)
		{
			// send adv on next channel
			NRF_RADIO->SHORTS = 0;
			
			NRF_RADIO->FREQUENCY   = m_channel_frequency_map[m_adv_ch].frequency;
			NRF_RADIO->DATAWHITEIV = m_channel_frequency_map[m_adv_ch].channel;
			
			NRF_RADIO->SHORTS = (
								  ((1 << RADIO_SHORTS_READY_START_Pos) & RADIO_SHORTS_READY_START_Msk) 
								| ((1 << RADIO_SHORTS_END_DISABLE_Pos) & RADIO_SHORTS_END_DISABLE_Msk)
							  );

			NRF_RADIO->INTENSET = ((RADIO_INTENSET_DISABLED_Set << RADIO_INTENSET_DISABLED_Pos) & RADIO_INTENSET_DISABLED_Msk);
			
			NRF_RADIO->TASKS_TXEN = 1;
		}
		else
		{
			// turn off radio
			NVIC_DisableIRQ(RADIO_IRQn);
            NRF_RADIO->TASKS_DISABLE = 1;
            NRF_RADIO->POWER = ((RADIO_POWER_POWER_Disabled << RADIO_POWER_POWER_Pos) & RADIO_POWER_POWER_Msk);
            NRF_RADIO->SHORTS = 0;
            NRF_RADIO->INTENCLR = ((RADIO_INTENCLR_DISABLED_Clear << RADIO_INTENCLR_DISABLED_Pos) & RADIO_INTENCLR_DISABLED_Msk);
            NRF_RADIO->EVENTS_DISABLED = 0;
            
			m_adv_sent = 1;
		}
	}
}

static void timer_handler(void * p_context)
{
	NRF_LOG_INFO("TIMER");
		
	m_send_adv = true;
}

static void log_init(void)
{
    ret_code_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}

static void timers_init(void)
{
    ret_code_t err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
    
    err_code = app_timer_create(&m_timer, APP_TIMER_MODE_REPEATED, timer_handler);
    APP_ERROR_CHECK(err_code);
}

static void power_management_init(void)
{
    ret_code_t err_code;
    err_code = nrf_pwr_mgmt_init();
    APP_ERROR_CHECK(err_code);
}

static void lfclk_config(void)
{
    uint32_t err_code;

    err_code = nrf_drv_clock_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_clock_lfclk_request(NULL);
}

static void idle_state_handle(void)
{
    if (NRF_LOG_PROCESS() == false)
    {
        nrf_pwr_mgmt_run();
    }
}

int main(void)
{
	log_init();
	lfclk_config();   
    timers_init();
    power_management_init();
    
    // start app_timer (1 sec.)
    APP_ERROR_CHECK(app_timer_start(m_timer, APP_TIMER_TICKS(1000), NULL));

    NRF_LOG_INFO("APP START");
    for (;;)
    {
    	// this is triggered from app_timer
    	if (m_send_adv)
    	{
    		m_send_adv = false;
    		
    		// enable hfclk
    		NRF_LOG_INFO("ENABLING HFCLK");
			nrf_drv_clock_hfclk_request(NULL);
			
			// wait for hfclk to be up
			while (!nrf_drv_clock_hfclk_is_running())
			{
				nrf_delay_ms(1);
			}			
    		NRF_LOG_INFO("HFCLK ENABLED");
			
			// send advertisements
			nrf_radio_advertise();
    	}
    	
    	// this is triggered after advertisement on all 3-channels have been done
    	if (m_adv_sent)
    	{
    		m_adv_sent = false;

    		// turn off hfclk    		
    		NRF_LOG_INFO("DISABLING HFCLK");
			nrf_drv_clock_hfclk_release();
    	}
    	
        idle_state_handle();
    }
}

Thanks for any help / suggestions!

Parents
  • The vector is incorrect, the Radio uses a fixed vector not a register pointer to a vector; it's a simple fix:

    Change
    void nrf_radio_event(void)
    {
    
    to
    void RADIO_IRQHandler(void)
    {

    I also moved the order of starting the transmission, that might not matter:

    Complete setup before very last thing starting transmission:
    
        /* trigger task early, the rest of the setup can be done in RXRU */
        NRF_RADIO->TASKS_TXEN = 1;
        NRF_LOG_INFO("TXEN");
    }

  • Good news. Looking at the advertising packet shows that the Radio is slightly confusing as the memory layout is different from the on-air layout due to the way the optional S0, Length and S1 fields are handled. These are my notes trying to clarify; I added an optional 0xFF manufacturing data field.

    // BLE Advertising Packet
    // ======================
    // Every packet begins with an 8 bit preamble, an alternating binary sequence. This is followed by a 32 bit access
    // address (AA) which can be thought of as a unique identifier which defines a particular connection. When a
    // device (master or slave) transmits on an advertising channel it uses a fixed value of 0x8e89bed6 as the access
    // address.
    // Following the 32 bit access address are optional S0, LENGTH and S1 fields then a variable length Protocol Data
    // Unit (PDU) which contains the message data payload. Finally all packets end with a 24 bit CRC.
    // By default, the Nordic BLE address is derived from the NRF_FICR->DEVICEADDR[] and is 48 bits in length (6 bytes)
    // If S0, LENGTH or S1 are specified with zero length their fields will be omitted in memory, otherwise each
    // field will be represented as a separate byte, regardless of the number of bits in their on-air counterpart
    // For the field sizes defined in bits, the occupation in RAM will always be rounded up to the next full byte size
    // (for instance 3 bit length will allocate 1 byte in RAM, 9 bit length will allocate 2 bytes, etc.).
    // On-air packet layout (Note Header in RAM is 3 bytes if S0INC is '1')
    // +-------------------------------------------------------------------------------------------------------------------------+
    // |                                         BLE Advertising Packet - Legacy                                                 |
    // +-------------------------------------------------------------------------------------------------------------------------+
    // |                                         1MHz: 47 octets max total length                                                |
    // +--------+-------+------------------------------------------------------------------------------------------+------+------+
    // |        |Access |                                                                                          | Add  |      |
    // |Preamble|Address|                   Protocol Data Unit PDU                                                 | On   | CRCC | Tone Ext
    // +--------+-------+------------------------------------------------------------------------------------------+------+------+
    // |   1(2) |   4   |                                    2 - 39                                                | (0)  |  3   | 16 - 160uSec
    // |        |       +-----------------+------------------------------------------------------------------------+      |      |
    // |        |       |    Header       |                  Payload                                               |      |      |
    // |        |       +-----------------+------------------------------------------------------------------------+      |      |
    // |        |       |       2         |                  0 - 37                                                |      |      |
    // |        |       +-----+-----+-----+------------------------------------------------------------------------+      |      |
    // |        |       | Opt | Opt | Opt |  MAC Addr  |     AdvData List (example 3 fields)                       |      |      |
    // |        |       | S0  | Len | S1  +------------+-----------------------------------------------------------+      |      |
    // |        |       |-----+-----+-----|    6       |     0 - 31                                                |      |      |
    // |        |       | (1) | (1) | (1) |            +-------------------+-------------------+-------------------+      |      |
    // |        |       |     |     |     |            |    AdvData Flags  |    AdvData Name   |   (AdvData Data)  |      |      |
    // |        |       |     |     |     |            +-------------------+-------------------+-------------------+      |      |
    // |        |       |     |     |     |            |        3          |          6        |       0 - 22      |      |      |
    // |        |       |     |     |     |            +-----+------+------+-----+------+------+-----+------+------+      |      |
    // |        |       |     |     |     |            | Len | Type | Data | Len | Type | Data | Len | Type | Data |      |      |
    // |        |       |     |     |     |            +-----+------+------+-----+------+------+-----+------+------+      |      |
    // |        |       |     |     |     |            |  1  |  1   |  1   |  1  |  1   |  4   |  1  |  1   |  20  |      |      |
    // |        |       |     |     |     |            |     |      |      |     |      |      |     |      |      |      |      |
    // |  1(2)  |  4    | (1) | (1) | (1) |   6        |  1  |  1   |  1   |  1  |  1   |  4   |  1  |  1   |  20  | (0)  |  3   |
    // +--------+-------+-----+-----+-----+------------+-----+------+------+-----+------+------+-----+------+------+------+------+
    
        m_adv_pdu[0]  = 0x40 | 0x02;    // S0:     Optional First header byte: TxAdd|RxAdd (0x40) + PDU Type
        m_adv_pdu[1]  = 0;              // Length: Optional Second header byte: LENGTH (will be updated at the end)
        m_adv_pdu[2]  = 0;              // S1:     Optional Third header byte

    Edit: To clarify, the Header has just 2 buyts transmitted over-the-air, but the Radio hardware uses 3 bytes in RAM for this 2-byte Header. I also updated the table above.

Reply
  • Good news. Looking at the advertising packet shows that the Radio is slightly confusing as the memory layout is different from the on-air layout due to the way the optional S0, Length and S1 fields are handled. These are my notes trying to clarify; I added an optional 0xFF manufacturing data field.

    // BLE Advertising Packet
    // ======================
    // Every packet begins with an 8 bit preamble, an alternating binary sequence. This is followed by a 32 bit access
    // address (AA) which can be thought of as a unique identifier which defines a particular connection. When a
    // device (master or slave) transmits on an advertising channel it uses a fixed value of 0x8e89bed6 as the access
    // address.
    // Following the 32 bit access address are optional S0, LENGTH and S1 fields then a variable length Protocol Data
    // Unit (PDU) which contains the message data payload. Finally all packets end with a 24 bit CRC.
    // By default, the Nordic BLE address is derived from the NRF_FICR->DEVICEADDR[] and is 48 bits in length (6 bytes)
    // If S0, LENGTH or S1 are specified with zero length their fields will be omitted in memory, otherwise each
    // field will be represented as a separate byte, regardless of the number of bits in their on-air counterpart
    // For the field sizes defined in bits, the occupation in RAM will always be rounded up to the next full byte size
    // (for instance 3 bit length will allocate 1 byte in RAM, 9 bit length will allocate 2 bytes, etc.).
    // On-air packet layout (Note Header in RAM is 3 bytes if S0INC is '1')
    // +-------------------------------------------------------------------------------------------------------------------------+
    // |                                         BLE Advertising Packet - Legacy                                                 |
    // +-------------------------------------------------------------------------------------------------------------------------+
    // |                                         1MHz: 47 octets max total length                                                |
    // +--------+-------+------------------------------------------------------------------------------------------+------+------+
    // |        |Access |                                                                                          | Add  |      |
    // |Preamble|Address|                   Protocol Data Unit PDU                                                 | On   | CRCC | Tone Ext
    // +--------+-------+------------------------------------------------------------------------------------------+------+------+
    // |   1(2) |   4   |                                    2 - 39                                                | (0)  |  3   | 16 - 160uSec
    // |        |       +-----------------+------------------------------------------------------------------------+      |      |
    // |        |       |    Header       |                  Payload                                               |      |      |
    // |        |       +-----------------+------------------------------------------------------------------------+      |      |
    // |        |       |       2         |                  0 - 37                                                |      |      |
    // |        |       +-----+-----+-----+------------------------------------------------------------------------+      |      |
    // |        |       | Opt | Opt | Opt |  MAC Addr  |     AdvData List (example 3 fields)                       |      |      |
    // |        |       | S0  | Len | S1  +------------+-----------------------------------------------------------+      |      |
    // |        |       |-----+-----+-----|    6       |     0 - 31                                                |      |      |
    // |        |       | (1) | (1) | (1) |            +-------------------+-------------------+-------------------+      |      |
    // |        |       |     |     |     |            |    AdvData Flags  |    AdvData Name   |   (AdvData Data)  |      |      |
    // |        |       |     |     |     |            +-------------------+-------------------+-------------------+      |      |
    // |        |       |     |     |     |            |        3          |          6        |       0 - 22      |      |      |
    // |        |       |     |     |     |            +-----+------+------+-----+------+------+-----+------+------+      |      |
    // |        |       |     |     |     |            | Len | Type | Data | Len | Type | Data | Len | Type | Data |      |      |
    // |        |       |     |     |     |            +-----+------+------+-----+------+------+-----+------+------+      |      |
    // |        |       |     |     |     |            |  1  |  1   |  1   |  1  |  1   |  4   |  1  |  1   |  20  |      |      |
    // |        |       |     |     |     |            |     |      |      |     |      |      |     |      |      |      |      |
    // |  1(2)  |  4    | (1) | (1) | (1) |   6        |  1  |  1   |  1   |  1  |  1   |  4   |  1  |  1   |  20  | (0)  |  3   |
    // +--------+-------+-----+-----+-----+------------+-----+------+------+-----+------+------+-----+------+------+------+------+
    
        m_adv_pdu[0]  = 0x40 | 0x02;    // S0:     Optional First header byte: TxAdd|RxAdd (0x40) + PDU Type
        m_adv_pdu[1]  = 0;              // Length: Optional Second header byte: LENGTH (will be updated at the end)
        m_adv_pdu[2]  = 0;              // S1:     Optional Third header byte

    Edit: To clarify, the Header has just 2 buyts transmitted over-the-air, but the Radio hardware uses 3 bytes in RAM for this 2-byte Header. I also updated the table above.

Children
  • Yes, you can choose to set PCNF0 in this way:

    /* PCNF-> Packet Configuration. Now we need to configure the sizes S0, S1 and length field to match the datapacket format of the advertisement packets. */
    NRF_RADIO->PCNF0 =  (
    		              (((1UL) << RADIO_PCNF0_S0LEN_Pos) & RADIO_PCNF0_S0LEN_Msk)    // length of S0 field in bytes 0-1.
    		            | (((2UL) << RADIO_PCNF0_S1LEN_Pos) & RADIO_PCNF0_S1LEN_Msk)    // length of S1 field in bits 0-8.
    		            | (((6UL) << RADIO_PCNF0_LFLEN_Pos) & RADIO_PCNF0_LFLEN_Msk)    // length of length field in bits 0-8.
    		          );
    
    // Mapped in 3 bytes into RAM
    m_adv_pdu[0]  = 0x40 | 0x02;
    m_adv_pdu[1]  = 0;
    m_adv_pdu[2]  = 0;

    or in this other way:

    /* PCNF-> Packet Configuration. Now we need to configure the sizes S0, S1 and length field to match the datapacket format of the advertisement packets. */
    NRF_RADIO->PCNF0 =  (
    		              (((1UL) << RADIO_PCNF0_S0LEN_Pos) & RADIO_PCNF0_S0LEN_Msk)    // length of S0 field in bytes 0-1.
    		            | (((0UL) << RADIO_PCNF0_S1LEN_Pos) & RADIO_PCNF0_S1LEN_Msk)    // length of S1 field in bits 0-8.
    		            | (((8UL) << RADIO_PCNF0_LFLEN_Pos) & RADIO_PCNF0_LFLEN_Msk)    // length of length field in bits 0-8.
    		          );
    		          
    m_adv_pdu[0]  = 0x40 | 0x02;
    m_adv_pdu[1]  = 0;

Related