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 interrupts
4. osal_int_disable( INTS_ALL ); // 关闭所有中断
5.
6. // Initialization for board related stuff such as LEDs
7. HAL_BOARD_INIT(); // 初始化板载资源,比如PA、时钟源等
8.
9. // Make sure supply voltage is high enough to run
10. zmain_vdd_check(); // 检测供电电压是否可以支撑芯片正常运行
11.
12. // Initialize board I/O
13. InitBoard( OB_COLD ); // 初始化板载I/O,比如按键配置为输入
14.
15. // Initialze HAL drivers
16. HalDriverInit(); // 初始化硬件适配层,比如串口、显示器等
17.
18. // Initialize NV System
19. osal_nv_init( NULL ); // 初始化NV(芯片内部FLASH的一块空间)
20.
21. // Initialize the MAC
22. ZMacInit(); // 初始化MAC层(数据链路层)
23.
24. // Determine the extended address
25. zmain_ext_addr(); // 确定芯片的物理地址
26.
27.#if defined ZCL_KEY_ESTABLISH
28. // Initialize the Certicom certificate information.
29. zmain_cert_init(); // 初始化认证信息
30.#endif
31.
32. // Initialize basic NV items
33. zgInit(); // 初始化存储在NV中的协议栈全局信息,如网络启动方式等
34.
35.#ifndef NONWK
36.// Since the AF isn't a task, call it's initialization routine
37. afInit(); // 初始化AF(射频)
38.#endif
39.
40. // Initialize the operating system
41. osal_init_system(); // 初始化OSAL(操作系统抽象层)
42.
43. // Allow interrupts
44. osal_int_enable( INTS_ALL ); // 使能所有中断
45.
46. // Final board initialization
47. InitBoard( OB_READY ); // 初始化板载IO资源,比如按键
48.
49. // Display information about this device
50. zmain_dev_info(); // 在显示器上显示设备物理地址
51.
52. /* Display the device info on the LCD */
53.#ifdef LCD_SUPPORTED
54. zmain_lcd_init(); // 在显示器上显示设备信息,比如制造商等
55.#endif
56.
57.
58.
59.#ifdef WDT_IN_PM1
60. /* If WDT is used, this is a good place to enable it. */
61. WatchDogEnable( WDTIMX ); // 启动看门狗功能
62.#endif
63.
64. /* 进入系统轮询 */
65. osal_start_system(); // No Return from here
66.
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_ICALL
osal_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 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()。顾名思义,它的工作内容就是初始化任务池。
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_ICALL
ICall_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.#endif
38.
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++ );
#endif
Hal_Init( taskID++ );
#if defined( MT_TASK )
MT_TaskInit( taskID++ );
#endif
APS_Init( taskID++ );
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_Init( taskID++ );
#endif
ZDApp_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++ );
#endif
zcl_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 functionality
20. #if defined ( INTER_PAN )
21. StubAPS_ProcessEvent,
22. #endif
//
//第11个数组元素
23. // Added to include TouchLink initiator functionality
24. #if defined ( BDB_TL_INITIATOR )
25. touchLinkInitiator_event_loop,
26. #endif
//
//第12个数组元素
27. // Added to include TouchLink target functionality
28. #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_loop
36.#endif
37.};
前面章节中曾经讲到,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_EVT
if( 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_EVT
if ( events & SAMPLEAPP_END_DEVICE_REJOIN_EVT )
{
bdb_ZedAttemptRecoverNwk();
return ( events ^ SAMPLEAPP_END_DEVICE_REJOIN_EVT );
}
#endif
//用户事件:SAMPLEAPP_LCD_AUTO_UPDATE_EVT
if ( events & SAMPLEAPP_LCD_AUTO_UPDATE_EVT )
{
UI_UpdateLcd();
return ( events ^ SAMPLEAPP_LCD_AUTO_UPDATE_EVT );
}
//用户事件:SAMPLEAPP_KEY_AUTO_REPEAT_EVT
if ( events & SAMPLEAPP_KEY_AUTO_REPEAT_EVT )
{
UI_MainStateMachine(UI_KEY_AUTO_PRESSED);
return ( events ^ SAMPLEAPP_KEY_AUTO_REPEAT_EVT );
}
// 处理刚才自己定义的用户事件:SAMPLEAPP_TEST_EVT
if ( events & SAMPLEAPP_TEST_EVT )
{
printf("Hello World!\r\n");
//消除已经处理的事件然后返回未处理的事件
return ( events ^ SAMPLEAPP_TEST_EVT );
}
// Discard unknown events
return 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_EVT
if ( 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!”,如图所示。