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.
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.
- Initialize the RCL (See RCL_init) and provide a handle (See RCL_open).
- Initialize and configure a RCL_CMD_BLE5_ADV_t command for periodic advertising establishment.
- Provide the necessary Tx buffer(s) such that they have been initialized and configured to contain the necessary syncInfo needed for the periodic advertising.
- 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.
- Initialize and configure a RCL_CMD_BLE5_PER_ADV_t command.
- 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.
- Provide the necessary Tx buffer(s) such that they have been initialized and configured to contain the desired advertising data.
- 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.
- 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);
RCL_Buffer_TxBuffer *advExtTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer;
RCL_Buffer_TxBuffer *auxAdvTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer2;
RCL_Buffer_TxBuffer *auxSyncTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer3;
RCL_Buffer_TxBuffer *auxChainTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer4;
AuxPtr advExtAuxPtr;
AuxPtr auxSyncAuxPtr;
SyncInfo auxAdvSyncInfo;
advCmd.chanMap = CH_MAP;
advCmd.common.phyFeatures = PHY_FEATURES;
advCmd.ctx = &advCtx;
advCmd.stats = &advStats;
advCmd.common.runtime.callback = advCallback;
advCmd.common.runtime.lrfCallbackMask.value = 0;
advStats = (RCL_StatsAdvScanInit) { 0 };
advStats.config.accumulate = true;
advCmd.common.timing.absStartTime = startTimeAdvCmd;
advExtAuxPtr.auxOffset = 0;
advExtAuxPtr.offsetUnits = 1;
advExtAuxPtr.auxPhy = 1;
advExtAuxPtr.ca = 0;
advExtAuxPtr.chIndex = 25;
auxAdvSyncInfo.accessAddress = PER_ADV_ACCESS_ADDRESS;
auxAdvSyncInfo.crcInit = PER_ADV_CRC_INIT;
auxAdvSyncInfo.interval = 500;
auxAdvSyncInfo.offsetInfo.adjust = 0;
auxAdvSyncInfo.offsetInfo.base = 2400;
auxAdvSyncInfo.offsetInfo.units = 0;
auxAdvSyncInfo.chMap.chMap0To7 = 0xFF;
auxAdvSyncInfo.chMap.chMap8To15 = 0xFF;
auxAdvSyncInfo.chMap.chMap16To23 = 0xFF;
auxAdvSyncInfo.chMap.chMap24To31 = 0xFF;
auxAdvSyncInfo.chMap.chMap32To36 = 0x1F;
auxAdvSyncInfo.sleepClockAccuracy = 4;
auxAdvSyncInfo.periodicEventCounter = 0x78;
auxSyncAuxPtr.auxOffset = 0;
auxSyncAuxPtr.offsetUnits = 1;
auxSyncAuxPtr.ca = 0;
auxSyncAuxPtr.chIndex = 16;
auxSyncAuxPtr.auxPhy = 1;
setAdvExtBuffer(advExtTxBuffer, &advExtAuxPtr);
setAuxAdvSyncBuffer(auxAdvTxBuffer, &auxAdvSyncInfo, AUX_ADV_DATA_LEN);
perAdvCmd.ctx = &perAdvCtx;
perAdvCmd.stats = &perAdvStats;
perAdvCmd.common.runtime.callback = advCallback;
perAdvCmd.common.runtime.lrfCallbackMask.value = 0;
perAdvStats = (RCL_StatsAdvScanInit) { 0 };
perAdvStats.config.accumulate = true;
setAuxSyncBuffer(auxSyncTxBuffer, &auxSyncAuxPtr, AUX_ADV_DATA_LEN);
setAuxChainBuffer(auxChainTxBuffer, NULL, AUX_CHAIN_DATA_LEN);
PHY_FEATURES,
CH_MAP,
ADV_EXT_IND_PAYLOAD_LEN);
uint32_t startTimePerAdvCmd = startTimeAuxAdvInd +
perAdvCmd.common.timing.absStartTime = startTimePerAdvCmd;
perAdvCmd.ctx->accessAddress = PER_ADV_ACCESS_ADDRESS;
perAdvCmd.ctx->crcInit = PER_ADV_CRC_INIT;
perAdvCmd.common.phyFeatures = advExtAuxPtr.auxPhy;
}
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)
{
uint8_t payloadLength = 1 + AUX_ADV_SYNC_HDR_LEN + advDataLen;
uint8_t *txData;
txData[0] = BLE_HDR_AUX_ADV_IND;
txData[1] = payloadLength;
txData[3] = BLE5_EXT_HDR_FLAG_ADVA_BM | BLE5_EXT_HDR_FLAG_ADI_BM | BLE5_EXT_HDR_FLAG_SYNC_INFO_BM;
txData[4] = 0xAA;
txData[5] = 0xBB;
txData[6] = 0xCC;
txData[7] = 0xDD;
txData[8] = 0xEE;
txData[9] = 0x01;
txData[10] = 0;
txData[11] = 0;
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);
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.