Other Parts Discussed in Thread: CC2592
Tool/software:
Hello Team,
I am using the CC2640R2F for a wireless sensor project. The ADC data is successfully transmitted to my phone (iPhone, iOS16, LightBlue app). However, I encountered an issue when trying to implement Long Range mode, where the transmission distance is only 15-20 meters, and beyond that, the connection disconnects. I tested the same setup with my colleague's Samsung Galaxy S21 (Android 14, LightBlue), but the issue persists, with the connection dropping. I am writing my code based on the BLE5 Simple Peripheral example from SDK 5_30_00_03 on CC2640R2F LaunchBoard.
In the SimplePeripheral_processGapMessage function, there is a section of the original code:
static void SimplePeripheral_processGapMessage(gapEventHdr_t *pMsg)
{
switch(pMsg->opcode)
{
case GAP_DEVICE_INIT_DONE_EVENT:
{
bStatus_t status = FAILURE;
gapDeviceInitDoneEvent_t *pPkt = (gapDeviceInitDoneEvent_t *)pMsg;
if(pPkt->hdr.status == SUCCESS)
{
// Store the system ID
uint8_t systemId[DEVINFO_SYSTEM_ID_LEN];
// use 6 bytes of device address for 8 bytes of system ID value
systemId[0] = pPkt->devAddr[0];
systemId[1] = pPkt->devAddr[1];
systemId[2] = pPkt->devAddr[2];
// set middle bytes to zero
systemId[4] = 0x00;
systemId[3] = 0x00;
// shift three bytes up
systemId[7] = pPkt->devAddr[5];
systemId[6] = pPkt->devAddr[4];
systemId[5] = pPkt->devAddr[3];
// Set Device Info Service Parameter
DevInfo_SetParameter(DEVINFO_SYSTEM_ID, DEVINFO_SYSTEM_ID_LEN, systemId);
Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "Initialized");
// Setup and start Advertising
// For more information, see the GAP section in the User's Guide:
// software-dl.ti.com/.../
// Temporary memory for advertising parameters for set #1. These will be copied
// by the GapAdv module
GapAdv_params_t advParamLegacy = GAPADV_PARAMS_LEGACY_SCANN_CONN;
// Create Advertisement set #1 and assign handle
status = GapAdv_create(&SimplePeripheral_advCallback, &advParamLegacy,
&advHandleLegacy);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
// Load advertising data for set #1 that is statically allocated by the app
status = GapAdv_loadByHandle(advHandleLegacy, GAP_ADV_DATA_TYPE_ADV,
sizeof(advertData), advertData);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
// Load scan response data for set #1 that is statically allocated by the app
status = GapAdv_loadByHandle(advHandleLegacy, GAP_ADV_DATA_TYPE_SCAN_RSP,
sizeof(scanRspData), scanRspData);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
// Set event mask for set #1
status = GapAdv_setEventMask(advHandleLegacy,
GAP_ADV_EVT_MASK_START_AFTER_ENABLE |
GAP_ADV_EVT_MASK_END_AFTER_DISABLE |
GAP_ADV_EVT_MASK_SET_TERMINATED);
// Enable legacy advertising for set #1
status = GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
// Use long range params to create long range set #2
GapAdv_params_t advParamLongRange = GAPADV_PARAMS_AE_LONG_RANGE_CONN;
// Change the secPhy to S8. The primPhy has already been specified as S2 in the "GAPADV_PARAMS_AE_LONG_RANGE_CONN" preset, so here we are only modifying the secPhy, changing it from S2 to S8. However, whether we change the secPhy or not, it still doesn't switch to Coded PHY.
advParamLongRange.secPhy = GAP_ADV_SEC_PHY_CODED_S8;
// Create Advertisement set #2 and assign handle
status = GapAdv_create(&SimplePeripheral_advCallback, &advParamLongRange,
&advHandleLongRange);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
// Load advertising data for set #2 that is statically allocated by the app
status = GapAdv_loadByHandle(advHandleLongRange, GAP_ADV_DATA_TYPE_ADV,
sizeof(advertData), advertData);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
// Set event mask for set #2
status = GapAdv_setEventMask(advHandleLongRange,
GAP_ADV_EVT_MASK_START_AFTER_ENABLE |
GAP_ADV_EVT_MASK_END_AFTER_DISABLE |
GAP_ADV_EVT_MASK_SET_TERMINATED);
// Enable long range advertising for set #2
status = GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
//Debug: CODED PHY, JL
Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "Coded PHY Advertising Started with status: %d", status);
……
Question 1: From the code, it seems that two advertising sets are defined in simple_peripheral.c: one for legacy and one for long range. However, when I build and run the code, I always see "Adv Set 1 Enabled" in the CCS terminal, but I never see Adv Set 2 being enabled. In the SimplePeripheral_startAutoPhyChange function, I noticed a comment saying "//Set Default PHY to 1M", and I found the SimplePeripheral_addConn(uint16_t connHandle) function where I changed the default 1M setting connList[i].currPhy = HCI_PHY_1_MBPS; to connList[i].currPhy = HCI_PHY_CODED; but the terminal still shows “Adv Set 1 Enabled”.Could you explain why adv set 1 is always enabled? Also, in the SimplePeripheral_processCmdCompleteEvt function, I see the following code:
static void SimplePeripheral_processCmdCompleteEvt(hciEvt_CmdComplete_t *pMsg)
{
uint8_t status = pMsg->pReturnParam[0];
//Find which command this command complete is for
switch (pMsg->cmdOpcode)
{
case HCI_READ_RSSI:
{
int8 rssi = (int8)pMsg->pReturnParam[3];
// Display RSSI value, if RSSI is higher than threshold, change to faster PHY
if (status == SUCCESS)
{
uint16_t handle = BUILD_UINT16(pMsg->pReturnParam[1], pMsg->pReturnParam[2]);
uint8_t index = SimplePeripheral_getConnIndex(handle);
SIMPLEPERIPHERAL_ASSERT(index < MAX_NUM_BLE_CONNS);
if (rssi != LL_RSSI_NOT_AVAILABLE)
{
connList[index].rssiArr[connList[index].rssiCntr++] = rssi;
connList[index].rssiCntr %= SP_MAX_RSSI_STORE_DEPTH;
int16_t sum_rssi = 0;
for(uint8_t cnt=0; cnt<SP_MAX_RSSI_STORE_DEPTH; cnt++)
{
sum_rssi += connList[index].rssiArr[cnt];
}
connList[index].rssiAvg = (uint32_t)(sum_rssi/SP_MAX_RSSI_STORE_DEPTH);
uint8_t phyRq = SP_PHY_NONE;
uint8_t phyRqS = SP_PHY_NONE;
uint8_t phyOpt = LL_PHY_OPT_NONE;
if(connList[index].phyCngRq == FALSE)
{
if((connList[index].rssiAvg >= RSSI_2M_THRSHLD) &&
(connList[index].currPhy != HCI_PHY_2_MBPS) &&
(connList[index].currPhy != SP_PHY_NONE))
{
// try to go to higher data rate
phyRqS = phyRq = HCI_PHY_2_MBPS;
}
else if((connList[index].rssiAvg < RSSI_2M_THRSHLD) &&
(connList[index].rssiAvg >= RSSI_1M_THRSHLD) &&
(connList[index].currPhy != HCI_PHY_1_MBPS) &&
(connList[index].currPhy != SP_PHY_NONE))
{
// try to go to legacy regular data rate
phyRqS = phyRq = HCI_PHY_1_MBPS;
}
else if((connList[index].rssiAvg >= RSSI_S2_THRSHLD) &&
(connList[index].rssiAvg < RSSI_1M_THRSHLD) &&
(connList[index].currPhy != SP_PHY_NONE))
{
// try to go to lower data rate S=2(500kb/s)
phyRqS = HCI_PHY_CODED;
phyOpt = LL_PHY_OPT_S2;
phyRq = BLE5_CODED_S2_PHY;
}
else if(connList[index].rssiAvg < RSSI_S2_THRSHLD )
{
// try to go to lowest data rate S=8(125kb/s)
phyRqS = HCI_PHY_CODED;
phyOpt = LL_PHY_OPT_S8;
phyRq = BLE5_CODED_S8_PHY;
}
if((phyRq != SP_PHY_NONE) &&
// First check if the request for this phy change is already not honored then don't request for change
(((connList[index].rqPhy == phyRq) &&
(connList[index].phyRqFailCnt < 2)) ||
(connList[index].rqPhy != phyRq)))
{
//Initiate PHY change based on RSSI
SimplePeripheral_setPhy(connList[index].connHandle, 0,
phyRqS, phyRqS, phyOpt);
connList[index].phyCngRq = TRUE;
// If it a request for different phy than failed request, reset the count
if(connList[index].rqPhy != phyRq)
{
// then reset the request phy counter and requested phy
connList[index].phyRqFailCnt = 0;
}
if(phyOpt == LL_PHY_OPT_NONE)
{
connList[index].rqPhy = phyRq;
}
else if(phyOpt == LL_PHY_OPT_S2)
{
connList[index].rqPhy = BLE5_CODED_S2_PHY;
}
else
{
connList[index].rqPhy = BLE5_CODED_S8_PHY;
}
} // end of if ((phyRq != SP_PHY_NONE) && ...
} // end of if (connList[index].phyCngRq == FALSE)
} // end of if (rssi != LL_RSSI_NOT_AVAILABLE)
Display_printf(dispHandle, SP_ROW_RSSI, 0,
"RSSI:%d dBm, AVG RSSI:%d dBm",
(uint32_t)(rssi),
connList[index].rssiAvg);
} // end of if (status == SUCCESS)
break;
}
case HCI_LE_READ_PHY:
{
if (status == SUCCESS)
{
Display_printf(dispHandle, SP_ROW_RSSI + 2, 0, "RXPh: %d, TXPh: %d",
pMsg->pReturnParam[3], pMsg->pReturnParam[4]);
}
break;
}
default:
break;
} // end of switch (pMsg->cmdOpcode)
}
Question 2: Based on the code mentioned above, I believe that the Simple Peripheral example is originally set to automatically switch to an appropriate PHY based on the RSSI value. Is that correct? If so, why does the signal only reach 15-20 meters during testing (in an open office environment with no obstructions or walls)? As I mentioned earlier, I only see “Adv Set 1 Enabled”. After connecting with the phone, I always see the terminal information: “PHY updated to 2M”, which comes from the SimplePeripheral_processStackMsg function, specifically in this Display_printf:
// A Phy Update Has Completed or Failed
if (pPUC->BLEEventCode == HCI_BLE_PHY_UPDATE_COMPLETE_EVENT)
{
if (pPUC->status != SUCCESS)
{
Display_printf(dispHandle, SP_ROW_STATUS_1, 0,
"PHY Change failure");
}
else
{
// Only symmetrical PHY is supported.
// rxPhy should be equal to txPhy.
Display_printf(dispHandle, SP_ROW_STATUS_2, 0,
"PHY Updated to %s",
(pPUC->rxPhy == PHY_UPDATE_COMPLETE_EVENT_1M) ? "1M" :
(pPUC->rxPhy == PHY_UPDATE_COMPLETE_EVENT_2M) ? "2M" :
(pPUC->rxPhy == PHY_UPDATE_COMPLETE_EVENT_CODED) ? "CODED" : "Unexpected PHY Value");
}
SimplePeripheral_updatePHYStat(HCI_BLE_PHY_UPDATE_COMPLETE_EVENT, (uint8_t *)pMsg);
}
break;
Therefore, I believe the device is capable of updating PHY. However, I am unsure why it fails to switch to the coded PHY when the transmission distance increases and instead disconnects. I have done some research, and there are mentions that Apple no longer supports coded PHY after iOS 13, which is why I borrowed my colleague's Samsung Galaxy S21 (Android 14, LightBlue App) for testing, but the results were the same.
Relevant links:
https://github.com/NordicSemiconductor/Android-BLE-Library/issues/166
https://forums.developer.apple.com/forums/thread/665542
https://issuetracker.google.com/issues/227887174
So, I would like to ask: does the code itself need further modification to support coded PHY, or is this a compatibility issue on the phone side? I am going to test on Samsung Galaxy S10 + Android 12 again, but I am not sure if I am in the correct direction. FYI, I didn't modify SimplePeripheral_processGapMessage, SimplePeripheral_processGATTMsg, SimplePeripheral_processStackMsg and SimplePeripheral_processCmdCompleteEvt. Could you provide some suggestions to help me achieve a long-range connection?
Thank you for your help!!!
In addition, I have a problem using the BTool in the SDK, when I open the application(app and bat file), a message window popped up: "Device reset Timeout. Device might not function properly".

Device: CC2640R2F LaunchBoard
SDK Version: simplelink_cc2640r2_sdk_5_30_00_03