协议栈工作原理介绍
CC2540集成了增强型的8051内核,TI为BLE协议栈搭建了一个简单的操作系统,即一种任务轮询机制。帮你做好了底层和蓝牙协议深层的内容,将复杂部分屏蔽掉。让用户通过API函数就可以轻易用蓝牙4.0,是开发起来更加方便,开发周期也可以相应缩短。
1.1.1工程文件介绍
安装完BLE协议栈之后,会在安装目录下看到以下文件结构:
图 3.2BLE栈目录
可看到Projects文件夹里面有很多工程,我们主要介绍SimpleBLECentral和SimpleBLEPeripheral。
ble文件夹中有很多工程文件,有些是具体的应用,例如
BloodPressure、GlucoseCollector、GlucoseSensor、HeartRate、HIDEmuKbd等都为传感器的实际应用,有相应标准的Profile(即通用的协议)。
其中还有4中角色:SimpleBLEBroadcaster、SimpleBLECentral、SimpleBLEObserver、SimpleBLEPeripheral。
他们都有自己的特点。
?Broadcaster广播员——非连接性的信号装置
?Observer观察者——扫描得到,但不能链接
?Peripheral从机——可链接,在单个链路层链接中作为从机?Central主机——扫描设备并发起链接,在单链路层或多链路层
中作为主机。
最后的BTool文件夹为BLE设备PC端的使用工具。
1.1.2OSAL介绍
协议栈是一个小操作系统。大家不要听到是操作系统就感觉到很复杂。回想
我们当初学习51单片机时候是不是会用到定时器的功能?嗯,我们会利用定时器计时,令LED一秒改变一次状态。好,现在进一步,我们利用同一个定时器计时,令LED1一秒闪烁一次,LED2二秒闪烁一次。这样就有2个任务了。再进一步…有n个LED,就有n个任务执行了。协议栈的最终工作原理也一样。从它工作开始,定时器周而复始地计时,有发送、接收…等任务要执行时就执行。这个方式称为任务轮询。
图 3.3任务轮询
现在我们直接打开协议栈,直接拿他们的东西来解剖!我们打开协议栈文件夹Texas Instruments\BLE-CC254x-1.2.1\Projects
\ble\SimpleBLEPeripheral\CC2540DB里面的工程文件SampleApp.eww。
图 3.4工程文件
打开后在IAR左边可看到左边的工程目录,我们暂时只需要关注App文件夹。
如图 3.5所示:
图 3.5工作空间目录
任何程序都在main函数开始运行,BLE也不例外。打开SimpleBLEPeripheral_Main.c文件,找到int main(void)函数。我们大概浏览一下main函数代码:
图 3.6协议栈主函数
1./**************************************************************
2.*@fn main
3.*@brief Start of application.
4.*@param none
5.*@return none
6.**************************************************************/
7.int main(void)
8.{
9./*Initialize hardware*/
10.HAL_BOARD_INIT();//初始化系统时钟
11.
12.//Initialize board I/O
13.InitBoard(OB_COLD);//初始化I/O,LED、Timer等
14.
15./*Initialze the HAL driver*/
16.HalDriverInit();//初始化芯片各硬件模块
17.
18./*Initialize NV system*/
19.osal_snv_init();//初始化Flash存储器
20.
21./*Initialize LL*/
22.
23./*Initialize the operating system*/
24.osal_init_system();//初始化操作系统
25.
26./*Enable interrupts*/
27.HAL_ENABLE_INTERRUPTS();//使能全部中断
28.//Final board initialization
29.InitBoard(OB_READY);//初始化按键
30.
31.#if defined(POWER_SAVING)
32.osal_pwrmgr_device(PWRMGR_BATTERY);//开启低功耗
33.#endif
34.
35./*Start OSAL*/
36.osal_start_system();
37.//No Return from here执行操作系统,进去后不会返回
38.
39.return0;
40.}
我们大概看了上面的代码后,可能感觉很多函数不认识。没关系,代码很有条理性,开始先执行初始化工作。包括硬件、GATT、GAP层、任务等的初始化。然后执行osal_start_system();操作系统。进去后可不会回来了。在这里,我
们重点了解2个函数:
初始化操作系统
osal_init_system();
运行操作系统
osal_start_system();
***怎么看?在函数名上单击右键——go to definition of…,便可以进入函数。***
1、我们先来看osal_init_system();系统初始化函数,进入函数。发现里面有
6个初始化函数,没事,我们需要做的是掐住咽喉。这里我们只关心
osalInitTasks();任务初始化函数。继续由该函数进入。
图 3.7osal_init_system()
终于到尽头了。这一下子代码更不熟悉了。不过我们可以发现,函数好像
能在taskID这个变量上找到一定的规律。请看下面程序注释。
图 3.8osalInitTasks()
1.void osalInitTasks(void)
2.{
3.uint8taskID=0;
4.//分配内存,返回指向缓冲区的指针
5.tasksEvents=(uint16*)osal_mem_alloc(sizeof(uint16)*
tasksCnt);
6.//设置所分配的内存空间单元值为0
7.osal_memset(tasksEvents,0,(sizeof(uint16)*tasksCnt));
8.
9.//任务优先级由高向低依次排列,高优先级对应taskID的值反而小
10./*LL Task*/
11.LL_Init(taskID++);
12.
13./*Hal Task*/
14.Hal_Init(taskID++);
15.
16./*HCI Task*/
17.HCI_Init(taskID++);
18.
19.#if defined(OSAL_CBTIMER_NUM_TASKS)
20./*Callback Timer Tasks*/
21.osal_CbTimerInit(taskID);
22.taskID+=OSAL_CBTIMER_NUM_TASKS;
23.#endif
24.
25./*L2CAP Task*/
26.L2CAP_Init(taskID++);
27.
28./*GAP Task*/
29.GAP_Init(taskID++);
30.
31./*GATT Task*/
32.GATT_Init(taskID++);
33.
34./*SM Task*/
35.SM_Init(taskID++);
36.
37./*Profiles*/
38.GAPRole_Init(taskID++);
39.GAPBondMgr_Init(taskID++);
40.
41.GATTServApp_Init(taskID++);
42.
43./*Application*/
44.SimpleBLEPeripheral_Init(taskID);//应用初始化,重点
45.}
第9-41行:BLE中各层的任务添加,越底层优先级越高
我们可以这样理解,函数对taskID个东西进行初始化,每初始化一个,taskID++。TI公司出品协议栈已完成的东西。这里先提前卖个关子SampleApp_Init(luste);很重要,也是我们应用协议栈例程的必需要函数,用户通常在这里初始化自己的东西。
至此,osal_init_system();大概了解完毕。
2、我们再来看第二个函数osal_start_system();运行操作系统。同样用go to
definition的方法进入该函数。再进入osal_run_system()
图 3.9osal_start_system()函数
图 3.10void osal_run_system(void)函数
1./****************************************************************
2.*@fn osal_run_system
3.*
4.*@brief
5.*
6.*This function will make one pass through the OSAL taskEvents
table
7.*and call the task_event_processor()function for the first task
that
8.*is found with at least one event pending.If there are no pending
9.*events(all tasks),this function puts the processor into Sleep.
10.*
11.*@param void
12.*
13.*@return none
14.*/
15.翻译:这个是任务系统轮询的主要函数。他会查找发生的事件然后调用相应
的事件执行函数。如果没有事件登记要发生,那么就进入睡眠模式。这个函数是永远不会返回的。
16.
17.void osal_run_system(void)
18.{
19.uint8idx=0;
20.
21.#ifndef HAL_BOARD_CC2538
22.//这里是在扫描哪个事件被触发了,然后置相应的标志位
23.osalTimeUpdate();
24.#endif
25.
26.Hal_ProcessPoll();
27.
28.do{
29.if(tasksEvents[idx])//Task is highest priority that is ready.
30.{
31.break;//得到待处理的最高优先级任务索引号idx
32.}
33.}while(++idx 34. 35.if(idx 36.{ 37.uint16events; 38.halIntState_t intState; 39. 40.HAL_ENTER_CRITICAL_SECTION(intState);//进入临界区,保护 41.events=tasksEvents[idx];//提取需要处理的任务中的事件 42.tasksEvents[idx]=0;//清除本次任务的事件 43.HAL_EXIT_CRITICAL_SECTION(intState);//退出临界区 44. 45.activeTaskID=idx; 46.events=(tasksArr[idx])(idx,events);//通过指针调用任务处理 函数,关键 47.activeTaskID=TASK_NO_TASK; 48. 49.HAL_ENTER_CRITICAL_SECTION(intState);//进入临界区 50.tasksEvents[idx]|=events;//Add back unprocessed events to the current task.保存未处理的事件 51.HAL_EXIT_CRITICAL_SECTION(intState);//退出临界区 52.} 53.#if defined(POWER_SAVING) 54.else//Complete pass through all task events with no activity? 55.{ 56.osal_pwrmgr_powerconserve();//Put the processor/system into sleep 57.} 58.#endif 59. 60./*Yield in case cooperative scheduling is being used.*/ 61.#if defined(configUSE_PREEMPTION)&&(configUSE_PREEMPTION==0) 62.{ 63.osal_task_yield(); 64.} 65.#endif 66.} 我们来关注一下events=tasksEvents[idx];进入tasksEvents[idx]数组定义,如下图,发现恰好在刚刚osalInitTasks(void)函数上面。而且taskID一一对应。这就是初始化与调用的关系。taskID把任务联系起来了 图 3.11tasksEvents[idx] 关于协议栈的介绍先到这里,其他会在以后的实例中结合程序来介绍,这样会更直观。大家可以根据需要再熟悉一下函数里面的内容。游一下这个代码的海洋。我们可以总结出一个协议栈简单的工作流程,如图 3.12所示。 图 3.12BLE栈的工作流程