用鴻蒙開發AI應用(五)HDF 驅動補光燈

bluishfish 2022-01-07 12:11:43 阅读数:643

ai hdf

前言

上一篇,我們在鴻蒙上運行了第一個程序,這一篇我們來編寫一個驅動開啟攝像頭的紅外補光燈,順便熟悉一下鴻蒙上的 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值相等
}
}

其中,moduleNameserviceNamedeviceMatchAttr 都比較重要,分布鏈接到源碼的不同比特置,我這裏都分開命名,便於理解。

5.2 驅動私有配置信息(可選)

如果驅動有私有配置,則可以添加一個驅動的配置文件,用來填寫一些驅動的默認配置信息,HDF框架在加載驅動的時候,會將對應的配置信息獲取並保存在HdfDeviceObject 中的property裏面,通過BindInit(參考驅動開發)傳遞給驅動。

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的控制器管理12GPIO管脚,每組8個。

GPIO號 = GPIO組索引(0~11)* 每組GPIO管脚數(8) + 組內偏移。

那麼GPIO5_1GPIO號 = 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_3GPIO3_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的驅動開發

界面部分礙於篇幅留在下一篇介紹了,

敬請期待...

往期推薦

版权声明:本文为[bluishfish]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071211425046.html