用鸿蒙开发AI应用(五)HDF 驱动补光灯

开发
文章由鸿蒙社区产出,想要了解更多内容请前往:51CTO和华为官方战略合作共建的鸿蒙技术社区https://harmonyos.51cto.com/#zz

 想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com/#zz

前言

上一篇,我们在鸿蒙上运行了第一个程序,这一篇我们来编写一个驱动开启摄像头的红外补光灯,顺便熟悉一下鸿蒙上的 HDF 驱动开发。

[[377472]]

硬件准备

先查一下原理图(具体可参考第一篇的硬件资料),找到红外灯的 IO 口编号,GPIO5_1。


HDF 驱动开发

1. 简介

HDF(OpenHarmony Driver Foundation)驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。

HDF框架以组件化的驱动模型作为核心设计思路,为开发者提供更精细化的驱动管理,让驱动开发和部署更加规范。HDF框架将一类设备驱动放在同一个host里面,驱动内部实现开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个node,HDF框架管理驱动模型如下图所示:


2. 驱动框架

2.1 驱动框架实现

在 huawei/hdf 目录下新建一个文件夹 led, 然后在其中新建一个源文件 led.c。

  1. #include "hdf_device_desc.h"  // HDF框架对驱动开放相关能力接口的头文件 
  2. #include "hdf_log.h"          // HDF 框架提供的日志接口头文件 
  3.  
  4. #define HDF_LOG_TAG led_driver   // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签 
  5.  
  6. //驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架 
  7. int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject) 
  8.     HDF_LOGD("Led driver bind success"); 
  9.     return 0; 
  10.  
  11. // 驱动自身业务初始的接口 
  12. int32_t HdfLedDriverInit(struct HdfDeviceObject *deviceObject) 
  13.     if (deviceObject == NULL) { 
  14.         HDF_LOGE("Led driver Init failed!"); 
  15.         return HDF_ERR_INVALID_OBJECT; 
  16.     } 
  17.     HDF_LOGD("Led driver Init success"); 
  18.     return HDF_SUCCESS; 
  19.  
  20. // 驱动资源释放的接口 
  21. void HdfLedDriverRelease(struct HdfDeviceObject *deviceObject) 
  22.     if (deviceObject == NULL) { 
  23.         HDF_LOGE("Led driver release failed!"); 
  24.         return
  25.     } 
  26.  
  27.     HDF_LOGD("Led driver release success"); 
  28.     return

 2.2 驱动入口注册到HDF框架

  1. // 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量 
  2. struct HdfDriverEntry g_ledDriverEntry = { 
  3.     .moduleVersion = 1, 
  4.     .moduleName = "led_driver"
  5.     .Bind = HdfLedDriverBind, 
  6.     .Init = HdfLedDriverInit, 
  7.     .Release = HdfLedDriverRelease, 
  8. }; 
  9.  
  10. // 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。 
  11. HDF_INIT(g_ledDriverEntry); 

 3. 驱动编译

在 huawei/hdf/led 目录下新建编译文件 Makefile

  1. include $(LITEOSTOPDIR)/../../drivers/hdf/lite/lite.mk  #导入hdf预定义内容,必需 
  2.  
  3. MODULE_NAME := hdf_led_driver  #生成的结果文件 
  4. LOCAL_SRCS += led.c  #本驱动的源代码文件 
  5. LOCAL_INCLUDE := ./include  #本驱动的头文件目录 
  6. LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror  #自定义的编译选项 
  7. include $(HDF_DRIVER)  #导入模板makefile完成编译 

 这里的hdf_led_driver为驱动文件名,注意对应关系。

4. 编译结果链接到内核镜像

修改 huawei/hdf/hdf_vendor.mk 文件,添加以下代码

  1. LITEOS_BASELIB += -lhdf_led_driver  #链接生成的静态库 
  2. LIB_SUBDIRS    += $(VENDOR_HDF_DRIVERS_ROOT)/led  #驱动代码Makefile的目录 

 填入驱动文件名和源码路径。

5. 驱动配置

驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息。

5.1 驱动设备描述(必选)

HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述。

修改 vendor/hisi/hi35xx/hi3516dv300/config/device_info/device_info.hcs配置文件,添加驱动的设备描述。

  1. platform :: host { 
  2.     hostName = "platform_host";  // host名称,host节点是用来存放某一类驱动的容器 
  3.     priority = 50;  // host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序 
  4.  
  5.     device_led :: device {                  // led设备节点 
  6.         device0 :: deviceNode {             // led驱动的DeviceNode节点 
  7.             policy = 2;                     // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍 
  8.             priority = 100;                 // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序 
  9.             preload = 0;                    // 驱动按需加载字段 
  10.             permission = 0666;              // 驱动创建设备节点权限 
  11.             moduleName = "led_driver";      // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致 
  12.             serviceName = "led_service";    // 驱动对外发布服务的名称,必须唯一 
  13.             deviceMatchAttr = "led_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等 
  14.         } 
  15.     }  

 其中,moduleName、serviceName和deviceMatchAttr 都比较重要,分布链接到源码的不同位置,我这里都分开命名,便于理解。

5.2 驱动私有配置信息(可选)

如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject 中的property里面,通过Bind和Init(参考驱动开发)传递给驱动。

在 vendor/hisi/hi35xx/hi3516dv300/config/ 目录下新建一个文件夹 led, 然后在其中新建一个源文件 led_config.hcs, 填入以下代码。

  1. root { 
  2.     LedDriverConfig { 
  3.         led_version = 1; 
  4.         match_attr = "led_config";   //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致 
  5.     } 

 配置信息定义之后,需要将该配置文件添加到板级配置入口文件hdf.hcs。

5.3 板级配置(可选)

修改 vendor/hisi/hi35xx/hi3516dv300/config/hdf.hcs文件,添加代码

  1. #include "device_info/device_info.hcs" 
  2. #include "led/led_config.hcs" 

 6. 驱动消息机制管理

当用户态应用和内核态驱动需要交互时,可以使用HDF框架的消息机制来实现。用消息管理可以在用户态和内核态之间架起桥梁,这为我们之后的APP提供了操控底层设备功能的能力。

这里我们在用户态实现一个简单的消息机制,内核态接受到消息后,翻转摄像头两侧的红外补光灯。

6.1 配置服务策略

HDF框架定了驱动对外发布服务的策略,是由配置文件中的policy字段来控制。

  1. typedef enum { 
  2.     /* 驱动不提供服务 */ 
  3.     SERVICE_POLICY_NONE = 0, 
  4.     /* 驱动对内核态发布服务 */ 
  5.     SERVICE_POLICY_PUBLIC = 1, 
  6.     /* 驱动对内核态和用户态都发布服务 */ 
  7.     SERVICE_POLICY_CAPACITY = 2, 
  8.     /* 驱动服务不对外发布服务,但可以被订阅 */ 
  9.     SERVICE_POLICY_FRIENDLY = 3, 
  10.     /* 驱动私有服务不对外发布服务,也不能被订阅 */ 
  11.     SERVICE_POLICY_PRIVATE = 4, 
  12.     /* 错误的服务策略 */ 
  13.     SERVICE_POLICY_INVALID 
  14. } ServicePolicy; 

 我们将驱动配置信息中服务策略policy字段设置为2,在之前的设备描述文件device_info.hcs里已经配置好了。

6.2 实现服务

在第2章,我们实现了一个空的驱动框架,现在继续实现内核态的消息服务接口。

编辑 huawei/hdf/led/led.c, 实现服务基类成员IDeviceIoService中的Dispatch方法。收到用户态发来的命令后,操作LED设备,然后将返回值通过reply传回,最后再将收到的命令回传给用户态程序。

  1. // Dispatch是用来处理用户态发下来的消息 
  2. int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply) 
  3.     int32_t result = HDF_FAILURE; 
  4.     HDF_LOGE("Led driver dispatch"); 
  5.     if (client == NULL || client->device == NULL
  6.     { 
  7.         HDF_LOGE("Led driver device is NULL"); 
  8.         return HDF_ERR_INVALID_OBJECT; 
  9.     } 
  10.  
  11.     switch (cmdCode) 
  12.     { 
  13.     case LED_WRITE_READ: 
  14.         const char *recv = HdfSbufReadString(data); 
  15.         if (recv != NULL
  16.         { 
  17.             HDF_LOGI("recv: %s", recv); 
  18.             result = CtlLED(-1);  # 操作设备 
  19.             // CtlLED(GPIO_VAL_HIGH); 
  20.             if (!HdfSbufWriteInt32(reply, result)) 
  21.             { 
  22.                 HDF_LOGE("replay is fail"); 
  23.             } 
  24.             return HdfDeviceSendEvent(client->device, cmdCode, data); 
  25.         } 
  26.         break; 
  27.  
  28.     default
  29.         break; 
  30.     } 
  31.     return result; 

 修改 HdfLedDriverBind函数,将服务绑定到框架。

  1. //驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架 
  2. int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject) 
  3.     if (deviceObject == NULL
  4.     { 
  5.         HDF_LOGE("Led driver bind failed!"); 
  6.         return HDF_ERR_INVALID_OBJECT; 
  7.     } 
  8.     static struct IDeviceIoService ledDriver = { 
  9.         .Dispatch = LedDriverDispatch, 
  10.     }; 
  11.     deviceObject->service = (struct IDeviceIoService *)(&ledDriver); 
  12.     HDF_LOGD("Led driver bind success"); 
  13.     return HDF_SUCCESS; 

 7. 业务代码

内核态核心功能,就简单实现一个每调用一次,就翻转一下LED状态的CtrlLED函数。这里mode为 -1 时为翻转,也可以直接指定高电平或低电平来开关,方便后续扩展。

其中Hi3516DV300的控制器管理12组GPIO管脚,每组8个。

GPIO号 = GPIO组索引(0~11)* 每组GPIO管脚数(8) + 组内偏移。

那么GPIO5_1的GPIO号 = 5 * 8 +1 = 41。

  1. static int32_t CtlLED(int mode) 
  2.     int32_t ret; 
  3.     uint16_t valRead; 
  4.     /* LED的GPIO管脚号 */ 
  5.     uint16_t gpio = 5 * 8 + 1;  // 红外补光灯 
  6.     // uint16_t gpio = 2 * 8 + 3;  // 绿色指示灯 
  7.     // uint16_t gpio = 3 * 8 + 4;  // 红色指示灯 
  8.  
  9.     /* 将GPIO管脚配置为输出 */ 
  10.     ret = GpioSetDir(gpio, GPIO_DIR_OUT); 
  11.     if (ret != 0) 
  12.     { 
  13.         HDF_LOGE("GpioSerDir: failed, ret %d\n", ret); 
  14.         return ret; 
  15.     } 
  16.  
  17.     if (mode == -1) 
  18.     { 
  19.         // 翻转输出口 
  20.         (void)GpioRead(gpio, &valRead); 
  21.         ret = GpioWrite(gpio, (valRead == GPIO_VAL_LOW) ? GPIO_VAL_HIGH : GPIO_VAL_LOW); 
  22.     } 
  23.     else 
  24.     { 
  25.         ret = GpioWrite(gpio, mode); 
  26.     } 
  27.  
  28.     if (ret != 0) 
  29.     { 
  30.         HDF_LOGE("GpioWrite: failed, ret %d\n", ret); 
  31.         return ret; 
  32.     } 
  33.     return ret; 

 同理,GPIO2_3、GPIO3_4和蜂鸣器组件等等通用IO设备也能相应控制,可以尽情发挥想象力了。

8. 配置Kconfig

在`vendor/huawei/hdf/led/`下,新建一个目录`driver`,再在其下新建`Kconfig`文件。

  1. config LOSCFG_DRIVERS_HDF_PLATFORM_LED 
  2.     bool "Enable HDF LED driver" 
  3.     default n 
  4.     depends on LOSCFG_DRIVERS_HDF_PLATFORM 
  5.     help 
  6.       Answer Y to enable HDF LED driver. 

 将其链接到板级Kconfig中,在vendor/huawei/hdf/Kconfig增加。

  1. source "../../vendor/huawei/hdf/led/driver/Kconfig" 

好了,内核态的程序基本都搞定了。

9. 用户态程序

我们开始写个主程序通过消息机制来与内核态交互。

新建applications/sample/camera/myApp/my_led_app.c源文件:

9.1 定义参数

led_service为服务名称,需要与之前定义的匹配;LED_WRITE_READ为命令标识,用户态和内核态通过这个来标识消息类型。

  1. #define LED_WRITE_READ 1 
  2. #define HDF_LOG_TAG LED_APP 
  3. #define LED_SERVICE "led_service" 

 9.2 发送消息

先实现一个发送消息的函数SendEvent,发送字符串命令后,收回内核态reply中操作设备后的返回值,放入replyData中打印出来。这里操作成功返回0。

  1. static int SendEvent(struct HdfIoService *serv, char *eventData) 
  2.     int ret = 0; 
  3.     struct HdfSBuf *data = HdfSBufObtainDefaultSize(); 
  4.     if (data == NULL
  5.     { 
  6.         HDF_LOGE("fail to obtain sbuf data"); 
  7.         return 1; 
  8.     } 
  9.  
  10.     struct HdfSBuf *reply = HdfSBufObtainDefaultSize(); 
  11.     if (reply == NULL
  12.     { 
  13.         HDF_LOGE("fail to obtain sbuf reply"); 
  14.         ret = HDF_DEV_ERR_NO_MEMORY; 
  15.         goto out
  16.     } 
  17.  
  18.     if (!HdfSbufWriteString(data, eventData)) 
  19.     { 
  20.         HDF_LOGE("fail to write sbuf"); 
  21.         ret = HDF_FAILURE; 
  22.         goto out
  23.     } 
  24.  
  25.     ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply); 
  26.     if (ret != HDF_SUCCESS) 
  27.     { 
  28.         HDF_LOGE("fail to send service call"); 
  29.         goto out
  30.     } 
  31.  
  32.     int replyData = 0; 
  33.     if (!HdfSbufReadInt32(reply, &replyData)) 
  34.     { 
  35.         HDF_LOGE("fail to get service call reply"); 
  36.         ret = HDF_ERR_INVALID_OBJECT; 
  37.         goto out
  38.     } 
  39.     HDF_LOGE("Get reply is: %d", replyData); 
  40. out
  41.     HdfSBufRecycle(data); 
  42.     HdfSBufRecycle(reply); 
  43.     return ret; 

 9.3 设置回调

收到内核态发来的字符串,简单打印一下。

  1. static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data) 
  2.     const char *string = HdfSbufReadString(data); 
  3.     if (string == NULL
  4.     { 
  5.         HDF_LOGE("fail to read string in event data"); 
  6.         return HDF_FAILURE; 
  7.     } 
  8.     HDF_LOGE("%s: dev event received: %u %s", (char *)priv, id, string); 
  9.  
  10.     return HDF_SUCCESS; 

 9.4 主程序

先构造一个服务,通过服务名称,绑定到对应的驱动。然后设置监听,等待来自内核的消息。最后每隔1秒发出一条翻转LED的指令。

  1. int main(void) 
  2.     struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE, 0); 
  3.     if (serv == NULL
  4.     { 
  5.         HDF_LOGE("fail to get service %s", LED_SERVICE); 
  6.         return HDF_FAILURE; 
  7.     } 
  8.     static struct HdfDevEventlistener listener = { 
  9.         .callBack = OnDevEventReceived, 
  10.         .priv = "Service0"}; 
  11.  
  12.     if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) 
  13.     { 
  14.         HDF_LOGE("fail to register event listener"); 
  15.         return HDF_FAILURE; 
  16.     } 
  17.  
  18.     char *send_cmd = "toggle LED"
  19.     while (1) 
  20.     { 
  21.         if (SendEvent(serv, send_cmd)) 
  22.         { 
  23.             HDF_LOGE("fail to send event"); 
  24.             return HDF_FAILURE; 
  25.         } 
  26.         sleep(1); 
  27.     } 
  28.  
  29.     if (HdfDeviceUnregisterEventListener(serv, &listener)) 
  30.     { 
  31.         HDF_LOGE("fail to  unregister listener"); 
  32.         return HDF_FAILURE; 
  33.     } 
  34.  
  35.     HdfIoServiceRecycle(serv); 
  36.     HDF_LOGI("exit"); 
  37.  
  38.     return HDF_SUCCESS; 

 9.5 配置BUILD.gn

在 drivers/hdf/lite/manager/BUILD.gn里增加以下代码,生成led_app应用。

  1. lite_component("hdf_manager") { 
  2.     features = [ 
  3.         ":hdf_core"
  4.     ] 
  5.  
  6. executable("led_app") { 
  7.     sources = [ 
  8.         "//applications/sample/camera/myApp/my_led_app.c" 
  9.          
  10.     ] 
  11.  
  12.     include_dirs = [ 
  13.         "../adapter/syscall/include"
  14.         "../adapter/vnode/include"
  15.         "$HDF_FRAMEWORKS/ability/sbuf/include",         
  16.         "$HDF_FRAMEWORKS/core/shared/include"
  17.         "$HDF_FRAMEWORKS/core/host/include"
  18.         "$HDF_FRAMEWORKS/core/master/include"
  19.         "$HDF_FRAMEWORKS/include/core"
  20.         "$HDF_FRAMEWORKS/include/utils"
  21.         "$HDF_FRAMEWORKS/utils/include"
  22.         "$HDF_FRAMEWORKS/include/osal"
  23.         "//third_party/bounds_checking_function/include"
  24.     ] 
  25.          
  26.     deps = [ 
  27.         "//drivers/hdf/lite/manager:hdf_core"
  28.         "//drivers/hdf/lite/adapter/osal/posix:hdf_posix_osal"
  29.     ] 
  30.  
  31.     public_deps = [ 
  32.         "//third_party/bounds_checking_function:libsec_shared"
  33.         ] 
  34.  
  35.     defines = [ 
  36.         "__USER__"
  37.     ] 
  38.  
  39.     cflags = [ 
  40.         "-Wall"
  41.         "-Wextra"
  42.         "-Werror"
  43.     ] 

 

10. 编译和烧录

由于这次我们要用到`HDF`框架,需要用到的组件比较多,简单复制一个`build\lite\product\ipcamera_hi3516dv300.json`改名为`my_hi3516dv300`即可。

  1. python build.py my_hi3516dv300 -b debug 

 

编译和烧录的过程参考前文,这里不再赘述了。顺利的话,启动程序就能看见LED欢快的闪烁了。

  1. ./bin/led_app 

 

[[377473]]

 [[377474]]

红外光在肉眼下不太显眼,在镜头下比较亮些,照度范围很大,后续再测一下夜视补光的效果。

总结

驱动开发涉及到文件和配置比较多,关系也比较纷繁,而且分散在各个目录。

这里列出主要文件再梳理一下:


大致上分三个部分,内核态、用户态和驱动配置。

1. 内核态

首先由led.c生成名为led_service的服务,以g_ledDriverEntry结构注册到HCS框架。编译成hdf_led_driver驱动,通过 huawei/hdf/hdf_vendor.mk链接到内核镜像中。

通过 HdfLedDriverBind函数将led_driver模块绑定到框架,IDeviceIoService中的Dispatch方法来处理来自用户态消息。

2. 用户态

用户态以HdfIoServiceBind通过服务名led_service来找到相应的驱动,用HdfSbufWriteString来发送消息,serv->dispatcher->Dispatch来接收返回值,通过HdfDeviceRegisterEventListener设置监听,来获取内核态主动发送的消息。

3. 驱动配置

以模块名led_driver找到注册到HCS框架的驱动程序(内核侧的别名);

以服务名led_service,暴露给用户态程序或内核态程序调用(用户侧的别名);

以led_config链接驱动私有配置文件。两个别名双向解绑,最后通过配置文件来进行耦合,保证了灵活性。这种设计在分布式的场合中,会有比较大的便利性。

资料下载

 

下一篇预告

本期主要介绍了一下HDF的驱动开发,

界面部分碍于篇幅留在下一篇介绍了,

敬请期待...


©著作权归作者和HarmonyOS技术社区共同所有,如需转载,请注明出处,否则将追究法律责任。

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com/#zz

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2023-09-06 15:35:14

2022-08-08 19:35:37

HDF驱动开发鸿蒙

2021-01-19 12:46:45

鸿蒙HarmonyOSHelloworld

2021-01-15 11:36:16

鸿蒙HarmonyOSAI应用

2021-01-14 12:06:22

鸿蒙HarmonyOSAI应用

2021-01-15 09:50:06

鸿蒙HarmonyOSAI应用

2021-01-25 09:58:01

鸿蒙HarmonyOS应用开发

2023-03-16 15:18:16

2023-03-20 16:05:49

HDF传感器驱动开发

2021-02-06 10:40:45

鸿蒙HarmonyOS应用开发

2023-03-24 14:47:24

NAPI框架HDF框架

2021-09-07 15:48:28

鸿蒙HarmonyOS应用

2021-12-15 10:02:25

鸿蒙HarmonyOS应用

2021-11-22 16:46:59

鸿蒙HarmonyOS应用

2021-12-15 15:30:38

鸿蒙HarmonyOS应用

2021-11-30 14:52:41

鸿蒙HarmonyOS应用

2022-02-14 14:28:57

驱动开发鸿蒙系统

2023-03-20 16:21:26

ADC数字转换器

2023-09-14 15:49:42

PWM鸿蒙

2021-09-10 15:12:04

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号