理论教育 OSAL操作系统调度机制及其在物联网开发中的应用

OSAL操作系统调度机制及其在物联网开发中的应用

时间:2023-10-27 理论教育 版权反馈
【摘要】:ZStack运行依靠的一个操作系统将各部分功能连接在一起。图4-25 OSAL文件结构图OSAL.c:操作系统的核心,用于调度各任务执行。OSAL_Clock.c:操作系统时钟管理代码,目前还没有实际意义。OSAL_Memory.c:OSAL的简单内存管理代码。OSAL_Timer.c:OSAL超时定时器管理,对系统内需要作延时操作或者作时间范围设定,为了提高效率而编写的代码。接下来我们从OSAL.c开始一步步学习该调度系统。超时定时器的超时时间一旦到达指定任务的事件将被设置。

OSAL操作系统调度机制及其在物联网开发中的应用

ZStack运行依靠的一个操作系统将各部分功能连接在一起。我们看看操作系统位于哪一个部分及相关文件结构图如图4-25所示。

978-7-111-44976-8-Chapter04-49.jpg

图4-25 OSAL文件结构图

OSAL.c:操作系统的核心,用于调度各任务执行。

OSAL_Clock.c:操作系统时钟管理代码,目前还没有实际意义。用户可以暂时不用了解。

OSAL_Math.s51:用汇编写成的一些数学运算函数,目的是为了节省运算时间,能精确地估算出一次运算需要多少微秒。

OSAL_Memory.c:OSAL的简单内存管理代码。

OSAL_Nv.c:OSAL对非易失性存储器进行管理的代码。

OSAL_PwrMgr.c:OSAL简单电源管理代码。

OSAL_Timer.c:OSAL超时定时器管理,对系统内需要作延时操作或者作时间范围设定,为了提高效率而编写的代码。

接下来我们从OSAL.c开始一步步学习该调度系统。

1.重要API函数及功能介绍

(1)信息管理

存储器及消息管理函数用于合理按需分配存储空间以让RAM发挥最大利用率,以及将各信息在任务之间传递。

①uint8∗osal_msg_allocate(uint16 len)

功能:该函数功能用于分配一个缓存区,该缓存区以全局缓存形式存在,通过不同的任务或者功能函数调用分配和使用,再通过osal_msg_send()来将已经分配的缓存数据传递给指定的任务进行处理。在ZStack中预留的可分配的尺寸通过不同的宏功能定义后为4072字节或者2048字节。在OnBoard.h中可以找到如下定义字段:

978-7-111-44976-8-Chapter04-50.jpg

如果预留的缓存空间中无法分配缓存,msg_ptr将被返回一个空指针。不要和osal_mem_alloc搞混淆。osal_mem_alloc基本不需要用户调用。读者还需注意该函数不能一味调用来获取缓存而不释放缓存,必须与osal_msg_deallocate配对使用以释放被占用的缓存区。不少刚使用Zstack的读者会忘记这个关键点而造成代码运行不稳定,刚运行的时候似乎功能很完整,但使用一段时间就变得无法使用。

输入参数:len:需要分配的缓存大小尺寸。

输出参数:返回的数据类型是uint8指针,实际上它是由msg_ptr指针强制转换过来的。如果分配成功将得到一个指针,否则将得到空指针NULL。

978-7-111-44976-8-Chapter04-51.jpg

②uint8osal_msg_deallocate(uint8∗msg_ptr)

功能:该功能函数与osal_msg_allocate功能函数配对使用,用于删除已经定义的缓存区。该功能不是将缓存区清空,而是将指定缓存区释放至空闲状态。

输入参数:msg_ptr:需要删除的缓存的首指针。

返回参数:返回操作是否成功以及不成功的部分原因,具体各参数值代表什么含义可查看Comdef.h中Generic Status Return Values。

③uint8osal_msg_send(uint8 destination_task,uint8∗msg_ptr)

功能:该函数在任务调用时用于发送数据或者命令到别的任务,主要作用是数据传递和命令同步等。

该函数调用会引起某一个任务的SYS_EVENT_MSG位被置位。

输入参数:destination_task:接收数据的任务ID。msg_ptr:用于指向待传递的缓存区地址,该地址必须是调用osal_msg_allocate得到。

返回参数:返回参数是一个字节,代表了该操作是否成功以及失败的原因。

978-7-111-44976-8-Chapter04-52.jpg

④uint8∗osal_msg_receive(uint8 task_id)

功能:该函数用于接收信息,某任务用于接收别的任务发过来的数据,通常是SYS_E-VENT_MSG事件被触发后使用。

输入参数:task_id:信息传递的目的地,目的任务ID。

返回参数:该函数调用后如果接收到信息将得到传递来的信息首地址,没有收到信息将返回空指针NULL。

(2)任务同步

任务同步API用于各任务同步触发。重要API函数有以下两个。

①uint8osal_set_event(uint8 task_id,uint16 event_flag)

功能:该函数用于触发某一个任务的指定事件,用于同步和跳转。

输入参数:task_id:指定的任务ID值,事件所在的ID号。event_flag:待触发的事件标志位,事件标志位为2字节,一定要注意最高位0x8000被SYS_EVENT_MSG占用,其余各事件位均由处理任务自行定义。

返回参数:返回成功、失败。

978-7-111-44976-8-Chapter04-53.jpg

②uint8 osal_clear_event(uint8 task_id,uint16 event_flag)

功能:该函数用于清除已经触发但没有响应的事件,将标志位清除。

输入参数:task_id:任务ID,指定清除的事件值所在的任务ID。event_flag:待触发的事件标志位。

返回参数:返回成功、失败。

978-7-111-44976-8-Chapter04-54.jpg

(3)超时器

①uint8 osal_start_timerEx(uint8 taskID,uint16 event_id,uint16 timeout_value)

功能:开启一个超时定时器。超时定时器的超时时间一旦到达指定任务的事件将被设置。该延时时间一般是一个较短的时间,以ms为单位,长度为16bit,最大值为65545。

输入参数:taskID:超时时间到需要触发的事件所在的任务ID。event_id:超时时间到需要触发的事件标志位。timeout_value:设置期望的超时时间,最大值为65545。

返回参数:

978-7-111-44976-8-Chapter04-55.jpg

②uint8 osal_stop_timerEx(uint8 task_id,uint16 event_id)

功能:停止一个已经开启的超时定时器。

输入参数:task_id:指定待停止的超时定时器的任务ID号。event_id:指定待停止的超时器指向的事件标志值。

返回参数:

978-7-111-44976-8-Chapter04-56.jpg

(4)非易失性存储器管理

前面我们提到ZStack为了存储一些参数给用户分配了一部分Flash作为非易失性存储器,对这些存储器的操作就是靠操作系统的一些API函数操作的。非易失性存储器被分为了若干块,以不同的索引ID编号作为代替。各索引ID范围内存放的参数表如图4-26所示。

①uint8 osal_nv_item_init(uint16 id,uint16 len,void∗buf)

功能:用于对指定的索引ID参数空间初始化,并将相关参数传递到Flash操作块。因此该功能函数在读写之前都需要调用。

输入参数:Id:参数对应的索引ID编号。len:索引ID所代表的参数的长度,单位以字节计算。buf:参数读写时使用的缓存指针。

返回参数:

978-7-111-44976-8-Chapter04-57.jpg

978-7-111-44976-8-Chapter04-58.jpg

图4-26 非易失性存储器参数表

②uint8 osal_nv_read(uint16 id,uint16 ndx,uint16 len,void∗buf)

功能:读取指定ID,指定偏移量,指定长度的数据。

输入参数:Id:存放于非易失性存储器中的参数索引ID编号。ndx:在指定索引ID范围内的偏移量,以字节为单位。len:指定需要读取数据的长度。buf:用于存放读取指定索引ID参数区的缓存区指针,注意该缓存区一定要开辟足以存放len长度的大小。

返回参数:返回成功或者失败。

978-7-111-44976-8-Chapter04-59.jpg

③uint8 osal_nv_write(uint16 id,uint16 ndx,uint16 len,void∗buf)

功能:写指定ID,指定偏移量,指定长度的数据。

输入参数:Id:存放于非易失性存储器中的参数索引ID编号。ndx:在指定索引ID范围内的偏移量,以字节为单位。len:指定需要读取数据的长度。buf:待写入该索引ID参数区的缓存区指针。

返回参数:返回成功或者失败。

978-7-111-44976-8-Chapter04-60.jpg

④uint16 osal_nv_item_len(uint16 id)(www.daowen.com)

功能:取指定参数索引ID所代表的参数在非易失性存储器中的长度,单位字节。

输入参数:id:参数索引ID。

返回参数:如果该参数索引ID存在即返回该参数索引ID所代表的参数长度,否则返回为0。

(5)重要函数

①操作系统初始化函数

978-7-111-44976-8-Chapter04-61.jpg

978-7-111-44976-8-Chapter04-62.jpg

②系统运行函数

osal_run_system函数用于调度系统各任务运行。其执行流程图如图4-27所示。

978-7-111-44976-8-Chapter04-63.jpg

图4-27 OS调度系统执行流程图

操作系统核心运行的重要参数如下:

tasksArr:任务函数列表库。每增加一个任务必须将进程函数放入该函数库。

tasksEvents:事件指针,每一个任务会对应一个唯一的十六进制事件值变量,该十六进制事件变量中每一个位分别代表一个不同的事件。

tasksCnt:系统中任务统计,统计该参数代表系统内有多少个任务。

978-7-111-44976-8-Chapter04-64.jpg

978-7-111-44976-8-Chapter04-65.jpg

978-7-111-44976-8-Chapter04-66.jpg

2.OSAL增加任务

为了便于读者们理解操作系统任务的增加及调度核的运行机制,我们特意为读者将操作系统裁剪了出来,接下来我们将通过新建一个工程将操作系统核增加进该新工程,之后在裁剪后的操作系统中增加任务,深入的了解操作系统的调度机制及任务运行机制。

新建一个工程的步骤如下。

第一步:打开开发工具软件新建工程。

打开IAR EW8051-8.10工作环境,在菜单样选择Project->CreateNew Project,如图4-28所示选择Enpty project。单击图中“OK”按钮进入工程保存界面,选择合适的路径,输入工程名并保存,例如我们建立一个工程叫OStest。

第二步:设置当前工程处理器及编译链接工具。

通过在工程名上单击右键如图4-29所示选择Options或者从菜单栏选择Project->Options也可以用快捷键ALT+F7获得如图4-29所示工程属性配置窗口。

978-7-111-44976-8-Chapter04-67.jpg

图4-28 创建新工程界面

第三步:配置主要的编译和链接选项。

该工程属性配置窗口配置的内容比较多,有基本配置,编译器和选择,链接工具配置,调试工具配置等,主要是将各编译链接的命令行简化,以方便使用。

在新建一个工程时需要配置的关键参数。在左边Category窗口中选择General Options,此时我们会看到右边书签栏里有若干书签,选中Target书签将该书签内Device information中Device选择为CC2540F256,如图4-29所示。

更改link控制文件:选择正确的linker控制文件,如图4-30所示。默认链接控制文件存放于开发工具安装目录下如图4-30左侧目录中。

选择合适的调试工具。在工程属性对话框中选择Debugger选项,在Debugger所属的书签中选择Setup,从Driver中选择Texas Instruments。

第四步:将工程需要的源文件增加至工程中。

从菜单栏选择Project->AddFiles,或者直接通过在工程名中单击右键选中右键弹出菜单中的AddFiles来增加工程需要的文件。

第五步:保存工作平台。

选中菜单栏中File->Save Workspace,选择正确的工作平台存放目录并录入期望的工作平台名称。这里我们将工作平台名称取名OStest与工程名相同。

978-7-111-44976-8-Chapter04-68.jpg

图4-29 新建工程时需要配置的关键参数过程

978-7-111-44976-8-Chapter04-69.jpg

图4-30 更改link控制文件的目录与画面

978-7-111-44976-8-Chapter04-70.jpg

图4-31 主函数流程图

3.新建主函数及任务函数

(1)新建主函数

在该范例中使用的精简操作系统暂不涉及外围处理,因此我们对外围的初始化只需要对系统时钟作正确的初始化。接下来就是对操作系统的初始化,最后就是运行操作系统循环。

新建主函数流程如图4-31所示。

978-7-111-44976-8-Chapter04-71.jpg

978-7-111-44976-8-Chapter04-72.jpg

(2)新建任务函数

任务函数包括当前任务的初始化函数及当前任务的执行过程函数。为了测试任务的运行我们会使用到任务唯一ID变量OSTestTaskId,事件标志位OSTEST_EVENTS,一个用于表现任务运行的操作变量tempVal。

978-7-111-44976-8-Chapter04-73.jpg

在ZStack中新建任务初始化函数必然包括一个传递进来的单字节参数。该参数用于表示操作系统为当前任务分配的任务ID,必须将该ID保存备用,我们这里建立了一个全局变量OSTestTaskId将其备存;将来所有的事件触发必须要指定正确的任务ID才能被触发。

978-7-111-44976-8-Chapter04-74.jpg

编写任务过程函数必须遵循任务过程函数指针格式:

1)传入参数:单字节任务ID,双字节事件标志位。

2)传出参数:处理之后的事件标志位。

978-7-111-44976-8-Chapter04-75.jpg

4.任务函数添加至工程

前面介绍了任务调度中有几个重要的参数需要处理:

1)TaskCont系统中任务的数量,这里我们只有一个任务,因此设置TaskCont=1。

2)TaskEvents事件缓存指针首指针,我们直接给该指针定义为16位数组缓存区TaskEvents[1]。

3)将任务过程函数增加到任务进程函数指针列表TasksFn[]中

978-7-111-44976-8-Chapter04-76.jpg

4)将任务初始化函数增加至OS_IntTasks函数中

978-7-111-44976-8-Chapter04-77.jpg

系统精简操作系统范例运行流程图如图4-32所示。

按照以上我们学习的范例理解ZStack中操作系统及应用层任务。

ZStack中任务进程函数指针表tasksArr[]中的任务进程函数就比较多了,有MAC层、网络层、物理层以及ZDO层的相应任务进程指针。而且读者还可以根据需要通过定义某一些特定功能宏开关来增加或者减少一个任务指针。

978-7-111-44976-8-Chapter04-78.jpg

978-7-111-44976-8-Chapter04-79.jpg

978-7-111-44976-8-Chapter04-80.jpg

图4-32 系统运行流程图

ZStack中任务相对比较多,但在本例中应用层任务只有SampleApp一个。可以根据需要通过定义某一些特定功能宏开关来增加或者减少一个任务,对协议栈没有达到一个特别好的熟悉程度的用户建议暂时不要裁减任务。

978-7-111-44976-8-Chapter04-81.jpg

978-7-111-44976-8-Chapter04-82.jpg

在ZStack中完成了任务的初始化之后就依靠操作系统调度各任务以及不同的API函数来触发对应的事件,从而达到ZStack运行的目的。在本节中我们深入地理解了ZStack中操作系统的实际调度机制,知道程序是从哪里入口怎么运行到操作系统中,怎么将一个全新的任务添加到一个全新的工程中,了解了如何调用一个API函数去触发一个事件。这些就是学会如何使用ZStack的基础,轻松在任意一个ZStack的范例中增加一个自己的任务。

因为篇幅有限,在本章中只能对两种操作系统作一个简明扼要的介绍。如果要深入详细了解操作系统,建议读者参看其他参考书籍

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈