Skip to content

3.2 任务初始化与事件处理 ​

本节导读

上一节我们看到了轮询循环:它拿 idx 去查 tasksEvents,查到了就调 tasksArr[idx]。但有两个问题还没回答:

  1. tasksEvents、tasksArr 这些"名单"是谁排的? —— 答案是 osalInitTasks()。
  2. 处理函数收到事件后,具体怎么干活的? —— 这一节我们把事件处理函数的"标准模板"拆开看。

原站本节内容已失效,本站按 Z-Stack 3.0 源码与教学实践重写,电路与例程以 GEC2530 开发板 + SampleSwitch 工程为准。

一、任务池是怎么"出生"的:osalInitTasks() ​

回想上一节,osal_init_system() 里最后一行是 osalInitTasks()。这个函数定义在 App 目录下的 OSAL_SampleSw.c 文件中:

osalInitTasks 在 OSAL_SampleSw.c 中

c
void osalInitTasks(void) {
    uint8 taskID = 0;

    // ① 先给任务池"买地皮":申请 tasksEvents 数组
    tasksEvents = (uint16 *)osal_mem_alloc(sizeof(uint16) * tasksCnt);
    osal_memset(tasksEvents, 0, (sizeof(uint16) * tasksCnt));

    // ② 逐层初始化任务,每初始化一层,taskID 加 1
    macTaskInit(taskID++);      // MAC 层任务
    nwk_init(taskID++);         // 网络层任务
    gp_Init(taskID++);          // 绿能 GP 层任务
    Hal_Init(taskID++);         // 硬件适配层任务
    MT_TaskInit(taskID++);      // MT 任务(调试用)
    APS_Init(taskID++);         // APS 层任务
    ZDApp_Init(taskID++);       // ZDO 任务
    zcl_Init(taskID++);         // ZCL 层任务
    bdb_Init(taskID++);         // BDB 层任务
    zclSampleSw_Init(taskID++); // ★ 应用层任务(SampleSwitch)★
}

它在干什么?两件事:

  1. 申请任务池内存:tasksEvents 数组就是任务池,先给它腾好地方、全部清零;
  2. 逐层注册任务:Z-Stack 的每一层都来"报名",报一个名,taskID 就 +1,于是每层拿到一个独一无二的编号。

生活类比:这就是新公司开业,各部门来前台报到——MAC 部、网络部、HAL 部……前台给每个部门发一个工号(taskID),并把工号记在"待办登记表"(tasksEvents)上。

二、任务与处理函数的"电话本":tasksArr[] ​

光有"待办登记表"还不够——轮询查到某个部门有事,得知道该通知谁。这个"电话本"就是 tasksArr[]:

c
const pTaskEventHandlerFn tasksArr[] = {
    macEventLoop,            // 第 0 号:MAC 层事件处理函数
    nwk_event_loop,          // 第 1 号:网络层事件处理函数
    gp_event_loop,           // 第 2 号:GP 层事件处理函数
    Hal_ProcessEvent,        // 第 3 号:HAL 事件处理函数
    MT_ProcessEvent,         // 第 4 号:MT 事件处理函数
    APS_event_loop,          // 第 5 号:APS 事件处理函数
    ZDApp_event_loop,        // 第 6 号:ZDO 事件处理函数
    zcl_event_loop,          // 第 7 号:ZCL 事件处理函数
    bdb_event_loop,          // 第 8 号:BDB 事件处理函数
    zclSampleSw_event_loop,  // 第 9 号:应用层事件处理函数
};

关键:tasksEvents 和 tasksArr 按下标一一对应。

  • tasksEvents[idx] 非 0 → 第 idx 号任务有事;
  • 就调用 tasksArr[idx] → 第 idx 号任务的处理函数。

生活类比:tasksArr 就是前台桌上的部门电话本——查到 3 号部门有工单,就拨 3 号分机;查到 9 号部门有事,就拨 9 号分机。编号对上号,一个萝卜一个坑。

三、事件处理函数的标准模板 ​

每个事件处理函数签名都统一:

c
uint16 zclSampleSw_event_loop(byte task_id, uint16 events);
//       ↑ 返回没处理完的事件    ↑ 任务号    ↑ 事件集合

SampleSwitch 应用层的事件处理函数长这样(已简化):

c
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
    (void)task_id;   // 本例不用 task_id,消除编译警告

    // ① 先处理"用户自定义事件"
    if (events & SAMPLEAPP_TEST_EVT) {        // 有 TEST 事件?
        printf("Hello World!\r\n");            // 干活
        return (events ^ SAMPLEAPP_TEST_EVT);  // 清掉已处理的,返回剩余
    }

    // ② 再处理"系统消息事件"
    if (events & SYS_EVENT_MSG) {              // 有系统消息?
        // ……读取消息、处理消息、释放消息……
        return (events ^ SYS_EVENT_MSG);       // 清掉,返回剩余
    }

    // ③ 其他没识别的事件:直接丢弃
    return 0;
}

模板套路就是三个步骤:判断 → 处理 → 清除返回。

步骤代码说明
判断events & SAMPLEAPP_TEST_EVT用按位与 & 看事件集合里有没有这个事件
处理printf(...)干该干的事
清除返回return (events ^ SAMPLEAPP_TEST_EVT)用按位异或 ^ 把已处理位清零,返回"还没处理完的"

⚠️ 为什么返回"没处理完的"而不直接返回 0? 因为事件可能"一次来好几个"。比如 events = 0x0041(同时有 TEST 事件和另一个事件),处理完 TEST 后,另一个还没处理——必须把它返回去,OSAL 会 |= 写回任务池,下一轮继续处理。

四、事件从哪里来?三个"进货渠道" ​

任务池里的"待办事件"不会凭空出现,主要有三个来源:

来源谁产生例子
定时器osal_start_timerEx()3 秒后触发 SAMPLEAPP_TEST_EVT
系统消息osal_msg_send()其他任务发来一封信(消息)
外设中断HAL 按键/串口你按了一下 S3 按键

生活类比:事件 = 工单。工单可能是闹钟响了(定时器)、同事递来一张纸条(消息)、或者有人按了门铃(外设)。无论哪种,最终都会写进对应部门的"待办登记表"(tasksEvents),等着秘书(轮询)来查。

五、小结 ​

问题答案
任务池谁创建的?osalInitTasks():申请 tasksEvents 数组 + 逐层注册任务
任务编号怎么来的?每注册一层 taskID++,从 0 开始递增
怎么找到处理函数?tasksArr[idx] 电话本,与任务池按下标一一对应
处理函数模板?判断(&)→ 处理 → 清除返回(^)
为什么返回未处理事件?事件可能多个一起来,没处理完的下轮接着干
事件从哪来?定时器 / 系统消息 / 外设中断

一句话总结:osalInitTasks() 让各层任务"报名"并排好号,tasksArr[] 把任务号和处理函数对上;处理函数按"判断 → 处理 → 清除返回"的模板干活——这就是 OSAL 世界里"任务"和"事件"的完整生命周期。


内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·3.2 节(原站已失效),本站按 Z-Stack 3.0 源码与教学实践重写;原站链接

aioteer · 物联网专业课程群

💬AI 伴学配置