# 应用程序 # 本章节将详细讲解 CC2640R2F BLE5.0 的应用程序框架,我们希望您已经按照学习线路图储备了 CC2640R2F平台的软硬件架构知识 ,明白应用工程区分 App 和 Stack 工程管理。 本章主要内容是围绕基于 TI-RTOS 的 App 应用程序框架。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/image43.jpeg) 图1. 应用程序框架图 以 simple_peripheral Demo 应用程序部分为例,包括以下内容: * Pre-main initialization * ICall * Simple Peripheral Task * Intertask Messages > **注意:**虽然 GAPRoleTask 也是工程的一部分,但会把它放在协议栈部分进行讨论,它的功能是与协议栈密切相关的。 ## Pre-main initialization ## `main` 函数包含在 IDE Startup 文件夹的资源文件 main.c 中。作为程序的入口,主要完成全局中断禁止、外设驱动初始化、电源管理、TI-RTOS 任务创建或构造、在启用 SYS / BIOS 内核调度时完成全局中断使能。main 函数不返回,将在整个项目生命周期内保留其资源。 基本 main.c 功能。 ```C int main() { /* Register Application callback to trap asserts raised in the Stack */ RegisterAssertCback(AssertHandler); PIN_init(BoardGpioInitTable); #ifndef POWER_SAVING /* Set constraints for Standby, powerdown and idle mode */ Power_setConstraint(PowerCC26XX_SB_DISALLOW); Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW); #endif // POWER_SAVING /* Initialize ICall module */ ICall_init(); /* Start tasks of external images - Priority 5 */ ICall_createRemoteTasks(); /* Kick off profile - Priority 3 */ GAPRole_createTask(); SimpleBLEPeripheral_createTask(); /* enable interrupts and start SYS/BIOS */ BIOS_start(); return 0; } ``` >**注意**:以上代码** ICALL **消息模块必须由 ICALL_init() 完成初始化,并且通过 ICall_createRemoteTasks() 完成协议栈任务创建。 ## ICALL ## ### 介绍 ### 在软件架构章节阐述了由于历史兼容原因会把整个应用工程区分 App 和 Stack 两个工程管理。因此,无法采用常规 API 调用和全局变量方式完成消息传递,App 和 Stack 之间的通信就需要重新考虑了。TI 引入了 ICALL 消息机制完成 App 和 Stack 独立工程管理的相互通信,接下来就着重理解原理和代码实现。 Indirect Call Framework (** ICall 消息框架**)是基于 TI-RTOS 提供服务(例如,同步线程、消息、事件)并完成 BLE 协议栈和应用程序在两个工程的消息交互的框架,它能够保证应用程序和协议栈在统一的 TI-RTOS 环境中完成高效运行、相互通信和资源共享。 ICall 架构的核心组件是其消息调度( dispatcher ),消息调度帮助程序在两个 镜像/工程 中完成 BLE5-Stack 协议栈以及库配置中同应用程序交互。尽管大多数 ICall 交互在 BLE5-Stack API(例如 GAP,HCI 等)中已经被抽象化(已经被封装为消息原语函数),但是还是需要理解它们在 BLE-Stack 和多线程 RTOS 环境中正常运行的基础架构。 ** ICALL **源码在应用工程(例如本文的 simple_peripheral )的 ICALL BLE/ICALL 文件夹路径。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/image68.jpeg) 图2. ICall 应用-协议栈 抽象 ### ICall BLE5 协议栈服务端 ### 如图 2 所示,** ICALL **实现包含一个客户端实体(例如我们的应用程序)和一个服务器实体(即这里的 BLE5.0 协议栈)之间的通信。 > **注意** :正确区分** ICALL **框架的 CS 架构以及 GATT 服务器和客户端的架构,后者主要由 BLE 协议栈所定义实现。 TI 之所以这样设计是因为: * 实现应用程序和 BLE 协议栈的独立管理和固件更新 * 从传统平台(即 CC254x 的 OSAL )移植到 CC2640R2F 的 TI-RTOS ,保持 API 一致性 ** ICall ** 作为 BLE5 协议栈 API 的服务接口。当调用一些协议栈接口时,ICall 模块会自动将命令分发(即调度)到 BLE5 协议栈,并将消息结果从 BLE5 协议栈传回应用程序。 ICall 消息模块本身就作为是应用程序工程的一部分,应用任务可以通过函数调用方式直接访问 ICall 。由于 BLE 协议栈任务总是以最高优先级执行,应用程序在没有数据返回时处于阻塞状态,必然有些 API 会在协议栈任务立即响应,应用任务只有在消息通过 ICALL 分发时才能唤醒处理;而另外一些 API 却只有等待应用任务通过 ICAlL(事件更新)异步返回结果。 ### ICALL 原语服务 ### ICALl 一系列的原语服务都被抽象成基于 RTOS 相关的函数接口。由于共享资源和多线程之间的通信,应用程序必须使用以下 ICALL 原语服务功能: * 消息传递和线程同步 * 堆分配与管理 #### 消息传递和线程同步 #### ** ICall **同协议栈的消息传递和线程同步都是是基于 TI-RTOS 多线程。 ** ICall **通过一个任务在消息队列给另外一个任务发送一个阻塞消息时实现消息传递。发送方动态分配一段内存,将消息的内容写入内存,将这段内存发送(即排队)到接收线程,然后再使用事件标志。接受任务在收到事件标志后唤醒,复制内存消息并处理,之后再将这段内存块释放。 协议栈使用 ICall 通知和发送消息到应用程序,ICall 传递这些服务消息,应用程序任务接收它然后处理。 #### 堆分配与管理 #### ICall 为应用提供了全局堆管理的 API 用以动态内存分配,其堆的大小通过宏 `HEAPMGR_SIZE` 配置。有关管理动态内存的更多详细信息,请参阅动态内存分配。BLE 协议栈的 ICall 使用堆管理进行所有消息相关的内存管理,建议应用程序也使用这些 ICall API 来分配动态内存。 ### ICALL 初始化和注册 ### 要实例和初始化 ICall 服务,应用程序必须在启动 TI-RTOS 内核调度程序之前调用 main()代码片段中的函数: 使用 ICall 的必需代码。 ```C /* 初始化 ICall 模块 */ ICall_init(); /* 将协议栈作作为任务创建 */ ICall _createRemoteTasks (); ``` 调用 ICall_init()初始化 ICALL 原语服务(例如,初始化堆管理)和框架,调用 ICall _createRemoteTasks()创建但不启动 BLE5-Stack 任务。在使用ICall 协议服务之前,服务器和客户端分别完成登记和注册。服务端在编译的时候就需要登记一个服务,登记函数使用一个全局的唯一标识符区分每个服务并作为通信地址。例如,BLE协议使用 `ICALL_SERVICE_CLASS_BLE` 做些蓝牙协议栈 ICALL的消息交互的标识。 服务端的登记在 `osal_icall_ble.c` 文件: ```C / * ICALL 服务端登记* / ICall _enrollService (ICALL _SERVICE_CLASS_BLE ,NULL ,&entity ,&syncHandle ); ``` 客户端在 ICALL 调度程序发送和/或接收消息之前需要注册。 对于使用 BLE5 的客户端(例如这里的应用程序任务),客户端必须首先向ICall注册其任务 。该注册通常发生在应用程序的任务初始化功能中。下面的代码片段是 `simple_peripheral_init` 在 simple_peripheral.c 中注册。 ```C //ICALL客户端注册 ICall _registerApp (&selfEntity ,&syncEvent); ``` 完成客户端注册前需要传入结构体变量 `selfEntity` 和 `syncEvent` ,其值在函数返回时被初始化,服务端通过这两个变量来进行消息传递。`syncEvent` 参数表示事件标识,`selfEntity` 表示处理消息的目的任务,也是客户端实体以后通信的源地址。每个注册 ICALL 的客户端都需要使用唯一的 `syncEvent` 和`selfEntity` 。 > **注意**: 在客户端注册 ICALL 服务之前,在 `ICallBLEApi.c` 文件里面定义的 ICALL 相关 API 都不能调用。 ### ICall 线程同步 ### ICALL 使用 TI-RTOS 的事件用以线程同步而不是信号量。 为了让客户端或服务端在收到消息前都保持阻塞状态, ICall 提供以下阻塞型 API 保持任务阻塞状态直到关联的信号量被 Post : ```C UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout); ``` `handle` 是构造的 Event_Handle 事件句柄(标识符)。 `andMask` 和 `orMask` 为用户选择要阻塞/挂起的事件标志。 `timeout` 是以毫秒为单位的超时周期。如果在此超时时间范围内后事件尚未被Post,该函数将返回。 `Event_pend` 函数表示阻塞当前任务等待某一事件标志位发生。 一共有 32 个事件标志位( int 类型),这些标识位可以根据其特定用途进行定义。不过需要注意的是,ICall 消息队列已经保留了特定事件标志位。 与 TI-RTOS 线程关联的事件由 Event_post 所需标志调用时发出/发布 `Event_post` 用以客户端/服务端将某个任务阻塞任务激活成运行状态,并且通过发送对应的标志位执行指定动作。 ```C void Event_post(Event_Handle handle,UInt eventMask); ``` 以上的事件句柄 `handle` 由服务端 ICall _enrollService()和 ICall _registerApp()调用后获得。 > **危险**: 不要从协议栈回调中调用 ICall 函数,此操作可能导致 ICall 中止(使用 ICall_abort())并中断系统。 ### 示例 ICall 用法 ### 图 3 显示了一个通过 ICall 框架从应用程序发送到 BLE5-Stack 的示例命令,并将相应的返回值传回给应用程序。 ICall _init()完成初始化 ICall 模块实例,ICall_createRemoteTasks()使用已知地址的入口函数为协议栈创建任务。 初始化后的 ICall ,App 作为客户端通过 ICall_registerApp()完成注册。 在 SYS / BIOS 调度程序启动并且应用程序任务运行之后,应用程序发送一个 ble_dispatch_JT.c 如 GAP_GetParamValue()中定义的协议命令。 协议命令不在应用程序的线程中执行,而是封装在ICall消息中执行,是由 ICall 框架发送到 BLE5-Stack 任务。该命令被发送到 ICALL 调度程序,它在 BLE5-Stack 上下文中调度和执行;同时应用程序线程阻塞,直到接收到相应的命令状态消息;BLE5-Stack 完成执行命令,然后通过 ICall 将命令状态消息响应发送回应用程序线程。这种交换的示例图如下所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/IcallBlcok.png) 图3. ICall 消息传递示例 有了以上理论知识储备,我们尝试解决以下问题: ### BLE-Stack 工程是如何作为 App 中 TI-RTOS 的一个任务运行的 ### * 直接走读 App 代码,以下代码片段创建了协议栈任务: ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_createRemoteTasks Line 519 void ICall_createRemoteTasks(void { size_t i; UInt keytask; /* Cheap locking mechanism to lock tasks * which may attempt to access the service call dispatcher * till all services are registered. */ keytask = Task_disable(); for (i = 0; i < ICALL_REMOTE_THREAD_COUNT; i++) { Task_Params params; Task_Handle task; ICall_CSState key; Task_Params_init(¶ms); params.priority = ICall_threadPriorities[i]; params.stackSize = ICall_threadStackSizes[i]; params.arg0 = (UArg) icall_threadEntries[i]; params.arg1 = (UArg) ICall_getInitParams(i); ``` * 该任务的任务函数实体 ICall_taskEntry 是通过 arg0 参数强制转换得到的。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_taskEntry Line 482 static Void ICall_taskEntry(UArg arg0, UArg arg1) { ICall_RemoteTaskEntry entryfn = (ICall_RemoteTaskEntry) arg0; entryfn(&ICall_taskEntryFuncs, (void *) arg1); } ``` * 任务实体 arg0 的参数地址要区分工程编译选项,这点已经在软件架构章节详细讲解过。`FlashROM_StackLirary` 链接的编译在静态库中的 `startup_entry` 函数中。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_createRemoteTasks Line 540 params.arg0 = (UArg) icall_threadEntries[i]; //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c Line228 static const ICall_RemoteTaskEntry icall_threadEntries[] = ICALL_ADDR_MAPS; //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\inc\icall_addrs.h Line99 #ifdef STACK_LIBRARY extern void startup_entry( const ICall_RemoteTaskArg *arg0, void *arg1 ); //extern ICall_RemoteTaskEntry startup_entry; #define ICALL_ADDR_MAPS \ { \ (ICall_RemoteTaskEntry) (startup_entry) \ } #else /* ! STACK_LIBRARY */ #define ICALL_ADDR_MAPS \ { \ (ICall_RemoteTaskEntry) (ICALL_STACK0_ADDR) \ } #endif /* STACK_LIBRARY */ ``` `FlashROM` 编译选项的 `arg0` 是一个绝对地址,该绝对地址是通过边界工具 `frontier.exe` 计算协议栈工程编译生成的存储映射文件 *.map 所得,并将计算结果保存在应用工程的 `iar_boundary.xcl` 文件中。 ``` //Project->Options(Alt+F7)->Build Actions "$TOOLS_BLE$/frontier/frontier.exe" iar "$PROJ_DIR$/$CONFIG_NAME$/List/simple_peripheral_cc2640r2lp_stack.map" "$PROJ_DIR$/../config/iar_boundary.bdef" "$PROJ_DIR$/../config/iar_boundary.xcl" ``` ```C /* ** Stack Frontier Generator 1.1.0 (2017-06-28 14:28:12.107000) ** ** WARNING - Auto-generated file. Modifications could be lost! */ --config_def ICALL_RAM0_START=0x20003fe8 --config_def ICALL_STACK0_START=0x00016a00 --config_def ICALL_STACK0_ADDR=0x00016a01 ``` Okay,大概说下结论:协议栈工程是作为 App 工程的一个任务启动的,该任务创建区分协议栈是编译成独立镜像还是静态链接库方式。独立镜像方式是通过工具自动计算首地址作为协议栈的任务的入口地址,静态链接库方式是直接链接协议栈的入口地址作为协议栈任务的任务实体。 ### 尝试走读一个 ICALL 的消息流程 ### * 服务端登记 在协议栈任务中完成 ICall 服务端的登记,`ICALL_SERVICE_CLASS_BLE_MSG` 作为服务端的源地址。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\stack\osal_icall_ble.c stack_main Line 229 if (ICall_enrollService(ICALL_SERVICE_CLASS_BLE_MSG, (ICall_ServiceFunc) osal_service_entry, &osal_entity, &osal_syncHandle) != ICALL_ERRNO_SUCCESS) { ``` * 客户端注册 客户端根据不同任务进行注册,`SimpleBLEPeripheral_taskFxn` 完成该任务的 Icall 客户端注册,并且返回 `selfEntity` 作为客户端 Icall 通信的源地址。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\app\simple_peripheral.c SimpleBLEPeripheral_init Line 452 ICall_registerApp(&selfEntity, &syncEvent); ``` * 发送消息 所有应用程序 Icall 消息发送都是封装在 `icall_directAPI` 函数中。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\app\simple_peripheral.c SimpleBLEPeripheral_init Line 497 // Setup the GAP GAP_SetParamValue(TGAP_CONN_PAUSE_PERIPHERAL, DEFAULT_CONN_PAUSE_PERIPHERAL); //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\inc\icall_ble_api.h GAP_SetParamValue Line 313 #define GAP_SetParamValue(...) (icall_directAPI(ICALL_SERVICE_CLASS_BLE, (uint32_t) IDX_GAP_SetParamValue , ##__VA_ARGS__)) ``` 而对于 `icall_directAPI` ,将所有变参封装到一个消息结构体,并且调用 `ICall_sendServiceMsg` -> `ICall_send` 。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c icall_directAPI Line3819 icallLiteMsg_t liteMsg; liteMsg.hdr.len = sizeof(icallLiteMsg_t); liteMsg.hdr.next = NULL; liteMsg.hdr.dest_id = ICALL_UNDEF_DEST_ID; liteMsg.msg.directAPI = id; liteMsg.msg.pointerStack = (uint32_t*)(*((uint32_t*)(&argp))); ICall_sendServiceMsg(ICall_getEntityId(), service, ICALL_MSG_FORMAT_DIRECT_API_ID, &(liteMsg.msg)); //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_sendServiceMsg Line 2419 return (ICall_send(src, dstentity, format, msg)); ``` 最终 `ICall_send` 是直接将消息放入了目的 Icall 消息实体的消息队列中,并且触发事件通知该任务唤醒解析处理消息。 ```C //C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c Icall_Send Line 2661 ICall_msgEnqueue(&ICall_entities[dest].task->queue, msg); ICALL_SYNC_HANDLE_POST(ICall_entities[dest].task->syncHandle); ``` ### 如何调试协议栈任务 ### 因为协议栈任务分布在另外一个工程,所以没有向原来那样直接加断点调试。已经了解协议栈是如何作为一个任务在应用工程中启动的,那么调试协议栈任务的关键是找到协议任务入口地址。 在调试协议栈任务之前,建议将分别设置协议栈和应用工程优化等级 Project -> Opitons -> C/C++ Compiler Optimizations -> Level -> None 为无,这对正常调试协议栈任务至关重要。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/Opitimaizations.jpg) 图4 .设置优化等级 `FlashROM` 编译选项的协议栈任务入口地址在 `iar_boundary.xcl` 中给出,对于 `FlashROM_StackLibrary` 编译选项,可以通过编译生成的 *.map 文件查找`startup_entry` 符号从而找到入口地址。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/FlashRomEntry.jpg) ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/LibEntry.jpg) 图5 .找到入口地址 拿到协议栈任务的入口地址后,就可以在汇编窗口 View -> Disassembly 直接输入该地址,然后加上断点,等待协议栈任务创建后运行,从而跳转到协议栈工程进行调试。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/DisassemblyWindow.png) 图6 .如何用协议栈工程进行调试 如果不知道协议栈任务的入口地址,则直接通过创建协议栈任务的任务实体入口,跳转到 `ICall_taskEntry` -> `entryfn` ,然后按** F11 **进入协议栈任务调试。 ![](http://www.leconiot.com/md_res/cc2640r2f/ble_stack_app/app/Images/EntryFn.png) 图7 .如何进入协议栈任务调试 >**注意**:这里 F11 Step Into 可能会优化选项跳转失败,所以建议关闭优化选项或者直接到聚焦到汇编窗口 BX R2 F11 跳转。 ## Simple Peripheral Task ## 简单外设任务是系统中最低优先级的应用程序任务,该任务的代码是在 Application IDE 文件夹中的 simple_peripheral 文件夹 下的`simple_peripheral.c` 中。 ### 应用程序初始化功能 ### TI-RTOS 概述详细介绍了任务构建。构建任务并启动 SYS / BIOS 内核调度程序后,构造过程中传递的函数会在任务准备就绪时运行(例如 SimpleBLEPeripheral_taskFxn )。任务实体函数运行前这里会先运行任务初始化。 simple_peripheral 任务函数伪代码 ```C static void SimpleBLEPeripheral_taskFxn (UArg a0 , UArg a1 ) { //初始化应用程序 SimpleBLEPeripheral_init (); // Application main loop for (;;) { } } ``` 初始化函数( simple_peripheral_init )为任务配置了几个服务,并设置了几个硬件和软件配置设置和参数。 以下列表包含一些常见示例: * 初始化 GATT 客户端 * 设置各种配置文件的服务读写等回调函数 * 设置 GAPRole * 建立 Bond 管理器 * 初始化 GAP 层 * 配置 LCD 或 SPI 等硬件模块。 >**注意**:在应用程序初始化函数中,调用任何协议栈 API 之前必须调用 ICall _registerApp() 完成注册。 ### 任务功能中的事件处理 ### simple_peripheral 实现以事件驱动任务的功能。任务函数进入一个无限循环,使其不间断地处理一个独立的任务,并且始终不会运行到完成。在无限循环过程中,任务保持阻塞并等待,直到事件标志更新后进入事件处理函数。 ICall任务保持阻塞并等待,直到发信号通知进行处理。 ```C // 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, SBP_ALL_EVENTS, ICALL_TIMEOUT_FOREVER); ``` 当事件发生并被处理后,任务又等待事件标志并且保持阻塞状态,直到有另一个事件发生。 ### 任务事件 ### 当 BLE5-Stack 通过 ICAll 消息模块在应用程序任务中设置事件时,任务事件发生。一个比较好的例子就是调用 HCI_EXT_ConnEventNoticeCmd()来指示协议栈 `connection event` 结束。表示该事件结束的任务事件也将显示在 simple_peripheral 的任务函数中。 SBP 任务检查任务事件: ```C 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; // Check for BLE stack events first if (pEvt->signature == 0xffff) { if (pEvt->event_flag & SBP_CONN_EVT_END_EVT) { // Try to retransmit pending ATT Response (if any) SimpleBLEPeripheral_sendAttRsp(); } } else { // Process inter-task message safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg); } } if (pMsg && safeToDealloc) { ICall_freeMsg(pMsg); } } // Additional Event Processing } ``` >**注意**:在当前代码中,如果事件来自 BLE5-Stack ,则 pEvt->signature 总是等于 0xFFFF 。 当为一个事件选择一个事件值的时候,该值对于给定的任务必须是唯一的,并且必须是 2 的幂(只有 1 bit 被设置为 1 )。这样做的原因是 `pEvt->event` 变量被初始化为 `uint16_t` 类型,也就是最多运行允许 16 个事件。有一个不能使用的事件值是已经用于 BLE5-Stack OSAL 全局事件( bcomdef.h 中所述)的事件值。 清单1. BLE OSAL事件在 bcomdef.h 中定义。 ```C /************************************************************ * BLE OSAL GAP GLOBAL Events */ #define GAP_EVENT_SIGN_COUNTER_CHANGED 0x4000 //!< The device level sign counter changed ``` > **注意**:这些任务间的事件是与 `请求和处理协议栈事件` 中提到的内部事件不同的事件集。 ## Intertask 消息 ## 这些消息通过 ICall 从一个任务(如 BLE5-Stack Service/ICALL 服务端)传递给应用程序任务( ICALL 客户端)。 正如以下情形: * 协议栈成功收到发送的无线数据 ACK ,需要发送确认从协议栈到应用。 * 与 HCI 命令相对应的事件。 * GATT 客户端操作的响应(请参阅直接使用 GATT 层) Task 事件是来自 simple_peripheral 的主要任务循环的一个例子。 ### 使用 TI-RTOS 事件模块 ### 所有 BLE5-Stack 1.00.00 项目使用 TI-RTOS 事件模块获取 ICall 堆栈消息事件,ICall 线程同步中描述了使用情况。有关事件模块的更多文档,请参见“ TI RTOS 内核用户指南”。 ### 处理队列的应用程序消息 ### 使用 Util_enqueueMsg() 函数将应用程序消息放入队列以先进先出的顺序进行处理。当 UTIL_QUEUE_EVENT_ID 发布事件时,应用程序应该从消息队列取出消息处理后释放。 下面的代码片段显示了 simple_peripheral 如何处理应用程序消息。 ```C #define SBP_QUEUE_EVT UTIL_QUEUE_EVENT_ID // Event_Id_30 // If TI-RTOS queue is not empty, process app message. if (events & SBP_QUEUE_EVT) { while (!Queue_empty(appMsgQueue)) { sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue); if (pMsg) { // Process message. SimpleBLEPeripheral_processAppMsg(pMsg); // Free the space from the message. ICall_free(pMsg); } } } ``` ### 请求和处理协议栈事件 ### 某些 API 可以选择在 BLE5-Stack 中发生特定事件时通知应用程序,启用此类事件通知的 API 将包含一个 `taskEvent` 参数。`taskEvent` 对于给定的ICall-aware 任务,值必须是唯一的。应用程序可以通过检查 `taskEvent` 是否包含在数据结构 `ICall_Stack_Event ` 的 `uint16_t event_flag` 变量中来处理所请求的协议栈事件。 >**注意** :event_flag 不与 TI-RTOS 事件模块事件混淆。 下面的代码片段显示了 simple_peripheral 如何请求协议栈事件标志。 连接间隔结束时请求通知的应用程序: ```C // Application specific event ID for HCI Connection Event End Events #define SBP_HCI_CONN_EVT_END_EVT 0x0001 static uint8_t SimpleBLEPeripheral_processGATTMsg(gattMsgEvent_t *pMsg) { // See if GATT server was unable to transmit an ATT response if (pMsg->hdr.status == blePending) { // No HCI buffer was available. Let's try to retransmit the response // on the next connection event. if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity, SBP_HCI_CONN_EVT_END_EVT) == SUCCESS) { // First free any pending response SimpleBLEPeripheral_freeAttRsp(FAILURE); // Hold on to the response message for retransmission pAttRsp = pMsg; //... } //... } //... } ``` 下面的代码片段显示了 simple_peripheral 如何处理协议栈事件标志。 处理请求 BLE5-Stack 事件: ```C // Application specific event ID for HCI Connection Event End Events #define SBP_HCI_CONN_EVT_END_EVT 0x0001 static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1) { // 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, SBP_ALL_EVENTS, ICALL_TIMEOUT_FOREVER); 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; // Check for BLE stack events first if (pEvt->signature == 0xffff) { if (pEvt->event_flag & SBP_HCI_CONN_EVT_END_EVT) { // Try to retransmit pending ATT Response (if any) SimpleBLEPeripheral_sendAttRsp(); } } else { // Process inter-task message safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg); } } } } } } ``` ### 回调 ### 应用程序代码还包括对协议栈层、配置文件和 TI-RTOS 模块的各种回调函数。为了确保线程的安全性,回调要尽量做最少事情。大部分的处理应该发生在应用程序上下文中。每个回调定义了两个函数,一个是回调本身,另外一个就是在任务环境中被处理回调事件函数。回调不直接处理,通过事件方式到任务中。可以参考 GAPRole 状态改变回调,它是用来处理 GAPRole 状态变化的。 >**危险**:在回调函数中执行阻塞 TI-RTOS 函数调用或协议栈 API 是很危险的,这样的函数调用可能导致中止或未定义的行为。 simple_peripheral状态变化回调。 ```C static void SimpleBLEPeripheral_stateChangeCB (gaprole_States_t newState ) { SimpleBLEPeripheral_enqueueMsg (SBP_STATE_CHANGE_EVT , newState ); } ``` 上面的代码片段是通过 `SimpleBLEPeripheral_gapRoleCBs `和 `GAPRole_StartDevice()` 回调的,回调只是在队列中放置一个消息来通知应用程序唤醒。一旦回调返回其父任务进入休眠状态,应用程序唤醒从消息队列取出消息处理同时调用以下代码片段。 simple_peripheral 任务环境中的状态变化事件处理。 ```C static void SimpleBLEPeripheral_processStateChangeEvt (gaprole_States_t newState ) { // ... } ``` ## 加入我们 ## 文章所有代码、工具、文档开源。加入我们[**QQ群 591679055**](http://shang.qq.com/wpa/qunwpa?idkey=d94f12d37c3b37892af4b757c6dc34bea140f3f3128a8d68e556a3d728148e85)获取更多支持,共同研究CC2640R2F&BLE5.0。

CC2640R2F&BLE5.0-乐控畅联 © Copyright 2017, 成都乐控畅联科技有限公司.