3.1 OSAL的任务调度原理
本节课将以分析Sample Switch这个例程源代码的方式讲解OSAL的任务调度原理。其中会涉及到复杂的源代码,读者暂时只需要大致地了解整个任务调度过程就可以了。
OSAL简介
OSAL(Operating System Abstraction Layer,系统抽象层),可以通俗地理解为一个简化版的操作系统,为Z-Stack的正确运行提供了内存管理、中断管理和任务调度等基本功能。
理解任务调度过程
OSAL的任务调度其实是与上节课笔者实现的任务调度是类似的,也就是初始化任务池以及轮询任务池。
打开本节课配套的工程代码,如图所示。
读者会发现配套的工程代码与之前的Z-Stack不同,这是因为为了方便读者学习,笔者已经把部分用不到文件给裁剪掉了。
打开SampleSwitch.eww工程文件所在的目录,如图所示。
双击打开SampleSwitch.eww文件,打开后如图所示。
程序一般是从main()函数开始的,Z-Stack 3.0 也不例外。它的main()函数在ZMain目录下的ZMain.c文件中,该文件在如图所示位置。
打开ZMain.c文件,可以找到main()函数,其代码如下:
1.int main( void )2.{3. // Turn off interrupts4. osal_int_disable( INTS_ALL ); // 关闭所有中断5.6. // Initialization for board related stuff such as LEDs7. HAL_BOARD_INIT(); // 初始化板载资源,比如PA、时钟源等8.9. // Make sure supply voltage is high enough to run10. zmain_vdd_check(); // 检测供电电压是否可以支撑芯片正常运行11.12. // Initialize board I/O13. InitBoard( OB_COLD ); // 初始化板载I/O,比如按键配置为输入14.15. // Initialze HAL drivers16. HalDriverInit(); // 初始化硬件适配层,比如串口、显示器等17.18. // Initialize NV System19. osal_nv_init( NULL ); // 初始化NV(芯片内部FLASH的一块空间)20.21. // Initialize the MAC22. ZMacInit(); // 初始化MAC层(数据链路层)23.24. // Determine the extended address25. zmain_ext_addr(); // 确定芯片的物理地址26.27.#if defined ZCL_KEY_ESTABLISH28. // Initialize the Certicom certificate information.29. zmain_cert_init(); // 初始化认证信息30.#endif31.32. // Initialize basic NV items33. zgInit(); // 初始化存储在NV中的协议栈全局信息,如网络启动方式等34.35.#ifndef NONWK36.// Since the AF isn't a task, call it's initialization routine37. afInit(); // 初始化AF(射频)38.#endif39.40. // Initialize the operating system41. osal_init_system(); // 初始化OSAL(操作系统抽象层)42.43. // Allow interrupts44. osal_int_enable( INTS_ALL ); // 使能所有中断45.46. // Final board initialization47. InitBoard( OB_READY ); // 初始化板载IO资源,比如按键48.49. // Display information about this device50. zmain_dev_info(); // 在显示器上显示设备物理地址51.52. /* Display the device info on the LCD */53.#ifdef LCD_SUPPORTED54. zmain_lcd_init(); // 在显示器上显示设备信息,比如制造商等55.#endif56.57.58.59.#ifdef WDT_IN_PM160. /* If WDT is used, this is a good place to enable it. */61. WatchDogEnable( WDTIMX ); // 启动看门狗功能62.#endif63.64. /* 进入系统轮询 */65. osal_start_system(); // No Return from here66.67.68. return 0; // Shouldn't get here.69.} // main()
这个函数中有两个关键的函数调用,代码如下:
//初始化OSAL,包括初始化任务池osal_init_system();//轮询任务池osal_start_system();
可以看到,OSAL的任务调度过程与上节课曾经讲解过的是类似的,也就是初始化任务池和轮询任务池。
osal_init_system()函数和osal_start_system()函数的定义可以在OSAL目录下的OSAL.c文件中找到,OSAL.c所在位置如图所示。
osal_init_system()函数代码如下:
uint8 osal_init_system( void ){#if !defined USE_ICALL && !defined OSAL_PORT2TIRTOS// 初始化内存分配系统osal_mem_init();#endif /* !defined USE_ICALL && !defined OSAL_PORT2TIRTOS */// 初始化消息队列osal_qHead = NULL;// 初始化OSAL定时器osalTimerInit();// 初始化电源管理系统osal_pwrmgr_init();#ifdef USE_ICALLosal_prepare_svc_enroll();#endif /* USE_ICALL */// 初始化任务池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 serviceosal_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()。顾名思义,它的工作内容就是初始化任务池。
osal_start_system()函数代码如下:
void osal_start_system( void ){#ifdef USE_ICALL/* Kick off timer service in order to allocate resources upfront.* The first timeout is required to schedule next OSAL timer event* as well. */ICall_Errno errno = ICall_setTimer(1, osal_msec_timer_cback,(void *) osal_msec_timer_seq,&osal_timerid_msec_timer);if (errno != ICALL_ERRNO_SUCCESS){ICall_abort();}#endif /* USE_ICALL */#if !defined ( ZBIT ) && !defined ( UBIT )//主循环for(;;)#endif{//系统轮询调度osal_run_system();#ifdef USE_ICALLICall_wait(ICALL_TIMEOUT_FOREVER);#endif /* USE_ICALL */}}
在osal_start_system()函数的主循环中,循环调用了 osal_run_system()函数,该函数主要工作轮询任务池。osal_run_system()函数的定义OSAL.c文件中,代码如下:
1.void osal_run_system( void )2.{3. uint8 idx = 0;4.5. /* 更新时间,并整理出到期的任务。系统的时钟周期是:320us */6. osalTimeUpdate();7. Hal_ProcessPoll();// 硬件适配层中断查询8.9. do {10. if (tasksEvents[idx])// 查看是否有任务需要处理11. {12. break;13. }14. } while (++idx < tasksCnt);// 轮询整个任务池15.16. if (idx < tasksCnt)//循环结束后,如果idx < tasksCnt表示任务池有任务需要处理17. {18. uint16 events;19. halIntState_t intState;20. HAL_ENTER_CRITICAL_SECTION(intState);//关闭中断21. events = tasksEvents[idx];//evets中保存了该任务中的待处理事件22. tasksEvents[idx] = 0;//清空此任务中的所有待处理事件23. HAL_EXIT_CRITICAL_SECTION(intState);//恢复中断24.25. activeTaskID = idx;26. events = (tasksArr[idx])( idx, events ); // 处理任务中的事件27. activeTaskID = TASK_NO_TASK;28.29. HAL_ENTER_CRITICAL_SECTION(intState);//关闭中断30. tasksEvents[idx] |= events;//保存还没被处理的事件到任务中31. HAL_EXIT_CRITICAL_SECTION(intState);//恢复中断32. }33.#if defined( POWER_SAVING ) && !defined(USE_ICALL)34. else// Complete pass through all task events with no activity? {35. osal_pwrmgr_powerconserve(); //如果没有任务需要处理则进入低功耗36. }37.#endif38.39. /* Yield in case cooperative scheduling is being used. */40.#if defined (configUSE_PREEMPTION)&&(configUSE_PREEMPTION == 0) {41. osal_task_yield();42. }43.#endif
为了更好地体现该函数的轮询逻辑,已对原代码进行了简化。
在上述代码中,重点讲解一下其中的这个do-while循环,代码如下:
9. do {10. if (tasksEvents[idx])// 查看是否有任务需要处理11. {12. break;13. }14. } while (++idx < tasksCnt);// 轮询整个任务池
这个循环的主要作用是轮询整个任务池,也就是看一下有没有要处理的任务。循环中只有一个条件判断,如果条件成立,那么就结束循环。
其中的tasksEvents是一个uint16类型的数组,其中的每一个元素都表示一种类型的任务,也就是说,tasksEvents就是一个任务池,tasksCnt是这个任务池的大小。
这个循环的运行逻辑是:
- 首先,idx的初始值为0;
- 当 tasksEvents[idx] 的值为0时,表示该任务中没有事情要处理,这时候条件判断不成立,进入下一次循环;
- 每执行1次循环前,idx加1,然后判断是否小于tasksCnt;
- 当 tasksEvents[idx] 的值不等于0时,表示该任务中有事情要处理,这时候条件判断成立,于是通过break结束循环;
- 当循环结束后,如果整个任务池中都没有任务要处理,那么idx必定会 >=tasksCnt。因此,如果 idx < tasksCnt,表示现在任务池中有任务需要处理,并且 tasksEvents[idx] 就是当前需要处理的任务。因此在循环结束后,Z-Stack 先用 if (idx < tasksCnt) 语句来判断有没有任务需要处理。
任务与事件
每个任务中可能包含一系列待处理的事情,这些待处理的事情,可以通俗的称为“事件”,例如一个任务中可以包含打开LED灯、关闭窗户和打开空调这3个事件(待处理的事情)。
tasksEvents中的每个元素都是一个uint16类型的变量,每一个元素都表示了一个任务,并且储存了这个任务中包含的一系列事件。那么一个uint16类型的变量是如何储存一系列的事件的呢?笔者将在后续章节详细讲解。
3.2 任务初池始化与事件处理
层次划分
通过前面的章节可以了解到,Z-Stack可以被分成多个层次,例如:
- MAC层
- NWK(网络层)
- HAL(硬件适配层)
- APP(应用层)
任务池初始化
每一个层次都有一个对应的任务来处理本层次的事务,例如MAC层对应一个MAC层的任务、网络层对应一个网络层的任务、HAL对应一个HAL的任务,以及应用层对应一个应用层的任务等,这些各个层次的任务构成一个任务池,这个任务池也就是上节课讲到的tasksEvents数组,如图所示。
上节课曾讲到的任务池初始化函数osalInitTasks()可以在App目录下的OSAL_SampleSw.c文件中找到,如图所示。
osalInitTasks()函数定义代码如下:
/********************************************************************** @fn osalInitTasks** @brief This function invokes the initialization function for each task.** @param void** @return none*/void osalInitTasks( void ){uint8 taskID = 0;tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));macTaskInit( taskID++ );nwk_init( taskID++ );#if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)gp_Init( taskID++ );#endifHal_Init( taskID++ );#if defined( MT_TASK )MT_TaskInit( taskID++ );#endifAPS_Init( taskID++ );#if defined ( ZIGBEE_FRAGMENTATION )APSF_Init( taskID++ );#endifZDApp_Init( taskID++ );#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )ZDNwkMgr_Init( taskID++ );#endif// Added to include TouchLink functionality#if defined ( INTER_PAN )StubAPS_Init( taskID++ );#endif// Added to include TouchLink initiator functionality#if defined( BDB_TL_INITIATOR )touchLinkInitiator_Init( taskID++ );#endif// Added to include TouchLink target functionality#if defined ( BDB_TL_TARGET )touchLinkTarget_Init( taskID++ );#endifzcl_Init( taskID++ );bdb_Init( taskID++ );zclSampleSw_Init( taskID++ );#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)zclOTA_Init( taskID );#endif}
这个函数的代码较为复杂,读者暂时按照笔者的讲解思路简单地了解一下其中的原理就可以了。
这个函数首先申请了一个任务池存储空间,也就是这个tasksEvents数组。接着调用了很多带有“init”字样的函数,这些函数的作用是初始化各个层次的任务,例如:
- 调用macTaskInit()函数初始化MAC层的任务。
- 调用nwk_init()函数初始化网络层的任务。
- 调用zclSampleSw_Init()函数初始化应用层的任务。
这个过程跟《OSAL的任务调度原理》章节中创建和初始化任务池是类似的,它们的主要差别在于:
- 首先,任务池存储的数据结构不同,这里的tasksEvents是一个uint16类型的数组。
- 其次,tasksEvents支持存放多种类型的任务,例如MAC层的任务、网络层的任务和应用层的任务等。
- 最后,这里的每一个任务中都可能会包含多个待处理事件。
事件处理函数
OSAL_SampleSw.c文件中还定义了一个数组,代码如下:
//创建一个元素类型为pTaskEventHandlerFn的数组1.const pTaskEventHandlerFn tasksArr[] = {2. macEventLoop, //第1个数组元素3. nwk_event_loop, //第2个数组元素////第3个数组元素4.#if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)5. gp_event_loop,6.#endif////第4个数组元素7. Hal_ProcessEvent,//第5个数组元素8.#if defined( MT_TASK )9. MT_ProcessEvent,10.#endif////第6个数组元素11. APS_event_loop,////第7个数组元素12.#if defined ( ZIGBEE_FRAGMENTATION )13. APSF_ProcessEvent,14.#endif////第8个数组元素15. ZDApp_event_loop,////第9个数组元素16.#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )17. ZDNwkMgr_event_loop,18.#endif////第10个数组元素19. //Added to include TouchLink functionality20. #if defined ( INTER_PAN )21. StubAPS_ProcessEvent,22. #endif////第11个数组元素23. // Added to include TouchLink initiator functionality24. #if defined ( BDB_TL_INITIATOR )25. touchLinkInitiator_event_loop,26. #endif////第12个数组元素27. // Added to include TouchLink target functionality28. #if defined ( BDB_TL_TARGET )29. touchLinkTarget_event_loop,30. #endif//31. zcl_event_loop, //第13个数组元素32. bdb_event_loop, //第14个数组元素33. zclSampleSw_event_loop, //第15个数组元素////第16个数组元素34.#if (defined OTA_CLIENT) && (OTA_CLIENT == TRUE)35. zclOTA_event_loop36.#endif37.};
前面章节中曾经讲到,tasksEvents中的每个任务可以包含0个或者多个待处理的事件,而这个数组类型变量pTaskEventHandlerFn是一个函数指针类型变量,用于指向事件对应的处理函数,因此这段代码定义了一个事件处理函数数组,这个数组中的每一个元素均表示某一个层次任务的事件处理函数,例如:
- MAC层任务对应的事件处理函数是macEventLoop(),它专门处理MAC层任务中的事件。
- 网络层任务对应的事件处理函数是nwk_event_loop(),它专门处理网络层任务中的事件。
- 应用层任务对应的事件处理函数是zclSampleSw_event_loop(),它专门处理应用层任中的事件。
以应用层事件处理函数zclSampleSw_event_loop()为例,可以在zcl_samplesw.h文件中找到该函数的声明,代码如下:
/** Event Process for the task*/extern UINT16 zclSampleSw_event_loop( byte task_id, UINT16 events );
3.3 Z-Stack 事件的应用
本节课的内容是后续课程的基础,希望大家能够好好学习,为后续课程打下良好的基础。
事件的类型与编码:
读者可以发现每个层次的事件处理函数的参数都包含1个task id和1个events参数,例如:
MAC层事件处理函数,如图所示。

网络层事件处理函数,如图所示。

应用层事件处理函数,如图所示。

以应用层事件处理函数为例,它的第2个参数UINT16 events表示了一个事件集合,其中包含了0个或多个待处理的事件。然而,events是一个16位的变量,它是怎么样表示一个事件集合的呢?
答案是Z-Stack 3.0采用了独热码(one-hot code)的方式对事件类型进行编码。
events的分类
在讲解独热码之前,先来了解一下events的分类。
- events的最高位为1时,表示这是系统事件集合,即events中的事件全是系统事件。
- events的最高位为0时,表示这是用户事件集合,即events中的事件全是用户事件。
用户事件可以由开发者自行定义其含义,以及相应的处理。
使用独热码
采用独热码的方式,把所有的用户事件编码列举出来,并从中分析独热码的规律,见下表。
| 2进制编码 | 16进制编码 | 事件名称 |
|---|---|---|
| 0000 0000 0000 0001 | 0x00 01 | 用户事件A |
| 0000 0000 0000 0010 | 0x00 02 | 用户事件B |
| 0000 0000 0000 0100 | 0x00 04 | 用户事件C |
| 0000 0000 0000 1000 | 0x00 08 | 用户事件D |
| 0000 0000 0001 0000 | 0x00 10 | 用户事件E |
| 0000 0000 0010 0000 | 0x00 20 | 用户事件F |
| 0000 0000 0100 0000 | 0x00 40 | 用户事件G |
| 0000 0000 1000 0000 | 0x00 80 | 用户事件H |
| 0000 0001 0000 0000 | 0x01 00 | 用户事件I |
| 0000 0010 0000 0000 | 0x02 00 | 用户事件J |
| 0000 0100 0000 0000 | 0x04 00 | 用户事件K |
| 0000 1000 0000 0000 | 0x08 00 | 用户事件L |
| 0001 0000 0000 0000 | 0x10 00 | 用户事件M |
| 0010 0000 0000 0000 | 0x20 00 | 用户事件N |
| 0100 0000 0000 0000 | 0x40 00 | 用户事件O |
其中事件名称可以根据实际需命名,例如开灯事件、关灯事件或者发送警告事件等。
从这些编码中,可以得出2个规律:
- 除了用于表示系统事件或者用户事件的最高位,其他15个比特位中,只有1位为1,其他位均为0。
- 使用15个比特位表示15种用户事件。
这两个规律也是独热码的规律。
利用规律1,可以很容易地理解为什么events可以表示一个事件集合。现在假设events的值为0000 0000 0101 0101,其中的右起第1、3、5和7位为1,于是可以理解为事件集合events包含了用户事件A、C、E和G。
利用规律2,可以得到events最多可以包含15种用户事件。
定义用户事件
可以使用以下方法在zcl_samplesw.h文件中定义一个用户事件。
1.定义事件名称和对应的编码。
#define SAMPLEAPP_TEST_EVT 0x0040
2.把它复制到zcl_samplesw.h文件中的如图所示位置。
处理用户事件
可以在zcl_samplesw.c文件中的应用层事件处理函数中添加相关的处理,代码如下:
uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events ){afIncomingMSGPacket_t *MSGpkt;(void)task_id; // Intentionally unreferenced parameter//用户事件:SAMPLESW_TOGGLE_TEST_EVTif( events & SAMPLESW_TOGGLE_TEST_EVT ){osal_start_timerEx(zclSampleSw_TaskID,SAMPLESW_TOGGLE_TEST_EVT,500);zclGeneral_SendOnOff_CmdToggle( SAMPLESW_ENDPOINT, &zclSampleSw_DstAddr, FALSE, 0 );//消除已经处理的事件然后返回未处理的事件return (events ^ SAMPLESW_TOGGLE_TEST_EVT);}//SYS_EVENT_MSG:0x8000表示系统事件,也就是说检测uint16最高位if ( events & SYS_EVENT_MSG ){//省略系统事件的处理代码.........// 消除系统事件标识然后返回未处理的事件return (events ^ SYS_EVENT_MSG);}#if ZG_BUILD_ENDDEVICE_TYPE//用户事件:SAMPLEAPP_END_DEVICE_REJOIN_EVTif ( events & SAMPLEAPP_END_DEVICE_REJOIN_EVT ){bdb_ZedAttemptRecoverNwk();return ( events ^ SAMPLEAPP_END_DEVICE_REJOIN_EVT );}#endif//用户事件:SAMPLEAPP_LCD_AUTO_UPDATE_EVTif ( events & SAMPLEAPP_LCD_AUTO_UPDATE_EVT ){UI_UpdateLcd();return ( events ^ SAMPLEAPP_LCD_AUTO_UPDATE_EVT );}//用户事件:SAMPLEAPP_KEY_AUTO_REPEAT_EVTif ( events & SAMPLEAPP_KEY_AUTO_REPEAT_EVT ){UI_MainStateMachine(UI_KEY_AUTO_PRESSED);return ( events ^ SAMPLEAPP_KEY_AUTO_REPEAT_EVT );}// 处理刚才自己定义的用户事件:SAMPLEAPP_TEST_EVTif ( events & SAMPLEAPP_TEST_EVT ){printf("Hello World!\r\n");//消除已经处理的事件然后返回未处理的事件return ( events ^ SAMPLEAPP_TEST_EVT );}// Discard unknown eventsreturn 0;}
通过前面讲解可以了解到,每一种用户事件类型编码中只有1位为1,其他比特位为0。SAMPLEAPP_TEST_EVT的事件类型编码为0x0040,其二进制数为:0000 0000 0100 0000。这个编码的右起第7为1,其余位为0。
于是上述代码利用events & SAMPLEAPP_TEST_EVT让事件集合参数events与预定义的事件类型SAMPLEAPP_TEST_EVT做与运算,判断events中的右起第3位是否为1。如果为1,那么events & SAMPLEAPP_TEST_EVT的值为1,这表示事件集合参数events包含SAMPLEAPP_TEST_EVT这个事件,因此程序执行对应的处理代码,即执行:
printf("Hello World!\r\n");
接着,代码中利用events ^ SAMPLEAPP_TEST_EVT把events中的第3位清0,然后把这个值作为函数的返回值,表示events中的这个事件已经被处理了。
这段代码中用到了 & 和 ^ 运算,如果不了解这两种运算,需要先补习一下。
触发用户事件
前面已经定义好事件类型和对应的处理方式了,但是需要在OSAL中触发该事件后,OSAL才会执行对应的处理代码。
OSAL提供了专门的API来触发事件。展开OSAL层,可以找到OSAL_Timers.h文件,如图所示。
在OSAL_Timers.h文件中,可以找到触发事件的API,函数声明如下:
uint8 osal_start_timerEx(uint8 task_id,uint16 event_id,uint32 timeout_value);
该函数有三个参数,其说明如下:
- task_id:任务ID,用于标记这个事件是属于哪一个层次的任务。
- event_id:事件ID,用于标记这个事件的类型。
- timeout_value:表示多少毫秒后才处理这个事件。
如果希望在触发事件的3s后处理刚才自定义的事件,可在应用层初始化函数zclSampleSw_Init()的末尾位置添加如下代码:
osal_start_timerEx(zclSampleSw_TaskID,//标记本事件属于应用层任务SAMPLEAPP_TEST_EVT,//标记本事件的类型3000);//表示3000ms后才处理这个事件
其中,zclSampleSw_TaskID是一个全局变量,用于标记这个事件是属于应用层任务的。
调试仿真
右击工程名称,选择Options,如图所示。
选择Debugger,然后在Driver选项卡中选择Texas Instruments,最后单击OK按钮完成设置,如图所示。
使用仿真器把配套的ZigBee开发板连接到电脑。
单击Download and Debug按钮进行程序的编译、链接和下载并进入仿真模式,如图所示。
进入仿真模式之后,单击Go按钮运行程序,如图所示。
程序在运行3后秒会在Terminal I/O窗口中输出“Hello World!”,如图所示。

3.4 使用动态内存
动态内存简介
在编写程序时,开发者可能不能确定需要使用多少内存来保存数据,希望在程序运行的过程中根据实际需要来使用相应的内存空间。这种能够在程序运行的过程中申请和释放的内存空间称为动态内存。
Z-Stack 3.0 中动态内存分配的API在OSAL_Memory.h文件中,这个文件在OSAL目录下,如图所示。
申请与释放动态内存
在OSAL_Memory.h文件中,可以找到两个重要的API,定义如下:
/*** @fn osal_mem_alloc** @brief 动态申请内存空间** @param size - 申请多少个字节的内存空间** @return 返回该内存空间的指针*/void *osal_mem_alloc( uint16 size );/*** @fn osal_mem_free** @brief 动态释放内存空间** @param ptr - 待释放的内存空间指针**/void osal_mem_free( void *ptr );
动态内存的操作
在申请完动态内存后,可以调用内存操作API来使用这些内存。内存操作API在OSAL.h文件中,下面这两个API是比较常用的,定义如下:
/*** @fn osal_memcpy** @brief 把内存空间的内容复制到另一个内存空间中** @param void* - 目标内存空间* @param const void GENERIC * - 源内存空间* @param unsigned int - 复制多少个字节** @return*/void *osal_memcpy(void*, const void GENERIC *,unsigned int);/*** @fn osal_memset** @brief 把内存空间的值设置为指定的值** @param dest - 内存空间* @param value - 指定的值* @param len - 把从dest起的len个字节的存储空间的值设置为value** @return*/extern void *osal_memset( void *dest, uint8 value, int len );
使用动态内存
在上节课中的事件处理函数中做一些修改,修改后代码如下:
//事件:SAMPLEAPP_TEST_EVTif ( events & SAMPLEAPP_TEST_EVT ){//字符串:”Hello World!\n”char *str = "Hello World!\n";//从堆空间中申请32个字节的内存空间char *mem = osal_mem_alloc(32);//如果申请成功if (mem != NULL) {//清零内存空间osal_memset(mem, 0, 32);//将字符串拷贝到内存空间中osal_memcpy(mem, str, osal_strlen(str));//打印内存空间内存printf(mem);//释放内存空间osal_mem_free(mem);}//重新触发事件,3000毫秒后执行osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_TEST_EVT, 3000);//消除已经处理的事件,然后返回未处理的事件return ( events ^ SAMPLEAPP_TEST_EVT );}
这段代码中,在执行完事件处理之后,重新触发了该事件,实现了以恒定的时间间隔不断触发该事件。
调试仿真
使用仿真器把配套的ZigBee开发板连接到电脑。
单击Download and Debug按钮进行程序的编译、链接和下载并进入仿真模式,如图所示。
进入仿真模式之后,单击Go按钮运行程序,如图所示。
程序运行后,会每隔3秒在Terminal I/O窗口中输出“Hello World!”,如图所示。
