OSAL小记
2016-06-12 13:14
369 查看
摘要: zigee协议栈ZStack下的osal系统学习小记
事件 :OSAL的运行是依赖事件进行的。每个事件都有对应的task_id,一个task_id可以对应多个事件。这些事件相当于一个个小的子线程。这些事件什么时候运行是由用户编程实现的。一般的,每个事件会有特定的标志位,这个标志位由特定的操作触发,比如在时间性事件中,当到达特定时间时,其标志位便会被置为1。通过查询这些标志位,我们可以决定是否运行某些事件。
是将定时器4的计数器EA的值保存起来,并置EA的值为0,相当于禁止了定时器4的中断
是设置EA的值为给定值,这两个函数经常配合使用。
我们将会一眼看到ZMain并顺手打开ZMain.c文件
看到主函数
当程序开始执行,它就会通过循环
这里是应用层,而大部分的应用功能都写在了这里。
重新回到
tasksEvents[x][b]是一个16位的标志位集合,一个事件使用一个标志位,一个tasksEvents[x]下可以有16个事件,当到达事件的发生时间时,会将这个标志位置为1。在示例程序中,在SampleApp.c[/b]中自定义了一个名为SAMPLEAPP_SEND_PERIODIC_MSG_EVT的事件,它使用了第一位的标志位,因此其值是0x0001。另外与
我们暂且叫它计划表。计划表中的每个节点包含5个数据,第一个是指针,指向下一个节点,第二个是执行时间,第三个是事件标志位,第四个是端口,第六个是重载时间。通过以下函数
都可以将一个新的事件注册到计划表里面,不同的是后者为reloadTimeout赋了值,其值等于传进去的参数timeout_value,这个区别将在下文说到。
每次进入
第一个函数更新了系统的时间值,确切说是系统的秒数以及系统的毫秒数。第二个函数遍历了链表即计划表。一个个节点遍历,首先会判断当前节点的timeout是否小于elapsedMSec,大于执行
小于,则说明当前节点代表的事件到了执行时间了,根据节点的task_id和event_flag可以找到在tasksEvents数组下的该节点的事件标志位,并将其置为1。接下来判断reloadTimeout是否大于0,大于0便将这个值赋值给timeout。这样事件标志就可以在下次到期时被触发,reloadTimeout决定了这个事件是周期事件还是仅仅只发生一次。
回到
回调时的传参分别是task_id端口号和events该端口号下的事件触发标志。示例程序在SampleApp_ProcessEvent中,第一步先检查定义的系统事件是否被触发,假如没有,就判断SAMPLEAPP_SEND_PERIODIC_MSG_EVT,一个示例程序定义的事件是否被触发,触发了就执行发送信号的操作。无论是哪种事件,最后都需要手动清零标志位,这就是为什么我们会看到类似这种写法,之所以return是因为一次只处理一个事件
总结一下,在应用层,可以自定义事件,并把它注册到链表中去,然后可以在回调函数中判断事件是否发生并提供关于事件的处理方法。
0 预备
端口 :在OSAL中,为每个子程序分配了一个端口号task_id。事件 :OSAL的运行是依赖事件进行的。每个事件都有对应的task_id,一个task_id可以对应多个事件。这些事件相当于一个个小的子线程。这些事件什么时候运行是由用户编程实现的。一般的,每个事件会有特定的标志位,这个标志位由特定的操作触发,比如在时间性事件中,当到达特定时间时,其标志位便会被置为1。通过查询这些标志位,我们可以决定是否运行某些事件。
HAL_ENTER_CRITICAL_SECTION(x) HAL_EXIT_CRITICAL_SECTION(x)
是将定时器4的计数器EA的值保存起来,并置EA的值为0,相当于禁止了定时器4的中断
是设置EA的值为给定值,这两个函数经常配合使用。
1 Main函数
安装好的zigbee协议栈中有示例程序,路径在Texas Instruments\ZStack-CC2530-2.5.1a\Projects\zstack\Samples\SampleApp\CC2530DB中,打开路径下面的工程就可以在IAR中查看这个示例程序了。我们将会一眼看到ZMain并顺手打开ZMain.c文件
看到主函数
int main( void ),它在执行了一系列的初始化函数后,便会执行
osal_start_system(),双击这个函数然后右键跳转到定义,于是我们看到了熟悉的永无止尽的循环
void osal_start_system( void ) { #if !defined ( ZBIT ) && !defined ( UBIT ) for(;;) // Forever Loop #endif { osal_run_system(); } }
当程序开始执行,它就会通过循环
osal_run_system()不断的运行这个系统,而这个系统就是被称为OSAL的操作系统。于是我们的重点自然就落到了函数
osal_run_system()上,不过在此之前,我们先来看osal初始化时做了些什么。
2 初始化OSAL
回到之前的主函数,在很多初始化函数中会看到osal_init_system(),这个就是osal系统的初始化函数。从
osalInitTasks()进入,在这个函数的最后一行,就是
SampleApp_Init(),这是示例程序在应用层的初始化函数,我们也可以将自己想要做的初始化操作函数放到这里来。从
SampleApp_Init进入,就来到了SampleApp.c文件,这个文件位于
这里是应用层,而大部分的应用功能都写在了这里。
重新回到
osalInitTasks()可以看到函数下的每个初始化的操作函数都分配了一个taskID,这个taskID相当于应用程序的端口号。因为其是8位的,因此最多能有255个端口号,其中包含了一些特殊的端口号。此处还申请了一个tasksEvents数组,每个taskID将对应其中的一个tasksEvents[x]。
tasksEvents[x][b]是一个16位的标志位集合,一个事件使用一个标志位,一个tasksEvents[x]下可以有16个事件,当到达事件的发生时间时,会将这个标志位置为1。在示例程序中,在SampleApp.c[/b]中自定义了一个名为SAMPLEAPP_SEND_PERIODIC_MSG_EVT的事件,它使用了第一位的标志位,因此其值是0x0001。另外与
osalInitTasks()在同一个文件中,有一个函数指针数组tasksArr[],这个数组与端口号一一对应,其作用在下文可以看到。
3 OSAL的Timer链表
osal维护了一个链表,其节点的数据结构如下typedef struct { void *next; uint16 timeout; uint16 event_flag; uint8 task_id; uint16 reloadTimeout; } osalTimerRec_t;
我们暂且叫它计划表。计划表中的每个节点包含5个数据,第一个是指针,指向下一个节点,第二个是执行时间,第三个是事件标志位,第四个是端口,第六个是重载时间。通过以下函数
uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value ) uint8 osal_start_reload_timer( uint8 taskID, uint16 event_id, uint16 timeout_value )
都可以将一个新的事件注册到计划表里面,不同的是后者为reloadTimeout赋了值,其值等于传进去的参数timeout_value,这个区别将在下文说到。
每次进入
osal_run_system(),先执行更新时间的操作
osalTimeUpdate(),这个函数先读取定时器2的计数(定时器2是专为mac层提供定时服务的),经过计算后将经过的时间(从上一次更新时间到当前的值)的毫秒数赋值给elapsedMSec,单位是毫秒。并判断elapsedMSec是否有值,即超过1ms的话就执行两个更新操作,分别是
osalClockUpdate( elapsedMSec ); osalTimerUpdate( elapsedMSec );
第一个函数更新了系统的时间值,确切说是系统的秒数以及系统的毫秒数。第二个函数遍历了链表即计划表。一个个节点遍历,首先会判断当前节点的timeout是否小于elapsedMSec,大于执行
timeout = timeout - elapsedMSec,
小于,则说明当前节点代表的事件到了执行时间了,根据节点的task_id和event_flag可以找到在tasksEvents数组下的该节点的事件标志位,并将其置为1。接下来判断reloadTimeout是否大于0,大于0便将这个值赋值给timeout。这样事件标志就可以在下次到期时被触发,reloadTimeout决定了这个事件是周期事件还是仅仅只发生一次。
回到
osal_run_system(),在
osalTimeUpdate()之后,是
Hal_ProcessPoll(),这个函数主要是去查询硬件上是否触发了其他事件。
4 对事件进行处理
通过上文已经可以知道,当我们将一个事件注册到链表计划表里面时,便可以在特定时间将这个事件对应的事件标志置为1,于是通过查询这个标志就可以知道事件是否应该发生了。而所有的事件的标志位其实都在tasksEvents数组中,这么多的事件,系统每次却只处理一个事件,所以都得排着队来处理。系统检索tasksEvents的每个元素是否有值,有则说明这个端口对应的事件至少有一个被触发,然后从tasksArr数组中选择端口对应的函数指针进行调用。比如示例程序中应用层的回调函数指针是SampleApp_ProcessEvent,而函数的主体则是位于应用层SamleApp.c文件中uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
回调时的传参分别是task_id端口号和events该端口号下的事件触发标志。示例程序在SampleApp_ProcessEvent中,第一步先检查定义的系统事件是否被触发,假如没有,就判断SAMPLEAPP_SEND_PERIODIC_MSG_EVT,一个示例程序定义的事件是否被触发,触发了就执行发送信号的操作。无论是哪种事件,最后都需要手动清零标志位,这就是为什么我们会看到类似这种写法,之所以return是因为一次只处理一个事件
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
总结一下,在应用层,可以自定义事件,并把它注册到链表中去,然后可以在回调函数中判断事件是否发生并提供关于事件的处理方法。
相关文章推荐
- zigbee网蜂cc2530组播实验,基于SampleApp例程
- 关于CC2530存储器映射的讨论
- zigbee网络单播、广播和组播--简单笔记
- zigbee 设备网络启动(非自动)
- CC2541 BLE源码阅读知识积累之OSAL小结
- [转载]Z-stack 应用程序编程接口(API)-网络层
- Z-Stack 样例程序分析1
- Zigbee协议栈中文说明
- 无线龙 zigbee 加密传输
- zigbee z-stack 地址类型
- zigbee 广播 组播 单播
- Wireless LCD Monitor
- Unable to open file 'lnk51ew_cc2530b.xcl'的解决方案
- ZigBee学习笔记——ZStack的架构
- ZigBee IEEE 802.15.4 Summary
- 短距离无线传输技术分析
- BLE的最大竞争对手是ZigBee、Wi-Fi、Ant+以及一系列广泛的专有协议
- CC2530串口通讯2
- CC2530主要的控制寄存器
- 小米温湿度传感器协议分析后续