从 SDK 例子的目录树结构上看,CC1352 的 BLE 程序是必须要用到 TI-RTOS 的,而且 SDK 的例子里面没有 BLE 的 GCC 工程。于是我不得已只能用慢吞吞的 CCS 来编译一个试试。尽管 "Simple peripheral" 编译和下载都成功了,手机也能发现和连接板子生成的 BLE 设备,我遇到了一个大问题——CCS中无法设置断点调试,只能单步指令执行(还不如GDB呢,但是CCS生成的ELF和GCC不一样),所以跟踪执行过程来分析代码就行不通了。
我阅读了 BLE SDK 的文档,其中并未提到支持GCC, 那我就不要试图用 GCC 去处理这个工程了。有些遗憾,下面只能直接看代码分析。
和已经分析过的例子比起来,Simple_periphral 的主函数多了一些东西:
int main()
{
/* Register Application callback to trap asserts raised in the Stack */
RegisterAssertCback(AssertHandler);
Board_initGeneral();
// Enable iCache prefetching
VIMSConfigure(VIMS_BASE, TRUE, TRUE);
// Enable cache
VIMSModeSet(VIMS_BASE, VIMS_MODE_ENABLED);
/* Update User Configuration of the stack */
user0Cfg.appServiceInfo->timerTickPeriod = Clock_tickPeriod;
user0Cfg.appServiceInfo->timerMaxMillisecond = ICall_getMaxMSecs();
/* Initialize ICall module */
ICall_init();
/* Start tasks of external images - Priority 5 */
ICall_createRemoteTasks();
SimplePeripheral_createTask();
/* enable interrupts and start SYS/BIOS */
BIOS_start();
return 0;
}
主要是多了 ICall_init() 以及类似的其它 ICall_... 的东西。ICall 是个啥只有看文档才知道,这里先不管。后面 SimplePeripheral_createTask() 才是要创建这个 BLE 应用的任务,它在另外一个源文件中。
void SimplePeripheral_createTask(void)
{
Task_Params taskParams;
// Configure task
Task_Params_init(&taskParams);
taskParams.stack = spTaskStack;
taskParams.stackSize = SP_TASK_STACK_SIZE;
taskParams.priority = SP_TASK_PRIORITY;
Task_construct(&spTask, SimplePeripheral_taskFxn, &taskParams, NULL);
}
显然,任务的主函数是 SimplePeripheral_taskFxn(), 还好写得并不长:
static void SimplePeripheral_taskFxn(UArg a0, UArg a1)
{
// Initialize application
SimplePeripheral_init();
// Application main loop
for (;;)
{
uint32_t events;
// Waits for an event to be posted associated with the calling thread.
// Note that an event associated with a thread is posted when a
// message is queued to the message receive queue of the thread
events = Event_pend(syncEvent, Event_Id_NONE, SP_ALL_EVENTS,
ICALL_TIMEOUT_FOREVER);
if (events)
{
ICall_EntityID dest;
ICall_ServiceEnum src;
ICall_HciExtEvt *pMsg = NULL;
// Fetch any available messages that might have been sent from the stack
if (ICall_fetchServiceMsg(&src, &dest,
(void **)&pMsg) == ICALL_ERRNO_SUCCESS)
{
uint8 safeToDealloc = TRUE;
if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
{
ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;
// Check for BLE stack events first
if (pEvt->signature != 0xffff)
{
// Process inter-task message
safeToDealloc = SimplePeripheral_processStackMsg((ICall_Hdr *)pMsg);
}
}
if (pMsg && safeToDealloc)
{
ICall_freeMsg(pMsg);
}
}
// If RTOS queue is not empty, process app message.
if (events & SP_QUEUE_EVT)
{
while (!Queue_empty(appMsgQueueHandle))
{
spEvt_t *pMsg = (spEvt_t *)Util_dequeueMsg(appMsgQueueHandle);
if (pMsg)
{
// Process message.
SimplePeripheral_processAppMsg(pMsg);
// Free the space from the message.
ICall_free(pMsg);
}
}
}
}
}
}
先是执行一个初始化函数,后面就进入循环,等待事件并处理事件。不究细节的话,这个程序的结构看起来还是简单的。实际上,把另外一个例子 "Simple central" 拿来比较,它们的 main 函数和任务主函数写法是相同的。在这里又遇到 ICall_... 的东西了,等等再看吧。先看下初始化做了哪些工作。
SimplePeripheral_init() 这个函数比较长,没有必要贴出来。在当中首先是执行了
ICall_registerApp(&selfEntity, &syncEvent);
两个参数都是指针,分别指向两个全局变量,调用后它们即被初始化,在后面程序中会用到。下一步是
appMsgQueueHandle = Util_constructQueue(&appMsgQueue);
字面意思是创建消息队列。后一个操作是
Util_constructClock(&clkPeriodic, SimplePeripheral_clockHandler, SP_PERIODIC_EVT_PERIOD, 0, false, (UArg)&argPeriodic);
这里创建了一个软件时钟。
再后面就开始 GAP, GATT 服务的配置了,有很多个调用。来看一个
GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);
的具体实现:它在 BLE stack 的源代码 icall_api.c 中定义:
bStatus_t GGS_SetParameter(uint8 param, uint8 len, void *value)
{
return profileSetParameter(param, len, value, DISPATCH_GAP_GATT_SERV,
DISPATCH_PROFILE_SET_PARAM, matchGGSSetParamCS);
}
static bStatus_t profileSetParameter(uint8 param, uint8 len, void *pValue,
uint8_t subgrp, uint8_t cmdId,
ICall_MsgMatchFn matchCSFn)
{
// Allocate message buffer space
ICall_ProfileSetParam *msg =
(ICall_ProfileSetParam *)ICall_allocMsg(sizeof(ICall_ProfileSetParam));
if (msg)
{
setDispatchCmdEvtHdr(&msg->hdr, subgrp, cmdId);
// copy param ID, len, value
msg->paramIdLenVal.paramId = param;
msg->paramIdLenVal.len = len;
msg->paramIdLenVal.pValue = pValue;
// Send the message
return sendWaitMatchCS(ICall_getEntityId(), msg, matchCSFn);
}
return MSG_BUFFER_NOT_AVAIL;
}
它是通过 ICall 先分配了一个消息,再按参数填写消息内容,最后发送消息。其中用的 sendWaitMatchCS() 函数里面调用了 ICall_sendServiceMsg() 函数。这个函数定义为
static ICall_Errno
ICall_sendServiceMsg(ICall_EntityID src,
ICall_ServiceEnum dest,
ICall_MSGFormat format, void *msg)
{
ICall_SendArgs args;
args.hdr.service = ICALL_SERVICE_CLASS_PRIMITIVE;
args.hdr.func = ICALL_PRIMITIVE_FUNC_SEND_SERV_MSG;
args.src = src;
args.dest.servId = dest;
args.format = format;
args.msg = msg;
return ICall_dispatcher((ICall_FuncArgsHdr *)&args);
}
也就是最终将BLE服务请求通过 ICall_dispatcher() 发送。那么,消息的接收者是谁呢?ICall, 这到底是个啥?在 BLE stack 源码的 icall.c 中,可以找到很多相关函数的实现。例如在 main() 中就用到的 ICall_init()
void ICall_init(void)
{
size_t i;
for (i = 0; i < ICALL_MAX_NUM_TASKS; i++)
{
ICall_tasks[i].task = NULL;
ICall_tasks[i].queue = NULL;
}
for (i = 0; i < ICALL_MAX_NUM_ENTITIES; i++)
{
ICall_entities[i].service = ICALL_SERVICE_CLASS_INVALID_ENTRY;
}
#ifndef ICALL_JT
/* Initialize primitive service */
ICall_initPrim();
#else
/* Initialize heap */
ICall_heapInit();
#endif
}
这暗示了,ICall 背后是若干个 TI-RTOS 的任务。在 SDK BLE5 stack 文档中有一个图举了这样的例子:
ICall 是一个中间层,应用程序对 BLE stack 的访问都通过 ICall 来实现。因为有 TI-RTOS 作为基础,应用程序就成了消息(事件)驱动型的。协议栈的部分也由 ICall 管理了,我们不用去管它需要创建多少 RTOS 任务,不用直接和那些任务通信,更不用管底层的两个CPU之间的信息交互(无线功能有一个Cortex-m0核在负责)。
因为有 ICall 在,基于 CC1352 (也包括CC26xx等其它MCU)进行 BLE 开发的时候,只要按照 SDK 例子做一个程序框架,再编写 BLE 事件处理程序就可以了,整个工程只需要少数几个源文件(其余都是库)。SDK 提供的几个工程例子代码读起来不难,虽然内容太多了不能熟练到从零开始自己写,TI 的 BLE stack 文档还是很详细的。看懂一两个例子之后再改写一个自己的 BLE 设备难度不会很大。
|