我们采用BLE-CC254x-1.3.2中的KeyFob工程展开分析.
我们都知道在C代码中,程序的入口都是main()函数,这个函数在KeyFob_Main.c中
打开文件,可以看到这个文件包含了一些必要的头文件和一个函数的申明,我们暂时不理会那个申明的函数,先看main都做了些什么工作:
通过代码我们可以看到,系统启动的过程,主要是做了一些初始化,如果开启了低功耗,则还需要开启低功耗管理。我们先不去理会初始化做了什么,但是我们知道main函数终启动了OSAL,所以我们只分析相关的两个函数osal_init_system()和osal_start_system().
我们进入osal_init_system()
我们从TI官方的注释就能大致知道每个函数的作用.我再简单介绍一下:
1044:初始化内存分配系统
1047:初始化消息队列
1050:初始化定时器
1053:初始化电源管理系统
1056:初始化系统任务,这个函数添加了所有的任务,我们在以后详细分析它.
1059: 设置有效的查找堆上的第一个空闲块
我们进入osal_start_system(),发现这里会循环调用osal_run_system()
osal_run_system()才是整个协议栈的核心,进入到osal_run_system()后发现他只有1102-1147这些行代码.这些代码就是整个协议栈运转的大脑,我们务必要把这里搞清楚.
我们发现代码里边有好多预编译宏,而这些宏我们都没有定义,所以对应的函数代码也没有执行,我们再次精简一下代码,
好了,我们就详细得分析下这段代码吧.
首先是一个do{}while()的组合,我们上来就执行do{}中得语句
我们先来解析下tasksEvents[idx],idx在1102行被赋值为0,那tasksEvents又是什么呢?我们右键点击变量然后go to definition或者按快捷键F12查看变量定义的位置
追到以后发现是个指针,那它何时被赋值呢 ,被赋值成什么呢??我们要用到高级搜索这个功能了,把它找出来.
我们选中以后,按下组合键ctrl+shift+f,然后点击find即可
我们发现有以下地方用到了这个指针
用到的地方不少,别害怕,照着我的步骤你你就会看见前方一片光明.
首先我们来看osalInitTasks()函数,在126行用malloc()分配了一片堆空间,后将这片空间的首地址赋值给tasksEvents.这里呢我们就可以将
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);这句话等价于你定义了一个数组uint16 tasksEvents[tasksCnt],tasksCnt是几呢?我一会告诉你.
127行将这片空间的数据全部清零,也就是将数组的全部元素清零.
这里我们先不去管这片内存空间中要存储什么数据,我们知道这里边全部是0;
再回到
do {
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break;
}
} while (++idx < tasksCnt);
刚开始idx得值是0, tasksEvents[idx]也就是0;那if (tasksEvents[idx])就不成立.随后会执行while()中得语句++idx < tasksCnt, 这里我们需要再去跟踪下tasksCnt这个变量,发现他原来是个常量:
问题又来了, tasksArr又是啥呢?我们继续跟踪
我们发现这是个数组,那数组中又存的是什么类型呢>?我们跟下pTaskEventHandlerFn,原来这是函数指针啊,那我们就清楚了tasksArr这个数组中全部存的是函数指针啊.至于这些函数指针是干什么得,又指向谁我们待会来分析.但我相信你已经知道了tasksCnt的值.
有点乱,我们现在先回忆下,刚才有几个重要的变量.
其中一个是存函数指针得数组tasksArr,这些函数指针具体干嘛我们一会分析.
还有一个是这个数组中函数指针的个数tasksCnt.
还有一个指针变量tasksEvents.我们知道这个函数指针指向了一片堆空间,并且空间中全是0;我们就把它当成一个数组,数组中有tasksCnt个成员,每个成员的大小是两个字节.再看do{}while()这个组合.
do {
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break;
}
} while (++idx < tasksCnt);
根据上边所说的,你应该能想到这个循环结束的时候idx的数值了吧.对的,他后是等于tasksCnt的,结束循环后就进入1136行得分支,这里就是系统在检测到没有事件发生的时候进入低功耗模式.
那什么时候才会进入1119行这个分支来处理发生的事件呢.我们再来认识一个新的函数osal_set_event();这个函数得功能就是标识有事件发生,我们来分析下代码.
904行判断传参task_id是否小于tasksCnt(你好还记得这个常量).907行关闭中断,进入临界区.908行有用到刚才那个函数指针tasksEvents了(其实也是数组首地址),那这个数组存这些变量有什么意义呢?
一句话告诉你,这个数组的每一个成员都代表一个任务,每个成员是两个字节就是16位,每一位都代表这个任务中得一个事件,那一个任务能有几个事件呢?怎么能表示事件发生还是没发生呢?聪明的你应该能想到是16个了.事件发生就将对应的位置一就好了.
我们只需要知道协议栈里会通过中断,函数调用等方式调osal_set_event()将系统事件置一.好了我们再回到下面的代码:
do {
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break;
}
} while (++idx < tasksCnt);
这时候tasksEvents[idx]随着idx得增加if (tasksEvents[idx])会成立了吧,这时候break出来idx的值刚好对应的是任务的编号.
终于能执行这段分支了,我们来分析下;
1122:进入临界区,关中断;
1123:将待处理的任务中所有的事件赋值给变量events
1124:将tasksEvents数组中该任务的事件全部清空
1125:退出临界区,开中断;
1127:将当前任务的标号复制给全局变量,它代表当前正在处理的任务的标号
1128:还记得tasksArr()存的是什么吗?是函数指针,他其实就是对应任务的处理函数,至于这个处理函数都做了什么?我们下次分析,现在大家只用知道你把 事件传进去,他会帮你处理,但每次只能处理一个事件,然后将处理完的事件对应的位清零随后将新的事件变量返回.再赋值给events
1129:将activeTaskID赋值为TASK_NO_TASK代表当前没有任务处理
1131: 进入临界区,关中断;
1132: 由于你刚才将事件变量清空了,你每次又只能处理一个事件,你总要把剩下没处理的变量告诉我吧.
1133: 退出临界区,开中断;
好了,到这里该停了,我们先来总结下:
一个任务中如何表示不同的事件呢?
用一个unsigned short类型的变量来记录不同的事件,unsigned short占两个字节,总共16位,每一位二进制表示一个事件.
任务对应的处理函数放在哪呢?
放在const pTaskEventHandlerFn tasksArr[];
如果知道某个任务发生了某个事件,怎么记录?
用这个函数 uint8 osal_set_event( uint8 task_id, uint16 event_flag )
下次我们会分析osalInitTasks()这个函数,还有任务和处理任务事件的函数是怎么联系起来的? 如何在协议栈添加一个自己的任务,处理任务里的事件?