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 领个"工号"
void Hal_Init(uint8 task_id) {
Hal_TaskID = task_id; // 记住自己的任务编号
// ……(CC2591 相关处理,一般不用管)……
}很简单:把自己的任务编号记下来。以后要发事件给自己(比如按键轮询定时器),就用这个编号。
生活类比:新员工入职第一天,去人事(OSAL)报到,领了一个工号(task_id),以后收发通知都用这个号。
三、HalDriverInit():按"菜单"点菜初始化
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;不用就不定义。
| 想用 | 定义宏 | 会执行 |
|---|---|---|
| LED | HAL_LED=TRUE | HalLedInit() |
| 串口 | HAL_UART=TRUE | HalUARTInit() |
| 按键 | HAL_KEY=TRUE | HalKeyInit() |
| 显示屏 | HAL_LCD=TRUE | HalLcdInit() |
| ADC | HAL_ADC=TRUE | HalAdcInit() |
生活类比:这就像自助餐厅的菜单——勾了哪道菜(定义宏),厨房(HalDriverInit)才做哪道菜(执行对应初始化)。没勾的菜不浪费资源。
这也是为什么第 6 章串口要定义
HAL_UART=TRUE、第 7 章显示屏要定义HAL_LCD=TRUE——不定义宏,驱动根本不会被初始化。
如果要新增一个自己的外设怎么办? 按这个架构加就行:① include 加头文件 → ② target 加实现 → ③ HalDriverInit 加一段 #if 初始化 → ④ 上层定义宏打开它。
四、Hal_ProcessEvent():HAL 的"事件处理中心"
HAL 任务的事件处理函数,处理各种外设事件(已简化):
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;
}两个值得注意的点:
- LED 闪烁、按键轮询都是靠事件驱动的——
HalLedUpdate()更新闪烁、HalKeyPoll()扫描按键,都是由 OSAL 定时发事件触发的; - 按键轮询模式的"100ms 定时器":如果用轮询(不开中断),HAL 处理完按键事件后会再设一个 100ms 的闹钟,让自己 100ms 后再来查一次——这就是"定时轮询"的实现方式。
五、Hal_ProcessPoll():每轮都来"巡逻"
这个函数由 OSAL 的 osal_run_system() 每一轮都调用,处理需要快速响应的外设(已简化):
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 节,经本站整理优化以便于理解;原站链接