更多内容请访问:与华为共建的Harmonyos技术社区https://harmonyos.51cto.com/#zz在本文中,我们运行了鸿蒙的第一个节目。在本文中,我们将编写一个驱动程序来打开相机的红外补光灯。顺便熟悉下鸿蒙上的HDF驱动开发。硬件准备,首先查看原理图(详见第一篇硬件资料),找到红外灯的IO口号,GPIO5_1。HDF驱动开发一、简介HDF(OpenHarmonyDriverFoundation)驱动框架为驱动开发者提供了驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为驱动开发者提供更加精准高效的开发环境,力求实现一次开发,多系统部署。HDF框架以组件化驱动模型为核心设计思想,为开发者提供更精细化的驱动管理,使驱动开发部署更加规范。HDF框架将一类设备驱动放在同一个宿主机中,驱动内部实现也可以分层独立开发和部署驱动功能,支持一个驱动多节点。HDF框架管理驱动模型如下图所示:2.驱动框架2.1驱动框架实现在huawei/hdf目录下创建一个文件夹led,然后在其中创建一个源文件led.c。#include"hdf_device_desc.h"//HDF框架向驱动打开相关能力接口的头文件#include"hdf_log.h"//HDF框架提供的log接口头文件#defineHDF_LOG_TAGled_driver//打印tags日志中包含,如果没有定义使用默认的HDF_TAG标签//驱动对外提供的服务能力,将相关服务接口绑定到HDF框架int32_tHdfLedDriverBind(structHdfDeviceObject*deviceObject){HDF_LOGD("Leddriverbindsuccess");return0;}//驱动自身业务初始化的接口int32_tHdfLedDriverInit(structHdfDeviceObject*deviceObject){if(deviceObject==NULL){HDF_LOGE("LeddriverInitfailed!");returnHDF_ERR_INVALID_OBJECT;}HDF_LOGD("LeddriverInitsuccess");returnHDF_SUCCESS;}//驱动器释放的接口voidHdfLedDriverRelease(structHdfDeviceObject*deviceObject){if(deviceObject==NULL){HDF_LOGE("Leddriverreleasefailed!");return;}HDF_LOGD("Leddriverreleasesuccess");return;}2.2注册驱动入口到theHDFframework//定义驱动入口的对象必须是HdfDriverEntry(定义在hdf_device_desc.h)类型的全局变量structHdfDriverEntryg_ledDriverEntry={.moduleVersion=1,.moduleName="led_driver",.Bind=HdfLedDriverBind,.Init=HdfLedDriverInit,.发布e=HdfLedDriverRelease,};//调用HDF_INIT将驱动入口注册到HDF框架中。在加载驱动时,HDF框架会先调用Bind函数,然后调用Init函数加载驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出HDF_INIT(g_ledDriverEntry);3.编译驱动新建一个编译文件Makefileinclude$(LITEOSTOPDIR)/../../drivers/hdf/lite/lite.mk#导入hdf预设定义内容,需要MODULE_NAME:=hdf_led_driver#生成的结果文件LOCAL_SRCS+=led.c#本驱动源码文件LOCAL_INCLUDE:=./include#本驱动头文件目录LOCAL_CFLAGS+=-fstack-protector-strong-Wextra-Wall-Werror#自定义编译选项include$(HDF_DRIVER)#Import模板makefile来完成编译。这里hdf_led_driver是驱动文件名,注意对应关系。4.将编译结果链接到内核镜像,修改huawei/hdf/hdf_vendor.mk文件,添加如下代码LITEOS_BASELIB+=-lhdf_led_driver#链接生成的静态库LIB_SUBDIRS+=$(VENDOR_HDF_DRIVERS_ROOT)/led#填写驱动驱动代码Makefile源码路径的文件名和目录。5.驱动配置驱动配置包括HDF框架定义的驱动设备描述和驱动私有配置信息两部分。5.1驱动设备描述(必填)HDF框架加载驱动所需的信息来自于HDF框架定义的驱动设备描述。修改vendor/hisi/hi35xx/hi3516dv300/config/device_info/device_info.hcs配置文件,增加驱动的设备描述。platform::host{hostName="platform_host";//主机名,主机节点,用于存放某类驱动容器priority=50;//主机启动优先级(0-200),值越大,higherthepriority低,建议默认配置100,相同的优先级不会保证host的加载顺序发布的策略在driverservicemanagement章节有详细介绍。priority=100;//驱动启动优先级(0-200),值越大优先级越低。建议默认配置100。如果优先级相同,不保证加载设备Sequencepreload=0;//驱动加载字段permission=0666;//驱动创建设备节点权限moduleName="led_driver";//驱动name,该字段的值必须与驱动入口结构体的moduleName值一致serviceName="led_service";//驱动发布的服务名称必须唯一匹配的驱动私有数据必须等于驱动私有数据配置表中的match_attr值}}其中,moduleName、serviceName和deviceMatchAttr比较重要,分布链接到源码的不同位置。为了便于理解,我在这里分别命名。5.2驱动私有配置信息(可选)如果驱动有私有配置,可以添加一个驱动配置文件,填写一些驱动默认配置信息。HDF框架加载驱动时,会获取相应的配置信息,并保存在HdfDeviceObject的属性中,通过Bind和Init传递给驱动(参考驱动开发)。在vendor/hisi/hi35xx/hi3516dv300/config/目录下新建一个文件夹led,然后在里面新建一个源文件led_config.hcs,并填写如下代码。root{LedDriverConfig{led_version=1;match_attr="led_config";//该字段的值必须与device_info.hcs中的deviceMatchAttr值一致}}配置信息定义完成后,需要在配置文件中加入板级配置入口文件hdf.hcs。5.3板级配置(可选)修改vendor/hisi/hi35xx/hi3516dv300/config/hdf.hcs文件,增加代码#include"device_info/device_info.hcs"#include"led/led_config.hcs"6.驱动消息机制管理When用户态应用程序和内核态驱动程序需要交互,可以使用HDF框架的消息机制来实现。消息管理可以用来搭建用户态和内核态之间的桥梁,为我们后续的APP提供操作底层设备功能的能力。这里我们在用户态实现了一个简单的消息机制。在内核态收到消息后,翻转摄像头两侧的红外补光灯。6.1配置服务策略HDF框架决定了驱动对外发布服务的策略,由配置文件中的policy字段控制。typedefenum{/*驱动不提供服务*/SERVICE_POLICY_NONE=0,/*驱动向内核态发布服务*/SERVICE_POLICY_PUBLIC=1,/*驱动同时向内核态和用户态发布服务*/SERVICE_POLICY_CAPACITY=2,/*驱动service不对外发布服务,但是可以订阅*/SERVICE_POLICY_FRIENDLY=3,/*驱动不对外发布服务,不能订阅的私有服务*/SERVICE_POLICY_PRIVATE=4,/*服务策略错误*/SERVICE_POLICY_INVALID}ServicePolicy;我们将驱动配置信息中的服务策略policy字段设置为2,这个在之前的设备描述文件device_info.hcs中已经配置好了。6.2实现服务在第2章中,我们实现了一个空的驱动框架,现在继续实现内核态消息服务接口。编辑huawei/hdf/led/led.c,实现服务基类成员IDeviceIoService中的Dispatch方法。接收到用户态的命令后,操作LED设备,然后通过reply返回返回值,最后将接收到的命令返回给用户态程序。//Dispatch用于处理从用户态发送的消息->device==NULL){HDF_LOGE("LeddriverdeviceisNULL");returnHDF_ERR_INVALID_OBJECT;}switch(cmdCode){caseLED_WRITE_READ:constchar*recv=HdfSbufReadString(data);if(recv!=NULL){HDF_LOGI("recv:%s",recv);result=CtlLED(-1);#operationdevice//CtlLED(GPIO_VAL_HIGH);if(!HdfSbufWriteInt32(reply,result)){HDF_LOGE("replayisfail");}returnHdfDeviceSendEvent(client->device,cmdCode,data);}break;default:break;}returnresult;}修改HdfLedDriverBind函数,将服务绑定到框架。//驱动对外提供的服务能力,将相关服务接口绑定到HDF框架上returnHDF_ERR_INVALID_OBJECT;}staticstructIDeviceIoServiceledDriver={.Dispatch=LedDriverDispatch,};deviceObject->service=(structIDeviceIoService*)(&ledDriver);HDF_LOGD("Leddriverbindsuccess");returnHDF_SUCCESS;}7.业务代码内核态的核心功能就是简单的实现了一个调用每次翻转LED状态的CtrlLED函数。这里mode为-1的时候是翻转的,也可以直接指定高电平或者低电平来切换,方便后续扩展。其中,Hi3516DV300的控制器管理着12组GPIO引脚,每组8个引脚。GPIO编号=GPIO组索引(0~11)*每组GPIO管脚数(8)+组内偏移量。那么GPIO5_1的GPIO数量=5*8+1=41。staticint32_tCtlLED(intmode){int32_tret;uint16_tvalRead;/*LEDGPIO管脚编号*/uint16_tgpio=5*8+1;//红外补光灯//uint16_tgpio=2*8+3;//绿灯//uint16_tgpio=3*8+4;//红色指示灯/*配置GPIO引脚为输出*/ret=GpioSetDir(gpio,GPIO_DIR_OUT);if(ret!=0){HDF_LOGE("GpioSerDir:failed,ret%d\n",ret);returnret;}if(mode==-1){//翻转输出端口(void)GpioRead(gpio,&valRead);ret=GpioWrite(gpio,(valRead==GPIO_VAL_LOW)?GPIO_VAL_HIGH:GPIO_VAL_LOW);}else{ret=GpioWrite(gpio,mode);}if(ret!=0){HDF_LOGE("GpioWrite:failed,ret%d\n",ret);returnret;}returnret;}同样,通用IO设备如GPIO2_3、GPIO3_4、蜂鸣器组件也可以相应控制,可以尽情发挥你的想象力。8、在`vendor/huawei/hdf/led/`下配置Kconfig,新建目录`driver`,并在其下创建`Kconfig`文件。configLOSCFG_DRIVERS_HDF_PLATFORM_LEDbool"EnableHDFLEDdriver"defaultndependsonLOSCFG_DRIVERS_HDF_PLATFORMhelpAnswerYtoenableHDFLEDdriver.链接到板级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是命令标识,用户态和内核态用它来标识消息类型。#defineLED_WRITE_READ1#defineHDF_LOG_TAGLED_APP#defineLED_SERVICE"led_service"9.2发送消息,首先实现一个函数SendEvent来发送消息。发送一个字符串命令后,在内核态reply中取回操作设备后的返回值,放入replyData中打印出来。此操作成功时返回0。staticintSendEvent(structHdfIoService*serv,char*e??ventData){intret=0;structHdfSBuf*data=HdfSBufObtainDefaultSize();if(data==NULL){HDF_LOGE("failtoobtainsbufdata");return1;}structHdfSBuf*reply=HdfSBufObtainDefaultSize();(回复==NULL){HDF_LOGE("failtoobtainsbufreply");ret=HDF_DEV_ERR_NO_MEMORY;gotoout;}if(!HdfSbufWriteString(data,eventData)){HDF_LOGE("failtowritesbuf");ret=HDF_FAILURE;gotoout;}ret=serv->dispatcher->Dispatch(&serv->object,LED_WRITE_READ,data,reply);if(ret!=HDF_SUCCESS){HDF_LOGE("failtosendservicecall");gotoout;}intreplyData=0;if(!HdfSbufReadInt32(reply,&replyData)){HDF_LOGE("failtogetservicecallreply");ret=HDF_ERR_INVALID_OBJECT;gotoout;}HDF_LOGE("Getreplyis:%d",replyData);out:HdfSBufRecycle(data);HdfSBufRecycle(reply);returnret;}到字符串,简单打印即可。staticintOnDevEventReceived(void*priv,uint32_tid,structHdfSBuf*data){constchar*string=HdfSbufReadString(data);if(string==NULL){HDF_LOGE("failtoreadstringineventdata");returnHDF_FAILURE;}HDF_LOGE("%s:deveventreceived:%u%s",(char*)priv,id,string);returnHDF_SUCCESS;}9.4主程序首先构造一个服务,通过服务名绑定对应的驱动。然后设置一个监听器,等待来自内核的消息。最后,每1秒发出一次翻转LED的指令。intmain(void){structHdfIoService*serv=HdfIoServiceBind(LED_SERVICE,0);if(serv==NULL){HDF_LOGE("failtogetservice%s",LED_SERVICE);returnHDF_FAILURE;}staticstructHdfDevEventlistenerlistener={.callBack=OnDevEventReceived,.priv="Service0"};if(HdfDeviceRegisterEventListener(serv,&listener)!=HDF_SUCCESS){HDF_LOGE("failtoregistereventlistener");returnHDF_FAILURE;}char*send_cmd="toggleLED";while(1){if(SendEvent(serv,send_cmd)){HDF_LOGE("failtosendevent");returnHDF_FAILURE;}sleep(1);}if(HdfDeviceUnregisterEventListener(serv,&listener)){HDF_LOGE("failtounregisterlistener");returnHDF_FAILURE;}HdfIoServiceRecycle(serv);HDF_LOGI("exit");returnHDF_SUCCESS;}9.5配置BUILD.gn在drivers/hdf/lite/manager/BUILD.gn里添加以下代码,生成led_app应用。lite_component("hdf_manager"){features=[":hdf_core",]}executable("led_app"){sources=["//applications/sample/camera/myApp/my_led_app.c"]include_dirs=["../适配器/系统调用/include","../adapter/vnode/include","$HDF_FRAMEWORKS/ability/sbuf/include","$HDF_FRAMEWORKS/core/shared/include","$HDF_FRAMEWORKS/core/host/include""$HDF_FRAMEWORKS/core/master/include","$HDF_FRAMEWORKS/include/core","$HDF_FRAMEWORKS/include/utils","$HDF_FRAMEWORKS/utils/include","$HDF_FRAMEWORKS/include/osal","//third_party/bounds_checking_function/include",]deps=["//drivers/hdf/lite/manager:hdf_core","//drivers/hdf/lite/adapter/osal/posix:hdf_posix_osal",]public_deps=["//third_party/bounds_checking_function:libsec_shared",]defines=["__USER__",]cflags=["-Wall","-Wextra","-Werror",]}10.编译烧录因为我们这次要用到`HDF`框架,所以需要用到的组件比较多,直接复制一个`build\lite\product\ipcamera_hi3516dv300.json`并重命名`my_hi3516dv300`可以是pythonbuild.pymy_hi3516dv300-bdebug编译烧录的过程参考上篇文章,这里不再赘述,如果顺利的话,启动程序就可以看到LED在欢快的闪烁。./bin/led_app红外灯肉眼不可见太显眼了,在镜头下比较亮,照度范围大,后面测试夜视补光效果综上,驱动开发涉及的文件很多和配置,关系复杂,分散在各个目录,这里罗列一下主要文件:大致分为三部分,内核态,用户态和驱动配置。1.内核态首先生成一个服务,名为led_service来自led.c,并使用g_ledDriverEntry结构将其注册到HCS框架中。编译成hdf_led_driver驱动通过huawei/hdf/hdf_vendor.mk链接到内核镜像。led_driver模块通过HdfLedDriverBind函数绑定到框架,IDeviceIoService中的Dispatch方法用于处理来自用户态的消息。2.用户态用户态通过服务名led_service使用HdfIoServiceBind找到对应的驱动,使用HdfSbufWriteString发送消息,serv->dispatcher->Dispatch接收返回值,通过HdfDeviceRegisterEventListener设置监听主动获取消息由内核态发送。3、驱动配置使用模块名led_driver查找并注册到HCS框架的驱动(内核端的别名);服务名称为led_service,暴露给用户态程序或内核态程序调用(用户端的别名);私有配置文件由led_config链接驱动。两个别名是双向不绑定的,最后通过配置文件耦合保证了灵活性。这种设计在分布式的情况下会更加方便。资料下载下一期预览本期主要介绍HDF的驱动开发。界面部分因篇幅留待下篇介绍,敬请期待...?版权归作者及HarmonyOS技术社区所有转载请注明出处,否则追究法律责任。了解更多请访问:与华为官方共建鸿蒙科技社区https://harmonyos.51cto.com/#zz
