飞道的博客

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

375人阅读  评论(0)

前言

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

硬件准备

先查一下原理图(具体可参考第一篇的硬件资料),找到红外灯的 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. #define HDF_LOG_TAG led_driver // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签
  4. //驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
  5. int32_t HdfLedDriverBind( struct HdfDeviceObject *deviceObject)
  6. {
  7. HDF_LOGD( "Led driver bind success");
  8. return 0;
  9. }
  10. // 驱动自身业务初始的接口
  11. int32_t HdfLedDriverInit( struct HdfDeviceObject *deviceObject)
  12. {
  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. {
  23. if (deviceObject == NULL) {
  24. HDF_LOGE( "Led driver release failed!");
  25. return;
  26. }
  27. HDF_LOGD( "Led driver release success");
  28. return;
  29. }

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. // 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
  10. HDF_INIT(g_ledDriverEntry);

3. 驱动编译

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


   
  1. include $(LITEOSTOPDIR)/../../drivers/hdf/lite/lite.mk #导入hdf预定义内容,必需
  2. MODULE_NAME := hdf_led_driver #生成的结果文件
  3. LOCAL_SRCS += led.c #本驱动的源代码文件
  4. LOCAL_INCLUDE := ./include #本驱动的头文件目录
  5. LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror #自定义的编译选项
  6. 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. device_led :: device { // led设备节点
  5. device0 :: deviceNode { // led驱动的DeviceNode节点
  6. policy = 2; // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍
  7. priority = 100; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序
  8. preload = 0; // 驱动按需加载字段
  9. permission = 0664; // 驱动创建设备节点权限
  10. moduleName = "led_driver"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
  11. serviceName = "led_service"; // 驱动对外发布服务的名称,必须唯一
  12. deviceMatchAttr = "led_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
  13. }
  14. }

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

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

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

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. }
  6. }

配置信息定义之后,需要将该配置文件添加到板级配置入口文件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. {
  4. int32_t result = HDF_FAILURE;
  5. HDF_LOGE( "Led driver dispatch");
  6. if (client == NULL || client->device == NULL)
  7. {
  8. HDF_LOGE( "Led driver device is NULL");
  9. return HDF_ERR_INVALID_OBJECT;
  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. default:
  28. break;
  29. }
  30. return result;
  31. }

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


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

7. 业务代码

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

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

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

那么GPIO5_1GPIO号 = 5 * 8 +1 = 41。


   
  1. static int32_t CtlLED( int mode)
  2. {
  3. int32_t ret;
  4. uint16_t valRead;
  5. /* LED的GPIO管脚号 */
  6. uint16_t gpio = 5 * 8 + 1; // 红外补光灯
  7. // uint16_t gpio = 2 * 8 + 3; // 绿色指示灯
  8. // uint16_t gpio = 3 * 8 + 4; // 红色指示灯
  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. if (mode == -1)
  17. {
  18. // 翻转输出口
  19. (void)GpioRead(gpio, &valRead);
  20. ret = GpioWrite(gpio, (valRead == GPIO_VAL_LOW) ? GPIO_VAL_HIGH : GPIO_VAL_LOW);
  21. }
  22. else
  23. {
  24. ret = GpioWrite(gpio, mode);
  25. }
  26. if (ret != 0)
  27. {
  28. HDF_LOGE( "GpioWrite: failed, ret %d\n", ret);
  29. return ret;
  30. }
  31. return ret;
  32. }

同理,GPIO2_3GPIO3_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增加

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. {
  3. int ret = 0;
  4. struct HdfSBuf *data = HdfSBufObtainDefaultSize();
  5. if (data == NULL)
  6. {
  7. HDF_LOGE( "fail to obtain sbuf data");
  8. return 1;
  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. if (!HdfSbufWriteString(data, eventData))
  18. {
  19. HDF_LOGE( "fail to write sbuf");
  20. ret = HDF_FAILURE;
  21. goto out;
  22. }
  23. ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
  24. if (ret != HDF_SUCCESS)
  25. {
  26. HDF_LOGE( "fail to send service call");
  27. goto out;
  28. }
  29. int replyData = 0;
  30. if (!HdfSbufReadInt32(reply, &replyData))
  31. {
  32. HDF_LOGE( "fail to get service call reply");
  33. ret = HDF_ERR_INVALID_OBJECT;
  34. goto out;
  35. }
  36. HDF_LOGE( "Get reply is: %d", replyData);
  37. out:
  38. HdfSBufRecycle(data);
  39. HdfSBufRecycle(reply);
  40. return ret;
  41. }

9.3 设置回调

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


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

9.4 主程序

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


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

9.5 配置BUILD.gn

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


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

10. 编译和烧录

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

python build.py my_hi3516dv300 -b debug

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

./bin/led_app

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

总结

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

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

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

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

资料下载

本期相关文件资料,可在公众号“深度觉醒”,后台回复:“ohos05”,获取下载链接。

下一篇

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

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

敬请期待...

往期推荐


转载:https://blog.csdn.net/weixin_47479625/article/details/111829430
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场