Skip to content

4.2 HAL 的架构简介 ​

本节导读

上一节我们看了 HAL 的"家底"(文件结构)。这一节看它怎么运转:HAL 作为协议栈的一个"任务",它的初始化、驱动初始化、事件处理、轮询分别在哪、怎么写的。

看完你会发现:HAL 就是一个普通的 OSAL 任务,只是它的"业务"是管理所有硬件外设。

一、HAL 的四个角色 ​

HAL 的代码集中在 hal_drivers.c(公共区域的"前台"文件)里,它有四个关键函数:

函数角色什么时候被调用
Hal_Init()注册 HAL 任务OSAL 任务池初始化时(osalInitTasks)
HalDriverInit()初始化各外设驱动程序启动(main 里)
Hal_ProcessEvent()处理 HAL 任务的事件OSAL 轮询到 HAL 任务时
Hal_ProcessPoll()快速轮询外设OSAL 每次轮询都调

二、Hal_Init():给 HAL 领个"工号" ​

c
void Hal_Init(uint8 task_id) {
    Hal_TaskID = task_id;   // 记住自己的任务编号
    // ……(CC2591 相关处理,一般不用管)……
}

很简单:把自己的任务编号记下来。以后要发事件给自己(比如按键轮询定时器),就用这个编号。

生活类比:新员工入职第一天,去人事(OSAL)报到,领了一个工号(task_id),以后收发通知都用这个号。

三、HalDriverInit():按"菜单"点菜初始化 ​

c
void HalDriverInit(void) {
#if (defined HAL_TIMER) && (HAL_TIMER == TRUE)
    // 定时器初始化(略)
#endif
#if (defined HAL_ADC) && (HAL_ADC == TRUE)
    HalAdcInit();        // ADC 初始化
#endif
#if (defined HAL_DMA) && (HAL_DMA == TRUE)
    HalDmaInit();        // DMA 初始化
#endif
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
    HalLcdInit();        // 显示屏初始化
#endif
#if (defined HAL_LED) && (HAL_LED == TRUE)
    HalLedInit();        // LED 初始化
#endif
#if (defined HAL_UART) && (HAL_UART == TRUE)
    HalUARTInit();       // 串口初始化
#endif
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
    HalKeyInit();        // 按键初始化
#endif
#if (defined HAL_SPI) && (HAL_SPI == TRUE)
    HalSpiInit();        // SPI 初始化
#endif
    // ……还有 AES、HID 等……
}

这段代码揭示了一个非常重要的设计思想——宏开关:

想用哪个外设,就定义哪个宏为 TRUE;不用就不定义。

想用定义宏会执行
LEDHAL_LED=TRUEHalLedInit()
串口HAL_UART=TRUEHalUARTInit()
按键HAL_KEY=TRUEHalKeyInit()
显示屏HAL_LCD=TRUEHalLcdInit()
ADCHAL_ADC=TRUEHalAdcInit()

生活类比:这就像自助餐厅的菜单——勾了哪道菜(定义宏),厨房(HalDriverInit)才做哪道菜(执行对应初始化)。没勾的菜不浪费资源。

这也是为什么第 6 章串口要定义 HAL_UART=TRUE、第 7 章显示屏要定义 HAL_LCD=TRUE——不定义宏,驱动根本不会被初始化。

如果要新增一个自己的外设怎么办? 按这个架构加就行:① include 加头文件 → ② target 加实现 → ③ HalDriverInit 加一段 #if 初始化 → ④ 上层定义宏打开它。

四、Hal_ProcessEvent():HAL 的"事件处理中心" ​

HAL 任务的事件处理函数,处理各种外设事件(已简化):

c
uint16 Hal_ProcessEvent(uint8 task_id, uint16 events) {
    if (events & SYS_EVENT_MSG) {
        // ……处理系统消息(其他任务发来的纸条)……
        return (events ^ SYS_EVENT_MSG);
    }

#if (defined HAL_BUZZER) && (HAL_BUZZER == TRUE)
    if (events & HAL_BUZZER_EVENT) {   // 蜂鸣器事件
        HalBuzzerStop();
        return (events ^ HAL_BUZZER_EVENT);
    }
#endif

    if (events & HAL_LED_BLINK_EVENT) {  // LED 闪烁事件
        HalLedUpdate();                  // 更新 LED 闪烁状态
        return (events ^ HAL_LED_BLINK_EVENT);
    }

    if (events & HAL_KEY_EVENT) {        // 按键事件
        HalKeyPoll();                    // 去"查岗":扫描按键
        if (!Hal_KeyIntEnable) {         // 如果用的是轮询模式
            osal_start_timerEx(Hal_TaskID, HAL_KEY_EVENT, 100); // 100ms 后再查一次
        }
        return (events ^ HAL_KEY_EVENT);
    }
    return 0;
}

两个值得注意的点:

  1. LED 闪烁、按键轮询都是靠事件驱动的——HalLedUpdate() 更新闪烁、HalKeyPoll() 扫描按键,都是由 OSAL 定时发事件触发的;
  2. 按键轮询模式的"100ms 定时器":如果用轮询(不开中断),HAL 处理完按键事件后会再设一个 100ms 的闹钟,让自己 100ms 后再来查一次——这就是"定时轮询"的实现方式。

五、Hal_ProcessPoll():每轮都来"巡逻" ​

这个函数由 OSAL 的 osal_run_system() 每一轮都调用,处理需要快速响应的外设(已简化):

c
void Hal_ProcessPoll(void) {
#if (defined HAL_UART) && (HAL_UART == TRUE)
    HalUARTPoll();      // 串口快速轮询
#endif
#if (defined HAL_SPI) && (HAL_SPI == TRUE)
    HalSpiPoll();       // SPI 快速轮询
#endif
    // ……HID 等……
}

与事件处理不同:事件要"排队"(进任务池等轮询),而 Hal_ProcessPoll() 是"插队"——每一轮主循环都会立刻执行,适合串口这种需要及时收发的设备。

六、小结 ​

函数角色关键点
Hal_Init()领工号记录 Hal_TaskID
HalDriverInit()点菜宏开关:定义哪个宏就初始化哪个外设
Hal_ProcessEvent()事件中心LED 闪烁/按键轮询都靠事件驱动
Hal_ProcessPoll()每轮巡逻快速轮询 UART/SPI 等

一句话总结:HAL 是个标准的 OSAL 任务——初始化靠"宏开关点菜"、干活靠"事件驱动"、快速响应靠"每轮巡逻"。**"想用哪个外设就开哪个宏"**是贯穿所有硬件章节的总开关,一定要记牢。


内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·4.2 节,经本站整理优化以便于理解;原站链接

aioteer · 物联网专业课程群

💬AI 伴学配置