This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Advertisement in time slot API at 100ms with connection handling in softdevice

I am using an nRF52 DK to build a prototype for a product.

The device needs to have a connectable advertisement (as peripheral with multiples connections possible) and at the same time advertise a non-connectable iBeacon.

In order to do that, I am using SDK 15.2 and softdevice S132 for the connectable advertisement and the time slot API (based on experimental_ble_app_multiactivity_beacon example from SDK 11) for the iBeacon non connectable advertisement.

At the moment, the standard advertisement has an interval of 100ms. The time slot ibeacon advertisement has an interval of 400ms.

If I reduce the interval of the time slot advertisement (through timeslot_request.params.normal.distance_us) to 100ms (value 100 * 1000), I get a lot of radio errors (from NRF_EVT_RADIO_BLOCKED or NRF_EVT_RADIO_CANCELES).

Is there a way to handle those errors in order to be able to have this 100ms interval for the time slots ?

How should I handle the NRF radio errors ?

I have joined the c file containing my time slot code.

/* Copyright (c) 2014 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */
 
#include "advertiser_beacon.h"
#include <stdio.h>
#include <string.h>

#include "nrf_soc.h"
#include "nrf_log.h"
#include "app_error.h"
#include "app_util.h"

#define ADV_PACK_LENGTH_IDX 1
#define ADV_DATA_LENGTH_IDX 12
#define ADV_HEADER_LEN      3

#define ADV_TYPE_LEN 2

#define APP_DEVICE_TYPE               0x02                              /**< 0x02 refers to Beacon. */

#define ADV_CHANNEL_37                 37
#define ADV_CHANNEL_38                 38
#define ADV_CHANNEL_39                 39

#define FREQ_ADV_CHANNEL_37             2
#define FREQ_ADV_CHANNEL_38            26
#define FREQ_ADV_CHANNEL_39            80

#define BEACON_SLOT_LENGTH             5500

static struct
{
    ble_uuid128_t           uuid;                               /** 128 proprietary service UUID to include in advertisement packets*/
    uint32_t                adv_interval;                       /** Advertising interval in milliseconds to be used for 'beacon' advertisements*/
    uint16_t                major;                              /** Major identifier to use for 'beacon'*/
    uint16_t                minor;                              /** Minor identifier to use for 'beacon'*/
    bool                    keep_running;                       /** */
    bool                    is_running;                         /** is the 'beacon' running*/
    uint32_t                slot_length;                        /** */
    nrf_radio_request_t     timeslot_request;                   /** */
    uint16_t                manuf_id;
    uint16_t                rssi;                               /** measured RSSI at 1 meter distance in dBm*/
    ble_gap_addr_t          beacon_addr;                        /** ble address to be used by the beacon*/
    ble_srv_error_handler_t error_handler;                      /**< Function to be called in case of an error. */
} m_beacon;

enum mode_t
{
  ADV_INIT,                                                 /** Initialisation*/
  ADV_RX_CH37,                                              /** Advertising on Rx channel 37*/
  ADV_RX_CH38,                                              /** Advertising on Rx channel 38*/
  ADV_RX_CH39,                                              /** Advertising on Rx channel 39*/
  ADV_DONE                                                  /** Done advertising*/
};

nrf_radio_request_t * m_configure_next_event(void)
{
    m_beacon.timeslot_request.request_type              = NRF_RADIO_REQ_TYPE_NORMAL;
    m_beacon.timeslot_request.params.normal.hfclk       = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
    m_beacon.timeslot_request.params.normal.priority    = NRF_RADIO_PRIORITY_HIGH;
    m_beacon.timeslot_request.params.normal.distance_us = m_beacon.adv_interval * 1000;
    m_beacon.timeslot_request.params.normal.length_us   = m_beacon.slot_length;
    return &m_beacon.timeslot_request;
}

uint32_t m_request_earliest(enum NRF_RADIO_PRIORITY priority)
{
    m_beacon.timeslot_request.request_type                = NRF_RADIO_REQ_TYPE_EARLIEST;
    m_beacon.timeslot_request.params.earliest.hfclk       = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
    m_beacon.timeslot_request.params.earliest.priority    = priority;
    m_beacon.timeslot_request.params.earliest.length_us   = m_beacon.slot_length;
    m_beacon.timeslot_request.params.earliest.timeout_us  = 1000000;
    return sd_radio_request(&m_beacon.timeslot_request);
}

static uint8_t * m_get_adv_packet(void)
{
    static uint8_t adv_pdu[40];
    uint8_t packet_len_start_idx, manuf_data_len_start_idx, beacon_data_len_start_idx;
    uint8_t offset    = 0;

    // Constructing header
    adv_pdu[offset]    = BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE; // BLE_GAP_ADV_TYPE_ADV_SCAN_IND;    // Advertisement type ADV_NONCONN_IND
    adv_pdu[offset++] |= 1 << 6;                           // TxAdd 1 (random address)
    adv_pdu[offset++]  = 0;                                // Packet length field (will be filled later)
    adv_pdu[offset++]  = 0x00;                             // Extra byte used to map into the radio register.
    packet_len_start_idx = offset;

    // Constructing base advertising packet
    memcpy(&adv_pdu[offset], m_beacon.beacon_addr.addr, BLE_GAP_ADDR_LEN);
    offset += BLE_GAP_ADDR_LEN;
    
    // Adding advertising data: Flags
    adv_pdu[offset++] =  ADV_TYPE_LEN;
    adv_pdu[offset++] =  BLE_GAP_AD_TYPE_FLAGS;
    adv_pdu[offset++] =  BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

    // Adding advertising data: Manufacturer specific data.
    adv_pdu[offset++]               =  0x00;                             // Manufacturer specific data length field (will be filled later).
    manuf_data_len_start_idx = offset;
    adv_pdu[offset++] =  BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA;
    offset += uint16_encode(m_beacon.manuf_id, &adv_pdu[offset]);
    adv_pdu[offset++] =  APP_DEVICE_TYPE;

    // Adding manufacturer specific data (beacon data).
    adv_pdu[offset++] =  0;                                              // Beacon data length field (will be filled later).
    beacon_data_len_start_idx = offset;
    memcpy(&adv_pdu[offset], &m_beacon.uuid, sizeof(ble_uuid128_t));
    offset += sizeof(ble_uuid128_t);
    offset += uint16_encode(m_beacon.major, &adv_pdu[offset]);
    offset += uint16_encode(m_beacon.minor, &adv_pdu[offset]);
    adv_pdu[offset++] = m_beacon.rssi;
    
    // Filling in length fields.
    adv_pdu[ADV_PACK_LENGTH_IDX]         = offset - packet_len_start_idx;
    adv_pdu[ADV_DATA_LENGTH_IDX]         = offset - manuf_data_len_start_idx;
    adv_pdu[beacon_data_len_start_idx-1] = offset - beacon_data_len_start_idx;
    
    return &adv_pdu[0];
}

static void m_set_adv_ch(uint32_t channel)
{
    if (channel == ADV_CHANNEL_37)
    {
        NRF_RADIO->FREQUENCY    = FREQ_ADV_CHANNEL_37;
        NRF_RADIO->DATAWHITEIV  = ADV_CHANNEL_37;
    }
    if (channel == ADV_CHANNEL_38)
    {
        NRF_RADIO->FREQUENCY    = FREQ_ADV_CHANNEL_38;
        NRF_RADIO->DATAWHITEIV  = ADV_CHANNEL_38;
    }
    if (channel == ADV_CHANNEL_39)
    {
        NRF_RADIO->FREQUENCY    = FREQ_ADV_CHANNEL_39;
        NRF_RADIO->DATAWHITEIV  = ADV_CHANNEL_39;
    }
}

static void m_configure_radio()
{
    NRF_LOG_INFO("config_radio");
    uint8_t * p_adv_pdu = m_get_adv_packet();

    NRF_RADIO->POWER        = 1;
    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) 37)                 << RADIO_PCNF1_MAXLEN_Pos    ) & RADIO_PCNF1_MAXLEN_Msk)
                              | ((RADIO_PCNF1_WHITEEN_Enabled     << RADIO_PCNF1_WHITEEN_Pos   ) & RADIO_PCNF1_WHITEEN_Msk);
    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->CRCPOLY      = 0x0000065b;
    NRF_RADIO->RXADDRESSES  = ((RADIO_RXADDRESSES_ADDR0_Enabled)  << RADIO_RXADDRESSES_ADDR0_Pos);
    NRF_RADIO->SHORTS       = ((1 << RADIO_SHORTS_READY_START_Pos) | (1 << RADIO_SHORTS_END_DISABLE_Pos));
    NRF_RADIO->MODE         = ((RADIO_MODE_MODE_Ble_1Mbit)        << RADIO_MODE_MODE_Pos       ) & RADIO_MODE_MODE_Msk;
    NRF_RADIO->TIFS         = 150;
    NRF_RADIO->INTENSET     = (1 << RADIO_INTENSET_DISABLED_Pos);
    NRF_RADIO->PREFIX0      = 0x0000008e; //access_addr[3]
    NRF_RADIO->BASE0        = 0x89bed600; //access_addr[0:3]
    NRF_RADIO->CRCINIT      = 0x00555555;
    NRF_RADIO->PACKETPTR    = (uint32_t) p_adv_pdu;
    
    NVIC_EnableIRQ(RADIO_IRQn);
}

void m_handle_start(void)
{
    // Configure TX_EN on TIMER EVENT_0
    NRF_PPI->CH[8].TEP    = (uint32_t)(&NRF_RADIO->TASKS_TXEN);
    NRF_PPI->CH[8].EEP    = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[0]);
    NRF_PPI->CHENSET      = (1 << 8);
    
    // Configure and initiate radio
    m_configure_radio();
    NRF_RADIO->TASKS_DISABLE = 1;
}

void m_handle_radio_disabled(enum mode_t mode)
{
    switch (mode)
    {
        case ADV_RX_CH37:
            m_set_adv_ch(ADV_CHANNEL_37);
            NRF_RADIO->TASKS_TXEN = 1;
            break;
        case ADV_RX_CH38:
            m_set_adv_ch(ADV_CHANNEL_38);
            NRF_TIMER0->TASKS_CLEAR = 1;
            NRF_TIMER0->CC[0]       = 400;
            break;
        case ADV_RX_CH39:
            m_set_adv_ch(ADV_CHANNEL_39);
            NRF_TIMER0->TASKS_CLEAR = 1;
            NRF_TIMER0->CC[0]       = 400;
            break;
        default:
            break;
    }
}

static nrf_radio_signal_callback_return_param_t * m_timeslot_callback(uint8_t signal_type)
{
  static nrf_radio_signal_callback_return_param_t signal_callback_return_param;
  static enum mode_t mode;

  signal_callback_return_param.params.request.p_next  = NULL;
  signal_callback_return_param.callback_action        = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;

  switch (signal_type)
  {
    case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START:
      bsp_board_led_invert(0);
      m_handle_start();

      mode = ADV_INIT;
      mode++;
      break;
    case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO:
      if (NRF_RADIO->EVENTS_DISABLED == 1)
      {
        NRF_RADIO->EVENTS_DISABLED = 0;

        m_handle_radio_disabled(mode);

        if (mode == ADV_DONE)
        {
            NRF_PPI->CHENCLR = (1 << 8);
            if (m_beacon.keep_running)
            {
                signal_callback_return_param.params.request.p_next = m_configure_next_event();
                signal_callback_return_param.callback_action       = NRF_RADIO_SIGNAL_CALLBACK_ACTION_REQUEST_AND_END;
            }
            else
            {
                signal_callback_return_param.callback_action       = NRF_RADIO_SIGNAL_CALLBACK_ACTION_END;
            }
            break;
        }
        mode++;
      }
      break;

    case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0:
        NRF_LOG_INFO("TIMER0 callback");
        break;

    case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:
        break;
    case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED:
        //Extension failed, schedule new timeslot at earliest time
        break;

    default:
        if (m_beacon.error_handler != NULL)
        {
            m_beacon.error_handler(NRF_ERROR_INVALID_STATE);
        }
      break;
  }

  return ( &signal_callback_return_param );
}

void app_beacon_on_sys_evt(uint32_t event)
{
    NRF_LOG_INFO("app_beacon_on_sys_evt");
    uint32_t err_code;
    switch (event)
    {
        case NRF_EVT_RADIO_SESSION_IDLE:
            if (m_beacon.is_running)
            {
                m_beacon.is_running = false;
                err_code = sd_radio_session_close();
                if ((err_code != NRF_SUCCESS) && (m_beacon.error_handler != NULL))
                {
                    m_beacon.error_handler(err_code);
                }
            }
            break;
        case NRF_EVT_RADIO_SESSION_CLOSED:
            break;
        case NRF_EVT_RADIO_BLOCKED:
        case NRF_EVT_RADIO_CANCELED: // Fall through
            NRF_LOG_INFO("Radio error");
            if (m_beacon.keep_running)
            {
                // TODO: A proper solution should try again in <block_count> * m_beacon.adv_interval
                err_code = m_request_earliest(NRF_RADIO_PRIORITY_HIGH);
                if ((err_code != NRF_SUCCESS) && (m_beacon.error_handler != NULL))
                {
                    m_beacon.error_handler(err_code);
                }
            }
            break;
        default:
            break;
    }
}

void app_beacon_init(ble_beacon_init_t * p_init)
{
    memcpy(&m_beacon.uuid, &p_init->uuid, sizeof(p_init->uuid));
    m_beacon.adv_interval = p_init->adv_interval;
    m_beacon.major        = p_init->major;
    m_beacon.minor        = p_init->minor;
    m_beacon.slot_length  = BEACON_SLOT_LENGTH;
    m_beacon.manuf_id     = p_init->manuf_id;
    m_beacon.rssi         = p_init->rssi;
    m_beacon.beacon_addr  = p_init->beacon_addr;
    m_beacon.error_handler= p_init->error_handler;
}

void app_beacon_start(void)
{
    NRF_LOG_INFO("app_beacon_start");
    m_beacon.keep_running = true;
    m_beacon.is_running   = true;

    uint32_t err_code = sd_radio_session_open(m_timeslot_callback);
    if ((err_code != NRF_SUCCESS) && (m_beacon.error_handler != NULL))
    {
        m_beacon.error_handler(err_code);
    }
    
    err_code = m_request_earliest(NRF_RADIO_PRIORITY_NORMAL);
    if ((err_code != NRF_SUCCESS) && (m_beacon.error_handler != NULL))
    {
        m_beacon.error_handler(err_code);
    }
}

void app_beacon_stop(void)
{
    NRF_LOG_INFO("app_beacon_stop");
    m_beacon.keep_running = false;
}

Parents
  • You get radio errors because your advertisements sometimes overlap with SoftDevice ones. This might be an issue when you're implementing a connection over a timeslot. But in case of advertisements, there's no strict timing requirements, so just ignore these errors - they're handled in  app_beacon_on_sys_evt().

  • OK. I see the "TODO: A proper solution should try again" in the evt_handler. Is there a better way to handle those errors ?

    When I look at the iBeacon advertisement packets in nrf Connect, it seems that the advertisement interval is not very stable. Sometimes I have about 100ms between two messages, and sometimes, I end up with less than 5ms between two messages. Is there a way to get a stable interval and avoid these <5ms interval packets ?

  • Hi,

    I'm not really sure why you have 5ms between two messages. Isn't it the interval you plan to have is 100ms ? 
    One trick to show how the timeslot is being scheduled is to toggle a pin when you are inside a timeslot. Then using the logic analyzer to visualize it. It would be very useful if you can provide one. 

  • Here is what nrfConnect shows me some messages with 100ms and some with <5ms. See below :

    Thanks for the responses. I see now that those errors might be normal.

    I will try tomorrow to check with a logic analyzer.

    Do you know by any chance if the length of the time slot I have configured (5500us) is enough or maybe too big a single advertisement packet ?

  • Do you have the same address on the connectable and the non-connectable advertising packets. If you do, this maybe just the distance between them not the distance between each non-connectable advertising packets. 

    It depends on the size of the advertising packet and the number of channel, I think 5.5ms should be enough, especially when you don't have any RX window to listen to any request. 

  • I have set up two different addresses so that the two advertisements show as two devices in nrf Connect.

    Do you think I should try to reduce the length of the windows in order to avoid being interrupted by the stack ?

  • You can count required length by yourself - for example, if your advertising data takes 28 bytes, one packet takes (16:header + 28:data + 3:crc) * 8 =  376 usec, and full 3-channel cycle will take 376+400+376+400+376 = 1928 usec.  Here is another point - since you don't expect any response from host, you can decrease interval between packets to 150 us (NRF_TIMER0->CC[0] = 150) and maybe even more. The shorter your slot, the better chance that it won't be blocked by SoftDevice.

Reply
  • You can count required length by yourself - for example, if your advertising data takes 28 bytes, one packet takes (16:header + 28:data + 3:crc) * 8 =  376 usec, and full 3-channel cycle will take 376+400+376+400+376 = 1928 usec.  Here is another point - since you don't expect any response from host, you can decrease interval between packets to 150 us (NRF_TIMER0->CC[0] = 150) and maybe even more. The shorter your slot, the better chance that it won't be blocked by SoftDevice.

Children
No Data
Related