TIRTOS----CC2640中的ICall模块
用TI的CC2640蓝牙有一段时间了,平时是做应用层的开发,没太关注ICall模块,但读完这篇文章,我收获很大,结合一些其他的学习资料,做一些总结。
1.ICall模块介绍
ICall是实现TI蓝牙芯片功能中应用程序和协议栈的不可缺的一部分。
ICALL 模块实现包含应用程序和 BLE协议栈通信,实现多任务间消息传递,发送方申请内存,接收方释放内存,上面这句话总体概括了ICall模块的作用:
1.消息传递和线程同步
1.1一个客户端实体和一个服务器实体,通过ICall实现协议栈的消息传递
1.2 ICall 通过任务在消息队列给另一任务发送一个阻塞消息完成线程同步
2.堆分配与管理
ICall 为应用提供了全局堆管理的 API 用以动态内存分配
2.ICall 模块初试化
ICall 初始化,在 main 函数中调用 ICall_init()。该函数初始化 ICall 初级服务,例如堆栈管理器,调用 ICall_createRemoteTasks()创建但不启动蓝牙协议栈任务。使用 ICall 之前,必须要先注册 ICall。
ICall_init(); /* 初始化 ICall 模块 */
ICall _createRemoteTasks ();/* 将协议栈作作为任务创建 */
在使用ICall 协议服务之前,服务器和客户端分别完成登记和注册
/ * ICALL 服务端登记* /
/* BLE协议使用 ICALL_SERVICE_CLASS_BLE 做蓝牙协议栈 ICALL的消息交互的标识*/
ICall _enrollService (ICALL _SERVICE_CLASS_BLE ,NULL ,&entity ,&syncHandle );
/ * ICALL 客户端注册* /
/*ICALL服务端通过两个变量来进行消息传递。
syncEvent 参数表示事件标识,
selfEntity 表示处理消息的目的任务,也是客户端实体以后通信的源地址。
注册 ICALL 的客户端使用唯一 syncEvent和selfEntity 。*/
ICall _registerApp (&selfEntity ,&syncEvent);
3.ICall 线程同步
ICALL 使用 TI-RTOS 的事件用以线程同步。
ICall 消息队列保留了特定事件标志位
#define ICALL_MSG_EVENT_ID Event_Id_31
客户端或服务端在收到消息前保持阻塞状态,ICall 使用阻塞型 API 保持任务阻塞状态直到关联的信号量被 Post
Event_pend 函数表示阻塞当前任务等待某一事件标志位发生。
UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout);
handle 是构造的 Event_Handle 事件句柄(标识符)。
andMask 和 orMask 为用户选择要阻塞/挂起的事件标志。
timeout 是以毫秒为单位的超时周期。如果在此超时时间范围内后事件尚未被Post,该函数将返回。
Event_post 用以客户端/服务端将某个任务阻塞任务激活成运行状态
void Event_post(Event_Handle handle,UInt eventMask);
上面的事件句柄 handle 由服务端 ICall _enrollService()和 ICall _registerApp()调用后获得。
4.ICall 的消息流程分析
1.ICall服务端创建
有关协议栈任务实体:TI软件架构的区分
独立镜像方式是通过工具自动计算首地址作为协议栈的任务的入口地址,
静态链接库方式是直接链接协议栈的入口地址作为协议栈任务的任务实体。
2.服务端登记
ICALL_SERVICE_CLASS_BLE_MSG 作为服务端的源地址
ICall_enrollService(ICALL_SERVICE_CLASS_BLE_MSG,ICall_ServiceFunc) osal_service_entry,
&osal_entity,&osal_syncHandle)
3.客户端注册,
selfEntity 作为客户端 Icall 通信的源地址
ICall_registerApp(&selfEntity, &syncEvent);
4.应用程序发送消息
所有应用程序 Icall 消息发送都是封装在 icall_directAPI 函数中。
4.1icall_direct将要传递的消息封装
4.1调用ICall_sendServiceMsg
Call_sendServiceMsg(ICall_getEntityId(),service,ICALL_MSG_FORMAT_DIRECT_API_ID,&(liteMsg.msg));
▪ ICall_getEntityId():调用API的应用程序的任务标识
▪ service:为ICALL_SERVICE_CLASS_BLE_MSG
▪ ICall_sendServiceMsg的函数原型
○ ICall_sendServiceMsg(ICall_EntityID src, ICall_ServiceEnum dest,ICall_MSGFormat format, void *msg)
○ ICall_EntityID客户端地址,调用API的应用程序
○ ICall_ServiceEnum服务器端地址,协议栈的
4.2调用 ICall_send
ICall_send 直接将消息放入了目的 Icall 消息实体的消息队列中,并且触发事件通知该任务唤醒解析处理消息
▪ICall_msgEnqueue(&ICall_entities[dest].task->queue, msg);
▪ICALL_SYNC_HANDLE_POST(ICall_entities[dest].task->syncHandle);# `
5.ICall 的调用过程
协议栈对应用程序的触发概括而言就是:回调函数,消息入队,异步事件,阻塞停止
下面以GATT层的事件接受为例
1.回调函数定义
static void SimplePeripheral_charValueChangeCB(uint8_t paramId)
{
uint8_t *pValue = ICall_malloc(sizeof(uint8_t));
if (pValue)
{
*pValue = paramId;
if (SimplePeripheral_enqueueMsg(SP_CHAR_CHANGE_EVT, pValue) != SUCCESS)
{
ICall_free(pValue);
}
}
}
static simpleProfileCBs_t SimplePeripheral_simpleProfileCBs =
{
SimplePeripheral_charValueChangeCB // Simple GATT Characteristic value change callback
};
2.绑定回调函数
SimpleProfile_RegisterAppCBs(&SimplePeripheral_simpleProfileCBs);
3.当GATT层收到蓝牙数据后,SP_CHAR_CHANGE_EVT入队,app任务唤醒后执行
if (events)
{
ICall_EntityID dest;
ICall_ServiceEnum src;
ICall_HciExtEvt *pMsg = NULL;
// 优先检查协议栈消息
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;
if (pEvt->signature != 0xffff)
{
safeToDealloc = SimplePeripheral_processStackMsg((ICall_Hdr *)pMsg);
}
}
if (pMsg && safeToDealloc)
{
ICall_freeMsg(pMsg);
}
}
// 判断APP消息
if (events & SP_QUEUE_EVT)
{
while (!Queue_empty(appMsgQueueHandle))
{
spEvt_t *pMsg = (spEvt_t *)Util_dequeueMsg(appMsgQueueHandle);
if (pMsg)
{
SimplePeripheral_processAppMsg(pMsg);
ICall_free(pMsg);
}
}
}
}
4.在协议栈任务确定是蓝牙哪一层的任务
static uint8_t SimplePeripheral_processStackMsg(ICall_Hdr *pMsg)
{
switch (pMsg->event)
{
case GAP_MSG_EVENT:
SimplePeripheral_processGapMessage((gapEventHdr_t*) pMsg);
break;
case GATT_MSG_EVENT:
// Process GATT message
safeToDealloc = SimplePeripheral_processGATTMsg((gattMsgEvent_t *)pMsg);
break;
case HCI_GAP_EVENT_EVENT:
{
// Process HCI message
************
break;
}
default:
// do nothing
break;
}
5.在GATT层任务中完成最终事件的处理
static uint8_t SimplePeripheral_processGATTMsg(gattMsgEvent_t *pMsg)
{
if (pMsg->method == ATT_FLOW_CTRL_VIOLATED_EVENT)
{
// Display the opcode of the message that caused the violation.
Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "FC Violated: %d", pMsg->msg.flowCtrlEvt.opcode);
}
else if (pMsg->method == ATT_MTU_UPDATED_EVENT)
{
// MTU size updated
Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "MTU Size: %d", pMsg->msg.mtuEvt.MTU);
}
// Free message payload. Needed only for ATT Protocol messages
GATT_bm_free(&pMsg->msg, pMsg->method);
// It's safe to free the incoming message
return (TRUE);
}