Radio Control Layer (RCL)
Periodic Advertiser Command Handler

Introduction

In Bluetooth® Low Energy (BLE), periodic advertising (without responses) refers to a feature in which an advertiser can broadcast data at regular intervals without expecting or allowing a response from receiving devices. This advertising method relies on the use of extended advertising PDUs, and is particularly useful for applications requiring consistent data transmission over time, such as broadcasting sensor data, location beacons, or public information in a power-efficient manner. By not waiting for acknowledgments or replies, periodic advertising minimizes power consumption and reduces the complexity of maintaining a connection.

Please note that periodic advertising with responses was introduced with version v5.4 of the Bluetooth Core Specification, but it is not currently supported by the periodic advertising command handler.

PDU Type PDU Name Physical Channel LE 1M LE 2M LE Coded Currently Supported
0b0111 ADV_EXT_IND Primary * * *
0b0111 AUX_ADV_IND Secondary * * * *
0b0111 AUX_SYNC_IND Periodic * * * *
0b0111 AUX_CHAIN_IND Secondary and Periodic * * * *
0b0111 AUX_SYNC_SUBEVENT_IND Periodic * * *
0b0111 AUX_SYNC_SUBEVENT_RSP Periodic * * *

Usage

The periodic advertising command handler can be considered a simplified version of the advertiser command handler, which only sends AUX_SYNC_IND and AUX_CHAIN_IND PDUs. To inform potential scanners about the periodic advertising, a procedure known as periodic advertising establishment is required.

This procedure involves sending an ADV_EXT_IND PDU pointing to an AUX_ADV_IND that contains the SyncInfo needed by scanners to synchronize to the periodic advertising event.

Once periodic advertising has been established, the user must decide whether to inform other devices about the ongoing periodic advertising by invoking the periodic advertising establishment procedure as needed.

periodic_advertising_example.png
Example of periodic advertising events from the same advertising set

The following basic steps need to be executed to perform periodic advertising, including periodic advertising establishment.

  1. Initialize the RCL (See RCL_init) and provide a handle (See RCL_open).
  2. Initialize and configure a RCL_CMD_BLE5_ADV_t command for periodic advertising establishment.
  3. Provide the necessary Tx buffer(s) such that they have been initialized and configured to contain the necessary syncInfo needed for the periodic advertising.
  4. Submit the command with RCL_Command_submit, and either use RCL_Command_pend or a callback to wait for the command's conclusion and proceed with any additional tasks such as post-processing or the submitting of new commands.

If the execution of the command is successful, it can be assumed that the periodic advertisement establishment is done, and the user can proceed with the periodic advertising.

  1. Initialize and configure a RCL_CMD_BLE5_PER_ADV_t command.
  2. Use the start time of the RCL_CMD_BLE5_ADV_t command, RCL_BLE5_getAuxAdvStartTimeDelta, and the SyncInfo needed to set up the start time of the RCL_CMD_BLE5_PER_ADV_t command.
  3. Provide the necessary Tx buffer(s) such that they have been initialized and configured to contain the desired advertising data.
  4. Submit the command RCL_Command_submit, and either use RCL_Command_pend or a callback to wait for the command's conclusion and proceed with any additional tasks such as post-processing or the submitting of new commands.
  5. Rely on the start time of the previous RCL_CMD_BLE5_PER_ADV_t command to schedule new periodic advertising commands.

The following code snippet shows how to establish a periodic advertising as an advertiser:

void runPeriodicAdvertiser(void)
{
RCL_Handle h = RCL_open(&rclClient, &LRF_configBle);
/* Tx buffers needed for periodic advertising establishment */
RCL_Buffer_TxBuffer *advExtTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer;
RCL_Buffer_TxBuffer *auxAdvTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer2;
/* Tx buffers needed for periodic advertising */
RCL_Buffer_TxBuffer *auxSyncTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer3;
RCL_Buffer_TxBuffer *auxChainTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer4;
AuxPtr advExtAuxPtr;
AuxPtr auxSyncAuxPtr;
SyncInfo auxAdvSyncInfo;
/* Setup advertiser command handler used for periodic advertising establishment */
advCmd.chanMap = CH_MAP;
advCmd.common.phyFeatures = PHY_FEATURES;
advCmd.ctx = &advCtx;
advCmd.stats = &advStats;
advCmd.common.scheduling = RCL_Schedule_AbsTime;
advCmd.common.runtime.callback = advCallback;
advCmd.common.runtime.lrfCallbackMask.value = 0;
advCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
advStats = (RCL_StatsAdvScanInit) { 0 };
advStats.config.accumulate = true;
uint32_t startTimeAdvCmd = RCL_Scheduler_getCurrentTime() + RCL_SCHEDULER_SYSTIM_US(ADV_START_DELAY_US);
advCmd.common.timing.absStartTime = startTimeAdvCmd;
/* AuxPtr to be used for the ADV_EXT_IND indicating that the auxiliary packet (i.e. AUX_ADV_IND) will be sent on:
* BLE 2M PHY, channel 25, and as soon as possible.
*/
advExtAuxPtr.auxOffset = 0;
advExtAuxPtr.offsetUnits = 1;
advExtAuxPtr.auxPhy = 1;
advExtAuxPtr.ca = 0;
advExtAuxPtr.chIndex = 25;
/* SyncInfo to be send in the AUX_ADV_IND to inform potential scanners about the characteristics of the
* periodic advertising event
*/
auxAdvSyncInfo.accessAddress = PER_ADV_ACCESS_ADDRESS;
auxAdvSyncInfo.crcInit = PER_ADV_CRC_INIT;
auxAdvSyncInfo.interval = 500; /* Indicating a periodic advertising interval of 500 x 1.25 [ms] = 625 [ms] */
auxAdvSyncInfo.offsetInfo.adjust = 0;
auxAdvSyncInfo.offsetInfo.base = 2400; /* Indicating an sync packet window offset of 2400 x 30 [us] = 72 [ms] */
auxAdvSyncInfo.offsetInfo.units = 0; /* 30 [us] offset units */
/* All RF channels of the periodic physical channel are used */
auxAdvSyncInfo.chMap.chMap0To7 = 0xFF;
auxAdvSyncInfo.chMap.chMap8To15 = 0xFF;
auxAdvSyncInfo.chMap.chMap16To23 = 0xFF;
auxAdvSyncInfo.chMap.chMap24To31 = 0xFF;
auxAdvSyncInfo.chMap.chMap32To36 = 0x1F;
auxAdvSyncInfo.sleepClockAccuracy = 4; /* Indicating a sleep clock accuracy of 51 ppm to 75 ppm */
auxAdvSyncInfo.periodicEventCounter = 0x78; /* paEventCounter value that applies for the AUX_SYNC_IND */
/* AuxPtr to be used for the AUX_ADV_IND indicating that a chained packet will be sent on:
* Channel 16, and as soon as possible. No PHY change allowed at this point.
*/
auxSyncAuxPtr.auxOffset = 0;
auxSyncAuxPtr.offsetUnits = 1;
auxSyncAuxPtr.ca = 0;
auxSyncAuxPtr.chIndex = 16;
auxSyncAuxPtr.auxPhy = 1;
/* Populate Tx buffer used for the ADV_EXT_IND that will be sent on all primary advertisement channels */
setAdvExtBuffer(advExtTxBuffer, &advExtAuxPtr);
/* Populate Tx buffer used for the AUX_ADV_IND that will be sent on a secondary advertisement channel */
setAuxAdvSyncBuffer(auxAdvTxBuffer, &auxAdvSyncInfo, AUX_ADV_DATA_LEN);
advExtTxBuffer->state = RCL_BufferStatePending;
auxAdvTxBuffer->state = RCL_BufferStatePending;
RCL_TxBuffer_put(&advCtx.txBuffers, advExtTxBuffer);
RCL_TxBuffer_put(&advCtx.txBuffers, auxAdvTxBuffer);
/* Submit command wait for it to conclude */
RCL_Command_submit(h, &advCmd);
RCL_Command_pend(&advCmd);
/* Setup periodic advertiser command handler */
perAdvCmd.ctx = &perAdvCtx;
perAdvCmd.stats = &perAdvStats;
perAdvCmd.common.scheduling = RCL_Schedule_AbsTime;
perAdvCmd.common.runtime.callback = advCallback;
perAdvCmd.common.runtime.lrfCallbackMask.value = 0;
perAdvCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
perAdvStats = (RCL_StatsAdvScanInit) { 0 };
perAdvStats.config.accumulate = true;
/* Populate Tx buffer used for the AUX_SYNC_IND */
setAuxSyncBuffer(auxSyncTxBuffer, &auxSyncAuxPtr, AUX_ADV_DATA_LEN);
/* Populate Tx buffer used for the AUX_CHAIN_IND */
setAuxChainBuffer(auxChainTxBuffer, NULL, AUX_CHAIN_DATA_LEN);
auxSyncTxBuffer->state = RCL_BufferStatePending;
auxChainTxBuffer->state = RCL_BufferStatePending;
RCL_TxBuffer_put(&perAdvCtx.txBuffers, auxSyncTxBuffer);
RCL_TxBuffer_put(&perAdvCtx.txBuffers, auxChainTxBuffer);
/* Start time of AUX_ADV_IND is calculated based on the start time of the advertiser command,
* the time delta to the transmitting of the AUX_ADV_IND, the primary and secondary PHYs,
* and the channel map.
*/
uint32_t startTimeAuxAdvInd = startTimeAdvCmd + RCL_BLE5_getAuxAdvStartTimeDelta(PHY_FEATURES,
PHY_FEATURES,
CH_MAP,
ADV_EXT_IND_PAYLOAD_LEN);
uint32_t startTimePerAdvCmd = startTimeAuxAdvInd +
RCL_SCHEDULER_SYSTIM_US(auxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_30_US);
perAdvCmd.common.timing.absStartTime = startTimePerAdvCmd;
/* Configure periodic advertising based on syncInfo information and start time of AUX_ADV_IND */
perAdvCmd.ctx->accessAddress = PER_ADV_ACCESS_ADDRESS;
perAdvCmd.ctx->crcInit = PER_ADV_CRC_INIT;
/* Use same PHY as the one used to send the AUX_ADV_IND */
perAdvCmd.common.phyFeatures = advExtAuxPtr.auxPhy;
/* Submit command wait for it to conclude. If needed, update start time of the periodic advertising command
* for other periodic advertising events.
*/
RCL_Command_submit(h, &perAdvCmd);
RCL_Command_pend(&perAdvCmd);
}

As mentioned before, periodic advertising establishment requires that an AUX_ADV_IND PDU be sent with the necessary SyncInfo. This information is used by potential scanners to properly follow the periodic advertising events. In the previous code snippet, the Tx buffer corresponding to the AUX_ADV_IND is built with a helper function that populates the Tx buffer with appropriate values for each field in the Common Extended Advertising Payload Format.

typedef struct
{
uint16_t base: 13;
uint16_t units: 1;
uint16_t adjust: 1;
uint16_t reserved: 1;
} packetOffsetInfo;
typedef struct
{
uint8_t chMap0To7;
uint8_t chMap8To15;
uint8_t chMap16To23;
uint8_t chMap24To31;
uint8_t chMap32To36: 5;
uint8_t reserved: 3;
} channelMap;
typedef struct
{
packetOffsetInfo offsetInfo;
uint16_t interval;
channelMap chMap;
uint8_t sleepClockAccuracy;
uint32_t accessAddress;
uint32_t crcInit;
uint16_t periodicEventCounter;
} SyncInfo;
static void setAuxAdvSyncBuffer(RCL_Buffer_TxBuffer *buffer, SyncInfo *auxAdvSyncInfo, uint8_t advDataLen)
{
/* Include extra octet corresponding to the extended header length and AdvMode */
uint8_t payloadLength = 1 + AUX_ADV_SYNC_HDR_LEN + advDataLen;
uint8_t *txData;
txData = RCL_TxBuffer_init(buffer, NUM_PAD, BLE_HDR_LEN, payloadLength);
txData[0] = BLE_HDR_AUX_ADV_IND;
txData[1] = payloadLength;
txData[2] = (BLE_ADV_MODE_NONCONN_NONSCAN << 6) | AUX_ADV_SYNC_HDR_LEN;
txData[3] = BLE5_EXT_HDR_FLAG_ADVA_BM | BLE5_EXT_HDR_FLAG_ADI_BM | BLE5_EXT_HDR_FLAG_SYNC_INFO_BM;
/* AdvA */
txData[4] = 0xAA;
txData[5] = 0xBB;
txData[6] = 0xCC;
txData[7] = 0xDD;
txData[8] = 0xEE;
txData[9] = 0x01;
/* ADI */
txData[10] = 0;
txData[11] = 0;
/* SyncInfo */
txData[12] = auxAdvSyncInfo->offsetInfo.base & 0xFF;
txData[13] = ((auxAdvSyncInfo->offsetInfo.base >> 8) & 0x1F) |
((auxAdvSyncInfo->offsetInfo.units & 0x01) << 5) |
((auxAdvSyncInfo->offsetInfo.adjust & 0x01) << 6);
txData[14] = (auxAdvSyncInfo->interval & 0xFF);
txData[15] = (auxAdvSyncInfo->interval >> 8);
txData[16] = (auxAdvSyncInfo->chMap.chMap0To7);
txData[17] = (auxAdvSyncInfo->chMap.chMap8To15);
txData[18] = (auxAdvSyncInfo->chMap.chMap16To23);
txData[19] = (auxAdvSyncInfo->chMap.chMap24To31);
txData[20] = (auxAdvSyncInfo->chMap.chMap32To36 & 0x1F) |
((auxAdvSyncInfo->sleepClockAccuracy & 0x07) << 5);
txData[21] = (auxAdvSyncInfo->accessAddress & 0xFF);
txData[22] = (auxAdvSyncInfo->accessAddress >> 8) & 0xFF;
txData[23] = (auxAdvSyncInfo->accessAddress >> 16) & 0xFF;
txData[24] = (auxAdvSyncInfo->accessAddress >> 24) & 0xFF;
txData[25] = (auxAdvSyncInfo->crcInit & 0xFF);
txData[26] = (auxAdvSyncInfo->crcInit >> 8) & 0xFF;
txData[27] = (auxAdvSyncInfo->crcInit >> 16) & 0xFF;
txData[28] = (auxAdvSyncInfo->periodicEventCounter & 0xFF);
txData[29] = (auxAdvSyncInfo->periodicEventCounter >> 8);
/* AdvData */
if (advDataLen > 0)
{
for(uint32_t i = 0; i < advDataLen; i++)
{
txData[30 + i] = i & 0xFF;
}
}
}

Notice how the previously defined SyncInfo is used to populate the Tx buffer with the help of a C struct.

Using the periodic advertising command handler requires at least one Tx buffer in the buffer list corresponding to the AUX_SYNC_IND PDU. If needed, the user can rely on the callback to the command to provide additional Tx buffers corresponding to AUX_CHAIN_IND PDUs.

Considering that the syncPacketWindowOffset indicates the transmission time of an AUX_SYNC_IND, and that the reference point is the start time of the AUX_ADV_IND, the RCL provides the RCL_BLE5_getAuxAdvStartTimeDelta API to ease the calculation of the start time of the periodic advertising command.

This API can also be used once periodic advertising is ongoing, but the advertiser needs to inform new scanner devices about the ongoing periodic advertising.

Architecture

The life cycle of the periodic advertiser command handler depends on the number of packets that are part of the periodic advertising event.

The command handler requires at least one Tx buffer in the buffer list at command start. This buffer will be inspected to determine if an AuxPtr has been provided, and based on this, the operation will either conclude after transmitting the packet or schedule a new operation corresponding to the auxiliary packet.

It's worth mentioning that only non-connectable/non-scannable PDUs can be sent with the periodic advertising command, and other PDUs will be rejected and the command will end indicating a Tx buffer corruption.

RCL Event (In) Description
RCL_EventSetup Setup has been performed
RCL_EventTimerStart Timer-based start signalled
RCL_EventGracefulStop Graceful stop has been observed
RCL_EventHardStop Hard stop has been observed
RCL Event (Out) Description
RCL_EventLastCmdDone The RCL is finished with the command
RCL_EventCmdStarted Command handler has accepted and started executing
RCL_EventTxBufferFinished An TX buffer is finished
LRF Event Description
LRF_EventOpDone The PBE operation has finished
LRF_EventOpError Something went wrong. Cause located in the PBE ENDCAUSE register