了解硬件和更多资料可点击:点击了解
新建一个物联网行业交流学习QQ群,感兴趣可加:928840648
=====CUT=====
*** 本章学习目的 ***
1)理解Z-Stack3.0系统抽象层(OSAL)。
2)掌握OSAL的使用。
4.1 OSAL的运行过程
OSAL也就是系统抽象层,其实并不是真正意义上的操作系统,不过实现了协议栈(ZStack)运行所必需的任务调度功能、内存管理、中断管理等基本功能。为了方便学习,我们将工程进行的裁剪,去除文档和附件,只留下协议栈的组件和工程文件:

本节课我们使用的和上节课一样的工程进行讲解(不同工程只是在应用层不一样,工程的结构是一样的):

打开工程:

* 我们先进入ZMain.c这个文件,并且找到main入口函数:
入口函数main的工作有两个:初始化、系统轮询(进行任务调度)。
- int main( void )
- {
- // Turn off interrupts
- osal_int_disable( INTS_ALL ); // 关闭所有中断
-
- // Initialization for board related stuff such as LEDs
- HAL_BOARD_INIT(); // 初始化板载资源,比如PA、时钟源等
-
- // Make sure supply voltage is high enough to run
- zmain_vdd_check(); // 检测供电电压是否可以支撑芯片正常运行
-
- // Initialize board I/O
- InitBoard( OB_COLD ); // 初始化板载I/O,比如按键配置为输入
-
- // Initialze HAL drivers
- HalDriverInit(); // 初始化硬件适配层,比如串口、显示器等
-
- // Initialize NV System
- osal_nv_init( NULL ); // 初始化NV(芯片内部FLASH的一块空间)
-
- // Initialize the MAC
- ZMacInit(); // 初始化MAC层(数据链路层)
-
- // Determine the extended address
- zmain_ext_addr(); // 确定芯片的物理地址
-
- #if defined ZCL_KEY_ESTABLISH
- // Initialize the Certicom certificate information.
- zmain_cert_init(); // 初始化认证信息
- #endif
-
- // Initialize basic NV items
- zgInit(); // 初始化存储在NV中的协议栈全局信息,如网络启动方式等
-
- #ifndef NONWK
- // Since the AF isn't a task, call it's initialization routine
- afInit(); // 初始化AF(射频)
- #endif
-
- // Initialize the operating system
- osal_init_system(); // 初始化OSAL(操作系统抽象层)
-
- // Allow interrupts
- osal_int_enable( INTS_ALL ); // 使能所有中断
-
- // Final board initialization
- InitBoard( OB_READY ); // 初始化板载IO资源,比如按键
-
- // Display information about this device
- zmain_dev_info(); // 在显示器上显示设备物理地址
-
- /* Display the device info on the LCD */
- #ifdef LCD_SUPPORTED
- zmain_lcd_init(); // 在显示器上显示设备信息,比如制造商等
- #endif
-
-
-
- #ifdef WDT_IN_PM1
- /* If WDT is used, this is a good place to enable it. */
- WatchDogEnable( WDTIMX ); // 启动看门狗功能
- #endif
-
- /* 进入系统轮询 */
- osal_start_system(); // No Return from here
-
-
- return 0; // Shouldn't get here.
- } // main()
重点的两个函数:
// Initialize the operating system
osal_init_system(); // 初始化OSAL(操作系统抽象层)
/* 进入系统轮询 */
osal_start_system(); // No Return from here
我们在“2. 操作系统的调度原理”中讲到了系统轮询,轮询的是任务池;这两个函数其实做的事情原理是一样的。osal_init_system()初始化了任务池,osal_start_system()基于调度周期在任务池中进行任务调度!用IAR看代码可在函数名上单击右键—— go to definition of…,便可以进入函数。
* osal_init_system():
- uint8 osal_init_system( void )
- {
- #if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS
- // Initialize the Memory Allocation System
- osal_mem_init(); // 初始化内存堆栈,用于动态内存申请、释放
- #endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */
-
- // Initialize the message queue
- osal_qHead = NULL; // 初始化消息队列
-
- // Initialize the timers
- osalTimerInit(); // 初始化系统时钟,系统轮询周期的基础
-
- // Initialize the Power Management System
- osal_pwrmgr_init(); // 初始化电源管理,主要用于低功耗
-
- #ifdef USE_ICALL
- /* Prepare memory space for service enrollment */
- osal_prepare_svc_enroll();
- #endif /* USE_ICALL */
-
- // Initialize the system tasks.
- osalInitTasks(); // 初始化任务池
-
- #if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS
- // Setup efficient search for the first free block of heap.
- osal_mem_kick();
- #endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */
-
- #ifdef USE_ICALL
- // Initialize variables used to track timing and provide OSAL timer service
- osal_last_timestamp = (uint_least32_t) ICall_getTicks();
- osal_tickperiod = (uint_least32_t) ICall_getTickPeriod();
- osal_max_msecs = (uint_least32_t) ICall_getMaxMSecs();
-
- /* Reduce ceiling considering potential latency */
- osal_max_msecs -= 2;
- #endif /* USE_ICALL */
-
- return ( SUCCESS );
- }
重要的函数: osalInitTasks(); // 初始化任务池
我们知道ZStack框架上是分层的,比如HAL(硬件适配层)、OSAL(系统抽象层)、MAC(数据链路层)、MT(监视器)、APP(应用层)等等;这一点很重要,因为ZStack框架设计上每一层都是任务池中的任务,而且任务是具有优先级的;系统在轮询进行任务调度时,会扫描每一层,如果发现有任务并且任务已经到期就会被处理!
* osalInitTasks():
- void osalInitTasks( void )
- {
- uint8 taskID = 0;
-
- /* 申请任务池内存空间并进行初始化 */
- tasksEvents=(uint16 *)osal_mem_alloc(sizeof(uint16)*tasksCnt);
- osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
-
- /* 下面是初始化各个层的任务,有些名称不认识是因为和
- ZigBee协议有关,我们暂且忽略,后面篇章会讲解到!
- */
- macTaskInit( taskID++ ); // 初始化MAC(数据链路层)任务
- nwk_init( taskID++ ); // 初始化网络层任务
- #if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)
- gp_Init( taskID++ ); // 初始化GP层任务
- #endif
- Hal_Init( taskID++ ); // 初始化HAL(硬件适配层)任务
- #if defined( MT_TASK )
- MT_TaskInit( taskID++ ); // 初始化MT(监视器)任务
- #endif
- APS_Init( taskID++ ); // 初始化APS层任务
- #if defined ( ZIGBEE_FRAGMENTATION )
- APSF_Init( taskID++ ); // 初始化APSF层任务
- #endif
- ZDApp_Init( taskID++ ); // 初始化ZDApp层任务
- #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
- ZDNwkMgr_Init( taskID++ ); // 初始化ZDNwkMgr层任务
- #endif
- // Added to include TouchLink functionality
- #if defined ( INTER_PAN )
- StubAPS_Init( taskID++ ); // 初始化StubAPS层任务
- #endif
- // Added to include TouchLink initiator functionality
- #if defined( BDB_TL_INITIATOR )
- touchLinkInitiator_Init( taskID++ ); // 初始化touchLink任务
- #endif
- // Added to include TouchLink target functionality
- #if defined ( BDB_TL_TARGET )
- touchLinkTarget_Init( taskID++ ); // 初始化touchLink任务
- #endif
- zcl_Init( taskID++ ); // 初始化ZCL任务
- bdb_Init( taskID++ ); // 初始化BDB任务
-
- zclSampleSw_Init( taskID++ ); // 初始化应用层任务
-
- #if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
- zclOTA_Init( taskID ); // 初始化OTA层任务
- #endif
- }
taskID其实就是任务的标识符,系统轮询也是根据taskID来的,taskID越小表示的是该任务的优先级越高(因为系统更早的轮询到该任务);实际上用户的开发是集中在应用层上面的,比如这里的zclSampleSw_Init函数初始化的就是应用层的任务,而且是针对智能插座的应用,大家会发现除了不同应用的应用层初始化函数名称不一样之外,其他都是一样的。接下来我们将进入到系统轮询函数:
* osal_start_system() -> osal_run_system():
- void osal_run_system( void )
- {
- uint8 idx = 0;
-
- /* 更新时间,并整理出到期的任务,系统的时钟周期是:320US */
- osalTimeUpdate();
- Hal_ProcessPoll(); // 硬件适配层中断查询
-
- do {
- if (tasksEvents[idx]) // 查看是否有任务需要处理
- {
- break;
- }
- } while (++idx < tasksCnt); // 轮询整个任务池
-
- if (idx < tasksCnt) // 找到需要处理的任务
- {
- uint16 events;
- halIntState_t intState;
- HAL_ENTER_CRITICAL_SECTION(intState); // 关中断
- events = tasksEvents[idx]; // 任务需要处理的所有事件
- tasksEvents[idx] = 0; // Clear the Events for this task.
- HAL_EXIT_CRITICAL_SECTION(intState); // 恢复中断
-
- activeTaskID = idx;
- events = (tasksArr[idx])( idx, events ); // 处理任务中的事件
- activeTaskID = TASK_NO_TASK;
-
- HAL_ENTER_CRITICAL_SECTION(intState); // 关中断
- tasksEvents[idx] |= events; // 保存还没被处理的事件到任务中
- HAL_EXIT_CRITICAL_SECTION(intState); // 恢复中断
- }
- #if defined( POWER_SAVING ) && !defined(USE_ICALL)
- else// Complete pass through all task events with no activity? {
- osal_pwrmgr_powerconserve(); // 没有任务需要处理则进入低功耗
- }
- #endif
-
- /* Yield in case cooperative scheduling is being used. */
- #if defined (configUSE_PREEMPTION)&&(configUSE_PREEMPTION == 0) {
- osal_task_yield();
- }
- #endif
注:源程序不止上面这么多的,为了简化程序方便理解,我们删除了那些没有使用到的代码!!!“处理任务中的事件”会在下一节课讲解到!
系统轮询是轮询每一层(任务),但是任务是由多个事件组成的,也就是说轮询到需要处理的任务时,是需要处理该任务相应的已经到时间的事件,而有些事件还没到时间所以不会被处理,等待下次时间到了再处理!
这里需要重新提一下:ZStack每一层的任务实际上是可以包含多个事件的,比如我们应用层任务可以同时包含“开灯1”和“关灯2”、“获取温湿度”等事件,只不过不一定在同一时间被执行,比如“开灯1”执行后再过3秒才会执行“关灯2”这个动作!
4.2 ZStack应用层
我们“系统篇”的程序大部分是在应用层进行讲解的,应用层:

我们首先进入到OSAL_SampleSw.c这个文件中,这个文件中只有一个函数:osalInitTasks(),这个函数在上一节课已经讲过,功能是初始化任务池;这个文件和OSAL关系密切,里面有一个数组:
- const pTaskEventHandlerFn tasksArr[] = {
- macEventLoop,
- nwk_event_loop,
- #if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)
- gp_event_loop,
- #endif
- Hal_ProcessEvent,
- #if defined( MT_TASK )
- MT_ProcessEvent,
- #endif
- APS_event_loop,
- #if defined ( ZIGBEE_FRAGMENTATION )
- APSF_ProcessEvent,
- #endif
- ZDApp_event_loop,
- #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
- ZDNwkMgr_event_loop,
- #endif
- //Added to include TouchLink functionality
- #if defined ( INTER_PAN )
- StubAPS_ProcessEvent,
- #endif
- // Added to include TouchLink initiator functionality
- #if defined ( BDB_TL_INITIATOR )
- touchLinkInitiator_event_loop,
- #endif
- // Added to include TouchLink target functionality
- #if defined ( BDB_TL_TARGET )
- touchLinkTarget_event_loop,
- #endif
- zcl_event_loop,
- bdb_event_loop,
- zclSampleSw_event_loop,
- #if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)
- zclOTA_event_loop
- #endif
- };
细心的同学可以发现,这里面的程序和初始化任务池函数osalInitTasks()的内容是对应的,那么这个数组有什么用呢?数组中的成员名称都带有“event”这个关键字!其实这个数组中存放的是函数,是每一层任务的事件处理函数,我们前面讲过,任务包含多个事件,事件的处理函数就在这里了!!
比如应用层任务的事件处理函数是:zclSampleSw_event_loop
接下来我们进入应用层最主要的文件中:

这个文件中我们可以找到两个函数(240行和303行):


函数zclSampleSw_Init()是应用层(任务)的初始化函数,我们先前讲过;而另一个函数zclSampleSw_event_loop()是应用层的事件处理函数,也就是说应用层有事件需要处理时,就会来到这个函数。到这里我们就将OSAL和应用层的关系理清了!
事件的编码方式:
我们可以看到应用层的事件处理函数(其它层的处理函数的参数是一样的),uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events ),其中函数参数 uint16 events说明事件是16位的变量所表示的,那是不是最多有65536个事件呢?答案是:“NO!”;这和事件的编码方式密切相关!
ZStack的事件编码方式采用“独热码(one-hot code)”,“独热码”直观来说就是有多少个状态就有多少比特位,比如uint16有16个比特位可以代表16种状态。另外,ZStack事件分为“系统事件”和“用户事件”两种,uint16的最高位为1时表示“系统事件”,为0表示“用户事件”,那么剩下的15位就是15种事件了;总结一下就是:ZStack每一层最多可以有15个用户事件!
uint16 events最高位为0才表示用户事件,所以用户事件有如下15种:

例:我们定义用户事件可以这样定义(在zcl_samplesw.h中进行定义):
#define USER_TEST_EVT 0x0001
下节课我们会讲如何设置、运行一个事件!
4.3 ZStack事件的应用
* 定义用户事件:
我们以应用层为例,在zcl_samplesw.h中定义一个自己的用户事件:

* 处理用户事件(在samplesw.c的函数zclSampleSw_event_loop中):
- uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events )
- {
- afIncomingMSGPacket_t *MSGpkt;
- (void)task_id; // Intentionally unreferenced parameter
-
- /* SYS_EVENT_MSG:0x8000表示系统事件,也就是说检测uint16最高位 */
- if ( events & SYS_EVENT_MSG )
- {
- ......... // 系统事件暂且不讲解
-
- /* 消除已经处理的事件然后返回未处理的事件! */
- return (events ^ SYS_EVENT_MSG);
- }
-
- #if ZG_BUILD_ENDDEVICE_TYPE
- if ( events & SAMPLEAPP_END_DEVICE_REJOIN_EVT ) // 用户事件
- {
- bdb_ZedAttemptRecoverNwk();
- return ( events ^ SAMPLEAPP_END_DEVICE_REJOIN_EVT );
- }
- #endif
-
- if ( events & SAMPLEAPP_LCD_AUTO_UPDATE_EVT ) // 用户事件
- {
- UI_UpdateLcd();
- return ( events ^ SAMPLEAPP_LCD_AUTO_UPDATE_EVT );
- }
-
- if ( events & SAMPLEAPP_KEY_AUTO_REPEAT_EVT ) // 用户事件
- {
- UI_MainStateMachine(UI_KEY_AUTO_PRESSED);
- return ( events ^ SAMPLEAPP_KEY_AUTO_REPEAT_EVT );
- }
-
- // Test Event
- if ( events & SAMPLEAPP_TEST_EVT ) // 用户事件,自定义!
- {
- printf("Hello World!\r\n");
-
- return ( events ^ SAMPLEAPP_TEST_EVT );
- }
-
- // Discard unknown events
- return 0;
- }
事件首先进行检测,也就是events & ....,其实是通过检测对应的比特位是否存在,如果存在表示有该事件,进行处理,处理完成后必须消除该事件所对应的比特位events ^ ....,然后返回其他未处理的事件。我们程序很简单,只是通过printf("Hello World!\r\n");打印出信息!
* 设置用户事件:
设置用户事件是有专门的API的,这个API是由OSAL所提供,因此首先我们要理解OSAL有哪些文件,分别有什么功能;OSAL除了做系统轮询进行任务事件调度外,也提供比如内存管理、Flash管理、电源管理、系统定时器服务等等这些基本功能。
展开OSAL内容如下:

设置用户事件的API在OSAL_Timers.h中:
uint8 osal_start_timerEx(uint8 task_id,uint16 event_id,uint32 timeout_value);
这个函数有三个参数:
task_id:任务ID,也就是具体那一层(任务)的标识符。
event_id:事件ID,也就是任务中的哪一个事件。
timeout_value:多少毫秒后才处理这个事件。
如果我们希望3秒后处理我们自定义的事件,可以这样调用API,调用的位置在应用层初始化函数(zclSampleSw_Init())的最后位置:
osal_start_timerEx(zclSampleSw_TaskID,SAMPLEAPP_TEST_EVT, 3000);
其中zclSampleSw_TaskID是一个全局变量,保存应用层的任务ID,保存的过程是在应用层初始化函数一开始:

最终设置用户事件的代码如下:

** 调试仿真
点击”Make”对工程进行编译:

然后通过仿真器连接开发板和电脑的USB口,然后将程序烧录到开发板中并进入仿真中,调出“Terminal I/O”窗口,最后“全速运行程序”:

可以看到,3秒后系统轮询发现应用层有任务需要处理,所以调度相应的任务处理函数(zclSampleSw_event_loop),任务处理函数中找到需要处理的事件进行处理(打印出“Hello World!”)。
本节课的内容虽然有点多,但却非常重要,是我们后续课程的基础,希望大家能够掌握,为后面课程打下良好的基础!!!
了解硬件和更多资料可点击:点击了解
新建一个物联网行业交流学习QQ群,感兴趣可加:928840648