1. 栈镜像文件:这个镜像包含BLE协议栈的底层从LL层到GAP和GATT层,大多数的协议栈的
代码都是以库的形式提供。
2. 应用镜像文件:这个镜像包含了相关的配置,应用代码,驱动,和Icall模块。 2.7.1标准工程任务层次
所有的工程都包含至少三个RTOS的任务。按照优先级的顺序排列,因此高任务序号对应着高优先级任务,对于SimpleBLEPeripheral工程来说,这些任务分别是:
5: BLE协议栈任务;
3: BAPRole任务(外设角色);
1:应用任务(SimpleBLEPeripheral); RTOS任务在3.3章中会介绍。BLE协议栈的接口在第五章中讲解,GapRole任务在5.2中进行介绍,应用任务在4.2.1中介绍。
3 RTOS 概述
TI-RTOS是操作在cc2640上的BLE工程的操作环境。TI-RTOS内核是SYS/BIOS的定制版,作为一个实时、抢占、多线程的操作系统操作同时结合同步和调度的工具(XDCTools)。SYS/BIOS内核管理四个层次的执行的线程:硬件中断(HWI)服务惯例,软件中断惯例, 任务和隐藏的空闲功能。
注意:TI-RTOS 内核和SYS/BIOS内核是可互换的。
这部分将描述着四个执行线程和各种贯穿于整个RTOS的消息和同步的结构体。
注意:在大多数案例中基本的RTOS功能都已经抽象在了util.c文件中,更高层的应用可以使用。底层的RTOS函数在SYS/BIOS模块部分描述。该文档中同时也定义了所有TI-RTOS中的包和模块。 3.1 RTOS 的配置
SYS/BIOS内核提供的安装包,可以通过修改RTOS配置文件来修改(比如,SimpleBLEPeripheral工程的appBLE.cfg文件)。 文档中没有说ccs中怎么修改配置
3.2 信号量
内核包提供了几个同步任务的模式,比如信号量。信号量CC2640软件中主要的同步资源。信号量用来调整一系列竞争任务到一个共享资源的通道,比如应用和BLE栈。信号量用于任务的同步和互斥。
信号量的功能如下图所示。信号量可以是计数的信号量或二项信号量。计数信号量可以被Semaphore_post()函数发送的信号持续的触发。这个实用性非常强,比如,当有一组用于任务间的资源。这些任务可能调用Semaphore_pend()来检查一个资源在使用前是否已经准备好了二项信号量只有两种状态:available(计数为1)和unavailable(计数为0)。可以用于在任务间分享单个资源。也可用于一个基本的信号机,这样信号就可以多次发送了。二向信号量不可以通过计数触发;他们只是简单的通过信号量是否被发送来触发。
3.2.1 信号量的初始化
下面的代码描述如何在RTOS中初始化一个信号量。一个这方面的例子就是
SimpleBLEPeripheral工程中的任务被Icall模块激活的时候:Icall_registerApp(),最终会调用Icall_primRegisterApp()。这些信号量用来调整任务的进程。4.2节中将会有更多的这方面的描述。
Semaphore_Handle Sem;
sem = Semaphore_create(0, NULL, NULL); 3.2.2 挂起一个信号量
Semaphore_pend()用来等待一个信号量。这是一个后台调用,允许其他的任务运行。超时参数允许一直等待直到超时,或者模糊的等待,或者一直等待。返回值用来指定信号量是否已经通知成功了。
Semaphore_pend(sem, timeout); 3.2.3 发送一个信号量
Semphore_post()用来发送一个信号量。如果一个任务正在等待信号量,这将任务从信号队列中移出并放到就绪队列。如果没有任务等待,Semphore_post()简单的增加信号量的计数
并返回。对于一个二向信号量,计数总是被设为1. Semaphore_post(sem); 3.3 任务
RTOS任务等同于一个独立的线程,在一个单个的C工程中的并行的执行函数。实际上,通过切换一个个任务来实现并发。
每个任务在运行中的任何时间点都处于五种运行模式中的一种: 运行:任务正在运行;
就绪:任务正在被调度执行; 阻塞:正在运行中的任务被挂起; 终止:任务被终止执行; 闲置:任务在闲置列表中。
永远只有一个(仅有一个)任务正在运行,即使是个空闲任务。当前的任务可以通过调用穿件任务模块的函数来从运行状态中阻塞起来,同时函数提供了其他的模式比如信号量。当前的任务也可以从运行状态中终止。在任何一个例子中,进程被切换至应经就绪的最高优先级的任务。
可以在SYS/BIOS接口中(package ti.sysbios.knl)查看任务模块的函数中查看更多的信息。 数字优先级被安排给任务,可能多个任务有同样的优先级。任务是严格的按照优先的顺序读取执行。同一优先级的任务按照先来先保存的原则调度。当前运行的任务的优先级永远不会低于已经就绪的任务的优先级。相反的,当前运行的任务会在任何时刻被某个存在的优先级更高的任务抢占和调用执行的。在SimpleBLEPeripheral应用中,BLE协议栈的任务被分配为最高优先级,应用任务则是最低优先级。
每个RTOS任务都有一个初始化函数,一个事件处理函数,和一个到多个回调函数。 3.3.1 创建一个任务 当一个任务被创建,它就会被提供一个用来保存当地变量的运行时间栈,为了下一次的函数调用。在单个工程中执行的所有的任务共享一个公共的全局变量,依据C函数定义的标准高的规则来调用。这一系类的内存就是所谓的任务的上下文。 下面是一个在SimpleBLEPeripheral工程创建应用任务的例子:
void SimpleBLEPeripheral_createTask(void) {
Task_Params taskParams; // Configure task
Task_Params_init(&taskParams); taskParams.stack = sbpTaskStack;
taskParams.stackSize = SBP_TASK_STACK_SIZE; taskParams.priority = SBP_TASK_PRIORITY;
Task_construct(&sbpTask, SimpleBLEPeripheral_taskFxn, &taskParams, NULL); }
任务的创建是在主函数main()中完成的,要先于SYS/BIOS调度进程被BIOS_start()函数启动。任务在调度进程开始后,按照被安排的优先级开始执行。 尽管要求使用存在的应用任务来满足应用指定的进程,但是如果需要其他的任务的话那么就要尊寻确定的指导方针。当添加一个额外的任务到应用工程中时,任务的优先级就必须在RTOS的优先级等级范围中安排一个优先级,定义在appBLe.cfg RTOS配置文件中:
/* Reduce number of Task priority levels to save RAM */ Task.numPriorities = 6;
另外新增任务的优先级不要大于等于BLE协议栈任务的优先级和相关支持的函数(见GapRole任务)。看2.7.1部分获取系统任务的层次。 最后,任务应该分配预先定义的最小的栈空间512 bytes。在最小程度,每个栈都必须足够大以备处理正常的子程序和一个任务抢占的上下文。上下文的抢占就是当一个优先级更高的准备就绪的中断进程到来是,正在执行的任务的上下文的保存。使用IDE中的TI-RTOS配置工具,任务可以通过分析来决定最高任务栈使用惯例。
注意:术语“created”用来描述一个任务的初始化。然而,正真的TI-ROTS的方法事构建任务。参考3.11.6部分获取创建RTOS实体的更多的细节。 3.3.2创建任务函数
如上所见,当一个任务被构建之后,一个任务函数的函数指针
(SimpleBLEPeripheral_taskFxn)就传递到了task_Construct函数。这是个当该函数第一次获得机会运行时RTOS就会云心更多函数。该函数的通常的拓扑结构如下:
在SimpleBLEPeripheral任务中,任务似乎在多数时间下都是处于阻塞状态等待一个信号量。一旦它的信号量从一个中断,回调函数或队列中发送出来,该任务就处于就绪状态,然后处理进入暂停前的数据和事件。4.2.1节中有更多的细节。 3.4 时钟
时钟实例是一种可以在确定次数的时钟脉冲后被调度的函数。时钟实例可以是单次的也可以是周期的。他们可以是在创建之后立即开始也可以配设置为在一定延时之后开始。它们也可以在任意时间停止。所有的时钟实例都是在一个软件中断的上下文中结束后执行的。 最小分辨率是在RTOS配置中设置的RTOS时钟脉冲周期:
/* 10 us tick period */ Clock.tickPeriod = 10;
每个脉冲,由RTC驱动,发送一个时钟软件中断,这个中断会把运行的时钟脉冲计数和每个时钟周期进行比较,然后决定想关联的函数是否运行。
对于高分变率的定时器来说,要求使用16位的硬件定时器通道或sensor Controller。 3.4.1 API(接口)
直接和使用RTOS时钟模块函数是可以的(参考时钟模块在SYS/BIOS API 0)。为了实用性,这里在util.c文件中提供了下面的函数:
Clock_Handle Util_constructClock (Clock_Struct *pClock, Clock_FuncPtr clockCB, uint32_t clockDuration, uint32_t clockPeriod, uint8_t startFlag,UArg arg) Description Parameters: Initialize a TIRTOS Clock instance. pClock –pointer to clock instance structure clockCB –function to be called upon clock expiration clockDuration –length of first expiration period. clockPeriod –length of subsequent expiration periods. If set to 0, clock is a one-shot clock. startFlag –TRUE to start immediately, FALSE to wait. If FALSE, Util_startClock() must be called later. arg –argument passed to callback funciton return void Util_startClock(Clock_Struct *pClock) Description Parameters: Start an (already constructed) clock. pClock –pointer to clock structure handle to the Clock instance bool Util_isActive(Clock_Struct *pClock) Description Parameters: Returns: Determine if a clock is currently running. pClock –pointer to clock structure TRUE: clock is running FALSE: clock is not running void Util_stopClock(Clock_Struct *pClock) Description: Parameters: stop a clock. pClock –pointer to clock structure 3.4.2 功能示例 下面的例子来自于SimpleBLEPeripheral工程中详细描述了一个时钟实例的创建和如何处理它的到期。
1 定义时钟处理函数来处理时钟到期的软件中断。 simpleBLEPeripheral.c:
//clock handler function
static void SimpleBLEPeripheral_clockHandler(UArg arg) {
// Store the event. events |= arg;
// Wake up the application. Semaphore_post(sem); }
2创建时钟实体
simpleBLEPeripheral.c
// Clock instances for internal periodic events. static Clock_Struct periodicClock;