第三章 设备开发基础:OpenHarmony架构、内核基础、HDF驱动框架初探、编译系统介绍
好,咱们进入第三章。这一章,说白了就是打地基。你想想看,做智能家居设备开发,如果连OpenHarmony长什么样、内核怎么跑、驱动怎么挂上去都不清楚,后面写代码心里肯定没底。
我个人习惯,每接触一个新平台,先花半天时间把它的架构图吃透。别急着敲代码,先把地图看明白,再上路。
3.1 OpenHarmony整体架构
OpenHarmony的架构,我习惯把它分成四层来看。就像盖房子,从地基到装修,一层层往上搭。
| 层次 | 名称 | 核心内容 |
|---|---|---|
| 第1层 | 内核层 | LiteOS-M / LiteOS-A / Linux内核,HDF驱动框架 |
| 第2层 | 系统服务层 | 分布式软总线、系统服务框架、安全服务 |
| 第3层 | 框架层 | UI框架、Ability框架、多媒体框架 |
| 第4层 | 应用层 | 系统应用、第三方应用、HAP包 |
这里我要特别强调一下内核层。做智能家居设备,我们打交道最多的就是内核层和系统服务层。尤其是HDF驱动框架,我后面会详细讲。
核心要点:OpenHarmony不是把Linux内核直接搬过来用。它针对IoT设备做了大量裁剪。比如LiteOS-M,只有几十KB的RAM就能跑起来。你想想看,一个智能灯泡,总共就128KB Flash,用Linux肯定不行。
3.2 内核基础:LiteOS-M与LiteOS-A
做智能家居设备,你大概率会遇到两个内核:LiteOS-M和LiteOS-A。怎么选?我直接给你答案。
- LiteOS-M:面向MCU类设备。比如智能灯、温湿度传感器、开关面板。资源极度受限,RAM通常小于512KB。
- LiteOS-A:面向带MMU的设备。比如智能音箱、带屏幕的中控面板。需要跑复杂应用,RAM通常在几MB以上。
我在项目中遇到过一个问题:用LiteOS-M做一款智能窗帘电机,结果发现任务栈开小了,电机一启动就栈溢出。排查了半天,最后用 LOS_TaskStackCheck 才定位到问题。嗯,这里要注意,LiteOS-M的任务栈默认只有2KB,如果你的任务里有大数组或者递归调用,一定要手动调大。
内核的任务调度,LiteOS-M用的是基于优先级的抢占式调度。说白了,高优先级的任务来了,低优先级的就得让路。但要注意优先级反转的问题。我曾经在一个多传感器采集项目里,就因为优先级设置不当,导致低优先级的温度传感器一直抢不到CPU,数据采集延迟了3秒。
避坑指南:我曾经在LiteOS-M上做GPIO中断处理,直接在中断服务函数里调用了 LOS_SemPend。结果系统直接死机。记住,中断里不能做阻塞操作。正确的做法是:中断里只发事件或信号量,把耗时操作放到任务里做。
3.3 HDF驱动框架初探
HDF,全称是Hardware Driver Foundation。说白了,它就是OpenHarmony的驱动管理框架。为什么要有它?因为传统的Linux驱动,每个驱动都是独立的模块,管理起来很乱。HDF把驱动抽象成统一的模型,方便移植和管理。
HDF的核心概念有三个:
- 驱动实体(Driver Entry):每个驱动对应一个实体,包含初始化、去初始化、绑定等接口。
- 设备对象(Device Object):代表一个具体的硬件设备,比如一个I2C温度传感器。
- 服务接口(Service Interface):驱动对外暴露的功能接口,上层应用通过它来操作硬件。
我拿一个实际的GPIO驱动来举例。假设你要控制一个LED灯,HDF驱动的代码结构大概是这样的:
// 驱动入口结构体
struct HdfDriverEntry g_ledDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_LED",
.Bind = LedDriverBind,
.Init = LedDriverInit,
.Release = LedDriverRelease,
};
// 驱动初始化
int32_t LedDriverInit(struct HdfDeviceObject *deviceObject)
{
// 配置GPIO引脚为输出模式
// 设置初始电平
HDF_LOGI("LED driver init success");
return HDF_SUCCESS;
}
// 驱动绑定
int32_t LedDriverBind(struct HdfDeviceObject *deviceObject)
{
// 创建服务接口
// 注册到HDF框架
return HDF_SUCCESS;
}
你看,每个驱动都要实现这三个接口:Bind、Init、Release。这是HDF框架的约定。我在做一款智能门锁项目时,就因为忘了在Release里释放GPIO资源,导致系统休眠后唤醒时GPIO状态错乱。嗯,这个坑我踩过,你们别踩了。
注意事项:HDF驱动的配置文件是 .hcs 格式(HDF Configuration Source)。它不像Linux的DTS那样是文本文件,而是经过编译的二进制文件。所以修改配置后,一定要重新编译烧录,不能直接改文件系统里的配置。
3.4 编译系统介绍
OpenHarmony的编译系统,用的是GN + Ninja。GN负责生成构建文件,Ninja负责真正编译。为什么不用Makefile?因为Makefile在大项目里太慢了。GN + Ninja的组合,编译速度能快好几倍。
编译系统的核心文件是 BUILD.gn。每个模块都要写一个BUILD.gn,告诉编译系统怎么编译这个模块。我举个例子:
# 定义一个静态库
static_library("my_led_driver") {
sources = [
"led_driver.c",
"led_driver_hdf.c",
]
include_dirs = [
"//drivers/framework/include",
"//kernel/liteos_m/include",
]
deps = [
"//drivers/framework/core:core",
]
}
# 定义一个可执行文件
executable("led_test_app") {
sources = [ "led_test.c" ]
deps = [ ":my_led_driver" ]
}
我个人习惯,写BUILD.gn时,include_dirs 尽量写绝对路径(以 // 开头),这样不管模块放在哪个目录下,都能正确找到头文件。相对路径容易出问题,尤其是模块被移动位置的时候。
编译命令也很简单:
# 全量编译
./build.sh --product-name=xxx
# 单独编译某个模块
./build.sh --product-name=xxx --build-target=my_led_driver
这里有个小技巧:调试驱动时,不要每次都全量编译。用 --build-target 指定模块,编译时间能从10分钟缩短到10秒。我在做智能插座项目时,一天要编译几十次,全靠这个命令救命。
总结一下:OpenHarmony的编译系统,你只需要记住三件事:写BUILD.gn、用 --build-target 单独编译、出错了先看 out/ 目录下的日志。其他的,用到再查文档就行。
好了,这一章的内容就到这里。下一章我们会真正开始动手,搭建开发环境,跑第一个Hello World程序。到时候你会发现,前面这些基础打好了,后面写代码就像搭积木一样顺畅。