第三章 设备开发基础: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的核心概念有三个:

  1. 驱动实体(Driver Entry):每个驱动对应一个实体,包含初始化、去初始化、绑定等接口。
  2. 设备对象(Device Object):代表一个具体的硬件设备,比如一个I2C温度传感器。
  3. 服务接口(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程序。到时候你会发现,前面这些基础打好了,后面写代码就像搭积木一样顺畅。