Radio Control Layer (RCL)
Periodic Scanner 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.

Periodic Advertising with Responses (PAwR) was introduced in Bluetooth Core Specification 5.4 to enable bi-directional communication between an advertiser and multiple scanners within a structured time frame. Unlike standard periodic advertising, PAwR allows devices to send responses within pre-defined response slots, enabling efficient communication while maintaining low power consumption.

A key feature of PAwR is that AUX_CONNECT_REQ PDUs are allowed, meaning that advertisers can act as initiators and trigger a transition from periodic advertising to an active connection with a particular scanner when needed. This enables both connectionless and connection-oriented communication models.

PDU Type PDU Name Physical Channel LE 1M LE 2M LE Coded Currently Supported Used in
0b0101 AUX_CONNECT_REQ Secondary * * * * PAwR
0b0111 ADV_EXT_IND Primary * * * PA
0b0111 AUX_ADV_IND Secondary * * * * PA
0b0111 AUX_SYNC_IND Periodic * * * * PA
0b0111 AUX_CHAIN_IND Secondary and Periodic * * * * PA
0b0111 AUX_SYNC_SUBEVENT_IND Periodic * * * * PAwR
0b0111 AUX_SYNC_SUBEVENT_RSP Periodic * * * * PAwR
0b1000 AUX_CONNECT_RSP Secondary * * * * PAwR

Usage

The periodic scanner command handler is tailored for both periodic advertising with responses and periodic advertising without responses. It can receive indication PDUs as detailed in the table of supported PDU types above. Unlike the general scanner command handler, it does not send scan requests (SCAN_REQ or AUX_SCAN_REQ) or receive scan responses (SCAN_RSP or AUX_SCAN_RSP).

Periodic Advertising Establishment

To receive periodic advertisements, a scanner device must perform a procedure known as periodic advertising establishment. During this procedure, the advertiser device sends an ADV_EXT_IND PDU pointing to an AUX_ADV_IND that contains the SyncInfo needed by the scanner to synchronize with the periodic advertising train.

The AUX_ADV_IND may include an ACAD field with an AD Structure corresponding to the Periodic Advertising Response Timing Information. The scanner can use this information to determine whether it is periodic advertising with responses.

To perform periodic advertising establishment, follow these basic steps:

  1. Initialize the RCL (See RCL_init) and provide a handle (See RCL_open).
  2. Initialize and configure a RCL_CMD_BLE5_SCANNER_t command for periodic advertising establishment.
  3. Provide the necessary Multibuffer(s) such that they have been initialized and configured to receive 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.

Periodic Advertising without Responses

When configured to do Periodic Advertising (without responses), the periodic scanner command handler can only receive AUX_SYNC_IND and AUX_CHAIN_IND PDUs and if needed follow the AuxPtrs in them.

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

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

  1. Initialize and configure a RCL_CMD_BLE5_PER_SCANNER_t command considering the received SyncInfo.
  2. Use the reception time of the AUX_ADV_IND to calculate the syncPacketWindowOffset and set up the start time of the RCL_CMD_BLE5_PER_SCANNER_t command.
  3. Provide the necessary Multibuffer(s) such that they have been initialized and configured to receive the 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_SCANNER_t command and the received periodic advertising interval to schedule new periodic advertising commands.

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

void runPeriodicScanner(void)
{
RCL_Handle h = RCL_open(&rclClient, &LRF_configBle);
/* One multibuffer for the packets received during periodic advertising establishment */
uint32_t rxBuffer[RCL_MultiBuffer_len_u32(BLE_TEST_MULTI_BUF_LEN)];
RCL_Buffer_DataEntry *rxAdvExtPkt;
RCL_Buffer_DataEntry *rxAuxAdvPkt;
/* One multibuffer for the packets received during periodic advertising */
uint32_t rxBuffer2[RCL_MultiBuffer_len_u32(BLE_TEST_MULTI_BUF_LEN)];
RCL_Buffer_DataEntry *rxAuxSyncPkt;
RCL_Buffer_DataEntry *rxAuxChainPkt;
SyncInfo rxAuxAdvSyncInfo;
/* Setup advertiser command handler used for periodic advertising establishment */
scanCmd.channel = 37;
scanCmd.common.phyFeatures = PHY_FEATURES;
scanCmd.ctx = &scanCtx;
scanCmd.stats = &scanStats;
scanCmd.common.scheduling = RCL_Schedule_AbsTime;
scanCmd.common.runtime.callback = scanCallback;
scanCmd.common.runtime.lrfCallbackMask.value = 0;
scanCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
scanStats = (RCL_StatsAdvScanInit) { 0 };
scanStats.config.accumulate = true;
uint32_t startTimeScanCmd = RCL_Scheduler_getCurrentTime() + RCL_SCHEDULER_SYSTIM_US(ADV_START_DELAY_US);
scanCmd.common.timing.absStartTime = startTimeScanCmd;
/* MultiBuffer configuration */
/* Needed to receive ADV_EXT_IND and AUX_ADV_IND */
RCL_MultiBuffer *multiBuffer = (RCL_MultiBuffer *)rxBuffer;
RCL_MultiBuffer_init(multiBuffer, sizeof(rxBuffer));
RCL_MultiBuffer_put(&scanCtx.rxBuffers, multiBuffer);
/* Submit command wait for it to conclude */
RCL_Command_submit(h, &scanCmd);
RCL_Command_pend(&scanCmd);
/* Prepare list of RX buffers that are done */
List_List finishedBuffers;
List_clearList(&finishedBuffers);
rxAdvExtPkt = RCL_MultiBuffer_RxEntry_get(&scanCtx.rxBuffers, &finishedBuffers);
if (rxAdvExtPkt != NULL)
{
// Do something with the data
}
rxAuxAdvPkt = RCL_MultiBuffer_RxEntry_get(&scanCtx.rxBuffers, &finishedBuffers);
if (rxAuxAdvPkt != NULL)
{
/* Extract info from received SyncInfo field and use it to schedule next periodic scanner */
getSyncInfoData(rxAuxAdvPkt, &rxAuxAdvSyncInfo);
/*
* Use the timestamp of the received AUX_ADV_IND packet to calculate start time of periodic scan operation.
* AUX_SYNC_IND should be sent within one offset unit of the sync packet window offset
*/
uint32_t startTimePerScanCmd = RCL_BLE5_getRxTimestamp(rxAuxAdvPkt);
startTimePerScanCmd += RCL_SCHEDULER_SYSTIM_US(rxAuxAdvSyncInfo.offsetInfo.units ?
rxAuxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_300_US :
rxAuxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_30_US);
/* Setup periodic scan command handler */
perScanCmd.ctx = &perScanCtx;
perScanCmd.stats = &perScanStats;
perScanCmd.common.scheduling = RCL_Schedule_AbsTime;
perScanCmd.common.runtime.callback = scanCallback;
perScanCmd.common.runtime.lrfCallbackMask.value = 0;
perScanCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
perScanStats = (RCL_StatsAdvScanInit) { 0 };
perScanStats.config.accumulate = true;
/*
* Real channel is determined using the channel selection algorithm #2.
* For this test, simply use a fixed channel that is aligned between both DUTs.
*/
perScanCmd.channel = 18;
perScanCtx.accessAddress = rxAuxAdvSyncInfo.accessAddress;
perScanCtx.crcInit = rxAuxAdvSyncInfo.crcInit;
perScanCmd.common.scheduling = RCL_Schedule_AbsTime;
perScanCmd.common.timing.absStartTime = startTimePerScanCmd - RCL_SCHEDULER_SYSTIM_US(150);
/* Periodic scanner will use the same PHY as the PHY used for the auxiliary packet that sent the syncInfo */
uint16_t receivedPhy = (uint16_t) RCL_BLE5_getRxStatus(rxAuxAdvPkt).phy;
perScanCmd.common.phyFeatures = receivedPhy;
/* MultiBuffer configuration */
/* Needed to receive AUX_SYNC_IND and possible AUX_CHAIN_IND */
RCL_MultiBuffer *multiBuffer2 = (RCL_MultiBuffer *)rxBuffer2;
RCL_MultiBuffer_init(multiBuffer2, sizeof(rxBuffer2));
RCL_MultiBuffer_put(&perScanCtx.rxBuffers, multiBuffer2);
/* Submit command wait for it to conclude. If needed, update start time of the periodic scanner command
* for other periodic advertising events.
*/
RCL_Command_submit(h, &perScanCmd);
RCL_Command_pend(&perScanCmd);
rxAuxSyncPkt = RCL_MultiBuffer_RxEntry_get(&perScanCtx.rxBuffers, &finishedBuffers);
if (rxAuxSyncPkt != NULL)
{
// Do something with the data
}
rxAuxChainPkt = RCL_MultiBuffer_RxEntry_get(&perScanCtx.rxBuffers, &finishedBuffers);
if (rxAuxChainPkt != NULL)
{
// Do something with the data
}
}
}

For demonstration purposes, a helper function that inspects the extended header flags of the received packet and stores it in a C struct is used to extract the information contained in the SyncInfo field.

static void getSyncInfoData(RCL_Buffer_DataEntry *rxEntry, SyncInfo *rxSyncInfo)
{
/* Check the extended header flags and determine index of SyncInfo */
uint8_t extHdrFlags = rxEntry->data[rxEntry->numPad - 1 + BLE_HDR_LEN + 1];
/* Identify the index of the syncInfo field starting from the extended header flags */
uint8_t syncInfoIndex = rxEntry->numPad - 1 + BLE_HDR_LEN + 1 + 1;
/* Check advertising type */
if (extHdrFlags & BLE5_EXT_HDR_FLAG_SYNC_INFO_BM)
{
if (extHdrFlags & BLE5_EXT_HDR_FLAG_ADVA_BM)
{
syncInfoIndex += BLE_EXT_HDR_ADVA_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_TARGETA_BM)
{
syncInfoIndex += BLE_EXT_HDR_TARGETA_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_CTE_INFO_BM)
{
syncInfoIndex += BLE_EXT_HDR_CTEINFO_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_ADI_BM)
{
syncInfoIndex += BLE_EXT_HDR_ADI_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_AUX_PTR_BM)
{
syncInfoIndex += BLE_EXT_HDR_AUXPTR_LEN;
}
}
rxSyncInfo->offsetInfo.base = (uint16_t)(rxEntry->data[syncInfoIndex + 1] & 0x1F) << 8 |
(uint16_t)(rxEntry->data[syncInfoIndex]);
rxSyncInfo->offsetInfo.units = (rxEntry->data[syncInfoIndex + 1] >> 5) & 0x01;
rxSyncInfo->offsetInfo.adjust = (rxEntry->data[syncInfoIndex + 1] >> 6) & 0x01;
rxSyncInfo->interval = (uint16_t)(rxEntry->data[syncInfoIndex + 3]) << 8 |
(uint16_t)(rxEntry->data[syncInfoIndex + 2]);
rxSyncInfo->chMap.chMap0To7 = rxEntry->data[syncInfoIndex + 4];
rxSyncInfo->chMap.chMap8To15 = rxEntry->data[syncInfoIndex + 5];
rxSyncInfo->chMap.chMap16To23 = rxEntry->data[syncInfoIndex + 6];
rxSyncInfo->chMap.chMap24To31 = rxEntry->data[syncInfoIndex + 7];
rxSyncInfo->chMap.chMap32To36 = rxEntry->data[syncInfoIndex + 8] & 0x1F;
rxSyncInfo->sleepClockAccuracy = rxEntry->data[syncInfoIndex + 8] >> 5;
rxSyncInfo->accessAddress = (uint32_t)(rxEntry->data[syncInfoIndex + 12]) << 24 |
(uint32_t)(rxEntry->data[syncInfoIndex + 11]) << 16 |
(uint32_t)(rxEntry->data[syncInfoIndex + 10]) << 8 |
(uint32_t)(rxEntry->data[syncInfoIndex + 9]);
rxSyncInfo->crcInit = (uint32_t)(rxEntry->data[syncInfoIndex + 15]) << 16 |
(uint32_t)(rxEntry->data[syncInfoIndex + 14]) << 8 |
(uint32_t)(rxEntry->data[syncInfoIndex + 13]);
rxSyncInfo->periodicEventCounter = (uint16_t)(rxEntry->data[syncInfoIndex + 17]) << 8 |
(uint16_t)(rxEntry->data[syncInfoIndex + 16]);
}

The scanning device can then use this information and the timestamp of the received AUX_ADV_IND (using the RCL_BLE5_getRxTimestamp API) to calculate the syncPacketWindowOffset and setup the start time of the periodic scanner command.

It's worth mentioning that the PHY to be used for the periodic advertising event corresponds to the PHY that the advertiser used when it sent the AUX_ADV_IND. For this reason, the example also relies on the use of the RCL_BLE5_getRxStatus API to return the status byte of the received packet.

Periodic Advertising With Responses

When configured to do periodic advertising with responses, the periodic scanner command handler can only receive AUX_SYNC_SUBEVENT_IND and AUX_CONNECT_REQ PDUs.

If an AUX_SYNC_SUBEVENT_IND is received, the caller of the command must process the received PDU and if needed use the RCL_CMD_BLE5_GENERIC_TX_t command to send the appropriate AUX_SYNC_SUBEVENT_RSP.

If an AUX_CONNECT_REQ is received, the command handler will automatically determine if it is the target of the connect request, and respond with an AUX_CONNECT_RSP if needed. In such case, the command handler will return a specific command status to indicate that a connection has been established.

pawr_scanning_example.png
Example of periodic advertising with responses events from the same advertising set

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

  1. Initialize and configure a RCL_CMD_BLE5_PER_SCANNER_t command considering the received SyncInfo and ACAD.
  2. Use the reception time of the AUX_ADV_IND to calculate the syncPacketWindowOffset and set up the start time of the RCL_CMD_BLE5_PER_SCANNER_t command.
  3. Ensure the TX buffer is properly configured to contain an AUX_CONNECT_RSP PDU in case an AUX_CONNECT_REQ is received.
  4. Provide the necessary Multibuffer(s) such that they have been initialized and configured to receive the advertising data.
  5. 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.
  6. If the RCL_CMD_BLE5_PER_SCANNER_t command concludes with RCL_CommandStatus_Connect, schedule the connection events with the advertiser.
  7. If the RCL_CMD_BLE5_PER_SCANNER_t command concludes with RCL_CommandStatus_Finished and the received packet is an AUX_SYNC_SUBEVENT_IND, use the reception time of the AUX_SYNC_SUBEVENT_IND and the designated subevent and response slot to initialize and configure a RCL_CMD_BLE5_GENERIC_TX_t command to send the appropriate AUX_SYNC_SUBEVENT_RSP.
  8. Use the reception time of the AUX_SYNC_SUBEVENT_IND, the periodic advertising subevent interval and the periodic advertising interval to schedule the listening for the next subevents.

The following code snippet shows how to establish a connection by using the PAwR scanner:

void runPAwRScannerConn(void)
{
RCL_Handle h = RCL_open(&rclClient, &LRF_configBle);
/* One multibuffer for the packets received during periodic advertising establishment */
uint32_t rxBuffer[RCL_MultiBuffer_len_u32(BLE_TEST_MULTI_BUF_LEN)];
RCL_Buffer_DataEntry *rxAdvExtPkt;
RCL_Buffer_DataEntry *rxAuxAdvPkt;
/* One multibuffer for the packets received during periodic advertising */
uint32_t rxBuffer2[RCL_MultiBuffer_len_u32(BLE_TEST_MULTI_BUF_LEN)];
/* One TX buffer for the AUX_CONNECT_RSP */
RCL_Buffer_TxBuffer *auxConnRspTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer;
SyncInfo rxAuxAdvSyncInfo;
/* Device address of this scanner */
DeviceAddr advA;
/* Device address of the initiator */
DeviceAddr targetA;
/* Setup advertiser command handler used for periodic advertising establishment */
scanCmd.channel = 37;
scanCmd.common.phyFeatures = PHY_FEATURES;
scanCmd.ctx = &scanCtx;
scanCmd.stats = &scanStats;
scanCmd.common.scheduling = RCL_Schedule_AbsTime;
scanCmd.common.runtime.callback = scanCallback;
scanCmd.common.runtime.lrfCallbackMask.value = 0;
scanCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
scanStats = (RCL_StatsAdvScanInit) { 0 };
scanStats.config.accumulate = true;
uint32_t startTimeScanCmd = RCL_Scheduler_getCurrentTime() + RCL_SCHEDULER_SYSTIM_US(ADV_START_DELAY_US);
scanCmd.common.timing.absStartTime = startTimeScanCmd;
/* MultiBuffer configuration */
/* Needed to receive ADV_EXT_IND and AUX_ADV_IND */
RCL_MultiBuffer *multiBuffer = (RCL_MultiBuffer *)rxBuffer;
RCL_MultiBuffer_init(multiBuffer, sizeof(rxBuffer));
RCL_MultiBuffer_put(&scanCtx.rxBuffers, multiBuffer);
/* Submit command wait for it to conclude */
RCL_Command_submit(h, &scanCmd);
RCL_Command_pend(&scanCmd);
/* Prepare list of RX buffers that are done */
List_List finishedBuffers;
List_clearList(&finishedBuffers);
rxAdvExtPkt = RCL_MultiBuffer_RxEntry_get(&scanCtx.rxBuffers, &finishedBuffers);
if (rxAdvExtPkt != NULL)
{
/* Do something with the data */
}
rxAuxAdvPkt = RCL_MultiBuffer_RxEntry_get(&scanCtx.rxBuffers, &finishedBuffers);
if (rxAuxAdvPkt != NULL)
{
/* Extract info from received SyncInfo field and use it to schedule next periodic scanner */
getSyncInfoData(rxAuxAdvPkt, &rxAuxAdvSyncInfo);
/*
* Use the timestamp of the received AUX_ADV_IND packet to calculate start time of periodic scan operation.
* AUX_SYNC_SUBEVENT_IND or AUX_CONNECT_REQ should be sent within one offset unit of the sync packet window offset
*/
uint32_t startTimePerScanCmd = RCL_BLE5_getRxTimestamp(rxAuxAdvPkt);
startTimePerScanCmd += RCL_SCHEDULER_SYSTIM_US(rxAuxAdvSyncInfo.offsetInfo.units ?
rxAuxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_300_US :
rxAuxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_30_US);
/* Setup PAwR scan command handler */
perScanCmd.perAdvType = 1;
perScanCmd.ctx = &perScanCtx;
perScanCmd.stats = &perScanStats;
perScanCmd.common.scheduling = RCL_Schedule_AbsTime;
perScanCmd.common.runtime.callback = scanCallback;
perScanCmd.common.runtime.lrfCallbackMask.value = 0;
perScanCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
perScanStats = (RCL_StatsAdvScanInit) { 0 };
perScanStats.config.accumulate = true;
/*
* Real channel is determined using the channel selection algorithm #2.
* For this test, simply use a fixed channel that is aligned between both DUTs.
*/
perScanCmd.channel = 18;
perScanCtx.accessAddress = rxAuxAdvSyncInfo.accessAddress;
perScanCtx.crcInit = rxAuxAdvSyncInfo.crcInit;
perScanCmd.common.scheduling = RCL_Schedule_AbsTime;
perScanCmd.common.timing.absStartTime = startTimePerScanCmd - RCL_SCHEDULER_SYSTIM_US(150);
/* Periodic scanner will use the same PHY as the PHY used for the auxiliary packet that sent the syncInfo */
uint16_t receivedPhy = (uint16_t) RCL_BLE5_getRxStatus(rxAuxAdvPkt).phy;
perScanCmd.common.phyFeatures = receivedPhy;
/* MultiBuffer configuration */
/* Needed to receive AUX_SYNC_SUBEVENT_IND or AUX_CONNECT_REQ */
RCL_MultiBuffer *multiBuffer2 = (RCL_MultiBuffer *)rxBuffer2;
RCL_MultiBuffer_init(multiBuffer2, sizeof(rxBuffer2));
RCL_MultiBuffer_put(&perScanCtx.rxBuffers, multiBuffer2);
/* Set the TX buffer containing AUX_CONNECT_RSP */
advA = (DeviceAddr) { .addrType = 0, .addrByte = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06} };
targetA = (DeviceAddr) { .addrType = 0, .addrByte = {0x06, 0x05, 0x04, 0x03, 0x02, 0x01} };
setAuxConnRspBuffer(auxConnRspTxBuffer, &advA, &targetA);
RCL_TxBuffer_put(&perScanCtx.txBuffers, auxConnRspTxBuffer);
/* Submit command and wait for it to conclude. If needed, update start time of the periodic scanner command
* for other periodic advertising events.
*/
RCL_Command_submit(h, &perScanCmd);
RCL_Command_pend(&perScanCmd);
/* PAwR scanner command can ends with the connect status
* which means the scanner has received AUX_CONNECT_REQ and sent AUX_CONNECT_RSP
* to establish the connection with the advertiser successfully
*/
if (perScanCmd.common.status == RCL_CommandStatus_Connect)
{
/* Connection established, do something, e.g. run connection events */
}
}
}

To enable PAwR, ensure the perAdvType field is set as demonstrated in the code snippet. The PAwR scanner requires a Tx buffer for the AUX_CONNECT_RSP (in case an AUX_CONNECT_REQ is received) and a Multibuffer for receiving AUX_SYNC_SUBEVENT_IND or AUX_CONNECT_REQ PDUs. Note that the AdvA in AUX_CONNECT_RSP corresponds to the scanner's device address, while the TargetA corresponds to the advertiser's device address.

The code snippet includes a helper function that configures the Tx buffer for AUX_CONNECT_RSP.

static void setAuxConnRspBuffer(RCL_Buffer_TxBuffer *buffer, DeviceAddr *advA, DeviceAddr *targetA)
{
/* Include extra octet corresponding to the extended header length and AdvMode */
uint8_t payloadLength = 1 + AUX_CONN_RSP_HDR_LEN;
uint8_t *txData = RCL_TxBuffer_init(buffer, NUM_PAD, BLE_HDR_LEN, payloadLength);
txData[0] = BLE_HDR_AUX_CONN_RSP_IND | (advA->addrType << 6) | (targetA->addrType << 7);
txData[1] = payloadLength;
txData[2] = (BLE_ADV_MODE_NONCONN_NONSCAN << 6) | AUX_CONN_RSP_HDR_LEN;
txData[3] = BLE5_EXT_HDR_FLAG_ADVA_BM | BLE5_EXT_HDR_FLAG_TARGETA_BM;
/* AdvA */
for (uint8_t i = 0; i < BLE_ADDR_LEN_BYTES; ++i)
{
txData[4 + i] = advA->addrByte[i];
}
/* TargetA */
for (uint8_t i = 0; i < BLE_ADDR_LEN_BYTES; ++i)
{
txData[10 + i] = targetA->addrByte[i];
}
}

The following code snippet demonstrates how to use the periodic scanner command handler to receive AUX_SYNC_SUBEVENT_IND PDUs:

void runPAwRScanner(void)
{
RCL_Handle h = RCL_open(&rclClient, &LRF_configBle);
/* One multibuffer for the packets received during periodic advertising establishment */
uint32_t rxBuffer[RCL_MultiBuffer_len_u32(BLE_TEST_MULTI_BUF_LEN)];
RCL_Buffer_DataEntry *rxAdvExtPkt;
RCL_Buffer_DataEntry *rxAuxAdvPkt;
RCL_Buffer_DataEntry *rxSubEventIndOrConnReqPkt;
/* One multibuffer for the packets received during periodic advertising */
uint32_t rxBuffer2[RCL_MultiBuffer_len_u32(BLE_TEST_MULTI_BUF_LEN)];
/* One TX buffer for the AUX_CONNECT_RSP */
RCL_Buffer_TxBuffer *auxConnRspTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer;
/* One TX buffer for the AUX_SYNC_SUBEVENT_RSP */
RCL_Buffer_TxBuffer *auxSyncSubEventRspTxBuffer = (RCL_Buffer_TxBuffer *)txBuffer2;
SyncInfo rxAuxAdvSyncInfo;
SubEventInfo rxAuxAdvSubEventInfo;
/* Device address of this scanner */
DeviceAddr advA;
/* Device address of the initiator */
DeviceAddr targetA;
/* Setup advertiser command handler used for periodic advertising establishment */
scanCmd.channel = 37;
scanCmd.common.phyFeatures = PHY_FEATURES;
scanCmd.ctx = &scanCtx;
scanCmd.stats = &scanStats;
scanCmd.common.scheduling = RCL_Schedule_AbsTime;
scanCmd.common.runtime.callback = scanCallback;
scanCmd.common.runtime.lrfCallbackMask.value = 0;
scanCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
scanStats = (RCL_StatsAdvScanInit) { 0 };
scanStats.config.accumulate = true;
uint32_t startTimeScanCmd = RCL_Scheduler_getCurrentTime() + RCL_SCHEDULER_SYSTIM_US(ADV_START_DELAY_US);
scanCmd.common.timing.absStartTime = startTimeScanCmd;
/* MultiBuffer configuration */
/* Needed to receive ADV_EXT_IND and AUX_ADV_IND */
RCL_MultiBuffer *multiBuffer = (RCL_MultiBuffer *)rxBuffer;
RCL_MultiBuffer_init(multiBuffer, sizeof(rxBuffer));
RCL_MultiBuffer_put(&scanCtx.rxBuffers, multiBuffer);
/* Submit command wait for it to conclude */
RCL_Command_submit(h, &scanCmd);
RCL_Command_pend(&scanCmd);
/* Prepare list of RX buffers that are done */
List_List finishedBuffers;
List_clearList(&finishedBuffers);
rxAdvExtPkt = RCL_MultiBuffer_RxEntry_get(&scanCtx.rxBuffers, &finishedBuffers);
if (rxAdvExtPkt != NULL)
{
/* Do something with the data */
}
rxAuxAdvPkt = RCL_MultiBuffer_RxEntry_get(&scanCtx.rxBuffers, &finishedBuffers);
if (rxAuxAdvPkt != NULL)
{
/* Extract info from received SyncInfo field and use it to schedule next periodic scanner */
getSyncInfoData(rxAuxAdvPkt, &rxAuxAdvSyncInfo);
/* Extract subevent timing information from the ACAD field and use it to schedule AUX_SYNC_SUBEVENT_RSP */
getSubEventInfoData(rxAuxAdvPkt, &rxAuxAdvSubEventInfo);
/*
* Use the timestamp of the received AUX_ADV_IND packet to calculate start time of periodic scan operation.
* AUX_SYNC_SUBEVENT_IND or AUX_CONNECT_REQ should be sent within one offset unit of the sync packet window offset
*/
uint32_t startTimePerScanCmd = RCL_BLE5_getRxTimestamp(rxAuxAdvPkt);
startTimePerScanCmd += RCL_SCHEDULER_SYSTIM_US(rxAuxAdvSyncInfo.offsetInfo.units ?
rxAuxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_300_US :
rxAuxAdvSyncInfo.offsetInfo.base * BLE_AUX_OFFSET_30_US);
/* Setup PAwR scan command handler */
perScanCmd.perAdvType = 1;
perScanCmd.ctx = &perScanCtx;
perScanCmd.stats = &perScanStats;
perScanCmd.common.scheduling = RCL_Schedule_AbsTime;
perScanCmd.common.runtime.callback = scanCallback;
perScanCmd.common.runtime.lrfCallbackMask.value = 0;
perScanCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value |
perScanStats = (RCL_StatsAdvScanInit) { 0 };
perScanStats.config.accumulate = true;
/*
* Real channel is determined using the channel selection algorithm #2.
* For this test, simply use a fixed channel that is aligned between both DUTs.
*/
perScanCmd.channel = 18;
perScanCtx.accessAddress = rxAuxAdvSyncInfo.accessAddress;
perScanCtx.crcInit = rxAuxAdvSyncInfo.crcInit;
perScanCmd.common.scheduling = RCL_Schedule_AbsTime;
perScanCmd.common.timing.absStartTime = startTimePerScanCmd - RCL_SCHEDULER_SYSTIM_US(150);
/* Periodic scanner will use the same PHY as the PHY used for the auxiliary packet that sent the syncInfo */
uint16_t receivedPhy = (uint16_t) RCL_BLE5_getRxStatus(rxAuxAdvPkt).phy;
perScanCmd.common.phyFeatures = receivedPhy;
/* Set the TX buffer containing AUX_CONNECT_RSP */
advA = (DeviceAddr) { .addrType = 0, .addrByte = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06} };
targetA = (DeviceAddr) { .addrType = 0, .addrByte = {0x06, 0x05, 0x04, 0x03, 0x02, 0x01} };
setAuxConnRspBuffer(auxConnRspTxBuffer, &advA, &targetA);
RCL_TxBuffer_put(&perScanCtx.txBuffers, auxConnRspTxBuffer);
/* MultiBuffer configuration */
/* Needed to receive AUX_SYNC_SUBEVENT_IND or AUX_CONNECT_REQ */
RCL_MultiBuffer *multiBuffer2 = (RCL_MultiBuffer *)rxBuffer2;
RCL_MultiBuffer_init(multiBuffer2, sizeof(rxBuffer2));
RCL_MultiBuffer_put(&perScanCtx.rxBuffers, multiBuffer2);
/* Submit command and wait for it to conclude. If needed, update start time of the periodic scanner command
* for other periodic advertising events.
*/
RCL_Command_submit(h, &perScanCmd);
RCL_Command_pend(&perScanCmd);
/* The PAwR scanner can receive an AUX_SYNC_SUBEVENT_IND and subsequently send an AUX_SYNC_SUBEVENT_RSP
* to the advertiser at a specific response slot within a subevent, as determined by the application.
*/
rxSubEventIndOrConnReqPkt = RCL_MultiBuffer_RxEntry_get(&perScanCtx.rxBuffers, &finishedBuffers);
if (rxSubEventIndOrConnReqPkt != NULL)
{
/* Ensure the received PDU is an AUX_SYNC_SUBEVENT_IND by verifying its PDU type */
if (RCL_BLE5_getPduType(rxSubEventIndOrConnReqPkt) == BLE_HDR_AUX_SYNC_SUBEVENT_IND)
{
/* Configure the RCL_CmdBle5GenericTx command */
/* According to the specification, AUX_SYNC_SUBEVENT_RSP must use the RspAA value from the ACAD field of AUX_ADV_IND */
genTxCtx.accessAddress = rxAuxAdvSubEventInfo.rspAA;
genTxCtx.crcInit = PAwR_TRAIN_CRC_INIT;
genTxCmd.channel = 20;
genTxCmd.ctx = &genTxCtx;
/* Per spec, AUX_SYNC_SUBEVENT_RSP must use the same PHY as the received AUX_ADV_IND */
genTxCmd.common.phyFeatures = receivedPhy;
genTxCmd.common.scheduling = RCL_Schedule_AbsTime;
genTxCmd.common.runtime.callback = NULL;
genTxCmd.common.runtime.lrfCallbackMask.value = 0;
genTxCmd.common.runtime.rclCallbackMask.value = RCL_EventLastCmdDone.value;
/* Set the TX buffer of AUX_SYNC_SUBEVENT_RSP */
setAuxSyncSubEventRspBuffer(auxSyncSubEventRspTxBuffer, AUX_SYNC_SUBEVENT_RSP_ADV_DATA_LEN);
RCL_TxBuffer_put(&genTxCtx.txBuffers, auxSyncSubEventRspTxBuffer);
/* Calculate the start time to send the AUX_SYNC_SUBEVENT_RSP:
* - base time is the received time of the AUX_SYNC_SUBEVENT_IND
* - subevent number and response slot number need to be decided by the application
*
* Assumption:
* - The AUX_SYNC_SUBEVENT_RSP is sent at the response slot 1 of the subevent 0
*/
uint32_t rspSlotTimeUs = rxAuxAdvSubEventInfo.subeventInterval * (PAwR_SUBEVENT_INTERVAL_UNIT_US) * (PAwR_SUBEVENT_NUMBER_ZERO) +
rxAuxAdvSubEventInfo.responseSlotDelay * (PAwR_SUBEVENT_RESPONSE_SLOT_DELAY_US) +
rxAuxAdvSubEventInfo.responseSlotSpacing * (PAwR_SUBEVENT_RESPONSE_SLOT_SPACING_US) * (PAwR_RESPONSE_SLOT_NUMBER_ONE);
genTxCmd.common.timing.absStartTime = perScanCmd.receivedPktTime + RCL_SCHEDULER_SYSTIM_US(rspSlotTimeUs);
/* Submit the command and pend on its completion */
RCL_Command_submit(h, &genTxCmd);
RCL_Command_pend(&genTxCmd);
}
}
}
}

When the PAwR scanner receives an AUX_SYNC_SUBEVENT_IND, the application can schedule an RCL_CMD_BLE5_GENERIC_TX_t command to send an AUX_SYNC_SUBEVENT_RSP at the approprite response slot. The start time for this transmission command is calculated based on the reception time of the AUX_SYNC_SUBEVENT_IND and the Periodic Advertising Response Timing Information extracted from the ACAD field in the received AUX_ADV_IND. It is important to note that the access address for the AUX_SYNC_SUBEVENT_RSP must match the one contained in the ACAD. Additionally, the PHY used for transmitting the AUX_SYNC_SUBEVENT_RSP must be the same as the one used to receive the AUX_ADV_IND.

A helper function is provided to demonstrate how to configure the Tx buffer for AUX_SYNC_SUBEVENT_RSP.

static void setAuxSyncSubEventRspBuffer(RCL_Buffer_TxBuffer *buffer, uint8_t advDataLen)
{
/* Optional fields in the extended header of AUX_SYNC_SUBEVENT_RSP
* - AdvA
* - CTE Info
* - Tx Power
*
* Assumption:
* - no optional fields are included
* - no ACAD data
*/
/* Include the extra octet needed by the extended header length and AdvMode */
uint8_t payloadLength = 1 + 1 + advDataLen;
uint8_t *txData = RCL_TxBuffer_init(buffer, NUM_PAD, BLE_HDR_LEN, payloadLength);
txData[0] = BLE_HDR_AUX_SYNC_SUBEVENT_RSP;
txData[1] = payloadLength;
txData[2] = (BLE_ADV_MODE_NONCONN_NONSCAN << 6) | 1;
txData[3] = 0; /* No fields in the extended header */
/* AdvData */
if (advDataLen > 0)
{
for(uint32_t i = 0; i < advDataLen; i++)
{
txData[4 + i] = i & 0xFF;
}
}
}

For demonstration purposes, a helper function is provided to inspect the extended header flags of the received packet and store the response timing information from the ACAD field into a C struct.

typedef struct
{
uint32_t rspAA;
uint8_t numSubevents;
uint8_t subeventInterval;
uint8_t responseSlotDelay;
uint8_t responseSlotSpacing;
} SubEventInfo;
static void getSubEventInfoData(RCL_Buffer_DataEntry *rxEntry, SubEventInfo *subEventInfo)
{
/* Check the extended header flags and determine index of SyncInfo */
uint8_t extHdrFlags = rxEntry->data[rxEntry->numPad - 1 + BLE_HDR_LEN + 1];
/* Count the index of the ACAD from the extended header flags */
uint8_t acadIndex = rxEntry->numPad - 1 + BLE_HDR_LEN + 1 + 1;
if (extHdrFlags & BLE5_EXT_HDR_FLAG_ADVA_BM)
{
acadIndex += BLE_EXT_HDR_ADVA_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_TARGETA_BM)
{
acadIndex += BLE_EXT_HDR_TARGETA_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_CTE_INFO_BM)
{
acadIndex += BLE_EXT_HDR_CTEINFO_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_ADI_BM)
{
acadIndex += BLE_EXT_HDR_ADI_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_AUX_PTR_BM)
{
acadIndex += BLE_EXT_HDR_AUXPTR_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_SYNC_INFO_BM)
{
acadIndex += BLE_EXT_HDR_SYNCINFO_LEN;
}
if (extHdrFlags & BLE5_EXT_HDR_FLAG_TX_POWER_BM)
{
acadIndex += BLE_EXT_HDR_TXPOWER_LEN;
}
/* In an AD structure, the first two bytes are the length and AD type */
subEventInfo->rspAA = (rxEntry->data[acadIndex + 2]) |
(rxEntry->data[acadIndex + 3] << 8) |
(rxEntry->data[acadIndex + 4] << 16) |
(rxEntry->data[acadIndex + 5] << 24);
subEventInfo->numSubevents = rxEntry->data[acadIndex + 6];
subEventInfo->subeventInterval = rxEntry->data[acadIndex + 7];
subEventInfo->responseSlotDelay = rxEntry->data[acadIndex + 8];
subEventInfo->responseSlotSpacing = rxEntry->data[acadIndex + 9];
}

Architecture

The life cycle of the periodic scanner command handler is influenced by the type of periodic advertising event, whether it involves responses or not.

For periodic advertising without responses, the command handler requires a Multibuffer capable of receiving the entire chain of packets that constitute the periodic advertising event. Upon receiving a packet, the command handler checks for the presence of an AuxPtr and schedules a new listening window to receive subsequent packets if necessary.

For periodic advertising with responses, the command handler needs a TxBuffer for a AUX_CONNECT_RSP (in case an AUX_CONNECT_REQ is received) and a Multibuffer for receiving either AUX_SYNC_SUBEVENT_IND or AUX_CONNECT_REQ PDUs. Depending on the received PDU, the scanner command handler can transition the scanner from the periodic advertising state to a connection state.

It's worth mentioning that only non-connectable/non-scannable extended PDUs can be received with the periodic scanner command, and other PDUs will be rejected and the command will end indicating an Rx 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_EventRxBufferUpdate RX buffer has been updated
RCL Event (Out) Description
RCL_EventLastCmdDone The RCL is finished with the command
RCL_EventCmdStarted Command handler has accepted and started executing
RCL_EventRxBufferFinished An RX multi-buffer is finished
RCL_EventRxEntryAvail An RX entry has been made available
RCL_EventTxBufferFinished A 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
LRF_EventRxOk Packet received with CRC OK and not to be ignored by the MCU
LRF_EventRxNok Packet received with CRC error
LRF_EventRxIgnored Packet received, but may be ignored by MCU