In most cases, a narrowband wireless connectivity application and a the wireless connectivity stack(s) run together on the same processor. However, there are scenarios where a split solution is required so that a more complex application will run on a platform (embedded or desktop) and the stack on the embedded connectivity SoC, communicating through a serial interface.
In this dual platform scenario, the connectivity stack runs in a configuration called black box which is interfaced through a binary serial command protocol to a host processor running the connectivity application. The most appropriate serial protocol to use in this use case is the Framework Serial Communication Interface (FSCI) and to make the transition from a single chip application to a Host application, an FSCI Host module needs to be implemented.
The host module can have various implementations, among which the NXP Test Tool for Connectivity Products command console, running on Windows systems or the Python-enabled FSCI Host SDK distributed with Kinetis connectivity stacks packages, portable across Python-supporting desktop or embedded systems.
This document aims to offer an example on how to implement a FSCI host in the software running on a microcontroller embedded system within a RTOS environment. For this example, we will consider the Kinetis Bluetooth low energy stack.
The interaction between a host and a black box is made possible through the following API concepts:
When running on a single chip solution, the BLE implementation is provided as a library. To port the application to a Host solution, the library is removed from the project, while keeping only the BLE interface header files. The BLE stack has FSCI Host support and these files need to be added in the project, which provide the following to create a host – black box solution:
Let us consider an example of a BLE function from the interface and describe what is required for it.
/*! *********************************************************************************
* \brief Returns whether or not a connected peer device is bonded.
* \param[in] deviceId Device ID of the GAP peer.
* \param[out] pOutIsBonded Boolean to be filled with the bonded flag.
* \return gBleSuccess_c or error.
* \remarks This function executes synchronously.
********************************************************************************** */
bleResult_t Gap_CheckIfBonded
(
deviceId_t deviceId,
bool_t* pOutIsBonded
);
This function has two parameters, one being an OUT parameter. It needs to send a command to the black box, wait for the result and if the result is success, wait for the event that will include the information if the device is bonded and fill the OUT parameter.
BLE API Implementation
bleResult_t Gap_CheckIfBonded
(
deviceId_t deviceId,
bool_t* pOutIsBonded
)
{
bleResult_t result = gBleSuccess_c;
FSCI_HostSyncLock(fsciBleInterfaceId, gFsciBleGapOpcodeGroup_c, gBleGapStatusOpCode_c);
FsciGapCmdMonitor(CheckIfBonded, deviceId, pOutIsBonded);
result = Ble_GetCmdStatus(TRUE);
FSCI_HostSyncUnlock(fsciBleInterfaceId);
return result;
}
The FSCI Host is a module in the FSCI connectivity framework that supports the FSCI Host BLE module with the following:
NOTE: It is mandatory for the BLE FSCI Host implementation to block in the context of the caller application and get the status and OUT parameters, if any, from the black box to allow the application to use them immediately after the call.
Command Monitor
The command monitor allocates a serial buffer, fills the parameters and saves the OUT parameter to be filled when the response arrives.
void fsciBleGapDeviceIdParamCmdMonitor(fsciBleGapOpCode_t opCode, deviceId_t deviceId)
{
clientPacketStructured_t* pClientPacket;
uint8_t* pBuffer;
/* Allocate the packet to be sent over UART */
pClientPacket = fsciBleGapAllocFsciPacket(opCode,
fsciBleGetDeviceIdBufferSize(&deviceId));
if(NULL == pClientPacket)
{
return;
}
pBuffer = &pClientPacket->payload[0];
/* Set command parameters in the buffer */
fsciBleGetBufferFromDeviceId(&deviceId, &pBuffer);
/* Transmit the packet over UART */
FSCI_transmitFormatedPacket(pClientPacket, fsciBleInterfaceId);
}
void fsciBleGapCheckIfBondedCmdMonitor(deviceId_t deviceId, bool_t* pOutIsBonded)
{
/* Monitor deviceId parameter */
fsciBleGapDeviceIdParamCmdMonitor(gBleGapCmdCheckIfBondedOpCode_c, deviceId);
/* Save out parameters pointers */
fsciBleGapSaveOutParams(pOutIsBonded, NULL);
}
Status and Event Handler
The GAP handler is registered in the FSCI connectivity framework module and is called whenever a GAP status or event is received.
void fsciBleGapHandler(void* pData, void* param, uint32_t fsciBleInterfaceId)
{
clientPacket_t* pClientPacket = (clientPacket_t*)pData;
uint8_t* pBuffer = &pClientPacket->structured.payload[0];
/* Select the GAP function to be called (using the FSCI opcode) */
switch(pClientPacket->structured.header.opCode)
{
case gBleGapStatusOpCode_c:
{
bleResult_t status;
/* Get status from buffer */
fsciBleGetEnumValueFromBuffer(status, pBuffer, bleResult_t);
if(gBleSuccess_c != status)
{
/* Clean out parameters pointers kept in FSCI */
fsciBleGapCleanOutParams();
}
}
break;
case gBleGapEvtCheckIfBondedOpCode_c:
{
fsciBleGapCheckIfBondedOutParams_t* pOutParams = (fsciBleGapCheckIfBondedOutParams_t*)fsciBleGapRestoreOutParams();
if(NULL != pOutParams->pIsBonded)
{
/* Get out parameter of the Gap_CheckIfBonded function from buffer */
fsciBleGetBoolValueFromBuffer(*pOutParams->pIsBonded, pBuffer);
/* Clean out parameters pointers kept in FSCI */
fsciBleGapCleanOutParams();
/* Signal out parameters ready */
Ble_OutParamsReady();
}
}
break;
}
MEM_BufferFree(pData);
}
FSCI Host BLE module
This function is called by all the BLE API and performs the following:
bleResult_t Ble_GetCmdStatus(bool_t bHasOutParams)
{
bleResult_t result = gBleSuccess_c;
uint64_t tStamp = 0;
/* Set out parameter flag if any */
bFunctionHasOutParams = bHasOutParams;
/* Wait for result from the serial interface */
while( !pFsciHostSyncRsp )
{
tStamp = TMR_GetTimestamp();
while( !pFsciHostSyncRsp )
{
FSCI_receivePacket((void*)fsciBleInterfaceId);
if( TMR_GetTimestamp() - tStamp > mFsciHost_WaitForStatusTimeout_us_c )
{
break;
}
}
}
if( pFsciHostSyncRsp )
{
result = (bleResult_t)pFsciHostSyncRsp->structured.payload[0];
/* Check status and wait for outParameters */
if( gBleSuccess_c == result )
{
tStamp = TMR_GetTimestamp();
while( bFunctionHasOutParams )
{
FSCI_receivePacket((void*)fsciBleInterfaceId);
if( TMR_GetTimestamp() - tStamp > mFsciHost_WaitForStatusTimeout_us_c )
{
/* Timeout on the receiving the response */
result = gBleUnexpectedError_c;
break;
}
}
}
/* Free FSCI packet */
MEM_BufferFree(pFsciHostSyncRsp);
}
else
{
/* Timeout on the receiving the response */
result = gBleUnexpectedError_c;
}
return result;
}
The rest of the BLE API is built in a very similar manner like the one in this example