前言
上一篇,我们在鸿蒙上运行了第一个程序,这一篇我们来编写一个驱动开启摄像头的红外补光灯,顺便熟悉一下鸿蒙上的 HDF 驱动开发。
硬件准备
先查一下原理图(具体可参考第一篇的硬件资料),找到红外灯的 IO 口编号,GPIO5_1。
HDF 驱动开发
1. 简介
HDF(OpenHarmony Driver Foundation)驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。
HDF框架以组件化的驱动模型作为核心设计思路,为开发者提供更精细化的驱动管理,让驱动开发和部署更加规范。HDF框架将一类设备驱动放在同一个host里面,驱动内部实现开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个node,HDF框架管理驱动模型如下图所示:
2. 驱动框架
2.1 驱动框架实现
在 huawei/hdf
目录下新建一个文件夹 led
, 然后在其中新建一个源文件 led.c
。
-
#include
"hdf_device_desc.h"
// HDF框架对驱动开放相关能力接口的头文件
-
#include
"hdf_log.h"
// HDF 框架提供的日志接口头文件
-
-
#define HDF_LOG_TAG led_driver
// 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签
-
-
//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
-
int32_t HdfLedDriverBind(
struct HdfDeviceObject *deviceObject)
-
{
-
HDF_LOGD(
"Led driver bind success");
-
return
0;
-
}
-
-
// 驱动自身业务初始的接口
-
int32_t HdfLedDriverInit(
struct HdfDeviceObject *deviceObject)
-
{
-
if (deviceObject == NULL) {
-
HDF_LOGE(
"Led driver Init failed!");
-
return HDF_ERR_INVALID_OBJECT;
-
}
-
HDF_LOGD(
"Led driver Init success");
-
return HDF_SUCCESS;
-
}
-
-
// 驱动资源释放的接口
-
void HdfLedDriverRelease(
struct HdfDeviceObject *deviceObject)
-
{
-
if (deviceObject == NULL) {
-
HDF_LOGE(
"Led driver release failed!");
-
return;
-
}
-
-
HDF_LOGD(
"Led driver release success");
-
return;
-
}
2.2 驱动入口注册到HDF框架
-
// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
-
struct HdfDriverEntry g_ledDriverEntry = {
-
.moduleVersion =
1,
-
.moduleName =
"led_driver",
-
.Bind = HdfLedDriverBind,
-
.Init = HdfLedDriverInit,
-
.Release = HdfLedDriverRelease,
-
};
-
-
// 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
-
HDF_INIT(g_ledDriverEntry);
3. 驱动编译
在 huawei/hdf/led
目录下新建编译文件 Makefile
-
include $(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) #导入模板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";
// host名称,host节点是用来存放某一类驱动的容器
-
priority =
50;
// host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序
-
-
device_led :: device {
// led设备节点
-
device0 :: deviceNode {
// led驱动的DeviceNode节点
-
policy =
2;
// policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍
-
priority =
100;
// 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序
-
preload =
0;
// 驱动按需加载字段
-
permission =
0664;
// 驱动创建设备节点权限
-
moduleName =
"led_driver";
// 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
-
serviceName =
"led_service";
// 驱动对外发布服务的名称,必须唯一
-
deviceMatchAttr =
"led_config";
// 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
-
}
-
}
其中,moduleName
、serviceName
和deviceMatchAttr
都比较重要,分布链接到源码的不同位置,我这里都分开命名,便于理解。
5.2 驱动私有配置信息(可选)
如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject
中的property
里面,通过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. 驱动消息机制管理
当用户态应用和内核态驱动需要交互时,可以使用HDF框架的消息机制来实现。用消息管理可以在用户态和内核态之间架起桥梁,这为我们之后的APP提供了操控底层设备功能的能力。
这里我们在用户态实现一个简单的消息机制,内核态接受到消息后,翻转摄像头两侧的红外补光灯。
6.1 配置服务策略
HDF框架定了驱动对外发布服务的策略,是由配置文件中的policy字段来控制。
-
typedef enum {
-
/* 驱动不提供服务 */
-
SERVICE_POLICY_NONE =
0,
-
/* 驱动对内核态发布服务 */
-
SERVICE_POLICY_PUBLIC =
1,
-
/* 驱动对内核态和用户态都发布服务 */
-
SERVICE_POLICY_CAPACITY =
2,
-
/* 驱动服务不对外发布服务,但可以被订阅 */
-
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是用来处理用户态发下来的消息
-
int32_t LedDriverDispatch(
struct HdfDeviceIoClient *client,
int cmdCode,
struct HdfSBuf *data,
struct HdfSBuf *reply)
-
{
-
int32_t result = HDF_FAILURE;
-
HDF_LOGE(
"Led driver dispatch");
-
if (client == NULL || client->device == NULL)
-
{
-
HDF_LOGE(
"Led driver device is NULL");
-
return HDF_ERR_INVALID_OBJECT;
-
}
-
-
switch (cmdCode)
-
{
-
case LED_WRITE_READ:
-
const char *recv = HdfSbufReadString(data);
-
if (recv != NULL)
-
{
-
HDF_LOGI(
"recv: %s", recv);
-
result = CtlLED(
-1); # 操作设备
-
// CtlLED(GPIO_VAL_HIGH);
-
if (!HdfSbufWriteInt32(reply, result))
-
{
-
HDF_LOGE(
"replay is fail");
-
}
-
return HdfDeviceSendEvent(client->device, cmdCode, data);
-
}
-
break;
-
-
default:
-
break;
-
}
-
return result;
-
}
修改 HdfLedDriverBind
函数,将服务绑定到框架。
-
//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
-
int32_t HdfLedDriverBind(
struct HdfDeviceObject *deviceObject)
-
{
-
if (deviceObject == NULL)
-
{
-
HDF_LOGE(
"Led driver bind failed!");
-
return HDF_ERR_INVALID_OBJECT;
-
}
-
static
struct IDeviceIoService ledDriver = {
-
.Dispatch = LedDriverDispatch,
-
};
-
deviceObject->service = (
struct IDeviceIoService *)(&ledDriver);
-
HDF_LOGD(
"Led driver bind success");
-
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。
-
static int32_t CtlLED(
int mode)
-
{
-
int32_t ret;
-
uint16_t valRead;
-
/* LED的GPIO管脚号 */
-
uint16_t gpio =
5 *
8 +
1;
// 红外补光灯
-
// uint16_t gpio = 2 * 8 + 3; // 绿色指示灯
-
// uint16_t gpio = 3 * 8 + 4; // 红色指示灯
-
-
/* 将GPIO管脚配置为输出 */
-
ret = GpioSetDir(gpio, GPIO_DIR_OUT);
-
if (ret !=
0)
-
{
-
HDF_LOGE(
"GpioSerDir: failed, ret %d\n", ret);
-
return ret;
-
}
-
-
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);
-
return ret;
-
}
-
return ret;
-
}
同理,GPIO2_3
、GPIO3_4
和蜂鸣器组件等等通用IO
设备也能相应控制,可以尽情发挥想象力了。
8. 配置Kconfig
在vendor/huawei/hdf/led/
下,新建一个目录driver
,再在其下新建Kconfig
文件。
-
config LOSCFG_DRIVERS_HDF_PLATFORM_LED
-
bool
"Enable HDF LED driver"
-
default n
-
depends on LOSCFG_DRIVERS_HDF_PLATFORM
-
help
-
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
为命令标识,用户态和内核态通过这个来标识消息类型。
-
#define LED_WRITE_READ
1
-
#define HDF_LOG_TAG LED_APP
-
#define LED_SERVICE
"led_service"
9.2 发送消息
先实现一个发送消息的函数SendEvent
,发送字符串命令后,收回内核态reply
中操作设备后的返回值,放入replyData
中打印出来。这里操作成功返回0
。
-
static
int SendEvent(
struct HdfIoService *serv, char *eventData)
-
{
-
int ret =
0;
-
struct HdfSBuf *data = HdfSBufObtainDefaultSize();
-
if (data == NULL)
-
{
-
HDF_LOGE(
"fail to obtain sbuf data");
-
return
1;
-
}
-
-
struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
-
if (reply == NULL)
-
{
-
HDF_LOGE(
"fail to obtain sbuf reply");
-
ret = HDF_DEV_ERR_NO_MEMORY;
-
goto out;
-
}
-
-
if (!HdfSbufWriteString(data, eventData))
-
{
-
HDF_LOGE(
"fail to write sbuf");
-
ret = HDF_FAILURE;
-
goto out;
-
}
-
-
ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
-
if (ret != HDF_SUCCESS)
-
{
-
HDF_LOGE(
"fail to send service call");
-
goto out;
-
}
-
-
int replyData =
0;
-
if (!HdfSbufReadInt32(reply, &replyData))
-
{
-
HDF_LOGE(
"fail to get service call reply");
-
ret = HDF_ERR_INVALID_OBJECT;
-
goto out;
-
}
-
HDF_LOGE(
"Get reply is: %d", replyData);
-
out:
-
HdfSBufRecycle(data);
-
HdfSBufRecycle(reply);
-
return ret;
-
}
9.3 设置回调
收到内核态发来的字符串,简单打印一下。
-
static
int OnDevEventReceived(void *priv, uint32_t id,
struct HdfSBuf *data)
-
{
-
const char *
string = HdfSbufReadString(data);
-
if (
string == NULL)
-
{
-
HDF_LOGE(
"fail to read string in event data");
-
return HDF_FAILURE;
-
}
-
HDF_LOGE(
"%s: dev event received: %u %s", (char *)priv, id,
string);
-
-
return HDF_SUCCESS;
-
}
9.4 主程序
先构造一个服务,通过服务名称,绑定到对应的驱动。然后设置监听,等待来自内核的消息。最后每隔1
秒发出一条翻转LED
的指令。
-
int main(void)
-
{
-
struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE,
0);
-
if (serv == NULL)
-
{
-
HDF_LOGE(
"fail to get service %s", LED_SERVICE);
-
return HDF_FAILURE;
-
}
-
static
struct HdfDevEventlistener listener = {
-
.callBack = OnDevEventReceived,
-
.priv =
"Service0"};
-
-
if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS)
-
{
-
HDF_LOGE(
"fail to register event listener");
-
return HDF_FAILURE;
-
}
-
-
char *send_cmd =
"toggle LED";
-
while (
1)
-
{
-
if (SendEvent(serv, send_cmd))
-
{
-
HDF_LOGE(
"fail to send event");
-
return HDF_FAILURE;
-
}
-
sleep(
1);
-
}
-
-
if (HdfDeviceUnregisterEventListener(serv, &listener))
-
{
-
HDF_LOGE(
"fail to unregister listener");
-
return HDF_FAILURE;
-
}
-
-
HdfIoServiceRecycle(serv);
-
HDF_LOGI(
"exit");
-
-
return HDF_SUCCESS;
-
}
9.5 配置BUILD.gn
在 drivers/hdf/lite/manager/BUILD.gn
里增加以下代码,生成led_app
应用。
-
lite_component(
"hdf_manager") {
-
features = [
-
":hdf_core",
-
":led_app",
-
]
-
}
-
-
executable(
"led_app") {
-
sources = [
-
"//applications/sample/camera/myApp/my_led_app.c"
-
-
]
-
-
include_dirs = [
-
"../adapter/syscall/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
即可。
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