这里会显示出您选择的修订版和当前版本之间的差别。
cc2640r2f:application_architecture [2017/09/02 11:39] long |
cc2640r2f:application_architecture [2021/06/22 23:14] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | < | ||
- | # 应用程序 # | ||
- | |||
- | 本章节将详细讲解 CC2640R2F BLE5.0 的应用程序框架,我们希望您已经按照学习线路图储备了< | ||
- | 本章主要内容是围绕 TI-RTOS 的 App 应用程序框架。 | ||
- | |||
- | ![](http:// | ||
- | |||
- | 以 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 函数不返回,将在整个项目生命周期内保留其资源。 | ||
- | |||
- | | ||
- | |||
- | ```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 ## | ||
- | |||
- | ### 介绍 ### | ||
- | |||
- | 在<a href=" | ||
- | |||
- | Indirect Call Framework (** ICall 消息框架**)是基于 TI-RTOS 提供服务(例如,同步线程、消息、事件)并完成 BLE 协议栈和应用程序在两个工程的消息交互的框架,它能够保证应用程序和协议栈在统一的 TI-RTOS 环境中完成高效运行、相互通信和资源共享。 | ||
- | |||
- | ICall 架构的核心组件是其消息调度( dispatcher ),消息调度帮助程序在两个 镜像/ | ||
- | |||
- | ** ICALL **源码在应用工程(例如本文的 simple_peripheral )的 ICALL BLE/ICALL 文件夹路径。 | ||
- | |||
- | ![ICall 应用-协议栈 抽象](http:// | ||
- | |||
- | ### ICall BLE5 协议栈服务端 ### | ||
- | |||
- | 如上图所示,** 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 _registerApp (& | ||
- | ``` | ||
- | 完成客户端注册前需要传入结构体变量 `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 框架从应用程序发送到 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 将命令状态消息响应发送回应用程序线程。这种交换的示例图如下所示。 | ||
- | |||
- | ![ ICall 消息传递示例](http:// | ||
- | |||
- | 通过以上理论知识储备,我们实际出发思考并解决以下问题: | ||
- | |||
- | ### BLE-Stack 工程是如何作为 App 中 TI-RTOS 的一个任务运行的 ### | ||
- | |||
- | * 直接走读 App 代码,以下代码片段创建了协议栈任务: | ||
- | |||
- | ```C | ||
- | // | ||
- | |||
- | 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; | ||
- | Task_Params params; | ||
- | Task_Handle task; | ||
- | ICall_CSState key; | ||
- | |||
- | Task_Params_init(& | ||
- | 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 | ||
- | // | ||
- | static Void ICall_taskEntry(UArg arg0, UArg arg1) { | ||
- | ICall_RemoteTaskEntry entryfn = (ICall_RemoteTaskEntry) arg0; | ||
- | |||
- | entryfn(& | ||
- | } | ||
- | ``` | ||
- | |||
- | * 任务实体 | ||
- | |||
- | arg0 的参数地址要区分工程编译选项,这点已经在< | ||
- | |||
- | ```C | ||
- | // | ||
- | params.arg0 = (UArg) icall_threadEntries[i]; | ||
- | |||
- | // | ||
- | static const ICall_RemoteTaskEntry icall_threadEntries[] = ICALL_ADDR_MAPS; | ||
- | |||
- | // | ||
- | #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 */ | ||
- | ``` | ||
- | |||
- | | ||
- | |||
- | ``` | ||
- | // | ||
- | " | ||
- | ``` | ||
- | |||
- | ```C | ||
- | /* | ||
- | ** Stack Frontier Generator 1.1.0 (2017-06-28 14: | ||
- | ** | ||
- | ** 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, | ||
- | |||
- | ### 尝试走读一个 ICALL 的消息流程 ### | ||
- | |||
- | * 服务端登记 | ||
- | |||
- | 在协议栈任务中完成 ICall 服务端的登记,`ICALL_SERVICE_CLASS_BLE_MSG` 作为服务端的源地址。 | ||
- | |||
- | ```C | ||
- | // | ||
- | if (ICall_enrollService(ICALL_SERVICE_CLASS_BLE_MSG, | ||
- | (ICall_ServiceFunc) osal_service_entry, | ||
- | & | ||
- | & | ||
- | { | ||
- | ``` | ||
- | |||
- | * 客户端注册 | ||
- | |||
- | 客户端根据不同任务进行注册,`SimpleBLEPeripheral_taskFxn` 完成该任务的 Icall 客户端注册,并且返回 `selfEntity` 作为客户端 Icall 通信的源地址。 | ||
- | |||
- | ```C | ||
- | // | ||
- | |||
- | ICall_registerApp(& | ||
- | |||
- | ``` | ||
- | |||
- | * 发送消息 | ||
- | |||
- | 所有应用程序 Icall 消息发送都是封装在 `icall_directAPI` 函数中。 | ||
- | |||
- | ```C | ||
- | // | ||
- | |||
- | // Setup the GAP | ||
- | GAP_SetParamValue(TGAP_CONN_PAUSE_PERIPHERAL, | ||
- | |||
- | // | ||
- | #define GAP_SetParamValue(...) | ||
- | |||
- | ``` | ||
- | |||
- | 而对于 `icall_directAPI` ,将所有变参封装到一个消息结构体,并且调用 `ICall_sendServiceMsg` -> `ICall_send` 。 | ||
- | |||
- | ```C | ||
- | // | ||
- | icallLiteMsg_t liteMsg; | ||
- | liteMsg.hdr.len = sizeof(icallLiteMsg_t); | ||
- | liteMsg.hdr.next = NULL; | ||
- | liteMsg.hdr.dest_id = ICALL_UNDEF_DEST_ID; | ||
- | liteMsg.msg.directAPI | ||
- | liteMsg.msg.pointerStack = (uint32_t*)(*((uint32_t*)(& | ||
- | ICall_sendServiceMsg(ICall_getEntityId(), | ||
- | | ||
- | |||
- | // | ||
- | return (ICall_send(src, | ||
- | |||
- | ``` | ||
- | |||
- | 最终 `ICall_send` 是直接将消息放入了目的 Icall 消息实体的消息队列中,并且触发事件通知该任务唤醒解析处理消息。 | ||
- | |||
- | ```C | ||
- | // | ||
- | ICall_msgEnqueue(& | ||
- | ICALL_SYNC_HANDLE_POST(ICall_entities[dest].task-> | ||
- | ``` | ||
- | |||
- | ### 如何调试协议栈任务 ### | ||
- | |||
- | 因为协议栈任务分布在另外一个工程,所以没有向原来那样直接加断点调试。经过前面的学习,我们已经了解协议栈是如何作为一个任务在应用工程中启动的,找到协议任务入口地址就是调试协议栈任务的关键。 | ||
- | 在调试协议栈任务之前,建议将分别设置协议栈和应用工程优化等级 Project -> Opitons -> C/C++ Compiler Optimizations -> Level -> None 为无,这对正常调试协议栈任务至关重要。 | ||
- | |||
- | ![](http:// | ||
- | |||
- | `FlashROM` 编译选项的协议栈任务入口地址在 `iar_boundary.xcl` 中给出,对于 `FlashROM_StackLibrary` 编译选项,可以通过编译生成的 *.map 文件查找`startup_entry` 符号从而找到入口地址。 | ||
- | |||
- | ![](http:// | ||
- | ![](http:// | ||
- | |||
- | 拿到协议栈任务的入口地址后,就可以在汇编窗口 View -> Disassembly 直接输入该地址,然后加上断点,等待协议栈任务创建后运行,从而跳转到协议栈工程进行调试。 | ||
- | |||
- | ![](http:// | ||
- | |||
- | 如果不知道协议栈任务的入口地址,则直接通过创建协议栈任务的任务实体入口,跳转到 `ICall_taskEntry` -> `entryfn` ,然后按** F11 **进入协议栈任务调试。 | ||
- | |||
- | ![](http:// | ||
- | |||
- | > | ||
- | |||
- | |||
- | ## Simple Peripheral Task ## | ||
- | |||
- | 简单外设任务是系统中最低优先级的应用程序任务,该任务的代码是在 Application IDE 文件夹中的 simple_peripheral 文件夹 下的`simple_peripheral.c` 中。 | ||
- | |||
- | ### 应用程序初始化功能 ### | ||
- | |||
- | <a href=" | ||
- | |||
- | simple_peripheral 任务函数伪代码 | ||
- | |||
- | ```C | ||
- | static | ||
- | // | ||
- | SimpleBLEPeripheral_init (); | ||
- | |||
- | // Application main loop | ||
- | for (;;) { | ||
- | |||
- | } | ||
- | } | ||
- | ``` | ||
- | |||
- | 初始化函数( simple_peripheral_init )为任务配置了几个服务,并设置了几个硬件和软件配置设置和参数。 | ||
- | 以下列表包含一些常见示例: | ||
- | |||
- | * 初始化 GATT 客户端 | ||
- | * 设置各种配置文件的服务读写等回调函数 | ||
- | * 设置 GAPRole | ||
- | * 建立 Bond 管理器 | ||
- | * 初始化 GAP 层 | ||
- | * 配置 LCD 或 SPI 等硬件模块。 | ||
- | |||
- | > | ||
- | |||
- | |||
- | ### 任务功能中的事件处理 ### | ||
- | |||
- | 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, | ||
- | 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(& | ||
- | (void **)& | ||
- | { | ||
- | 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-> | ||
- | { | ||
- | if (pEvt-> | ||
- | { | ||
- | // 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 | ||
- | } | ||
- | ``` | ||
- | |||
- | > | ||
- | |||
- | 当为一个事件选择一个事件值的时候,该值对于给定的任务必须是唯一的,并且必须是 2 的幂(只有 1 bit 被设置为 1 )。这样做的原因是 `pEvt-> | ||
- | |||
- | 清单49. 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/ | ||
- | |||
- | 正如以下情形: | ||
- | |||
- | * 协议栈成功收到发送的无线数据 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 | ||
- | |||
- | // 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` 变量中来处理所请求的协议栈事件。 | ||
- | |||
- | > | ||
- | |||
- | |||
- | 下面的代码片段显示了 simple_peripheral 如何请求协议栈事件标志。 | ||
- | |||
- | 连接间隔结束时请求通知的应用程序: | ||
- | |||
- | ```C | ||
- | // Application specific event ID for HCI Connection Event End Events | ||
- | #define SBP_HCI_CONN_EVT_END_EVT | ||
- | |||
- | static uint8_t SimpleBLEPeripheral_processGATTMsg(gattMsgEvent_t *pMsg) | ||
- | { | ||
- | // See if GATT server was unable to transmit an ATT response | ||
- | if (pMsg-> | ||
- | { | ||
- | // No HCI buffer was available. Let's try to retransmit the response | ||
- | // on the next connection event. | ||
- | if (HCI_EXT_ConnEventNoticeCmd(pMsg-> | ||
- | | ||
- | { | ||
- | // 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 | ||
- | |||
- | 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, | ||
- | ICALL_TIMEOUT_FOREVER); | ||
- | |||
- | if (events) | ||
- | { | ||
- | ICall_EntityID dest; | ||
- | ICall_ServiceEnum src; | ||
- | ICall_HciExtEvt *pMsg = NULL; | ||
- | |||
- | if (ICall_fetchServiceMsg(& | ||
- | (void **)& | ||
- | { | ||
- | 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-> | ||
- | { | ||
- | if (pEvt-> | ||
- | { | ||
- | // 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 状态变化的。 | ||
- | |||
- | > | ||
- | |||
- | simple_peripheral状态变化回调。 | ||
- | |||
- | ```C | ||
- | static | ||
- | SimpleBLEPeripheral_enqueueMsg (SBP_STATE_CHANGE_EVT , newState ); | ||
- | } | ||
- | ``` | ||
- | |||
- | 上面的代码片段是通过 `SimpleBLEPeripheral_gapRoleCBs `和 `GAPRole_StartDevice()` 回调的,回调只是在队列中放置一个消息来通知应用程序唤醒。一旦回调返回其父任务进入休眠状态,应用程序唤醒从消息队列取出消息处理同时调用以下代码片段。 | ||
- | |||
- | simple_peripheral 任务环境中的状态变化事件处理。 | ||
- | |||
- | ```C | ||
- | static | ||
- | // ... | ||
- | } | ||
- | ``` | ||
- | |||
- | ## 加入我们 ## | ||
- | |||
- | 文章所有代码、工具、文档开源。加入我们[**QQ群 591679055**](http:// | ||
- | <div> | ||
- | <p align=" | ||
- | <a target=" | ||
- | © Copyright 2017, 成都乐控畅联科技有限公司. | ||
- | </p> | ||
- | </ | ||
- | </ |